diff options
Diffstat (limited to 'services')
508 files changed, 224592 insertions, 0 deletions
diff --git a/services/automation/AutomationComponents.manifest b/services/automation/AutomationComponents.manifest new file mode 100644 index 0000000000..d44fe90a22 --- /dev/null +++ b/services/automation/AutomationComponents.manifest @@ -0,0 +1,2 @@ +# Register resource aliases +resource services-automation resource://gre/modules/services-automation/ diff --git a/services/automation/ServicesAutomation.sys.mjs b/services/automation/ServicesAutomation.sys.mjs new file mode 100644 index 0000000000..24316d8827 --- /dev/null +++ b/services/automation/ServicesAutomation.sys.mjs @@ -0,0 +1,416 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This module is used in automation to connect the browser to + * a specific FxA account and trigger FX Sync. + * + * To use it, you can call this sequence: + * + * initConfig("https://accounts.stage.mozaws.net"); + * await Authentication.signIn(username, password); + * await Sync.triggerSync(); + * await Authentication.signOut(); + * + * + * Where username is your FxA e-mail. it will connect your browser + * to that account and trigger a Sync (on stage servers.) + * + * You can also use the convenience function that does everything: + * + * await triggerSync(username, password, "https://accounts.stage.mozaws.net"); + * + */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FxAccountsClient: "resource://gre/modules/FxAccountsClient.sys.mjs", + FxAccountsConfig: "resource://gre/modules/FxAccountsConfig.sys.mjs", + Log: "resource://gre/modules/Log.sys.mjs", + Svc: "resource://services-sync/util.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", + clearTimeout: "resource://gre/modules/Timer.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +const AUTOCONFIG_PREF = "identity.fxaccounts.autoconfig.uri"; + +/* + * Log helpers. + */ +var _LOG = []; + +function LOG(msg, error) { + console.debug(msg); + _LOG.push(msg); + if (error) { + console.debug(JSON.stringify(error)); + _LOG.push(JSON.stringify(error)); + } +} + +function dumpLogs() { + let res = _LOG.join("\n"); + _LOG = []; + return res; +} + +function promiseObserver(aEventName) { + LOG("wait for " + aEventName); + return new Promise(resolve => { + let handler = () => { + lazy.Svc.Obs.remove(aEventName, handler); + resolve(); + }; + let handlerTimeout = () => { + lazy.Svc.Obs.remove(aEventName, handler); + LOG("handler timed out " + aEventName); + resolve(); + }; + lazy.Svc.Obs.add(aEventName, handler); + lazy.setTimeout(handlerTimeout, 3000); + }); +} + +/* + * Authentication + * + * Used to sign in an FxA account, takes care of + * the e-mail verification flow. + * + * Usage: + * + * await Authentication.signIn(username, password); + */ +export var Authentication = { + async isLoggedIn() { + return !!(await this.getSignedInUser()); + }, + + async isReady() { + let user = await this.getSignedInUser(); + if (user) { + LOG("current user " + JSON.stringify(user)); + } + return user && user.verified; + }, + + async getSignedInUser() { + try { + return await lazy.fxAccounts.getSignedInUser(); + } catch (error) { + LOG("getSignedInUser() failed", error); + throw error; + } + }, + + async shortWaitForVerification(ms) { + LOG("shortWaitForVerification"); + let userData = await this.getSignedInUser(); + let timeoutID; + LOG("set a timeout"); + let timeoutPromise = new Promise(resolve => { + timeoutID = lazy.setTimeout(() => { + LOG(`Warning: no verification after ${ms}ms.`); + resolve(); + }, ms); + }); + LOG("set a fxAccounts.whenVerified"); + await Promise.race([ + lazy.fxAccounts + .whenVerified(userData) + .finally(() => lazy.clearTimeout(timeoutID)), + timeoutPromise, + ]); + LOG("done"); + return this.isReady(); + }, + + async _confirmUser(uri) { + LOG("Open new tab and load verification page"); + let mainWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let newtab = mainWindow.gBrowser.addWebTab(uri); + let win = mainWindow.gBrowser.getBrowserForTab(newtab); + win.addEventListener("load", function (e) { + LOG("load"); + }); + + win.addEventListener("loadstart", function (e) { + LOG("loadstart"); + }); + + win.addEventListener("error", function (msg, url, lineNo, columnNo, error) { + var string = msg.toLowerCase(); + var substring = "script error"; + if (string.indexOf(substring) > -1) { + LOG("Script Error: See Browser Console for Detail"); + } else { + var message = [ + "Message: " + msg, + "URL: " + url, + "Line: " + lineNo, + "Column: " + columnNo, + "Error object: " + JSON.stringify(error), + ].join(" - "); + + LOG(message); + } + }); + + LOG("wait for page to load"); + await new Promise(resolve => { + let handlerTimeout = () => { + LOG("timed out "); + resolve(); + }; + var timer = lazy.setTimeout(handlerTimeout, 10000); + win.addEventListener("loadend", function () { + resolve(); + lazy.clearTimeout(timer); + }); + }); + LOG("Page Loaded"); + let didVerify = await this.shortWaitForVerification(10000); + LOG("remove tab"); + mainWindow.gBrowser.removeTab(newtab); + return didVerify; + }, + + /* + * This whole verification process may be bypassed if the + * account is allow-listed. + */ + async _completeVerification(username) { + LOG("Fetching mail (from restmail) for user " + username); + let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent( + username + )}`; + let triedAlready = new Set(); + const tries = 10; + const normalWait = 4000; + for (let i = 0; i < tries; ++i) { + let resp = await fetch(restmailURI); + let messages = await resp.json(); + // Sort so that the most recent emails are first. + messages.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt)); + for (let m of messages) { + // We look for a link that has a x-link that we haven't yet tried. + if (!m.headers["x-link"] || triedAlready.has(m.headers["x-link"])) { + continue; + } + if (!m.headers["x-verify-code"]) { + continue; + } + let confirmLink = m.headers["x-link"]; + triedAlready.add(confirmLink); + LOG("Trying confirmation link " + confirmLink); + try { + if (await this._confirmUser(confirmLink)) { + LOG("confirmation done"); + return true; + } + LOG("confirmation failed"); + } catch (e) { + LOG( + "Warning: Failed to follow confirmation link: " + + lazy.Log.exceptionStr(e) + ); + } + } + if (i === 0) { + // first time through after failing we'll do this. + LOG("resendVerificationEmail"); + await lazy.fxAccounts.resendVerificationEmail(); + } + if (await this.shortWaitForVerification(normalWait)) { + return true; + } + } + // One last try. + return this.shortWaitForVerification(normalWait); + }, + + async signIn(username, password) { + LOG("Login user: " + username); + try { + // Required here since we don't go through the real login page + LOG("Calling FxAccountsConfig.ensureConfigured"); + await lazy.FxAccountsConfig.ensureConfigured(); + let client = new lazy.FxAccountsClient(); + LOG("Signing in"); + let credentials = await client.signIn(username, password, true); + LOG("Signed in, setting up the signed user in fxAccounts"); + await lazy.fxAccounts._internal.setSignedInUser(credentials); + + // If the account is not allow-listed for tests, we need to verify it + if (!credentials.verified) { + LOG("We need to verify the account"); + await this._completeVerification(username); + } else { + LOG("Credentials already verified"); + } + return true; + } catch (error) { + LOG("signIn() failed", error); + throw error; + } + }, + + async signOut() { + if (await Authentication.isLoggedIn()) { + // Note: This will clean up the device ID. + await lazy.fxAccounts.signOut(); + } + }, +}; + +/* + * Sync + * + * Used to trigger sync. + * + * usage: + * + * await Sync.triggerSync(); + */ +export var Sync = { + getSyncLogsDirectory() { + return PathUtils.join(PathUtils.profileDir, "weave", "logs"); + }, + + async init() { + lazy.Svc.Obs.add("weave:service:sync:error", this); + lazy.Svc.Obs.add("weave:service:setup-complete", this); + lazy.Svc.Obs.add("weave:service:tracking-started", this); + // Delay the automatic sync operations, so we can trigger it manually + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.idleInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.activeInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("syncThreshold", 10000000); + // Wipe all the logs + await this.wipeLogs(); + }, + + observe(subject, topic, data) { + LOG("Event received " + topic); + }, + + async configureSync() { + // todo, enable all sync engines here + // the addon engine requires kinto creds... + LOG("configuring sync"); + console.assert(await Authentication.isReady(), "You are not connected"); + await lazy.Weave.Service.configure(); + if (!lazy.Weave.Status.ready) { + await promiseObserver("weave:service:ready"); + } + if (lazy.Weave.Service.locked) { + await promiseObserver("weave:service:resyncs-finished"); + } + }, + + /* + * triggerSync() runs the whole process of Syncing. + * + * returns 1 on success, 0 on failure. + */ + async triggerSync() { + if (!(await Authentication.isLoggedIn())) { + LOG("Not connected"); + return 1; + } + await this.init(); + let result = 1; + try { + await this.configureSync(); + LOG("Triggering a sync"); + await lazy.Weave.Service.sync(); + + // wait a second for things to settle... + await new Promise(resolve => lazy.setTimeout(resolve, 1000)); + + LOG("Sync done"); + result = 0; + } catch (error) { + LOG("triggerSync() failed", error); + } + + return result; + }, + + async wipeLogs() { + let outputDirectory = this.getSyncLogsDirectory(); + if (!(await IOUtils.exists(outputDirectory))) { + return; + } + LOG("Wiping existing Sync logs"); + try { + await IOUtils.remove(outputDirectory, { recursive: true }); + } catch (error) { + LOG("wipeLogs() failed", error); + } + }, + + async getLogs() { + let outputDirectory = this.getSyncLogsDirectory(); + let entries = []; + + if (await IOUtils.exists(outputDirectory)) { + // Iterate through the directory + for (const path of await IOUtils.getChildren(outputDirectory)) { + const info = await IOUtils.stat(path); + + entries.push({ + path, + name: PathUtils.filename(path), + lastModified: info.lastModified, + }); + } + + entries.sort(function (a, b) { + return b.lastModified - a.lastModified; + }); + } + + const promises = entries.map(async entry => { + const content = await IOUtils.readUTF8(entry.path); + return { + name: entry.name, + content, + }; + }); + return Promise.all(promises); + }, +}; + +export function initConfig(autoconfig) { + Services.prefs.setStringPref(AUTOCONFIG_PREF, autoconfig); +} + +export async function triggerSync(username, password, autoconfig) { + initConfig(autoconfig); + await Authentication.signIn(username, password); + var result = await Sync.triggerSync(); + await Authentication.signOut(); + var logs = { + sync: await Sync.getLogs(), + condprof: [ + { + name: "console.txt", + content: dumpLogs(), + }, + ], + }; + return { + result, + logs, + }; +} diff --git a/services/automation/moz.build b/services/automation/moz.build new file mode 100644 index 0000000000..61b4815f42 --- /dev/null +++ b/services/automation/moz.build @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Sync") + +EXTRA_COMPONENTS += [ + "AutomationComponents.manifest", +] + +EXTRA_JS_MODULES["services-automation"] += [ + "ServicesAutomation.sys.mjs", +] diff --git a/services/common/app_services_logger/AppServicesLoggerComponents.h b/services/common/app_services_logger/AppServicesLoggerComponents.h new file mode 100644 index 0000000000..015eae230b --- /dev/null +++ b/services/common/app_services_logger/AppServicesLoggerComponents.h @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_services_AppServicesLoggerComponents_h_ +#define mozilla_services_AppServicesLoggerComponents_h_ + +#include "mozIAppServicesLogger.h" +#include "nsCOMPtr.h" + +extern "C" { + +// Implemented in Rust, in the `app_services_logger` crate. +nsresult NS_NewAppServicesLogger(mozIAppServicesLogger** aResult); + +} // extern "C" + +namespace mozilla { +namespace appservices { + +// The C++ constructor for a `services.appServicesLogger` service. This wrapper +// exists because `components.conf` requires a component class constructor to +// return an `already_AddRefed<T>`, but Rust doesn't have such a type. So we +// call the Rust constructor using a `nsCOMPtr` (which is compatible with Rust's +// `xpcom::RefPtr`) out param, and return that. +already_AddRefed<mozIAppServicesLogger> NewLogService() { + nsCOMPtr<mozIAppServicesLogger> logger; + nsresult rv = NS_NewAppServicesLogger(getter_AddRefs(logger)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + return logger.forget(); +} + +} // namespace appservices +} // namespace mozilla + +#endif // mozilla_services_AppServicesLoggerComponents_h_ diff --git a/services/common/app_services_logger/Cargo.toml b/services/common/app_services_logger/Cargo.toml new file mode 100644 index 0000000000..d68c378845 --- /dev/null +++ b/services/common/app_services_logger/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "app_services_logger" +version = "0.1.0" +authors = ["lougeniac64 <lougeniaC64@users.noreply.github.com>"] +edition = "2018" +license = "MPL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cstr = "0.2" +golden_gate = { path = "../../../services/sync/golden_gate" } +log = "0.4" +once_cell = "1.4.0" +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +xpcom = { path = "../../../xpcom/rust/xpcom" } diff --git a/services/common/app_services_logger/components.conf b/services/common/app_services_logger/components.conf new file mode 100644 index 0000000000..cdea60a04b --- /dev/null +++ b/services/common/app_services_logger/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{d2716568-f5fa-4989-91dd-e11599e932a1}', + 'contract_ids': ['@mozilla.org/appservices/logger;1'], + 'type': 'mozIAppServicesLogger', + 'headers': ['mozilla/appservices/AppServicesLoggerComponents.h'], + 'constructor': 'mozilla::appservices::NewLogService', + }, +] diff --git a/services/common/app_services_logger/src/lib.rs b/services/common/app_services_logger/src/lib.rs new file mode 100644 index 0000000000..92125b62c4 --- /dev/null +++ b/services/common/app_services_logger/src/lib.rs @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! This provides a XPCOM service to send app services logs to the desktop + +#[macro_use] +extern crate cstr; + +#[macro_use] +extern crate xpcom; + +use golden_gate::log::LogSink; +use nserror::{nsresult, NS_OK}; +use nsstring::nsAString; +use once_cell::sync::Lazy; +use std::os::raw::c_char; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicBool, Ordering}, + RwLock, + }, +}; +use xpcom::{ + interfaces::{mozIAppServicesLogger, mozIServicesLogSink, nsIObserverService, nsISupports}, + RefPtr, +}; + +/// A flag that's set after we register our observer to clear the map of loggers +/// on shutdown. +static SHUTDOWN_OBSERVED: AtomicBool = AtomicBool::new(false); + +#[xpcom(implement(mozIAppServicesLogger), nonatomic)] +pub struct AppServicesLogger {} + +pub static LOGGERS_BY_TARGET: Lazy<RwLock<HashMap<String, LogSink>>> = Lazy::new(|| { + let h: HashMap<String, LogSink> = HashMap::new(); + let m = RwLock::new(h); + m +}); + +impl AppServicesLogger { + xpcom_method!(register => Register(target: *const nsAString, logger: *const mozIServicesLogSink)); + fn register(&self, target: &nsAString, logger: &mozIServicesLogSink) -> Result<(), nsresult> { + let log_sink_logger = LogSink::with_logger(Some(logger))?; + + ensure_observing_shutdown(); + + LOGGERS_BY_TARGET + .write() + .unwrap() + .insert(target.to_string(), log_sink_logger); + Ok(()) + } + + pub fn is_app_services_logger_registered(target: String) -> bool { + match LOGGERS_BY_TARGET.read() { + Ok(loggers_by_target) => loggers_by_target.contains_key(&target), + Err(_e) => false, + } + } +} + +// Import the `NS_IsMainThread` symbol from Gecko... +extern "C" { + fn NS_IsMainThread() -> bool; +} + +/// Registers an observer to clear the loggers map on `xpcom-shutdown`. This +/// function must be called from the main thread, because the observer service +//// is main thread-only. +fn ensure_observing_shutdown() { + assert!(unsafe { NS_IsMainThread() }); + // If we've already registered our observer, bail. Relaxed ordering is safe + // here and below, because we've asserted we're only called from the main + // thread, and only check the flag here. + if SHUTDOWN_OBSERVED.load(Ordering::Relaxed) { + return; + } + if let Ok(service) = xpcom::components::Observer::service::<nsIObserverService>() { + let observer = ShutdownObserver::allocate(InitShutdownObserver {}); + let rv = unsafe { + service.AddObserver(observer.coerce(), cstr!("xpcom-shutdown").as_ptr(), false) + }; + // If we fail to register the observer now, or fail to get the observer + // service, the flag will remain `false`, and we'll try again on the + // next call to `ensure_observing_shutdown`. + SHUTDOWN_OBSERVED.store(rv.succeeded(), Ordering::Relaxed); + } +} + +#[xpcom(implement(nsIObserver), nonatomic)] +struct ShutdownObserver {} + +impl ShutdownObserver { + xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: *const c_char, _data: *const u16)); + /// Remove our shutdown observer and clear the map. + fn observe( + &self, + _subject: &nsISupports, + topic: *const c_char, + _data: *const u16, + ) -> Result<(), nsresult> { + LOGGERS_BY_TARGET.write().unwrap().clear(); + if let Ok(service) = xpcom::components::Observer::service::<nsIObserverService>() { + // Ignore errors, since we're already shutting down. + let _ = unsafe { service.RemoveObserver(self.coerce(), topic) }; + } + Ok(()) + } +} + +/// The constructor for an `AppServicesLogger` service. This uses C linkage so that it +/// can be called from C++. See `AppServicesLoggerComponents.h` for the C++ +/// constructor that's passed to the component manager. +/// +/// # Safety +/// +/// This function is unsafe because it dereferences `result`. +#[no_mangle] +pub unsafe extern "C" fn NS_NewAppServicesLogger( + result: *mut *const mozIAppServicesLogger, +) -> nsresult { + let logger = AppServicesLogger::allocate(InitAppServicesLogger {}); + RefPtr::new(logger.coerce::<mozIAppServicesLogger>()).forget(&mut *result); + NS_OK +} diff --git a/services/common/async.sys.mjs b/services/common/async.sys.mjs new file mode 100644 index 0000000000..564b46a071 --- /dev/null +++ b/services/common/async.sys.mjs @@ -0,0 +1,301 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer"); + +/* + * Helpers for various async operations. + */ +export var Async = { + /** + * Execute an arbitrary number of asynchronous functions one after the + * other, passing the callback arguments on to the next one. All functions + * must take a callback function as their last argument. The 'this' object + * will be whatever chain()'s is. + * + * @usage this._chain = Async.chain; + * this._chain(this.foo, this.bar, this.baz)(args, for, foo) + * + * This is equivalent to: + * + * let self = this; + * self.foo(args, for, foo, function (bars, args) { + * self.bar(bars, args, function (baz, params) { + * self.baz(baz, params); + * }); + * }); + */ + chain: function chain(...funcs) { + let thisObj = this; + return function callback() { + if (funcs.length) { + let args = [...arguments, callback]; + let f = funcs.shift(); + f.apply(thisObj, args); + } + }; + }, + + /** + * Check if the app is still ready (not quitting). Returns true, or throws an + * exception if not ready. + */ + checkAppReady: function checkAppReady() { + // Watch for app-quit notification to stop any sync calls + Services.obs.addObserver(function onQuitApplication() { + Services.obs.removeObserver(onQuitApplication, "quit-application"); + Async.checkAppReady = Async.promiseYield = function () { + let exception = Components.Exception( + "App. Quitting", + Cr.NS_ERROR_ABORT + ); + exception.appIsShuttingDown = true; + throw exception; + }; + }, "quit-application"); + // In the common case, checkAppReady just returns true + return (Async.checkAppReady = function () { + return true; + })(); + }, + + /** + * Check if the app is still ready (not quitting). Returns true if the app + * is ready, or false if it is being shut down. + */ + isAppReady() { + try { + return Async.checkAppReady(); + } catch (ex) { + if (!Async.isShutdownException(ex)) { + throw ex; + } + } + return false; + }, + + /** + * Check if the passed exception is one raised by checkAppReady. Typically + * this will be used in exception handlers to allow such exceptions to + * make their way to the top frame and allow the app to actually terminate. + */ + isShutdownException(exception) { + return exception && exception.appIsShuttingDown === true; + }, + + /** + * A "tight loop" of promises can still lock up the browser for some time. + * Periodically waiting for a promise returned by this function will solve + * that. + * You should probably not use this method directly and instead use jankYielder + * below. + * Some reference here: + * - https://gist.github.com/jesstelford/bbb30b983bddaa6e5fef2eb867d37678 + * - https://bugzilla.mozilla.org/show_bug.cgi?id=1094248 + */ + promiseYield() { + return new Promise(resolve => { + Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL); + }); + }, + + /** + * Shared state for yielding every N calls. + * + * Can be passed to multiple Async.yieldingForEach to have them overall yield + * every N iterations. + */ + yieldState(yieldEvery = 50) { + let iterations = 0; + + return { + shouldYield() { + ++iterations; + return iterations % yieldEvery === 0; + }, + }; + }, + + /** + * Apply the given function to each element of the iterable, yielding the + * event loop every yieldEvery iterations. + * + * @param iterable {Iterable} + * The iterable or iterator to iterate through. + * + * @param fn {(*) -> void|boolean} + * The function to be called on each element of the iterable. + * + * Returning true from the function will stop the iteration. + * + * @param [yieldEvery = 50] {number|object} + * The number of iterations to complete before yielding back to the event + * loop. + * + * @return {boolean} + * Whether or not the function returned early. + */ + async yieldingForEach(iterable, fn, yieldEvery = 50) { + const yieldState = + typeof yieldEvery === "number" + ? Async.yieldState(yieldEvery) + : yieldEvery; + let iteration = 0; + + for (const item of iterable) { + let result = fn(item, iteration++); + if (typeof result !== "undefined" && typeof result.then !== "undefined") { + // If we await result when it is not a Promise, we create an + // automatically resolved promise, which is exactly the case that we + // are trying to avoid. + result = await result; + } + + if (result === true) { + return true; + } + + if (yieldState.shouldYield()) { + await Async.promiseYield(); + Async.checkAppReady(); + } + } + + return false; + }, + + asyncQueueCaller(log) { + return new AsyncQueueCaller(log); + }, + + asyncObserver(log, obj) { + return new AsyncObserver(log, obj); + }, + + watchdog() { + return new Watchdog(); + }, +}; + +/** + * Allows consumers to enqueue asynchronous callbacks to be called in order. + * Typically this is used when providing a callback to a caller that doesn't + * await on promises. + */ +class AsyncQueueCaller { + constructor(log) { + this._log = log; + this._queue = Promise.resolve(); + this.QueryInterface = ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]); + } + + /** + * /!\ Never await on another function that calls enqueueCall /!\ + * on the same queue or we will deadlock. + */ + enqueueCall(func) { + this._queue = (async () => { + await this._queue; + try { + return await func(); + } catch (e) { + this._log.error(e); + return false; + } + })(); + } + + promiseCallsComplete() { + return this._queue; + } +} + +/* + * Subclass of AsyncQueueCaller that can be used with Services.obs directly. + * When this observe() is called, it will enqueue a call to the consumers's + * observe(). + */ +class AsyncObserver extends AsyncQueueCaller { + constructor(obj, log) { + super(log); + this.obj = obj; + } + + observe(subject, topic, data) { + this.enqueueCall(() => this.obj.observe(subject, topic, data)); + } + + promiseObserversComplete() { + return this.promiseCallsComplete(); + } +} + +/** + * Woof! Signals an operation to abort, either at shutdown or after a timeout. + * The buffered engine uses this to abort long-running merges, so that they + * don't prevent Firefox from quitting, or block future syncs. + */ +class Watchdog { + constructor() { + this.controller = new AbortController(); + this.timer = new Timer(); + + /** + * The reason for signaling an abort. `null` if not signaled, + * `"timeout"` if the watchdog timer fired, or `"shutdown"` if the app is + * is quitting. + * + * @type {String?} + */ + this.abortReason = null; + } + + /** + * Returns the abort signal for this watchdog. This can be passed to APIs + * that take a signal for cancellation, like `SyncedBookmarksMirror::apply` + * or `fetch`. + * + * @type {AbortSignal} + */ + get signal() { + return this.controller.signal; + } + + /** + * Starts the watchdog timer, and listens for the app quitting. + * + * @param {Number} delay + * The time to wait before signaling the operation to abort. + */ + start(delay) { + if (!this.signal.aborted) { + Services.obs.addObserver(this, "quit-application"); + this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT); + } + } + + /** + * Stops the watchdog timer and removes any listeners. This should be called + * after the operation finishes. + */ + stop() { + if (!this.signal.aborted) { + Services.obs.removeObserver(this, "quit-application"); + this.timer.cancel(); + } + } + + observe(subject, topic, data) { + if (topic == "timer-callback") { + this.abortReason = "timeout"; + } else if (topic == "quit-application") { + this.abortReason = "shutdown"; + } + this.stop(); + this.controller.abort(); + } +} diff --git a/services/common/hawkclient.sys.mjs b/services/common/hawkclient.sys.mjs new file mode 100644 index 0000000000..05a3fa7336 --- /dev/null +++ b/services/common/hawkclient.sys.mjs @@ -0,0 +1,336 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * HAWK is an HTTP authentication scheme using a message authentication code + * (MAC) algorithm to provide partial HTTP request cryptographic verification. + * + * For details, see: https://github.com/hueniverse/hawk + * + * With HAWK, it is essential that the clocks on clients and server not have an + * absolute delta of greater than one minute, as the HAWK protocol uses + * timestamps to reduce the possibility of replay attacks. However, it is + * likely that some clients' clocks will be more than a little off, especially + * in mobile devices, which would break HAWK-based services (like sync and + * firefox accounts) for those clients. + * + * This library provides a stateful HAWK client that calculates (roughly) the + * clock delta on the client vs the server. The library provides an interface + * for deriving HAWK credentials and making HAWK-authenticated REST requests to + * a single remote server. Therefore, callers who want to interact with + * multiple HAWK services should instantiate one HawkClient per service. + */ + +import { HAWKAuthenticatedRESTRequest } from "resource://services-common/hawkrequest.sys.mjs"; + +import { Observers } from "resource://services-common/observers.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +// log.appender.dump should be one of "Fatal", "Error", "Warn", "Info", "Config", +// "Debug", "Trace" or "All". If none is specified, "Error" will be used by +// default. +// Note however that Sync will also add this log to *its* DumpAppender, so +// in a Sync context it shouldn't be necessary to adjust this - however, that +// also means error logs are likely to be dump'd twice but that's OK. +const PREF_LOG_LEVEL = "services.common.hawk.log.appender.dump"; + +// A pref that can be set so "sensitive" information (eg, personally +// identifiable info, credentials, etc) will be logged. +const PREF_LOG_SENSITIVE_DETAILS = "services.common.hawk.log.sensitive"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "log", function () { + let log = Log.repository.getLogger("Hawk"); + // We set the log itself to "debug" and set the level from the preference to + // the appender. This allows other things to send the logs to different + // appenders, while still allowing the pref to control what is seen via dump() + log.level = Log.Level.Debug; + let appender = new Log.DumpAppender(); + log.addAppender(appender); + appender.level = Log.Level.Error; + try { + let level = + Services.prefs.getPrefType(PREF_LOG_LEVEL) == + Ci.nsIPrefBranch.PREF_STRING && + Services.prefs.getStringPref(PREF_LOG_LEVEL); + appender.level = Log.Level[level] || Log.Level.Error; + } catch (e) { + log.error(e); + } + + return log; +}); + +// A boolean to indicate if personally identifiable information (or anything +// else sensitive, such as credentials) should be logged. +ChromeUtils.defineLazyGetter(lazy, "logPII", function () { + try { + return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS); + } catch (_) { + return false; + } +}); + +/* + * A general purpose client for making HAWK authenticated requests to a single + * host. Keeps track of the clock offset between the client and the host for + * computation of the timestamp in the HAWK Authorization header. + * + * Clients should create one HawkClient object per each server they wish to + * interact with. + * + * @param host + * The url of the host + */ +export var HawkClient = function (host) { + this.host = host; + + // Clock offset in milliseconds between our client's clock and the date + // reported in responses from our host. + this._localtimeOffsetMsec = 0; +}; + +HawkClient.prototype = { + /* + * Construct an error message for a response. Private. + * + * @param restResponse + * A RESTResponse object from a RESTRequest + * + * @param error + * A string or object describing the error + */ + _constructError(restResponse, error) { + let errorObj = { + error, + // This object is likely to be JSON.stringify'd, but neither Error() + // objects nor Components.Exception objects do the right thing there, + // so we add a new element which is simply the .toString() version of + // the error object, so it does appear in JSON'd values. + errorString: error.toString(), + message: restResponse.statusText, + code: restResponse.status, + errno: restResponse.status, + toString() { + return this.code + ": " + this.message; + }, + }; + let retryAfter = + restResponse.headers && restResponse.headers["retry-after"]; + retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter; + if (retryAfter) { + errorObj.retryAfter = retryAfter; + // and notify observers of the retry interval + if (this.observerPrefix) { + Observers.notify(this.observerPrefix + ":backoff:interval", retryAfter); + } + } + return errorObj; + }, + + /* + * + * Update clock offset by determining difference from date gives in the (RFC + * 1123) Date header of a server response. Because HAWK tolerates a window + * of one minute of clock skew (so two minutes total since the skew can be + * positive or negative), the simple method of calculating offset here is + * probably good enough. We keep the value in milliseconds to make life + * easier, even though the value will not have millisecond accuracy. + * + * @param dateString + * An RFC 1123 date string (e.g., "Mon, 13 Jan 2014 21:45:06 GMT") + * + * For HAWK clock skew and replay protection, see + * https://github.com/hueniverse/hawk#replay-protection + */ + _updateClockOffset(dateString) { + try { + let serverDateMsec = Date.parse(dateString); + this._localtimeOffsetMsec = serverDateMsec - this.now(); + lazy.log.debug( + "Clock offset vs " + this.host + ": " + this._localtimeOffsetMsec + ); + } catch (err) { + lazy.log.warn("Bad date header in server response: " + dateString); + } + }, + + /* + * Get the current clock offset in milliseconds. + * + * The offset is the number of milliseconds that must be added to the client + * clock to make it equal to the server clock. For example, if the client is + * five minutes ahead of the server, the localtimeOffsetMsec will be -300000. + */ + get localtimeOffsetMsec() { + return this._localtimeOffsetMsec; + }, + + /* + * return current time in milliseconds + */ + now() { + return Date.now(); + }, + + /* A general method for sending raw RESTRequest calls authorized using HAWK + * + * @param path + * API endpoint path + * @param method + * The HTTP request method + * @param credentials + * Hawk credentials + * @param payloadObj + * An object that can be encodable as JSON as the payload of the + * request + * @param extraHeaders + * An object with header/value pairs to send with the request. + * @return Promise + * Returns a promise that resolves to the response of the API call, + * or is rejected with an error. If the server response can be parsed + * as JSON and contains an 'error' property, the promise will be + * rejected with this JSON-parsed response. + */ + async request( + path, + method, + credentials = null, + payloadObj = {}, + extraHeaders = {}, + retryOK = true + ) { + method = method.toLowerCase(); + + let uri = this.host + path; + + let extra = { + now: this.now(), + localtimeOffsetMsec: this.localtimeOffsetMsec, + headers: extraHeaders, + }; + + let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra); + let error; + let restResponse = await request[method](payloadObj).catch(e => { + // Keep a reference to the error, log a message about it, and return the + // response anyway. + error = e; + lazy.log.warn("hawk request error", error); + return request.response; + }); + + // This shouldn't happen anymore, but it's not exactly difficult to handle. + if (!restResponse) { + throw error; + } + + let status = restResponse.status; + + lazy.log.debug( + "(Response) " + + path + + ": code: " + + status + + " - Status text: " + + restResponse.statusText + ); + if (lazy.logPII) { + lazy.log.debug("Response text", restResponse.body); + } + + // All responses may have backoff headers, which are a server-side safety + // valve to allow slowing down clients without hurting performance. + this._maybeNotifyBackoff(restResponse, "x-weave-backoff"); + this._maybeNotifyBackoff(restResponse, "x-backoff"); + + if (error) { + // When things really blow up, reconstruct an error object that follows + // the general format of the server on error responses. + throw this._constructError(restResponse, error); + } + + this._updateClockOffset(restResponse.headers.date); + + if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) { + // Retry once if we were rejected due to a bad timestamp. + // Clock offset is adjusted already in the top of this function. + lazy.log.debug("Received 401 for " + path + ": retrying"); + return this.request( + path, + method, + credentials, + payloadObj, + extraHeaders, + false + ); + } + + // If the server returned a json error message, use it in the rejection + // of the promise. + // + // In the case of a 401, in which we are probably being rejected for a + // bad timestamp, retry exactly once, during which time clock offset will + // be adjusted. + + let jsonResponse = {}; + try { + jsonResponse = JSON.parse(restResponse.body); + } catch (notJSON) {} + + let okResponse = 200 <= status && status < 300; + if (!okResponse || jsonResponse.error) { + if (jsonResponse.error) { + throw jsonResponse; + } + throw this._constructError(restResponse, "Request failed"); + } + + // It's up to the caller to know how to decode the response. + // We just return the whole response. + return restResponse; + }, + + /* + * The prefix used for all notifications sent by this module. This + * allows the handler of notifications to be sure they are handling + * notifications for the service they expect. + * + * If not set, no notifications will be sent. + */ + observerPrefix: null, + + // Given an optional header value, notify that a backoff has been requested. + _maybeNotifyBackoff(response, headerName) { + if (!this.observerPrefix || !response.headers) { + return; + } + let headerVal = response.headers[headerName]; + if (!headerVal) { + return; + } + let backoffInterval; + try { + backoffInterval = parseInt(headerVal, 10); + } catch (ex) { + lazy.log.error( + "hawkclient response had invalid backoff value in '" + + headerName + + "' header: " + + headerVal + ); + return; + } + Observers.notify( + this.observerPrefix + ":backoff:interval", + backoffInterval + ); + }, + + // override points for testing. + newHAWKAuthenticatedRESTRequest(uri, credentials, extra) { + return new HAWKAuthenticatedRESTRequest(uri, credentials, extra); + }, +}; diff --git a/services/common/hawkrequest.sys.mjs b/services/common/hawkrequest.sys.mjs new file mode 100644 index 0000000000..a856ef032d --- /dev/null +++ b/services/common/hawkrequest.sys.mjs @@ -0,0 +1,197 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { RESTRequest } from "resource://services-common/rest.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { Credentials } from "resource://gre/modules/Credentials.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CryptoUtils: "resource://services-crypto/utils.sys.mjs", +}); + +/** + * Single-use HAWK-authenticated HTTP requests to RESTish resources. + * + * @param uri + * (String) URI for the RESTRequest constructor + * + * @param credentials + * (Object) Optional credentials for computing HAWK authentication + * header. + * + * @param payloadObj + * (Object) Optional object to be converted to JSON payload + * + * @param extra + * (Object) Optional extra params for HAWK header computation. + * Valid properties are: + * + * now: <current time in milliseconds>, + * localtimeOffsetMsec: <local clock offset vs server>, + * headers: <An object with header/value pairs to be sent + * as headers on the request> + * + * extra.localtimeOffsetMsec is the value in milliseconds that must be added to + * the local clock to make it agree with the server's clock. For instance, if + * the local clock is two minutes ahead of the server, the time offset in + * milliseconds will be -120000. + */ + +export var HAWKAuthenticatedRESTRequest = function HawkAuthenticatedRESTRequest( + uri, + credentials, + extra = {} +) { + RESTRequest.call(this, uri); + + this.credentials = credentials; + this.now = extra.now || Date.now(); + this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0; + this._log.trace( + "local time, offset: " + this.now + ", " + this.localtimeOffsetMsec + ); + this.extraHeaders = extra.headers || {}; + + // Expose for testing + this._intl = getIntl(); +}; + +HAWKAuthenticatedRESTRequest.prototype = { + async dispatch(method, data) { + let contentType = "text/plain"; + if (method == "POST" || method == "PUT" || method == "PATCH") { + contentType = "application/json"; + } + if (this.credentials) { + let options = { + now: this.now, + localtimeOffsetMsec: this.localtimeOffsetMsec, + credentials: this.credentials, + payload: (data && JSON.stringify(data)) || "", + contentType, + }; + let header = await lazy.CryptoUtils.computeHAWK( + this.uri, + method, + options + ); + this.setHeader("Authorization", header.field); + } + + for (let header in this.extraHeaders) { + this.setHeader(header, this.extraHeaders[header]); + } + + this.setHeader("Content-Type", contentType); + + this.setHeader("Accept-Language", this._intl.accept_languages); + + return super.dispatch(method, data); + }, +}; + +Object.setPrototypeOf( + HAWKAuthenticatedRESTRequest.prototype, + RESTRequest.prototype +); + +/** + * Generic function to derive Hawk credentials. + * + * Hawk credentials are derived using shared secrets, which depend on the token + * in use. + * + * @param tokenHex + * The current session token encoded in hex + * @param context + * A context for the credentials. A protocol version will be prepended + * to the context, see Credentials.keyWord for more information. + * @param size + * The size in bytes of the expected derived buffer, + * defaults to 3 * 32. + * @return credentials + * Returns an object: + * { + * id: the Hawk id (from the first 32 bytes derived) + * key: the Hawk key (from bytes 32 to 64) + * extra: size - 64 extra bytes (if size > 64) + * } + */ +export async function deriveHawkCredentials(tokenHex, context, size = 96) { + let token = CommonUtils.hexToBytes(tokenHex); + let out = await lazy.CryptoUtils.hkdfLegacy( + token, + undefined, + Credentials.keyWord(context), + size + ); + + let result = { + key: out.slice(32, 64), + id: CommonUtils.bytesAsHex(out.slice(0, 32)), + }; + if (size > 64) { + result.extra = out.slice(64); + } + + return result; +} + +// With hawk request, we send the user's accepted-languages with each request. +// To keep the number of times we read this pref at a minimum, maintain the +// preference in a stateful object that notices and updates itself when the +// pref is changed. +function Intl() { + // We won't actually query the pref until the first time we need it + this._accepted = ""; + this._everRead = false; + this.init(); +} + +Intl.prototype = { + init() { + Services.prefs.addObserver("intl.accept_languages", this); + }, + + uninit() { + Services.prefs.removeObserver("intl.accept_languages", this); + }, + + observe(subject, topic, data) { + this.readPref(); + }, + + readPref() { + this._everRead = true; + try { + this._accepted = Services.prefs.getComplexValue( + "intl.accept_languages", + Ci.nsIPrefLocalizedString + ).data; + } catch (err) { + let log = Log.repository.getLogger("Services.Common.RESTRequest"); + log.error("Error reading intl.accept_languages pref", err); + } + }, + + get accept_languages() { + if (!this._everRead) { + this.readPref(); + } + return this._accepted; + }, +}; + +// Singleton getter for Intl, creating an instance only when we first need it. +var intl = null; +function getIntl() { + if (!intl) { + intl = new Intl(); + } + return intl; +} diff --git a/services/common/kinto-http-client.sys.mjs b/services/common/kinto-http-client.sys.mjs new file mode 100644 index 0000000000..6ccaa520c1 --- /dev/null +++ b/services/common/kinto-http-client.sys.mjs @@ -0,0 +1,3061 @@ +/* + * + * 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. + * + * This file is generated from kinto.js - do not modify directly. + */ + +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +/* + * Version 15.0.0 - c8775d9 + */ + +import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, + r = + c < 3 + ? target + : desc === null + ? (desc = Object.getOwnPropertyDescriptor(target, key)) + : desc, + d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") + r = Reflect.decorate(decorators, target, key, desc); + else + for (var i = decorators.length - 1; i >= 0; i--) + if ((d = decorators[i])) + r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +/** + * Chunks an array into n pieces. + * + * @private + * @param {Array} array + * @param {Number} n + * @return {Array} + */ +function partition(array, n) { + if (n <= 0) { + return [array]; + } + return array.reduce((acc, x, i) => { + if (i === 0 || i % n === 0) { + acc.push([x]); + } else { + acc[acc.length - 1].push(x); + } + return acc; + }, []); +} +/** + * Returns a Promise always resolving after the specified amount in milliseconds. + * + * @return Promise<void> + */ +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +/** + * Always returns a resource data object from the provided argument. + * + * @private + * @param {Object|String} resource + * @return {Object} + */ +function toDataBody(resource) { + if (isObject(resource)) { + return resource; + } + if (typeof resource === "string") { + return { id: resource }; + } + throw new Error("Invalid argument."); +} +/** + * Transforms an object into an URL query string, stripping out any undefined + * values. + * + * @param {Object} obj + * @return {String} + */ +function qsify(obj) { + const encode = v => + encodeURIComponent(typeof v === "boolean" ? String(v) : v); + const stripped = cleanUndefinedProperties(obj); + return Object.keys(stripped) + .map(k => { + const ks = encode(k) + "="; + if (Array.isArray(stripped[k])) { + return ks + stripped[k].map(v => encode(v)).join(","); + } + return ks + encode(stripped[k]); + }) + .join("&"); +} +/** + * Checks if a version is within the provided range. + * + * @param {String} version The version to check. + * @param {String} minVersion The minimum supported version (inclusive). + * @param {String} maxVersion The minimum supported version (exclusive). + * @throws {Error} If the version is outside of the provided range. + */ +function checkVersion(version, minVersion, maxVersion) { + const extract = str => str.split(".").map(x => parseInt(x, 10)); + const [verMajor, verMinor] = extract(version); + const [minMajor, minMinor] = extract(minVersion); + const [maxMajor, maxMinor] = extract(maxVersion); + const checks = [ + verMajor < minMajor, + verMajor === minMajor && verMinor < minMinor, + verMajor > maxMajor, + verMajor === maxMajor && verMinor >= maxMinor, + ]; + if (checks.some(x => x)) { + throw new Error( + `Version ${version} doesn't satisfy ${minVersion} <= x < ${maxVersion}` + ); + } +} +/** + * Generates a decorator function ensuring a version check is performed against + * the provided requirements before executing it. + * + * @param {String} min The required min version (inclusive). + * @param {String} max The required max version (inclusive). + * @return {Function} + */ +function support(min, max) { + return function ( + // @ts-ignore + target, + key, + descriptor + ) { + const fn = descriptor.value; + return { + configurable: true, + get() { + const wrappedMethod = (...args) => { + // "this" is the current instance which its method is decorated. + const client = this.client ? this.client : this; + return client + .fetchHTTPApiVersion() + .then(version => checkVersion(version, min, max)) + .then(() => fn.apply(this, args)); + }; + Object.defineProperty(this, key, { + value: wrappedMethod, + configurable: true, + writable: true, + }); + return wrappedMethod; + }, + }; + }; +} +/** + * Generates a decorator function ensuring that the specified capabilities are + * available on the server before executing it. + * + * @param {Array<String>} capabilities The required capabilities. + * @return {Function} + */ +function capable(capabilities) { + return function ( + // @ts-ignore + target, + key, + descriptor + ) { + const fn = descriptor.value; + return { + configurable: true, + get() { + const wrappedMethod = (...args) => { + // "this" is the current instance which its method is decorated. + const client = this.client ? this.client : this; + return client + .fetchServerCapabilities() + .then(available => { + const missing = capabilities.filter(c => !(c in available)); + if (missing.length) { + const missingStr = missing.join(", "); + throw new Error( + `Required capabilities ${missingStr} not present on server` + ); + } + }) + .then(() => fn.apply(this, args)); + }; + Object.defineProperty(this, key, { + value: wrappedMethod, + configurable: true, + writable: true, + }); + return wrappedMethod; + }, + }; + }; +} +/** + * Generates a decorator function ensuring an operation is not performed from + * within a batch request. + * + * @param {String} message The error message to throw. + * @return {Function} + */ +function nobatch(message) { + return function ( + // @ts-ignore + target, + key, + descriptor + ) { + const fn = descriptor.value; + return { + configurable: true, + get() { + const wrappedMethod = (...args) => { + // "this" is the current instance which its method is decorated. + if (this._isBatch) { + throw new Error(message); + } + return fn.apply(this, args); + }; + Object.defineProperty(this, key, { + value: wrappedMethod, + configurable: true, + writable: true, + }); + return wrappedMethod; + }, + }; + }; +} +/** + * Returns true if the specified value is an object (i.e. not an array nor null). + * @param {Object} thing The value to inspect. + * @return {bool} + */ +function isObject(thing) { + return typeof thing === "object" && thing !== null && !Array.isArray(thing); +} +/** + * Parses a data url. + * @param {String} dataURL The data url. + * @return {Object} + */ +function parseDataURL(dataURL) { + const regex = /^data:(.*);base64,(.*)/; + const match = dataURL.match(regex); + if (!match) { + throw new Error(`Invalid data-url: ${String(dataURL).substring(0, 32)}...`); + } + const props = match[1]; + const base64 = match[2]; + const [type, ...rawParams] = props.split(";"); + const params = rawParams.reduce((acc, param) => { + const [key, value] = param.split("="); + return { ...acc, [key]: value }; + }, {}); + return { ...params, type, base64 }; +} +/** + * Extracts file information from a data url. + * @param {String} dataURL The data url. + * @return {Object} + */ +function extractFileInfo(dataURL) { + const { name, type, base64 } = parseDataURL(dataURL); + const binary = atob(base64); + const array = []; + for (let i = 0; i < binary.length; i++) { + array.push(binary.charCodeAt(i)); + } + const blob = new Blob([new Uint8Array(array)], { type }); + return { blob, name }; +} +/** + * Creates a FormData instance from a data url and an existing JSON response + * body. + * @param {String} dataURL The data url. + * @param {Object} body The response body. + * @param {Object} [options={}] The options object. + * @param {Object} [options.filename] Force attachment file name. + * @return {FormData} + */ +function createFormData(dataURL, body, options = {}) { + const { filename = "untitled" } = options; + const { blob, name } = extractFileInfo(dataURL); + const formData = new FormData(); + formData.append("attachment", blob, name || filename); + for (const property in body) { + if (typeof body[property] !== "undefined") { + formData.append(property, JSON.stringify(body[property])); + } + } + return formData; +} +/** + * Clones an object with all its undefined keys removed. + * @private + */ +function cleanUndefinedProperties(obj) { + const result = {}; + for (const key in obj) { + if (typeof obj[key] !== "undefined") { + result[key] = obj[key]; + } + } + return result; +} +/** + * Handle common query parameters for Kinto requests. + * + * @param {String} [path] The endpoint base path. + * @param {Array} [options.fields] Fields to limit the + * request to. + * @param {Object} [options.query={}] Additional query arguments. + */ +function addEndpointOptions(path, options = {}) { + const query = { ...options.query }; + if (options.fields) { + query._fields = options.fields; + } + const queryString = qsify(query); + if (queryString) { + return path + "?" + queryString; + } + return path; +} +/** + * Replace authorization header with an obscured version + */ +function obscureAuthorizationHeader(headers) { + const h = new Headers(headers); + if (h.has("authorization")) { + h.set("authorization", "**** (suppressed)"); + } + const obscuredHeaders = {}; + for (const [header, value] of h.entries()) { + obscuredHeaders[header] = value; + } + return obscuredHeaders; +} + +/** + * Kinto server error code descriptors. + */ +const ERROR_CODES = { + 104: "Missing Authorization Token", + 105: "Invalid Authorization Token", + 106: "Request body was not valid JSON", + 107: "Invalid request parameter", + 108: "Missing request parameter", + 109: "Invalid posted data", + 110: "Invalid Token / id", + 111: "Missing Token / id", + 112: "Content-Length header was not provided", + 113: "Request body too large", + 114: "Resource was created, updated or deleted meanwhile", + 115: "Method not allowed on this end point (hint: server may be readonly)", + 116: "Requested version not available on this server", + 117: "Client has sent too many requests", + 121: "Resource access is forbidden for this user", + 122: "Another resource violates constraint", + 201: "Service Temporary unavailable due to high load", + 202: "Service deprecated", + 999: "Internal Server Error", +}; +class NetworkTimeoutError extends Error { + constructor(url, options) { + super( + `Timeout while trying to access ${url} with ${JSON.stringify(options)}` + ); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, NetworkTimeoutError); + } + this.url = url; + this.options = options; + } +} +class UnparseableResponseError extends Error { + constructor(response, body, error) { + const { status } = response; + super( + `Response from server unparseable (HTTP ${ + status || 0 + }; ${error}): ${body}` + ); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, UnparseableResponseError); + } + this.status = status; + this.response = response; + this.stack = error.stack; + this.error = error; + } +} +/** + * "Error" subclass representing a >=400 response from the server. + * + * Whether or not this is an error depends on your application. + * + * The `json` field can be undefined if the server responded with an + * empty response body. This shouldn't generally happen. Most "bad" + * responses come with a JSON error description, or (if they're + * fronted by a CDN or nginx or something) occasionally non-JSON + * responses (which become UnparseableResponseErrors, above). + */ +class ServerResponse extends Error { + constructor(response, json) { + const { status } = response; + let { statusText } = response; + let errnoMsg; + if (json) { + // Try to fill in information from the JSON error. + statusText = json.error || statusText; + // Take errnoMsg from either ERROR_CODES or json.message. + if (json.errno && json.errno in ERROR_CODES) { + errnoMsg = ERROR_CODES[json.errno]; + } else if (json.message) { + errnoMsg = json.message; + } + // If we had both ERROR_CODES and json.message, and they differ, + // combine them. + if (errnoMsg && json.message && json.message !== errnoMsg) { + errnoMsg += ` (${json.message})`; + } + } + let message = `HTTP ${status} ${statusText}`; + if (errnoMsg) { + message += `: ${errnoMsg}`; + } + super(message.trim()); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ServerResponse); + } + this.response = response; + this.data = json; + } +} + +var errors = /*#__PURE__*/ Object.freeze({ + __proto__: null, + NetworkTimeoutError, + ServerResponse, + UnparseableResponseError, + default: ERROR_CODES, +}); + +/** + * Enhanced HTTP client for the Kinto protocol. + * @private + */ +class HTTP { + /** + * Default HTTP request headers applied to each outgoing request. + * + * @type {Object} + */ + static get DEFAULT_REQUEST_HEADERS() { + return { + Accept: "application/json", + "Content-Type": "application/json", + }; + } + /** + * Default options. + * + * @type {Object} + */ + static get defaultOptions() { + return { timeout: null, requestMode: "cors" }; + } + /** + * Constructor. + * + * @param {EventEmitter} events The event handler. + * @param {Object} [options={}} The options object. + * @param {Number} [options.timeout=null] The request timeout in ms, if any (default: `null`). + * @param {String} [options.requestMode="cors"] The HTTP request mode (default: `"cors"`). + */ + constructor(events, options = {}) { + // public properties + /** + * The event emitter instance. + * @type {EventEmitter} + */ + this.events = events; + /** + * The request mode. + * @see https://fetch.spec.whatwg.org/#requestmode + * @type {String} + */ + this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode; + /** + * The request timeout. + * @type {Number} + */ + this.timeout = options.timeout || HTTP.defaultOptions.timeout; + /** + * The fetch() function. + * @type {Function} + */ + this.fetchFunc = options.fetchFunc || globalThis.fetch.bind(globalThis); + } + /** + * @private + */ + timedFetch(url, options) { + let hasTimedout = false; + return new Promise((resolve, reject) => { + // Detect if a request has timed out. + let _timeoutId; + if (this.timeout) { + _timeoutId = setTimeout(() => { + hasTimedout = true; + if (options && options.headers) { + options = { + ...options, + headers: obscureAuthorizationHeader(options.headers), + }; + } + reject(new NetworkTimeoutError(url, options)); + }, this.timeout); + } + function proceedWithHandler(fn) { + return arg => { + if (!hasTimedout) { + if (_timeoutId) { + clearTimeout(_timeoutId); + } + fn(arg); + } + }; + } + this.fetchFunc(url, options) + .then(proceedWithHandler(resolve)) + .catch(proceedWithHandler(reject)); + }); + } + /** + * @private + */ + async processResponse(response) { + const { status, headers } = response; + const text = await response.text(); + // Check if we have a body; if so parse it as JSON. + let json; + if (text.length !== 0) { + try { + json = JSON.parse(text); + } catch (err) { + throw new UnparseableResponseError(response, text, err); + } + } + if (status >= 400) { + throw new ServerResponse(response, json); + } + return { status, json: json, headers }; + } + /** + * @private + */ + async retry(url, retryAfter, request, options) { + await delay(retryAfter); + return this.request(url, request, { + ...options, + retry: options.retry - 1, + }); + } + /** + * Performs an HTTP request to the Kinto server. + * + * Resolves with an objet containing the following HTTP response properties: + * - `{Number} status` The HTTP status code. + * - `{Object} json` The JSON response body. + * - `{Headers} headers` The response headers object; see the ES6 fetch() spec. + * + * @param {String} url The URL. + * @param {Object} [request={}] The request object, passed to + * fetch() as its options object. + * @param {Object} [request.headers] The request headers object (default: {}) + * @param {Object} [options={}] Options for making the + * request + * @param {Number} [options.retry] Number of retries (default: 0) + * @return {Promise} + */ + async request(url, request = { headers: {} }, options = { retry: 0 }) { + // Ensure default request headers are always set + request.headers = { ...HTTP.DEFAULT_REQUEST_HEADERS, ...request.headers }; + // If a multipart body is provided, remove any custom Content-Type header as + // the fetch() implementation will add the correct one for us. + if (request.body && request.body instanceof FormData) { + if (request.headers instanceof Headers) { + request.headers.delete("Content-Type"); + } else if (!Array.isArray(request.headers)) { + delete request.headers["Content-Type"]; + } + } + request.mode = this.requestMode; + const response = await this.timedFetch(url, request); + const { headers } = response; + this._checkForDeprecationHeader(headers); + this._checkForBackoffHeader(headers); + // Check if the server summons the client to retry after a while. + const retryAfter = this._checkForRetryAfterHeader(headers); + // If number of allowed of retries is not exhausted, retry the same request. + if (retryAfter && options.retry > 0) { + return this.retry(url, retryAfter, request, options); + } + return this.processResponse(response); + } + _checkForDeprecationHeader(headers) { + const alertHeader = headers.get("Alert"); + if (!alertHeader) { + return; + } + let alert; + try { + alert = JSON.parse(alertHeader); + } catch (err) { + console.warn("Unable to parse Alert header message", alertHeader); + return; + } + console.warn(alert.message, alert.url); + if (this.events) { + this.events.emit("deprecated", alert); + } + } + _checkForBackoffHeader(headers) { + let backoffMs; + const backoffHeader = headers.get("Backoff"); + const backoffSeconds = backoffHeader ? parseInt(backoffHeader, 10) : 0; + if (backoffSeconds > 0) { + backoffMs = new Date().getTime() + backoffSeconds * 1000; + } else { + backoffMs = 0; + } + if (this.events) { + this.events.emit("backoff", backoffMs); + } + } + _checkForRetryAfterHeader(headers) { + const retryAfter = headers.get("Retry-After"); + if (!retryAfter) { + return null; + } + const delay = parseInt(retryAfter, 10) * 1000; + const tryAgainAfter = new Date().getTime() + delay; + if (this.events) { + this.events.emit("retry-after", tryAgainAfter); + } + return delay; + } +} + +/** + * Endpoints templates. + * @type {Object} + */ +const ENDPOINTS = { + root: () => "/", + batch: () => "/batch", + permissions: () => "/permissions", + bucket: bucket => "/buckets" + (bucket ? `/${bucket}` : ""), + history: bucket => `${ENDPOINTS.bucket(bucket)}/history`, + collection: (bucket, coll) => + `${ENDPOINTS.bucket(bucket)}/collections` + (coll ? `/${coll}` : ""), + group: (bucket, group) => + `${ENDPOINTS.bucket(bucket)}/groups` + (group ? `/${group}` : ""), + record: (bucket, coll, id) => + `${ENDPOINTS.collection(bucket, coll)}/records` + (id ? `/${id}` : ""), + attachment: (bucket, coll, id) => + `${ENDPOINTS.record(bucket, coll, id)}/attachment`, +}; + +const requestDefaults = { + safe: false, + // check if we should set default content type here + headers: {}, + patch: false, +}; +/** + * @private + */ +function safeHeader(safe, last_modified) { + if (!safe) { + return {}; + } + if (last_modified) { + return { "If-Match": `"${last_modified}"` }; + } + return { "If-None-Match": "*" }; +} +/** + * @private + */ +function createRequest(path, { data, permissions }, options = {}) { + const { headers, safe } = { + ...requestDefaults, + ...options, + }; + const method = options.method || (data && data.id) ? "PUT" : "POST"; + return { + method, + path, + headers: { ...headers, ...safeHeader(safe) }, + body: { data, permissions }, + }; +} +/** + * @private + */ +function updateRequest(path, { data, permissions }, options = {}) { + const { headers, safe, patch } = { ...requestDefaults, ...options }; + const { last_modified } = { ...data, ...options }; + const hasNoData = + data && + Object.keys(data).filter(k => k !== "id" && k !== "last_modified") + .length === 0; + if (hasNoData) { + data = undefined; + } + return { + method: patch ? "PATCH" : "PUT", + path, + headers: { ...headers, ...safeHeader(safe, last_modified) }, + body: { data, permissions }, + }; +} +/** + * @private + */ +function jsonPatchPermissionsRequest(path, permissions, opType, options = {}) { + const { headers, safe, last_modified } = { ...requestDefaults, ...options }; + const ops = []; + for (const [type, principals] of Object.entries(permissions)) { + if (principals) { + for (const principal of principals) { + ops.push({ + op: opType, + path: `/permissions/${type}/${principal}`, + }); + } + } + } + return { + method: "PATCH", + path, + headers: { + ...headers, + ...safeHeader(safe, last_modified), + "Content-Type": "application/json-patch+json", + }, + body: ops, + }; +} +/** + * @private + */ +function deleteRequest(path, options = {}) { + const { headers, safe, last_modified } = { + ...requestDefaults, + ...options, + }; + if (safe && !last_modified) { + throw new Error("Safe concurrency check requires a last_modified value."); + } + return { + method: "DELETE", + path, + headers: { ...headers, ...safeHeader(safe, last_modified) }, + }; +} +/** + * @private + */ +function addAttachmentRequest( + path, + dataURI, + { data, permissions } = {}, + options = {} +) { + const { headers, safe, gzipped } = { ...requestDefaults, ...options }; + const { last_modified } = { ...data, ...options }; + const body = { data, permissions }; + const formData = createFormData(dataURI, body, options); + const customPath = `${path}${ + gzipped !== null ? "?gzipped=" + (gzipped ? "true" : "false") : "" + }`; + return { + method: "POST", + path: customPath, + headers: { ...headers, ...safeHeader(safe, last_modified) }, + body: formData, + }; +} + +/** + * Exports batch responses as a result object. + * + * @private + * @param {Array} responses The batch subrequest responses. + * @param {Array} requests The initial issued requests. + * @return {Object} + */ +function aggregate(responses = [], requests = []) { + if (responses.length !== requests.length) { + throw new Error("Responses length should match requests one."); + } + const results = { + errors: [], + published: [], + conflicts: [], + skipped: [], + }; + return responses.reduce((acc, response, index) => { + const { status } = response; + const request = requests[index]; + if (status >= 200 && status < 400) { + acc.published.push(response.body); + } else if (status === 404) { + // Extract the id manually from request path while waiting for Kinto/kinto#818 + const regex = /(buckets|groups|collections|records)\/([^/]+)$/; + const extracts = request.path.match(regex); + const id = extracts && extracts.length === 3 ? extracts[2] : undefined; + acc.skipped.push({ + id, + path: request.path, + error: response.body, + }); + } else if (status === 412) { + acc.conflicts.push({ + // XXX: specifying the type is probably superfluous + type: "outgoing", + local: request.body, + remote: + (response.body.details && response.body.details.existing) || null, + }); + } else { + acc.errors.push({ + path: request.path, + sent: request, + error: response.body, + }); + } + return acc; + }, results); +} + +// Unique ID creation requires a high quality random # generator. In the browser we therefore +// require the crypto API and do not support built-in fallback to lower quality random number +// generators (like Math.random()). +let getRandomValues; +const rnds8 = new Uint8Array(16); +function rng() { + // lazy load so that environments that need to polyfill have a chance to do so + if (!getRandomValues) { + // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. + getRandomValues = + typeof crypto !== "undefined" && + crypto.getRandomValues && + crypto.getRandomValues.bind(crypto); + + if (!getRandomValues) { + throw new Error( + "crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported" + ); + } + } + + return getRandomValues(rnds8); +} + +/** + * Convert array of 16 byte values to UUID string format of the form: + * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + */ + +const byteToHex = []; + +for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 0x100).toString(16).slice(1)); +} + +function unsafeStringify(arr, offset = 0) { + // Note: Be careful editing this code! It's been tuned for performance + // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 + return ( + byteToHex[arr[offset + 0]] + + byteToHex[arr[offset + 1]] + + byteToHex[arr[offset + 2]] + + byteToHex[arr[offset + 3]] + + "-" + + byteToHex[arr[offset + 4]] + + byteToHex[arr[offset + 5]] + + "-" + + byteToHex[arr[offset + 6]] + + byteToHex[arr[offset + 7]] + + "-" + + byteToHex[arr[offset + 8]] + + byteToHex[arr[offset + 9]] + + "-" + + byteToHex[arr[offset + 10]] + + byteToHex[arr[offset + 11]] + + byteToHex[arr[offset + 12]] + + byteToHex[arr[offset + 13]] + + byteToHex[arr[offset + 14]] + + byteToHex[arr[offset + 15]] + ).toLowerCase(); +} + +const randomUUID = + typeof crypto !== "undefined" && + crypto.randomUUID && + crypto.randomUUID.bind(crypto); +var native = { + randomUUID, +}; + +function v4(options, buf, offset) { + if (native.randomUUID && !buf && !options) { + return native.randomUUID(); + } + + options = options || {}; + const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; // Copy bytes to buffer, if provided + + if (buf) { + offset = offset || 0; + + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + + return buf; + } + + return unsafeStringify(rnds); +} + +/** + * Abstract representation of a selected collection. + * + */ +class Collection { + /** + * Constructor. + * + * @param {KintoClient} client The client instance. + * @param {Bucket} bucket The bucket instance. + * @param {String} name The collection name. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry] The retry option. + * @param {Boolean} [options.batch] (Private) Whether this + * Collection is operating as part of a batch. + */ + constructor(client, bucket, name, options = {}) { + /** + * @ignore + */ + this.client = client; + /** + * @ignore + */ + this.bucket = bucket; + /** + * The collection name. + * @type {String} + */ + this.name = name; + this._endpoints = client.endpoints; + /** + * @ignore + */ + this._retry = options.retry || 0; + this._safe = !!options.safe; + // FIXME: This is kind of ugly; shouldn't the bucket be responsible + // for doing the merge? + this._headers = { + ...this.bucket.headers, + ...options.headers, + }; + } + get execute() { + return this.client.execute.bind(this.client); + } + /** + * Get the value of "headers" for a given request, merging the + * per-request headers with our own "default" headers. + * + * @private + */ + _getHeaders(options) { + return { + ...this._headers, + ...options.headers, + }; + } + /** + * Get the value of "safe" for a given request, using the + * per-request option if present or falling back to our default + * otherwise. + * + * @private + * @param {Object} options The options for a request. + * @returns {Boolean} + */ + _getSafe(options) { + return { safe: this._safe, ...options }.safe; + } + /** + * As _getSafe, but for "retry". + * + * @private + */ + _getRetry(options) { + return { retry: this._retry, ...options }.retry; + } + /** + * Retrieves the total number of records in this collection. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Number, Error>} + */ + async getTotalRecords(options = {}) { + const path = this._endpoints.record(this.bucket.name, this.name); + const request = { + headers: this._getHeaders(options), + path, + method: "HEAD", + }; + const { headers } = await this.client.execute(request, { + raw: true, + retry: this._getRetry(options), + }); + return parseInt(headers.get("Total-Records"), 10); + } + /** + * Retrieves the ETag of the records list, for use with the `since` filtering option. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<String, Error>} + */ + async getRecordsTimestamp(options = {}) { + const path = this._endpoints.record(this.bucket.name, this.name); + const request = { + headers: this._getHeaders(options), + path, + method: "HEAD", + }; + const { headers } = await this.client.execute(request, { + raw: true, + retry: this._getRetry(options), + }); + return headers.get("ETag"); + } + /** + * Retrieves collection data. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Object} [options.query] Query parameters to pass in + * the request. This might be useful for features that aren't + * yet supported by this library. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async getData(options = {}) { + const path = this._endpoints.collection(this.bucket.name, this.name); + const request = { headers: this._getHeaders(options), path }; + const { data } = await this.client.execute(request, { + retry: this._getRetry(options), + query: options.query, + fields: options.fields, + }); + return data; + } + /** + * Set collection data. + * @param {Object} data The collection data object. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Boolean} [options.patch] The patch option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async setData(data, options = {}) { + if (!isObject(data)) { + throw new Error("A collection object is required."); + } + const { patch, permissions } = options; + const { last_modified } = { ...data, ...options }; + const path = this._endpoints.collection(this.bucket.name, this.name); + const request = updateRequest( + path, + { data, permissions }, + { + last_modified, + patch, + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of permissions for this collection. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async getPermissions(options = {}) { + const path = this._endpoints.collection(this.bucket.name, this.name); + const request = { headers: this._getHeaders(options), path }; + const { permissions } = await this.client.execute(request, { + retry: this._getRetry(options), + }); + return permissions; + } + /** + * Replaces all existing collection permissions with the ones provided. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async setPermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.collection(this.bucket.name, this.name); + const data = { last_modified: options.last_modified }; + const request = updateRequest( + path, + { data, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Append principals to the collection permissions. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async addPermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.collection(this.bucket.name, this.name); + const { last_modified } = options; + const request = jsonPatchPermissionsRequest(path, permissions, "add", { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Remove principals from the collection permissions. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async removePermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.collection(this.bucket.name, this.name); + const { last_modified } = options; + const request = jsonPatchPermissionsRequest(path, permissions, "remove", { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Creates a record in current collection. + * + * @param {Object} record The record to create. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.permissions] The permissions option. + * @return {Promise<Object, Error>} + */ + async createRecord(record, options = {}) { + const { permissions } = options; + const path = this._endpoints.record(this.bucket.name, this.name, record.id); + const request = createRequest( + path, + { data: record, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Adds an attachment to a record, creating the record when it doesn't exist. + * + * @param {String} dataURL The data url. + * @param {Object} [record={}] The record data. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @param {Object} [options.permissions] The permissions option. + * @param {String} [options.filename] Force the attachment filename. + * @param {String} [options.gzipped] Force the attachment to be gzipped or not. + * @return {Promise<Object, Error>} + */ + async addAttachment(dataURI, record = {}, options = {}) { + const { permissions } = options; + const id = record.id || v4(); + const path = this._endpoints.attachment(this.bucket.name, this.name, id); + const { last_modified } = { ...record, ...options }; + const addAttachmentRequest$1 = addAttachmentRequest( + path, + dataURI, + { data: record, permissions }, + { + last_modified, + filename: options.filename, + gzipped: options.gzipped, + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + await this.client.execute(addAttachmentRequest$1, { + stringify: false, + retry: this._getRetry(options), + }); + return this.getRecord(id); + } + /** + * Removes an attachment from a given record. + * + * @param {Object} recordId The record id. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + */ + async removeAttachment(recordId, options = {}) { + const { last_modified } = options; + const path = this._endpoints.attachment( + this.bucket.name, + this.name, + recordId + ); + const request = deleteRequest(path, { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Updates a record in current collection. + * + * @param {Object} record The record to update. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @param {Object} [options.permissions] The permissions option. + * @return {Promise<Object, Error>} + */ + async updateRecord(record, options = {}) { + if (!isObject(record)) { + throw new Error("A record object is required."); + } + if (!record.id) { + throw new Error("A record id is required."); + } + const { permissions } = options; + const { last_modified } = { ...record, ...options }; + const path = this._endpoints.record(this.bucket.name, this.name, record.id); + const request = updateRequest( + path, + { data: record, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + last_modified, + patch: !!options.patch, + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes a record from the current collection. + * + * @param {Object|String} record The record to delete. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async deleteRecord(record, options = {}) { + const recordObj = toDataBody(record); + if (!recordObj.id) { + throw new Error("A record id is required."); + } + const { id } = recordObj; + const { last_modified } = { ...recordObj, ...options }; + const path = this._endpoints.record(this.bucket.name, this.name, id); + const request = deleteRequest(path, { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes records from the current collection. + * + * Sorting is done by passing a `sort` string option: + * + * - The field to order the results by, prefixed with `-` for descending. + * Default: `-last_modified`. + * + * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html + * + * Filtering is done by passing a `filters` option object: + * + * - `{fieldname: "value"}` + * - `{min_fieldname: 4000}` + * - `{in_fieldname: "1,2,3"}` + * - `{not_fieldname: 0}` + * - `{exclude_fieldname: "0,1"}` + * + * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.filters={}] The filters object. + * @param {String} [options.sort="-last_modified"] The sort field. + * @param {String} [options.at] The timestamp to get a snapshot at. + * @param {String} [options.limit=null] The limit field. + * @param {String} [options.pages=1] The number of result pages to aggregate. + * @param {Number} [options.since=null] Only retrieve records modified since the provided timestamp. + * @param {Array} [options.fields] Limit response to just some fields. + * @return {Promise<Object, Error>} + */ + async deleteRecords(options = {}) { + const path = this._endpoints.record(this.bucket.name, this.name); + return this.client.paginatedDelete(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Retrieves a record from the current collection. + * + * @param {String} id The record id to retrieve. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Object} [options.query] Query parameters to pass in + * the request. This might be useful for features that aren't + * yet supported by this library. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async getRecord(id, options = {}) { + const path = this._endpoints.record(this.bucket.name, this.name, id); + const request = { headers: this._getHeaders(options), path }; + return this.client.execute(request, { + retry: this._getRetry(options), + query: options.query, + fields: options.fields, + }); + } + /** + * Lists records from the current collection. + * + * Sorting is done by passing a `sort` string option: + * + * - The field to order the results by, prefixed with `-` for descending. + * Default: `-last_modified`. + * + * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html + * + * Filtering is done by passing a `filters` option object: + * + * - `{fieldname: "value"}` + * - `{min_fieldname: 4000}` + * - `{in_fieldname: "1,2,3"}` + * - `{not_fieldname: 0}` + * - `{exclude_fieldname: "0,1"}` + * + * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html + * + * Paginating is done by passing a `limit` option, then calling the `next()` + * method from the resolved result object to fetch the next page, if any. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.filters={}] The filters object. + * @param {String} [options.sort="-last_modified"] The sort field. + * @param {String} [options.at] The timestamp to get a snapshot at. + * @param {String} [options.limit=null] The limit field. + * @param {String} [options.pages=1] The number of result pages to aggregate. + * @param {Number} [options.since=null] Only retrieve records modified since the provided timestamp. + * @param {Array} [options.fields] Limit response to just some fields. + * @return {Promise<Object, Error>} + */ + async listRecords(options = {}) { + const path = this._endpoints.record(this.bucket.name, this.name); + if (options.at) { + return this.getSnapshot(options.at); + } + return this.client.paginatedList(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * @private + */ + async isHistoryComplete() { + // We consider that if we have the collection creation event part of the + // history, then all records change events have been tracked. + const { + data: [oldestHistoryEntry], + } = await this.bucket.listHistory({ + limit: 1, + filters: { + action: "create", + resource_name: "collection", + collection_id: this.name, + }, + }); + return !!oldestHistoryEntry; + } + /** + * @private + */ + async getSnapshot(at) { + if (!at || !Number.isInteger(at) || at <= 0) { + throw new Error("Invalid argument, expected a positive integer."); + } + // Retrieve history and check it covers the required time range. + // Ensure we have enough history data to retrieve the complete list of + // changes. + if (!(await this.isHistoryComplete())) { + throw new Error( + "Computing a snapshot is only possible when the full history for a " + + "collection is available. Here, the history plugin seems to have " + + "been enabled after the creation of the collection." + ); + } + // Because of https://github.com/Kinto/kinto-http.js/issues/963 + // we cannot simply rely on the history endpoint. + // Our strategy here is to clean-up the history entries from the + // records that were deleted via the plural endpoint. + // We will detect them by comparing the current state of the collection + // and the full history of the collection since its genesis. + // List full history of collection. + const { data: fullHistory } = await this.bucket.listHistory({ + pages: Infinity, + sort: "last_modified", + filters: { + resource_name: "record", + collection_id: this.name, + }, + }); + // Keep latest entry ever, and latest within snapshot window. + // (history is sorted chronologically) + const latestEver = new Map(); + const latestInSnapshot = new Map(); + for (const entry of fullHistory) { + if (entry.target.data.last_modified <= at) { + // Snapshot includes changes right on timestamp. + latestInSnapshot.set(entry.record_id, entry); + } + latestEver.set(entry.record_id, entry); + } + // Current records ids in the collection. + const { data: current } = await this.listRecords({ + pages: Infinity, + fields: ["id"], // we don't need attributes. + }); + const currentIds = new Set(current.map(record => record.id)); + // If a record is not in the current collection, and its + // latest history entry isn't a delete then this means that + // it was deleted via the plural endpoint (and that we lost track + // of this deletion because of bug #963) + const deletedViaPlural = new Set(); + for (const entry of latestEver.values()) { + if (entry.action != "delete" && !currentIds.has(entry.record_id)) { + deletedViaPlural.add(entry.record_id); + } + } + // Now reconstruct the collection based on latest version in snapshot + // filtering all deleted records. + const reconstructed = []; + for (const entry of latestInSnapshot.values()) { + if (entry.action != "delete" && !deletedViaPlural.has(entry.record_id)) { + reconstructed.push(entry.target.data); + } + } + return { + last_modified: String(at), + data: Array.from(reconstructed).sort( + (a, b) => b.last_modified - a.last_modified + ), + next: () => { + throw new Error("Snapshots don't support pagination"); + }, + hasNextPage: false, + totalRecords: reconstructed.length, + }; + } + /** + * Performs batch operations at the current collection level. + * + * @param {Function} fn The batch operation function. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry] The retry option. + * @param {Boolean} [options.aggregate] Produces a grouped result object. + * @return {Promise<Object, Error>} + */ + async batch(fn, options = {}) { + return this.client.batch(fn, { + bucket: this.bucket.name, + collection: this.name, + headers: this._getHeaders(options), + retry: this._getRetry(options), + safe: this._getSafe(options), + aggregate: !!options.aggregate, + }); + } +} +__decorate( + [capable(["attachments"])], + Collection.prototype, + "addAttachment", + null +); +__decorate( + [capable(["attachments"])], + Collection.prototype, + "removeAttachment", + null +); +__decorate([capable(["history"])], Collection.prototype, "getSnapshot", null); + +/** + * Abstract representation of a selected bucket. + * + */ +class Bucket { + /** + * Constructor. + * + * @param {KintoClient} client The client instance. + * @param {String} name The bucket name. + * @param {Object} [options={}] The headers object option. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry] The retry option. + */ + constructor(client, name, options = {}) { + /** + * @ignore + */ + this.client = client; + /** + * The bucket name. + * @type {String} + */ + this.name = name; + this._endpoints = client.endpoints; + /** + * @ignore + */ + this._headers = options.headers || {}; + this._retry = options.retry || 0; + this._safe = !!options.safe; + } + get execute() { + return this.client.execute.bind(this.client); + } + get headers() { + return this._headers; + } + /** + * Get the value of "headers" for a given request, merging the + * per-request headers with our own "default" headers. + * + * @private + */ + _getHeaders(options) { + return { + ...this._headers, + ...options.headers, + }; + } + /** + * Get the value of "safe" for a given request, using the + * per-request option if present or falling back to our default + * otherwise. + * + * @private + * @param {Object} options The options for a request. + * @returns {Boolean} + */ + _getSafe(options) { + return { safe: this._safe, ...options }.safe; + } + /** + * As _getSafe, but for "retry". + * + * @private + */ + _getRetry(options) { + return { retry: this._retry, ...options }.retry; + } + /** + * Selects a collection. + * + * @param {String} name The collection name. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @return {Collection} + */ + collection(name, options = {}) { + return new Collection(this.client, this, name, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + safe: this._getSafe(options), + }); + } + /** + * Retrieves the ETag of the collection list, for use with the `since` filtering option. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<String, Error>} + */ + async getCollectionsTimestamp(options = {}) { + const path = this._endpoints.collection(this.name); + const request = { + headers: this._getHeaders(options), + path, + method: "HEAD", + }; + const { headers } = await this.client.execute(request, { + raw: true, + retry: this._getRetry(options), + }); + return headers.get("ETag"); + } + /** + * Retrieves the ETag of the group list, for use with the `since` filtering option. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<String, Error>} + */ + async getGroupsTimestamp(options = {}) { + const path = this._endpoints.group(this.name); + const request = { + headers: this._getHeaders(options), + path, + method: "HEAD", + }; + const { headers } = await this.client.execute(request, { + raw: true, + retry: this._getRetry(options), + }); + return headers.get("ETag"); + } + /** + * Retrieves bucket data. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Object} [options.query] Query parameters to pass in + * the request. This might be useful for features that aren't + * yet supported by this library. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async getData(options = {}) { + const path = this._endpoints.bucket(this.name); + const request = { + headers: this._getHeaders(options), + path, + }; + const { data } = await this.client.execute(request, { + retry: this._getRetry(options), + query: options.query, + fields: options.fields, + }); + return data; + } + /** + * Set bucket data. + * @param {Object} data The bucket data object. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers={}] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.patch] The patch option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async setData(data, options = {}) { + if (!isObject(data)) { + throw new Error("A bucket object is required."); + } + const bucket = { + ...data, + id: this.name, + }; + // For default bucket, we need to drop the id from the data object. + // Bug in Kinto < 3.1.1 + const bucketId = bucket.id; + if (bucket.id === "default") { + delete bucket.id; + } + const path = this._endpoints.bucket(bucketId); + const { patch, permissions } = options; + const { last_modified } = { ...data, ...options }; + const request = updateRequest( + path, + { data: bucket, permissions }, + { + last_modified, + patch, + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of history entries in the current bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Array<Object>, Error>} + */ + async listHistory(options = {}) { + const path = this._endpoints.history(this.name); + return this.client.paginatedList(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of collections in the current bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.filters={}] The filters object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Array<Object>, Error>} + */ + async listCollections(options = {}) { + const path = this._endpoints.collection(this.name); + return this.client.paginatedList(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Creates a new collection in current bucket. + * + * @param {String|undefined} id The collection id. + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.permissions] The permissions object. + * @param {Object} [options.data] The data object. + * @return {Promise<Object, Error>} + */ + async createCollection(id, options = {}) { + const { permissions, data = {} } = options; + data.id = id; + const path = this._endpoints.collection(this.name, id); + const request = createRequest( + path, + { data, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes a collection from the current bucket. + * + * @param {Object|String} collection The collection to delete. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async deleteCollection(collection, options = {}) { + const collectionObj = toDataBody(collection); + if (!collectionObj.id) { + throw new Error("A collection id is required."); + } + const { id } = collectionObj; + const { last_modified } = { ...collectionObj, ...options }; + const path = this._endpoints.collection(this.name, id); + const request = deleteRequest(path, { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes collections from the current bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.filters={}] The filters object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Array<Object>, Error>} + */ + async deleteCollections(options = {}) { + const path = this._endpoints.collection(this.name); + return this.client.paginatedDelete(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of groups in the current bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.filters={}] The filters object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Array<Object>, Error>} + */ + async listGroups(options = {}) { + const path = this._endpoints.group(this.name); + return this.client.paginatedList(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Fetches a group in current bucket. + * + * @param {String} id The group id. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.query] Query parameters to pass in + * the request. This might be useful for features that aren't + * yet supported by this library. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Object, Error>} + */ + async getGroup(id, options = {}) { + const path = this._endpoints.group(this.name, id); + const request = { + headers: this._getHeaders(options), + path, + }; + return this.client.execute(request, { + retry: this._getRetry(options), + query: options.query, + fields: options.fields, + }); + } + /** + * Creates a new group in current bucket. + * + * @param {String|undefined} id The group id. + * @param {Array<String>} [members=[]] The list of principals. + * @param {Object} [options={}] The options object. + * @param {Object} [options.data] The data object. + * @param {Object} [options.permissions] The permissions object. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async createGroup(id, members = [], options = {}) { + const data = { + ...options.data, + id, + members, + }; + const path = this._endpoints.group(this.name, id); + const { permissions } = options; + const request = createRequest( + path, + { data, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Updates an existing group in current bucket. + * + * @param {Object} group The group object. + * @param {Object} [options={}] The options object. + * @param {Object} [options.data] The data object. + * @param {Object} [options.permissions] The permissions object. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async updateGroup(group, options = {}) { + if (!isObject(group)) { + throw new Error("A group object is required."); + } + if (!group.id) { + throw new Error("A group id is required."); + } + const data = { + ...options.data, + ...group, + }; + const path = this._endpoints.group(this.name, group.id); + const { patch, permissions } = options; + const { last_modified } = { ...data, ...options }; + const request = updateRequest( + path, + { data, permissions }, + { + last_modified, + patch, + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes a group from the current bucket. + * + * @param {Object|String} group The group to delete. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async deleteGroup(group, options = {}) { + const groupObj = toDataBody(group); + const { id } = groupObj; + const { last_modified } = { ...groupObj, ...options }; + const path = this._endpoints.group(this.name, id); + const request = deleteRequest(path, { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Deletes groups from the current bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.filters={}] The filters object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Array<Object>, Error>} + */ + async deleteGroups(options = {}) { + const path = this._endpoints.group(this.name); + return this.client.paginatedDelete(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of permissions for this bucket. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async getPermissions(options = {}) { + const request = { + headers: this._getHeaders(options), + path: this._endpoints.bucket(this.name), + }; + const { permissions } = await this.client.execute(request, { + retry: this._getRetry(options), + }); + return permissions; + } + /** + * Replaces all existing bucket permissions with the ones provided. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers={}] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async setPermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.bucket(this.name); + const { last_modified } = options; + const data = { last_modified }; + const request = updateRequest( + path, + { data, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Append principals to the bucket permissions. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async addPermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.bucket(this.name); + const { last_modified } = options; + const request = jsonPatchPermissionsRequest(path, permissions, "add", { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Remove principals from the bucket permissions. + * + * @param {Object} permissions The permissions object. + * @param {Object} [options={}] The options object + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async removePermissions(permissions, options = {}) { + if (!isObject(permissions)) { + throw new Error("A permissions object is required."); + } + const path = this._endpoints.bucket(this.name); + const { last_modified } = options; + const request = jsonPatchPermissionsRequest(path, permissions, "remove", { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }); + return this.client.execute(request, { + retry: this._getRetry(options), + }); + } + /** + * Performs batch operations at the current bucket level. + * + * @param {Function} fn The batch operation function. + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry=0] The retry option. + * @param {Boolean} [options.aggregate] Produces a grouped result object. + * @return {Promise<Object, Error>} + */ + async batch(fn, options = {}) { + return this.client.batch(fn, { + bucket: this.name, + headers: this._getHeaders(options), + retry: this._getRetry(options), + safe: this._getSafe(options), + aggregate: !!options.aggregate, + }); + } +} +__decorate([capable(["history"])], Bucket.prototype, "listHistory", null); + +/** + * Currently supported protocol version. + * @type {String} + */ +const SUPPORTED_PROTOCOL_VERSION = "v1"; +/** + * High level HTTP client for the Kinto API. + * + * @example + * const client = new KintoClient("https://demo.kinto-storage.org/v1"); + * client.bucket("default") + * .collection("my-blog") + * .createRecord({title: "First article"}) + * .then(console.log.bind(console)) + * .catch(console.error.bind(console)); + */ +class KintoClientBase { + /** + * Constructor. + * + * @param {String} remote The remote URL. + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.safe=true] Adds concurrency headers to every requests. + * @param {EventEmitter} [options.events=EventEmitter] The events handler instance. + * @param {Object} [options.headers={}] The key-value headers to pass to each request. + * @param {Object} [options.retry=0] Number of retries when request fails (default: 0) + * @param {String} [options.bucket="default"] The default bucket to use. + * @param {String} [options.requestMode="cors"] The HTTP request mode (from ES6 fetch spec). + * @param {Number} [options.timeout=null] The request timeout in ms, if any. + * @param {Function} [options.fetchFunc=fetch] The function to be used to execute HTTP requests. + */ + constructor(remote, options) { + if (typeof remote !== "string" || !remote.length) { + throw new Error("Invalid remote URL: " + remote); + } + if (remote[remote.length - 1] === "/") { + remote = remote.slice(0, -1); + } + this._backoffReleaseTime = null; + this._requests = []; + this._isBatch = !!options.batch; + this._retry = options.retry || 0; + this._safe = !!options.safe; + this._headers = options.headers || {}; + // public properties + /** + * The remote server base URL. + * @type {String} + */ + this.remote = remote; + /** + * Current server information. + * @ignore + * @type {Object|null} + */ + this.serverInfo = null; + /** + * The event emitter instance. Should comply with the `EventEmitter` + * interface. + * @ignore + * @type {Class} + */ + this.events = options.events; + this.endpoints = ENDPOINTS; + const { fetchFunc, requestMode, timeout } = options; + /** + * The HTTP instance. + * @ignore + * @type {HTTP} + */ + this.http = new HTTP(this.events, { fetchFunc, requestMode, timeout }); + this._registerHTTPEvents(); + } + /** + * The remote endpoint base URL. Setting the value will also extract and + * validate the version. + * @type {String} + */ + get remote() { + return this._remote; + } + /** + * @ignore + */ + set remote(url) { + let version; + try { + version = url.match(/\/(v\d+)\/?$/)[1]; + } catch (err) { + throw new Error("The remote URL must contain the version: " + url); + } + if (version !== SUPPORTED_PROTOCOL_VERSION) { + throw new Error(`Unsupported protocol version: ${version}`); + } + this._remote = url; + this._version = version; + } + /** + * The current server protocol version, eg. `v1`. + * @type {String} + */ + get version() { + return this._version; + } + /** + * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is + * ongoing. + * + * @type {Number} + */ + get backoff() { + const currentTime = new Date().getTime(); + if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) { + return this._backoffReleaseTime - currentTime; + } + return 0; + } + /** + * Registers HTTP events. + * @private + */ + _registerHTTPEvents() { + // Prevent registering event from a batch client instance + if (!this._isBatch && this.events) { + this.events.on("backoff", backoffMs => { + this._backoffReleaseTime = backoffMs; + }); + } + } + /** + * Retrieve a bucket object to perform operations on it. + * + * @param {String} name The bucket name. + * @param {Object} [options={}] The request options. + * @param {Boolean} [options.safe] The resulting safe option. + * @param {Number} [options.retry] The resulting retry option. + * @param {Object} [options.headers] The extended headers object option. + * @return {Bucket} + */ + bucket(name, options = {}) { + return new Bucket(this, name, { + headers: this._getHeaders(options), + safe: this._getSafe(options), + retry: this._getRetry(options), + }); + } + /** + * Set client "headers" for every request, updating previous headers (if any). + * + * @param {Object} headers The headers to merge with existing ones. + */ + setHeaders(headers) { + this._headers = { + ...this._headers, + ...headers, + }; + this.serverInfo = null; + } + /** + * Get the value of "headers" for a given request, merging the + * per-request headers with our own "default" headers. + * + * Note that unlike other options, headers aren't overridden, but + * merged instead. + * + * @private + * @param {Object} options The options for a request. + * @returns {Object} + */ + _getHeaders(options) { + return { + ...this._headers, + ...options.headers, + }; + } + /** + * Get the value of "safe" for a given request, using the + * per-request option if present or falling back to our default + * otherwise. + * + * @private + * @param {Object} options The options for a request. + * @returns {Boolean} + */ + _getSafe(options) { + return { safe: this._safe, ...options }.safe; + } + /** + * As _getSafe, but for "retry". + * + * @private + */ + _getRetry(options) { + return { retry: this._retry, ...options }.retry; + } + /** + * Retrieves the server's "hello" endpoint. This endpoint reveals + * server capabilities and settings as well as telling the client + * "who they are" according to their given authorization headers. + * + * @private + * @param {Object} [options={}] The request options. + * @param {Object} [options.headers={}] Headers to use when making + * this request. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async _getHello(options = {}) { + const path = this.remote + ENDPOINTS.root(); + const { json } = await this.http.request( + path, + { headers: this._getHeaders(options) }, + { retry: this._getRetry(options) } + ); + return json; + } + /** + * Retrieves server information and persist them locally. This operation is + * usually performed a single time during the instance lifecycle. + * + * @param {Object} [options={}] The request options. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async fetchServerInfo(options = {}) { + if (this.serverInfo) { + return this.serverInfo; + } + this.serverInfo = await this._getHello({ retry: this._getRetry(options) }); + return this.serverInfo; + } + /** + * Retrieves Kinto server settings. + * + * @param {Object} [options={}] The request options. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async fetchServerSettings(options = {}) { + const { settings } = await this.fetchServerInfo(options); + return settings; + } + /** + * Retrieve server capabilities information. + * + * @param {Object} [options={}] The request options. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async fetchServerCapabilities(options = {}) { + const { capabilities } = await this.fetchServerInfo(options); + return capabilities; + } + /** + * Retrieve authenticated user information. + * + * @param {Object} [options={}] The request options. + * @param {Object} [options.headers={}] Headers to use when making + * this request. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async fetchUser(options = {}) { + const { user } = await this._getHello(options); + return user; + } + /** + * Retrieve authenticated user information. + * + * @param {Object} [options={}] The request options. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async fetchHTTPApiVersion(options = {}) { + const { http_api_version } = await this.fetchServerInfo(options); + return http_api_version; + } + /** + * Process batch requests, chunking them according to the batch_max_requests + * server setting when needed. + * + * @param {Array} requests The list of batch subrequests to perform. + * @param {Object} [options={}] The options object. + * @return {Promise<Object, Error>} + */ + async _batchRequests(requests, options = {}) { + const headers = this._getHeaders(options); + if (!requests.length) { + return []; + } + const serverSettings = await this.fetchServerSettings({ + retry: this._getRetry(options), + }); + const maxRequests = serverSettings.batch_max_requests; + if (maxRequests && requests.length > maxRequests) { + const chunks = partition(requests, maxRequests); + const results = []; + for (const chunk of chunks) { + const result = await this._batchRequests(chunk, options); + results.push(...result); + } + return results; + } + const { responses } = await this.execute( + { + // FIXME: is this really necessary, since it's also present in + // the "defaults"? + headers, + path: ENDPOINTS.batch(), + method: "POST", + body: { + defaults: { headers }, + requests, + }, + }, + { retry: this._getRetry(options) } + ); + return responses; + } + /** + * Sends batch requests to the remote server. + * + * Note: Reserved for internal use only. + * + * @ignore + * @param {Function} fn The function to use for describing batch ops. + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.safe] The safe option. + * @param {Number} [options.retry] The retry option. + * @param {String} [options.bucket] The bucket name option. + * @param {String} [options.collection] The collection name option. + * @param {Object} [options.headers] The headers object option. + * @param {Boolean} [options.aggregate=false] Produces an aggregated result object. + * @return {Promise<Object, Error>} + */ + async batch(fn, options = {}) { + const rootBatch = new KintoClientBase(this.remote, { + events: this.events, + batch: true, + safe: this._getSafe(options), + retry: this._getRetry(options), + }); + if (options.bucket && options.collection) { + fn(rootBatch.bucket(options.bucket).collection(options.collection)); + } else if (options.bucket) { + fn(rootBatch.bucket(options.bucket)); + } else { + fn(rootBatch); + } + const responses = await this._batchRequests(rootBatch._requests, options); + if (options.aggregate) { + return aggregate(responses, rootBatch._requests); + } + return responses; + } + async execute(request, options = {}) { + const { raw = false, stringify = true } = options; + // If we're within a batch, add the request to the stack to send at once. + if (this._isBatch) { + this._requests.push(request); + // Resolve with a message in case people attempt at consuming the result + // from within a batch operation. + const msg = + "This result is generated from within a batch " + + "operation and should not be consumed."; + return raw ? { status: 0, json: msg, headers: new Headers() } : msg; + } + const uri = this.remote + addEndpointOptions(request.path, options); + const result = await this.http.request( + uri, + cleanUndefinedProperties({ + // Limit requests to only those parts that would be allowed in + // a batch request -- don't pass through other fancy fetch() + // options like integrity, redirect, mode because they will + // break on a batch request. A batch request only allows + // headers, method, path (above), and body. + method: request.method, + headers: request.headers, + body: stringify ? JSON.stringify(request.body) : request.body, + }), + { retry: this._getRetry(options) } + ); + return raw ? result : result.json; + } + /** + * Perform an operation with a given HTTP method on some pages from + * a paginated list, following the `next-page` header automatically + * until we have processed the requested number of pages. Return a + * response with a `.next()` method that can be called to perform + * the requested HTTP method on more results. + * + * @private + * @param {String} path + * The path to make the request to. + * @param {Object} params + * The parameters to use when making the request. + * @param {String} [params.sort="-last_modified"] + * The sorting order to use when doing operation on pages. + * @param {Object} [params.filters={}] + * The filters to send in the request. + * @param {Number} [params.limit=undefined] + * The limit to send in the request. Undefined means no limit. + * @param {Number} [params.pages=undefined] + * The number of pages to operate on. Undefined means one page. Pass + * Infinity to operate on everything. + * @param {String} [params.since=undefined] + * The ETag from which to start doing operation on pages. + * @param {Array} [params.fields] + * Limit response to just some fields. + * @param {Object} [options={}] + * Additional request-level parameters to use in all requests. + * @param {Object} [options.headers={}] + * Headers to use during all requests. + * @param {Number} [options.retry=0] + * Number of times to retry each request if the server responds + * with Retry-After. + * @param {String} [options.method="GET"] + * The method to use in the request. + */ + async paginatedOperation(path, params = {}, options = {}) { + // FIXME: this is called even in batch requests, which doesn't + // make any sense (since all batch requests get a "dummy" + // response; see execute() above). + const { sort, filters, limit, pages, since, fields } = { + sort: "-last_modified", + ...params, + }; + // Safety/Consistency check on ETag value. + if (since && typeof since !== "string") { + throw new Error( + `Invalid value for since (${since}), should be ETag value.` + ); + } + const query = { + ...filters, + _sort: sort, + _limit: limit, + _since: since, + }; + if (fields) { + query._fields = fields; + } + const querystring = qsify(query); + let results = [], + current = 0; + const next = async function (nextPage) { + if (!nextPage) { + throw new Error("Pagination exhausted."); + } + return processNextPage(nextPage); + }; + const processNextPage = async nextPage => { + const { headers } = options; + return handleResponse(await this.http.request(nextPage, { headers })); + }; + const pageResults = (results, nextPage, etag) => { + // ETag string is supposed to be opaque and stored «as-is». + // ETag header values are quoted (because of * and W/"foo"). + return { + last_modified: etag ? etag.replace(/"/g, "") : etag, + data: results, + next: next.bind(null, nextPage), + hasNextPage: !!nextPage, + totalRecords: -1, + }; + }; + const handleResponse = async function ({ + headers = new Headers(), + json = {}, + }) { + const nextPage = headers.get("Next-Page"); + const etag = headers.get("ETag"); + if (!pages) { + return pageResults(json.data, nextPage, etag); + } + // Aggregate new results with previous ones + results = results.concat(json.data); + current += 1; + if (current >= pages || !nextPage) { + // Pagination exhausted + return pageResults(results, nextPage, etag); + } + // Follow next page + return processNextPage(nextPage); + }; + return handleResponse( + await this.execute( + // N.B.: This doesn't use _getHeaders, because all calls to + // `paginatedList` are assumed to come from calls that already + // have headers merged at e.g. the bucket or collection level. + { + headers: options.headers ? options.headers : {}, + path: path + "?" + querystring, + method: options.method, + }, + // N.B. This doesn't use _getRetry, because all calls to + // `paginatedList` are assumed to come from calls that already + // used `_getRetry` at e.g. the bucket or collection level. + { raw: true, retry: options.retry || 0 } + ) + ); + } + /** + * Fetch some pages from a paginated list, following the `next-page` + * header automatically until we have fetched the requested number + * of pages. Return a response with a `.next()` method that can be + * called to fetch more results. + * + * @private + * @param {String} path + * The path to make the request to. + * @param {Object} params + * The parameters to use when making the request. + * @param {String} [params.sort="-last_modified"] + * The sorting order to use when fetching. + * @param {Object} [params.filters={}] + * The filters to send in the request. + * @param {Number} [params.limit=undefined] + * The limit to send in the request. Undefined means no limit. + * @param {Number} [params.pages=undefined] + * The number of pages to fetch. Undefined means one page. Pass + * Infinity to fetch everything. + * @param {String} [params.since=undefined] + * The ETag from which to start fetching. + * @param {Array} [params.fields] + * Limit response to just some fields. + * @param {Object} [options={}] + * Additional request-level parameters to use in all requests. + * @param {Object} [options.headers={}] + * Headers to use during all requests. + * @param {Number} [options.retry=0] + * Number of times to retry each request if the server responds + * with Retry-After. + */ + async paginatedList(path, params = {}, options = {}) { + return this.paginatedOperation(path, params, options); + } + /** + * Delete multiple objects, following the pagination if the number of + * objects exceeds the page limit until we have deleted the requested + * number of pages. Return a response with a `.next()` method that can + * be called to delete more results. + * + * @private + * @param {String} path + * The path to make the request to. + * @param {Object} params + * The parameters to use when making the request. + * @param {String} [params.sort="-last_modified"] + * The sorting order to use when deleting. + * @param {Object} [params.filters={}] + * The filters to send in the request. + * @param {Number} [params.limit=undefined] + * The limit to send in the request. Undefined means no limit. + * @param {Number} [params.pages=undefined] + * The number of pages to delete. Undefined means one page. Pass + * Infinity to delete everything. + * @param {String} [params.since=undefined] + * The ETag from which to start deleting. + * @param {Array} [params.fields] + * Limit response to just some fields. + * @param {Object} [options={}] + * Additional request-level parameters to use in all requests. + * @param {Object} [options.headers={}] + * Headers to use during all requests. + * @param {Number} [options.retry=0] + * Number of times to retry each request if the server responds + * with Retry-After. + */ + paginatedDelete(path, params = {}, options = {}) { + const { headers, safe, last_modified } = options; + const deleteRequest$1 = deleteRequest(path, { + headers, + safe: safe ? safe : false, + last_modified, + }); + return this.paginatedOperation(path, params, { + ...options, + headers: deleteRequest$1.headers, + method: "DELETE", + }); + } + /** + * Lists all permissions. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers={}] Headers to use when making + * this request. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object[], Error>} + */ + async listPermissions(options = {}) { + const path = ENDPOINTS.permissions(); + // Ensure the default sort parameter is something that exists in permissions + // entries, as `last_modified` doesn't; here, we pick "id". + const paginationOptions = { sort: "id", ...options }; + return this.paginatedList(path, paginationOptions, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Retrieves the list of buckets. + * + * @param {Object} [options={}] The options object. + * @param {Object} [options.headers={}] Headers to use when making + * this request. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.filters={}] The filters object. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @return {Promise<Object[], Error>} + */ + async listBuckets(options = {}) { + const path = ENDPOINTS.bucket(); + return this.paginatedList(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + }); + } + /** + * Creates a new bucket on the server. + * + * @param {String|null} id The bucket name (optional). + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.data] The bucket data option. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @return {Promise<Object, Error>} + */ + async createBucket(id, options = {}) { + const { data, permissions } = options; + const _data = { ...data, id: id ? id : undefined }; + const path = _data.id ? ENDPOINTS.bucket(_data.id) : ENDPOINTS.bucket(); + return this.execute( + createRequest( + path, + { data: _data, permissions }, + { + headers: this._getHeaders(options), + safe: this._getSafe(options), + } + ), + { retry: this._getRetry(options) } + ); + } + /** + * Deletes a bucket from the server. + * + * @ignore + * @param {Object|String} bucket The bucket to delete. + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers] The headers object option. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object, Error>} + */ + async deleteBucket(bucket, options = {}) { + const bucketObj = toDataBody(bucket); + if (!bucketObj.id) { + throw new Error("A bucket id is required."); + } + const path = ENDPOINTS.bucket(bucketObj.id); + const { last_modified } = { ...bucketObj, ...options }; + return this.execute( + deleteRequest(path, { + last_modified, + headers: this._getHeaders(options), + safe: this._getSafe(options), + }), + { retry: this._getRetry(options) } + ); + } + /** + * Deletes buckets. + * + * @param {Object} [options={}] The options object. + * @param {Boolean} [options.safe] The safe option. + * @param {Object} [options.headers={}] Headers to use when making + * this request. + * @param {Number} [options.retry=0] Number of retries to make + * when faced with transient errors. + * @param {Object} [options.filters={}] The filters object. + * @param {Array} [options.fields] Limit response to + * just some fields. + * @param {Number} [options.last_modified] The last_modified option. + * @return {Promise<Object[], Error>} + */ + async deleteBuckets(options = {}) { + const path = ENDPOINTS.bucket(); + return this.paginatedDelete(path, options, { + headers: this._getHeaders(options), + retry: this._getRetry(options), + safe: options.safe, + last_modified: options.last_modified, + }); + } + async createAccount(username, password) { + return this.execute( + createRequest( + `/accounts/${username}`, + { data: { password } }, + { method: "PUT" } + ) + ); + } +} +__decorate( + [nobatch("This operation is not supported within a batch operation.")], + KintoClientBase.prototype, + "fetchServerSettings", + null +); +__decorate( + [nobatch("This operation is not supported within a batch operation.")], + KintoClientBase.prototype, + "fetchServerCapabilities", + null +); +__decorate( + [nobatch("This operation is not supported within a batch operation.")], + KintoClientBase.prototype, + "fetchUser", + null +); +__decorate( + [nobatch("This operation is not supported within a batch operation.")], + KintoClientBase.prototype, + "fetchHTTPApiVersion", + null +); +__decorate( + [nobatch("Can't use batch within a batch!")], + KintoClientBase.prototype, + "batch", + null +); +__decorate( + [capable(["permissions_endpoint"])], + KintoClientBase.prototype, + "listPermissions", + null +); +__decorate( + [support("1.4", "2.0")], + KintoClientBase.prototype, + "deleteBuckets", + null +); +__decorate( + [capable(["accounts"])], + KintoClientBase.prototype, + "createAccount", + null +); + +/* + * + * 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. + */ +/* @ts-ignore */ +class KintoHttpClient extends KintoClientBase { + constructor(remote, options = {}) { + const events = {}; + EventEmitter.decorate(events); + super(remote, { events: events, ...options }); + } +} +KintoHttpClient.errors = errors; + +export { KintoHttpClient }; diff --git a/services/common/kinto-offline-client.js b/services/common/kinto-offline-client.js new file mode 100644 index 0000000000..7b11347555 --- /dev/null +++ b/services/common/kinto-offline-client.js @@ -0,0 +1,2643 @@ +/* + * + * 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. + */ +"use strict"; + +/* + * This file is generated from kinto.js - do not modify directly. + */ + +// This is required because with Babel compiles ES2015 modules into a +// require() form that tries to keep its modules on "this", but +// doesn't specify "this", leaving it to default to the global +// object. However, in strict mode, "this" no longer defaults to the +// global object, so expose the global object explicitly. Babel's +// compiled output will use a variable called "global" if one is +// present. +// +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1394556#c3 for +// more details. +const global = this; + +var EXPORTED_SYMBOLS = ["Kinto"]; + +/* + * Version 13.0.0 - 7fbf95d + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Kinto = factory()); +}(this, (function () { 'use strict'; + + /** + * Base db adapter. + * + * @abstract + */ + class BaseAdapter { + /** + * Deletes every records present in the database. + * + * @abstract + * @return {Promise} + */ + clear() { + throw new Error("Not Implemented."); + } + /** + * Executes a batch of operations within a single transaction. + * + * @abstract + * @param {Function} callback The operation callback. + * @param {Object} options The options object. + * @return {Promise} + */ + execute(callback, options = { preload: [] }) { + throw new Error("Not Implemented."); + } + /** + * Retrieve a record by its primary key from the database. + * + * @abstract + * @param {String} id The record id. + * @return {Promise} + */ + get(id) { + throw new Error("Not Implemented."); + } + /** + * Lists all records from the database. + * + * @abstract + * @param {Object} params The filters and order to apply to the results. + * @return {Promise} + */ + list(params = { filters: {}, order: "" }) { + throw new Error("Not Implemented."); + } + /** + * Store the lastModified value. + * + * @abstract + * @param {Number} lastModified + * @return {Promise} + */ + saveLastModified(lastModified) { + throw new Error("Not Implemented."); + } + /** + * Retrieve saved lastModified value. + * + * @abstract + * @return {Promise} + */ + getLastModified() { + throw new Error("Not Implemented."); + } + /** + * Load records in bulk that were exported from a server. + * + * @abstract + * @param {Array} records The records to load. + * @return {Promise} + */ + importBulk(records) { + throw new Error("Not Implemented."); + } + /** + * Load a dump of records exported from a server. + * + * @deprecated Use {@link importBulk} instead. + * @abstract + * @param {Array} records The records to load. + * @return {Promise} + */ + loadDump(records) { + throw new Error("Not Implemented."); + } + saveMetadata(metadata) { + throw new Error("Not Implemented."); + } + getMetadata() { + throw new Error("Not Implemented."); + } + } + + const RE_RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/; + /** + * Checks if a value is undefined. + * @param {Any} value + * @return {Boolean} + */ + function _isUndefined(value) { + return typeof value === "undefined"; + } + /** + * Sorts records in a list according to a given ordering. + * + * @param {String} order The ordering, eg. `-last_modified`. + * @param {Array} list The collection to order. + * @return {Array} + */ + function sortObjects(order, list) { + const hasDash = order[0] === "-"; + const field = hasDash ? order.slice(1) : order; + const direction = hasDash ? -1 : 1; + return list.slice().sort((a, b) => { + if (a[field] && _isUndefined(b[field])) { + return direction; + } + if (b[field] && _isUndefined(a[field])) { + return -direction; + } + if (_isUndefined(a[field]) && _isUndefined(b[field])) { + return 0; + } + return a[field] > b[field] ? direction : -direction; + }); + } + /** + * Test if a single object matches all given filters. + * + * @param {Object} filters The filters object. + * @param {Object} entry The object to filter. + * @return {Boolean} + */ + function filterObject(filters, entry) { + return Object.keys(filters).every(filter => { + const value = filters[filter]; + if (Array.isArray(value)) { + return value.some(candidate => candidate === entry[filter]); + } + else if (typeof value === "object") { + return filterObject(value, entry[filter]); + } + else if (!Object.prototype.hasOwnProperty.call(entry, filter)) { + console.error(`The property ${filter} does not exist`); + return false; + } + return entry[filter] === value; + }); + } + /** + * Resolves a list of functions sequentially, which can be sync or async; in + * case of async, functions must return a promise. + * + * @param {Array} fns The list of functions. + * @param {Any} init The initial value. + * @return {Promise} + */ + function waterfall(fns, init) { + if (!fns.length) { + return Promise.resolve(init); + } + return fns.reduce((promise, nextFn) => { + return promise.then(nextFn); + }, Promise.resolve(init)); + } + /** + * Simple deep object comparison function. This only supports comparison of + * serializable JavaScript objects. + * + * @param {Object} a The source object. + * @param {Object} b The compared object. + * @return {Boolean} + */ + function deepEqual(a, b) { + if (a === b) { + return true; + } + if (typeof a !== typeof b) { + return false; + } + if (!(a && typeof a == "object") || !(b && typeof b == "object")) { + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + for (const k in a) { + if (!deepEqual(a[k], b[k])) { + return false; + } + } + return true; + } + /** + * Return an object without the specified keys. + * + * @param {Object} obj The original object. + * @param {Array} keys The list of keys to exclude. + * @return {Object} A copy without the specified keys. + */ + function omitKeys(obj, keys = []) { + const result = Object.assign({}, obj); + for (const key of keys) { + delete result[key]; + } + return result; + } + function arrayEqual(a, b) { + if (a.length !== b.length) { + return false; + } + for (let i = a.length; i--;) { + if (a[i] !== b[i]) { + return false; + } + } + return true; + } + function makeNestedObjectFromArr(arr, val, nestedFiltersObj) { + const last = arr.length - 1; + return arr.reduce((acc, cv, i) => { + if (i === last) { + return (acc[cv] = val); + } + else if (Object.prototype.hasOwnProperty.call(acc, cv)) { + return acc[cv]; + } + else { + return (acc[cv] = {}); + } + }, nestedFiltersObj); + } + function transformSubObjectFilters(filtersObj) { + const transformedFilters = {}; + for (const key in filtersObj) { + const keysArr = key.split("."); + const val = filtersObj[key]; + makeNestedObjectFromArr(keysArr, val, transformedFilters); + } + return transformedFilters; + } + + const INDEXED_FIELDS = ["id", "_status", "last_modified"]; + /** + * Small helper that wraps the opening of an IndexedDB into a Promise. + * + * @param dbname {String} The database name. + * @param version {Integer} Schema version + * @param onupgradeneeded {Function} The callback to execute if schema is + * missing or different. + * @return {Promise<IDBDatabase>} + */ + async function open(dbname, { version, onupgradeneeded }) { + return new Promise((resolve, reject) => { + const request = indexedDB.open(dbname, version); + request.onupgradeneeded = event => { + const db = event.target.result; + db.onerror = event => reject(event.target.error); + // When an upgrade is needed, a transaction is started. + const transaction = event.target.transaction; + transaction.onabort = event => { + const error = event.target.error || + transaction.error || + new DOMException("The operation has been aborted", "AbortError"); + reject(error); + }; + // Callback for store creation etc. + return onupgradeneeded(event); + }; + request.onerror = event => { + reject(event.target.error); + }; + request.onsuccess = event => { + const db = event.target.result; + resolve(db); + }; + }); + } + /** + * Helper to run the specified callback in a single transaction on the + * specified store. + * The helper focuses on transaction wrapping into a promise. + * + * @param db {IDBDatabase} The database instance. + * @param name {String} The store name. + * @param callback {Function} The piece of code to execute in the transaction. + * @param options {Object} Options. + * @param options.mode {String} Transaction mode (default: read). + * @return {Promise} any value returned by the callback. + */ + async function execute(db, name, callback, options = {}) { + const { mode } = options; + return new Promise((resolve, reject) => { + // On Safari, calling IDBDatabase.transaction with mode == undefined raises + // a TypeError. + const transaction = mode + ? db.transaction([name], mode) + : db.transaction([name]); + const store = transaction.objectStore(name); + // Let the callback abort this transaction. + const abort = e => { + transaction.abort(); + reject(e); + }; + // Execute the specified callback **synchronously**. + let result; + try { + result = callback(store, abort); + } + catch (e) { + abort(e); + } + transaction.onerror = event => reject(event.target.error); + transaction.oncomplete = event => resolve(result); + transaction.onabort = event => { + const error = event.target.error || + transaction.error || + new DOMException("The operation has been aborted", "AbortError"); + reject(error); + }; + }); + } + /** + * Helper to wrap the deletion of an IndexedDB database into a promise. + * + * @param dbName {String} the database to delete + * @return {Promise} + */ + async function deleteDatabase(dbName) { + return new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(dbName); + request.onsuccess = event => resolve(event.target); + request.onerror = event => reject(event.target.error); + }); + } + /** + * IDB cursor handlers. + * @type {Object} + */ + const cursorHandlers = { + all(filters, done) { + const results = []; + return event => { + const cursor = event.target.result; + if (cursor) { + const { value } = cursor; + if (filterObject(filters, value)) { + results.push(value); + } + cursor.continue(); + } + else { + done(results); + } + }; + }, + in(values, filters, done) { + const results = []; + let i = 0; + return function (event) { + const cursor = event.target.result; + if (!cursor) { + done(results); + return; + } + const { key, value } = cursor; + // `key` can be an array of two values (see `keyPath` in indices definitions). + // `values` can be an array of arrays if we filter using an index whose key path + // is an array (eg. `cursorHandlers.in([["bid/cid", 42], ["bid/cid", 43]], ...)`) + while (key > values[i]) { + // The cursor has passed beyond this key. Check next. + ++i; + if (i === values.length) { + done(results); // There is no next. Stop searching. + return; + } + } + const isEqual = Array.isArray(key) + ? arrayEqual(key, values[i]) + : key === values[i]; + if (isEqual) { + if (filterObject(filters, value)) { + results.push(value); + } + cursor.continue(); + } + else { + cursor.continue(values[i]); + } + }; + }, + }; + /** + * Creates an IDB request and attach it the appropriate cursor event handler to + * perform a list query. + * + * Multiple matching values are handled by passing an array. + * + * @param {String} cid The collection id (ie. `{bid}/{cid}`) + * @param {IDBStore} store The IDB store. + * @param {Object} filters Filter the records by field. + * @param {Function} done The operation completion handler. + * @return {IDBRequest} + */ + function createListRequest(cid, store, filters, done) { + const filterFields = Object.keys(filters); + // If no filters, get all results in one bulk. + if (filterFields.length == 0) { + const request = store.index("cid").getAll(IDBKeyRange.only(cid)); + request.onsuccess = event => done(event.target.result); + return request; + } + // Introspect filters and check if they leverage an indexed field. + const indexField = filterFields.find(field => { + return INDEXED_FIELDS.includes(field); + }); + if (!indexField) { + // Iterate on all records for this collection (ie. cid) + const isSubQuery = Object.keys(filters).some(key => key.includes(".")); // (ie. filters: {"article.title": "hello"}) + if (isSubQuery) { + const newFilter = transformSubObjectFilters(filters); + const request = store.index("cid").openCursor(IDBKeyRange.only(cid)); + request.onsuccess = cursorHandlers.all(newFilter, done); + return request; + } + const request = store.index("cid").openCursor(IDBKeyRange.only(cid)); + request.onsuccess = cursorHandlers.all(filters, done); + return request; + } + // If `indexField` was used already, don't filter again. + const remainingFilters = omitKeys(filters, [indexField]); + // value specified in the filter (eg. `filters: { _status: ["created", "updated"] }`) + const value = filters[indexField]; + // For the "id" field, use the primary key. + const indexStore = indexField == "id" ? store : store.index(indexField); + // WHERE IN equivalent clause + if (Array.isArray(value)) { + if (value.length === 0) { + return done([]); + } + const values = value.map(i => [cid, i]).sort(); + const range = IDBKeyRange.bound(values[0], values[values.length - 1]); + const request = indexStore.openCursor(range); + request.onsuccess = cursorHandlers.in(values, remainingFilters, done); + return request; + } + // If no filters on custom attribute, get all results in one bulk. + if (remainingFilters.length == 0) { + const request = indexStore.getAll(IDBKeyRange.only([cid, value])); + request.onsuccess = event => done(event.target.result); + return request; + } + // WHERE field = value clause + const request = indexStore.openCursor(IDBKeyRange.only([cid, value])); + request.onsuccess = cursorHandlers.all(remainingFilters, done); + return request; + } + class IDBError extends Error { + constructor(method, err) { + super(`IndexedDB ${method}() ${err.message}`); + this.name = err.name; + this.stack = err.stack; + } + } + /** + * IndexedDB adapter. + * + * This adapter doesn't support any options. + */ + class IDB extends BaseAdapter { + /* Expose the IDBError class publicly */ + static get IDBError() { + return IDBError; + } + /** + * Constructor. + * + * @param {String} cid The key base for this collection (eg. `bid/cid`) + * @param {Object} options + * @param {String} options.dbName The IndexedDB name (default: `"KintoDB"`) + * @param {String} options.migrateOldData Whether old database data should be migrated (default: `false`) + */ + constructor(cid, options = {}) { + super(); + this.cid = cid; + this.dbName = options.dbName || "KintoDB"; + this._options = options; + this._db = null; + } + _handleError(method, err) { + throw new IDBError(method, err); + } + /** + * Ensures a connection to the IndexedDB database has been opened. + * + * @override + * @return {Promise} + */ + async open() { + if (this._db) { + return this; + } + // In previous versions, we used to have a database with name `${bid}/${cid}`. + // Check if it exists, and migrate data once new schema is in place. + // Note: the built-in migrations from IndexedDB can only be used if the + // database name does not change. + const dataToMigrate = this._options.migrateOldData + ? await migrationRequired(this.cid) + : null; + this._db = await open(this.dbName, { + version: 2, + onupgradeneeded: event => { + const db = event.target.result; + if (event.oldVersion < 1) { + // Records store + const recordsStore = db.createObjectStore("records", { + keyPath: ["_cid", "id"], + }); + // An index to obtain all the records in a collection. + recordsStore.createIndex("cid", "_cid"); + // Here we create indices for every known field in records by collection. + // Local record status ("synced", "created", "updated", "deleted") + recordsStore.createIndex("_status", ["_cid", "_status"]); + // Last modified field + recordsStore.createIndex("last_modified", ["_cid", "last_modified"]); + // Timestamps store + db.createObjectStore("timestamps", { + keyPath: "cid", + }); + } + if (event.oldVersion < 2) { + // Collections store + db.createObjectStore("collections", { + keyPath: "cid", + }); + } + }, + }); + if (dataToMigrate) { + const { records, timestamp } = dataToMigrate; + await this.importBulk(records); + await this.saveLastModified(timestamp); + console.log(`${this.cid}: data was migrated successfully.`); + // Delete the old database. + await deleteDatabase(this.cid); + console.warn(`${this.cid}: old database was deleted.`); + } + return this; + } + /** + * Closes current connection to the database. + * + * @override + * @return {Promise} + */ + close() { + if (this._db) { + this._db.close(); // indexedDB.close is synchronous + this._db = null; + } + return Promise.resolve(); + } + /** + * Returns a transaction and an object store for a store name. + * + * To determine if a transaction has completed successfully, we should rather + * listen to the transaction’s complete event rather than the IDBObjectStore + * request’s success event, because the transaction may still fail after the + * success event fires. + * + * @param {String} name Store name + * @param {Function} callback to execute + * @param {Object} options Options + * @param {String} options.mode Transaction mode ("readwrite" or undefined) + * @return {Object} + */ + async prepare(name, callback, options) { + await this.open(); + await execute(this._db, name, callback, options); + } + /** + * Deletes every records in the current collection. + * + * @override + * @return {Promise} + */ + async clear() { + try { + await this.prepare("records", store => { + const range = IDBKeyRange.only(this.cid); + const request = store.index("cid").openKeyCursor(range); + request.onsuccess = event => { + const cursor = event.target.result; + if (cursor) { + store.delete(cursor.primaryKey); + cursor.continue(); + } + }; + return request; + }, { mode: "readwrite" }); + } + catch (e) { + this._handleError("clear", e); + } + } + /** + * Executes the set of synchronous CRUD operations described in the provided + * callback within an IndexedDB transaction, for current db store. + * + * The callback will be provided an object exposing the following synchronous + * CRUD operation methods: get, create, update, delete. + * + * Important note: because limitations in IndexedDB implementations, no + * asynchronous code should be performed within the provided callback; the + * promise will therefore be rejected if the callback returns a Promise. + * + * Options: + * - {Array} preload: The list of record IDs to fetch and make available to + * the transaction object get() method (default: []) + * + * @example + * const db = new IDB("example"); + * const result = await db.execute(transaction => { + * transaction.create({id: 1, title: "foo"}); + * transaction.update({id: 2, title: "bar"}); + * transaction.delete(3); + * return "foo"; + * }); + * + * @override + * @param {Function} callback The operation description callback. + * @param {Object} options The options object. + * @return {Promise} + */ + async execute(callback, options = { preload: [] }) { + // Transactions in IndexedDB are autocommited when a callback does not + // perform any additional operation. + // The way Promises are implemented in Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1193394) + // prevents using within an opened transaction. + // To avoid managing asynchronocity in the specified `callback`, we preload + // a list of record in order to execute the `callback` synchronously. + // See also: + // - http://stackoverflow.com/a/28388805/330911 + // - http://stackoverflow.com/a/10405196 + // - https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ + let result; + await this.prepare("records", (store, abort) => { + const runCallback = (preloaded = []) => { + // Expose a consistent API for every adapter instead of raw store methods. + const proxy = transactionProxy(this, store, preloaded); + // The callback is executed synchronously within the same transaction. + try { + const returned = callback(proxy); + if (returned instanceof Promise) { + // XXX: investigate how to provide documentation details in error. + throw new Error("execute() callback should not return a Promise."); + } + // Bring to scope that will be returned (once promise awaited). + result = returned; + } + catch (e) { + // The callback has thrown an error explicitly. Abort transaction cleanly. + abort(e); + } + }; + // No option to preload records, go straight to `callback`. + if (!options.preload.length) { + return runCallback(); + } + // Preload specified records using a list request. + const filters = { id: options.preload }; + createListRequest(this.cid, store, filters, records => { + // Store obtained records by id. + const preloaded = {}; + for (const record of records) { + delete record["_cid"]; + preloaded[record.id] = record; + } + runCallback(preloaded); + }); + }, { mode: "readwrite" }); + return result; + } + /** + * Retrieve a record by its primary key from the IndexedDB database. + * + * @override + * @param {String} id The record id. + * @return {Promise} + */ + async get(id) { + try { + let record; + await this.prepare("records", store => { + store.get([this.cid, id]).onsuccess = e => (record = e.target.result); + }); + return record; + } + catch (e) { + this._handleError("get", e); + } + } + /** + * Lists all records from the IndexedDB database. + * + * @override + * @param {Object} params The filters and order to apply to the results. + * @return {Promise} + */ + async list(params = { filters: {} }) { + const { filters } = params; + try { + let results = []; + await this.prepare("records", store => { + createListRequest(this.cid, store, filters, _results => { + // we have received all requested records that match the filters, + // we now park them within current scope and hide the `_cid` attribute. + for (const result of _results) { + delete result["_cid"]; + } + results = _results; + }); + }); + // The resulting list of records is sorted. + // XXX: with some efforts, this could be fully implemented using IDB API. + return params.order ? sortObjects(params.order, results) : results; + } + catch (e) { + this._handleError("list", e); + } + } + /** + * Store the lastModified value into metadata store. + * + * @override + * @param {Number} lastModified + * @return {Promise} + */ + async saveLastModified(lastModified) { + const value = parseInt(lastModified, 10) || null; + try { + await this.prepare("timestamps", store => { + if (value === null) { + store.delete(this.cid); + } + else { + store.put({ cid: this.cid, value }); + } + }, { mode: "readwrite" }); + return value; + } + catch (e) { + this._handleError("saveLastModified", e); + } + } + /** + * Retrieve saved lastModified value. + * + * @override + * @return {Promise} + */ + async getLastModified() { + try { + let entry = null; + await this.prepare("timestamps", store => { + store.get(this.cid).onsuccess = e => (entry = e.target.result); + }); + return entry ? entry.value : null; + } + catch (e) { + this._handleError("getLastModified", e); + } + } + /** + * Load a dump of records exported from a server. + * + * @deprecated Use {@link importBulk} instead. + * @abstract + * @param {Array} records The records to load. + * @return {Promise} + */ + async loadDump(records) { + return this.importBulk(records); + } + /** + * Load records in bulk that were exported from a server. + * + * @abstract + * @param {Array} records The records to load. + * @return {Promise} + */ + async importBulk(records) { + try { + await this.execute(transaction => { + // Since the put operations are asynchronous, we chain + // them together. The last one will be waited for the + // `transaction.oncomplete` callback. (see #execute()) + let i = 0; + putNext(); + function putNext() { + if (i == records.length) { + return; + } + // On error, `transaction.onerror` is called. + transaction.update(records[i]).onsuccess = putNext; + ++i; + } + }); + const previousLastModified = await this.getLastModified(); + const lastModified = Math.max(...records.map(record => record.last_modified)); + if (lastModified > previousLastModified) { + await this.saveLastModified(lastModified); + } + return records; + } + catch (e) { + this._handleError("importBulk", e); + } + } + async saveMetadata(metadata) { + try { + await this.prepare("collections", store => store.put({ cid: this.cid, metadata }), { mode: "readwrite" }); + return metadata; + } + catch (e) { + this._handleError("saveMetadata", e); + } + } + async getMetadata() { + try { + let entry = null; + await this.prepare("collections", store => { + store.get(this.cid).onsuccess = e => (entry = e.target.result); + }); + return entry ? entry.metadata : null; + } + catch (e) { + this._handleError("getMetadata", e); + } + } + } + /** + * IDB transaction proxy. + * + * @param {IDB} adapter The call IDB adapter + * @param {IDBStore} store The IndexedDB database store. + * @param {Array} preloaded The list of records to make available to + * get() (default: []). + * @return {Object} + */ + function transactionProxy(adapter, store, preloaded = []) { + const _cid = adapter.cid; + return { + create(record) { + store.add(Object.assign(Object.assign({}, record), { _cid })); + }, + update(record) { + return store.put(Object.assign(Object.assign({}, record), { _cid })); + }, + delete(id) { + store.delete([_cid, id]); + }, + get(id) { + return preloaded[id]; + }, + }; + } + /** + * Up to version 10.X of kinto.js, each collection had its own collection. + * The database name was `${bid}/${cid}` (eg. `"blocklists/certificates"`) + * and contained only one store with the same name. + */ + async function migrationRequired(dbName) { + let exists = true; + const db = await open(dbName, { + version: 1, + onupgradeneeded: event => { + exists = false; + }, + }); + // Check that the DB we're looking at is really a legacy one, + // and not some remainder of the open() operation above. + exists &= + db.objectStoreNames.contains("__meta__") && + db.objectStoreNames.contains(dbName); + if (!exists) { + db.close(); + // Testing the existence creates it, so delete it :) + await deleteDatabase(dbName); + return null; + } + console.warn(`${dbName}: old IndexedDB database found.`); + try { + // Scan all records. + let records; + await execute(db, dbName, store => { + store.openCursor().onsuccess = cursorHandlers.all({}, res => (records = res)); + }); + console.log(`${dbName}: found ${records.length} records.`); + // Check if there's a entry for this. + let timestamp = null; + await execute(db, "__meta__", store => { + store.get(`${dbName}-lastModified`).onsuccess = e => { + timestamp = e.target.result ? e.target.result.value : null; + }; + }); + // Some previous versions, also used to store the timestamps without prefix. + if (!timestamp) { + await execute(db, "__meta__", store => { + store.get("lastModified").onsuccess = e => { + timestamp = e.target.result ? e.target.result.value : null; + }; + }); + } + console.log(`${dbName}: ${timestamp ? "found" : "no"} timestamp.`); + // Those will be inserted in the new database/schema. + return { records, timestamp }; + } + catch (e) { + console.error("Error occured during migration", e); + return null; + } + finally { + db.close(); + } + } + + var uuid4 = {}; + + const RECORD_FIELDS_TO_CLEAN = ["_status"]; + const AVAILABLE_HOOKS = ["incoming-changes"]; + const IMPORT_CHUNK_SIZE = 200; + /** + * Compare two records omitting local fields and synchronization + * attributes (like _status and last_modified) + * @param {Object} a A record to compare. + * @param {Object} b A record to compare. + * @param {Array} localFields Additional fields to ignore during the comparison + * @return {boolean} + */ + function recordsEqual(a, b, localFields = []) { + const fieldsToClean = RECORD_FIELDS_TO_CLEAN.concat(["last_modified"]).concat(localFields); + const cleanLocal = r => omitKeys(r, fieldsToClean); + return deepEqual(cleanLocal(a), cleanLocal(b)); + } + /** + * Synchronization result object. + */ + class SyncResultObject { + /** + * Public constructor. + */ + constructor() { + /** + * Current synchronization result status; becomes `false` when conflicts or + * errors are registered. + * @type {Boolean} + */ + this.lastModified = null; + this._lists = {}; + [ + "errors", + "created", + "updated", + "deleted", + "published", + "conflicts", + "skipped", + "resolved", + "void", + ].forEach(l => (this._lists[l] = [])); + this._cached = {}; + } + /** + * Adds entries for a given result type. + * + * @param {String} type The result type. + * @param {Array} entries The result entries. + * @return {SyncResultObject} + */ + add(type, entries) { + if (!Array.isArray(this._lists[type])) { + console.warn(`Unknown type "${type}"`); + return; + } + if (!Array.isArray(entries)) { + entries = [entries]; + } + this._lists[type] = this._lists[type].concat(entries); + delete this._cached[type]; + return this; + } + get ok() { + return this.errors.length + this.conflicts.length === 0; + } + get errors() { + return this._lists["errors"]; + } + get conflicts() { + return this._lists["conflicts"]; + } + get skipped() { + return this._deduplicate("skipped"); + } + get resolved() { + return this._deduplicate("resolved"); + } + get created() { + return this._deduplicate("created"); + } + get updated() { + return this._deduplicate("updated"); + } + get deleted() { + return this._deduplicate("deleted"); + } + get published() { + return this._deduplicate("published"); + } + _deduplicate(list) { + if (!(list in this._cached)) { + // Deduplicate entries by id. If the values don't have `id` attribute, just + // keep all. + const recordsWithoutId = new Set(); + const recordsById = new Map(); + this._lists[list].forEach(record => { + if (!record.id) { + recordsWithoutId.add(record); + } + else { + recordsById.set(record.id, record); + } + }); + this._cached[list] = Array.from(recordsById.values()).concat(Array.from(recordsWithoutId)); + } + return this._cached[list]; + } + /** + * Reinitializes result entries for a given result type. + * + * @param {String} type The result type. + * @return {SyncResultObject} + */ + reset(type) { + this._lists[type] = []; + delete this._cached[type]; + return this; + } + toObject() { + // Only used in tests. + return { + ok: this.ok, + lastModified: this.lastModified, + errors: this.errors, + created: this.created, + updated: this.updated, + deleted: this.deleted, + skipped: this.skipped, + published: this.published, + conflicts: this.conflicts, + resolved: this.resolved, + }; + } + } + class ServerWasFlushedError extends Error { + constructor(clientTimestamp, serverTimestamp, message) { + super(message); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, ServerWasFlushedError); + } + this.clientTimestamp = clientTimestamp; + this.serverTimestamp = serverTimestamp; + } + } + function createUUIDSchema() { + return { + generate() { + return uuid4(); + }, + validate(id) { + return typeof id == "string" && RE_RECORD_ID.test(id); + }, + }; + } + function markStatus(record, status) { + return Object.assign(Object.assign({}, record), { _status: status }); + } + function markDeleted(record) { + return markStatus(record, "deleted"); + } + function markSynced(record) { + return markStatus(record, "synced"); + } + /** + * Import a remote change into the local database. + * + * @param {IDBTransactionProxy} transaction The transaction handler. + * @param {Object} remote The remote change object to import. + * @param {Array<String>} localFields The list of fields that remain local. + * @param {String} strategy The {@link Collection.strategy}. + * @return {Object} + */ + function importChange(transaction, remote, localFields, strategy) { + const local = transaction.get(remote.id); + if (!local) { + // Not found locally but remote change is marked as deleted; skip to + // avoid recreation. + if (remote.deleted) { + return { type: "skipped", data: remote }; + } + const synced = markSynced(remote); + transaction.create(synced); + return { type: "created", data: synced }; + } + // Apply remote changes on local record. + const synced = Object.assign(Object.assign({}, local), markSynced(remote)); + // With pull only, we don't need to compare records since we override them. + if (strategy === Collection.strategy.PULL_ONLY) { + if (remote.deleted) { + transaction.delete(remote.id); + return { type: "deleted", data: local }; + } + transaction.update(synced); + return { type: "updated", data: { old: local, new: synced } }; + } + // With other sync strategies, we detect conflicts, + // by comparing local and remote, ignoring local fields. + const isIdentical = recordsEqual(local, remote, localFields); + // Detect or ignore conflicts if record has also been modified locally. + if (local._status !== "synced") { + // Locally deleted, unsynced: scheduled for remote deletion. + if (local._status === "deleted") { + return { type: "skipped", data: local }; + } + if (isIdentical) { + // If records are identical, import anyway, so we bump the + // local last_modified value from the server and set record + // status to "synced". + transaction.update(synced); + return { type: "updated", data: { old: local, new: synced } }; + } + if (local.last_modified !== undefined && + local.last_modified === remote.last_modified) { + // If our local version has the same last_modified as the remote + // one, this represents an object that corresponds to a resolved + // conflict. Our local version represents the final output, so + // we keep that one. (No transaction operation to do.) + // But if our last_modified is undefined, + // that means we've created the same object locally as one on + // the server, which *must* be a conflict. + return { type: "void" }; + } + return { + type: "conflicts", + data: { type: "incoming", local: local, remote: remote }, + }; + } + // Local record was synced. + if (remote.deleted) { + transaction.delete(remote.id); + return { type: "deleted", data: local }; + } + // Import locally. + transaction.update(synced); + // if identical, simply exclude it from all SyncResultObject lists + const type = isIdentical ? "void" : "updated"; + return { type, data: { old: local, new: synced } }; + } + /** + * Abstracts a collection of records stored in the local database, providing + * CRUD operations and synchronization helpers. + */ + class Collection { + /** + * Constructor. + * + * Options: + * - `{BaseAdapter} adapter` The DB adapter (default: `IDB`) + * + * @param {String} bucket The bucket identifier. + * @param {String} name The collection name. + * @param {KintoBase} kinto The Kinto instance. + * @param {Object} options The options object. + */ + constructor(bucket, name, kinto, options = {}) { + this._bucket = bucket; + this._name = name; + this._lastModified = null; + const DBAdapter = options.adapter || IDB; + if (!DBAdapter) { + throw new Error("No adapter provided"); + } + const db = new DBAdapter(`${bucket}/${name}`, options.adapterOptions); + if (!(db instanceof BaseAdapter)) { + throw new Error("Unsupported adapter."); + } + // public properties + /** + * The db adapter instance + * @type {BaseAdapter} + */ + this.db = db; + /** + * The KintoBase instance. + * @type {KintoBase} + */ + this.kinto = kinto; + /** + * The event emitter instance. + * @type {EventEmitter} + */ + this.events = options.events; + /** + * The IdSchema instance. + * @type {Object} + */ + this.idSchema = this._validateIdSchema(options.idSchema); + /** + * The list of remote transformers. + * @type {Array} + */ + this.remoteTransformers = this._validateRemoteTransformers(options.remoteTransformers); + /** + * The list of hooks. + * @type {Object} + */ + this.hooks = this._validateHooks(options.hooks); + /** + * The list of fields names that will remain local. + * @type {Array} + */ + this.localFields = options.localFields || []; + } + /** + * The HTTP client. + * @type {KintoClient} + */ + get api() { + return this.kinto.api; + } + /** + * The collection name. + * @type {String} + */ + get name() { + return this._name; + } + /** + * The bucket name. + * @type {String} + */ + get bucket() { + return this._bucket; + } + /** + * The last modified timestamp. + * @type {Number} + */ + get lastModified() { + return this._lastModified; + } + /** + * Synchronization strategies. Available strategies are: + * + * - `MANUAL`: Conflicts will be reported in a dedicated array. + * - `SERVER_WINS`: Conflicts are resolved using remote data. + * - `CLIENT_WINS`: Conflicts are resolved using local data. + * + * @type {Object} + */ + static get strategy() { + return { + CLIENT_WINS: "client_wins", + SERVER_WINS: "server_wins", + PULL_ONLY: "pull_only", + MANUAL: "manual", + }; + } + /** + * Validates an idSchema. + * + * @param {Object|undefined} idSchema + * @return {Object} + */ + _validateIdSchema(idSchema) { + if (typeof idSchema === "undefined") { + return createUUIDSchema(); + } + if (typeof idSchema !== "object") { + throw new Error("idSchema must be an object."); + } + else if (typeof idSchema.generate !== "function") { + throw new Error("idSchema must provide a generate function."); + } + else if (typeof idSchema.validate !== "function") { + throw new Error("idSchema must provide a validate function."); + } + return idSchema; + } + /** + * Validates a list of remote transformers. + * + * @param {Array|undefined} remoteTransformers + * @return {Array} + */ + _validateRemoteTransformers(remoteTransformers) { + if (typeof remoteTransformers === "undefined") { + return []; + } + if (!Array.isArray(remoteTransformers)) { + throw new Error("remoteTransformers should be an array."); + } + return remoteTransformers.map(transformer => { + if (typeof transformer !== "object") { + throw new Error("A transformer must be an object."); + } + else if (typeof transformer.encode !== "function") { + throw new Error("A transformer must provide an encode function."); + } + else if (typeof transformer.decode !== "function") { + throw new Error("A transformer must provide a decode function."); + } + return transformer; + }); + } + /** + * Validate the passed hook is correct. + * + * @param {Array|undefined} hook. + * @return {Array} + **/ + _validateHook(hook) { + if (!Array.isArray(hook)) { + throw new Error("A hook definition should be an array of functions."); + } + return hook.map(fn => { + if (typeof fn !== "function") { + throw new Error("A hook definition should be an array of functions."); + } + return fn; + }); + } + /** + * Validates a list of hooks. + * + * @param {Object|undefined} hooks + * @return {Object} + */ + _validateHooks(hooks) { + if (typeof hooks === "undefined") { + return {}; + } + if (Array.isArray(hooks)) { + throw new Error("hooks should be an object, not an array."); + } + if (typeof hooks !== "object") { + throw new Error("hooks should be an object."); + } + const validatedHooks = {}; + for (const hook in hooks) { + if (!AVAILABLE_HOOKS.includes(hook)) { + throw new Error("The hook should be one of " + AVAILABLE_HOOKS.join(", ")); + } + validatedHooks[hook] = this._validateHook(hooks[hook]); + } + return validatedHooks; + } + /** + * Deletes every records in the current collection and marks the collection as + * never synced. + * + * @return {Promise} + */ + async clear() { + await this.db.clear(); + await this.db.saveMetadata(null); + await this.db.saveLastModified(null); + return { data: [], permissions: {} }; + } + /** + * Encodes a record. + * + * @param {String} type Either "remote" or "local". + * @param {Object} record The record object to encode. + * @return {Promise} + */ + _encodeRecord(type, record) { + if (!this[`${type}Transformers`].length) { + return Promise.resolve(record); + } + return waterfall(this[`${type}Transformers`].map(transformer => { + return record => transformer.encode(record); + }), record); + } + /** + * Decodes a record. + * + * @param {String} type Either "remote" or "local". + * @param {Object} record The record object to decode. + * @return {Promise} + */ + _decodeRecord(type, record) { + if (!this[`${type}Transformers`].length) { + return Promise.resolve(record); + } + return waterfall(this[`${type}Transformers`].reverse().map(transformer => { + return record => transformer.decode(record); + }), record); + } + /** + * Adds a record to the local database, asserting that none + * already exist with this ID. + * + * Note: If either the `useRecordId` or `synced` options are true, then the + * record object must contain the id field to be validated. If none of these + * options are true, an id is generated using the current IdSchema; in this + * case, the record passed must not have an id. + * + * Options: + * - {Boolean} synced Sets record status to "synced" (default: `false`). + * - {Boolean} useRecordId Forces the `id` field from the record to be used, + * instead of one that is generated automatically + * (default: `false`). + * + * @param {Object} record + * @param {Object} options + * @return {Promise} + */ + create(record, options = { useRecordId: false, synced: false }) { + // Validate the record and its ID (if any), even though this + // validation is also done in the CollectionTransaction method, + // because we need to pass the ID to preloadIds. + const reject = msg => Promise.reject(new Error(msg)); + if (typeof record !== "object") { + return reject("Record is not an object."); + } + if ((options.synced || options.useRecordId) && + !Object.prototype.hasOwnProperty.call(record, "id")) { + return reject("Missing required Id; synced and useRecordId options require one"); + } + if (!options.synced && + !options.useRecordId && + Object.prototype.hasOwnProperty.call(record, "id")) { + return reject("Extraneous Id; can't create a record having one set."); + } + const newRecord = Object.assign(Object.assign({}, record), { id: options.synced || options.useRecordId + ? record.id + : this.idSchema.generate(record), _status: options.synced ? "synced" : "created" }); + if (!this.idSchema.validate(newRecord.id)) { + return reject(`Invalid Id: ${newRecord.id}`); + } + return this.execute(txn => txn.create(newRecord), { + preloadIds: [newRecord.id], + }).catch(err => { + if (options.useRecordId) { + throw new Error("Couldn't create record. It may have been virtually deleted."); + } + throw err; + }); + } + /** + * Like {@link CollectionTransaction#update}, but wrapped in its own transaction. + * + * Options: + * - {Boolean} synced: Sets record status to "synced" (default: false) + * - {Boolean} patch: Extends the existing record instead of overwriting it + * (default: false) + * + * @param {Object} record + * @param {Object} options + * @return {Promise} + */ + update(record, options = { synced: false, patch: false }) { + // Validate the record and its ID, even though this validation is + // also done in the CollectionTransaction method, because we need + // to pass the ID to preloadIds. + if (typeof record !== "object") { + return Promise.reject(new Error("Record is not an object.")); + } + if (!Object.prototype.hasOwnProperty.call(record, "id")) { + return Promise.reject(new Error("Cannot update a record missing id.")); + } + if (!this.idSchema.validate(record.id)) { + return Promise.reject(new Error(`Invalid Id: ${record.id}`)); + } + return this.execute(txn => txn.update(record, options), { + preloadIds: [record.id], + }); + } + /** + * Like {@link CollectionTransaction#upsert}, but wrapped in its own transaction. + * + * @param {Object} record + * @return {Promise} + */ + upsert(record) { + // Validate the record and its ID, even though this validation is + // also done in the CollectionTransaction method, because we need + // to pass the ID to preloadIds. + if (typeof record !== "object") { + return Promise.reject(new Error("Record is not an object.")); + } + if (!Object.prototype.hasOwnProperty.call(record, "id")) { + return Promise.reject(new Error("Cannot update a record missing id.")); + } + if (!this.idSchema.validate(record.id)) { + return Promise.reject(new Error(`Invalid Id: ${record.id}`)); + } + return this.execute(txn => txn.upsert(record), { preloadIds: [record.id] }); + } + /** + * Like {@link CollectionTransaction#get}, but wrapped in its own transaction. + * + * Options: + * - {Boolean} includeDeleted: Include virtually deleted records. + * + * @param {String} id + * @param {Object} options + * @return {Promise} + */ + get(id, options = { includeDeleted: false }) { + return this.execute(txn => txn.get(id, options), { preloadIds: [id] }); + } + /** + * Like {@link CollectionTransaction#getAny}, but wrapped in its own transaction. + * + * @param {String} id + * @return {Promise} + */ + getAny(id) { + return this.execute(txn => txn.getAny(id), { preloadIds: [id] }); + } + /** + * Same as {@link Collection#delete}, but wrapped in its own transaction. + * + * Options: + * - {Boolean} virtual: When set to `true`, doesn't actually delete the record, + * update its `_status` attribute to `deleted` instead (default: true) + * + * @param {String} id The record's Id. + * @param {Object} options The options object. + * @return {Promise} + */ + delete(id, options = { virtual: true }) { + return this.execute(transaction => { + return transaction.delete(id, options); + }, { preloadIds: [id] }); + } + /** + * Same as {@link Collection#deleteAll}, but wrapped in its own transaction, execulding the parameter. + * + * @return {Promise} + */ + async deleteAll() { + const { data } = await this.list({}, { includeDeleted: false }); + const recordIds = data.map(record => record.id); + return this.execute(transaction => { + return transaction.deleteAll(recordIds); + }, { preloadIds: recordIds }); + } + /** + * The same as {@link CollectionTransaction#deleteAny}, but wrapped + * in its own transaction. + * + * @param {String} id The record's Id. + * @return {Promise} + */ + deleteAny(id) { + return this.execute(txn => txn.deleteAny(id), { preloadIds: [id] }); + } + /** + * Lists records from the local database. + * + * Params: + * - {Object} filters Filter the results (default: `{}`). + * - {String} order The order to apply (default: `-last_modified`). + * + * Options: + * - {Boolean} includeDeleted: Include virtually deleted records. + * + * @param {Object} params The filters and order to apply to the results. + * @param {Object} options The options object. + * @return {Promise} + */ + async list(params = {}, options = { includeDeleted: false }) { + params = Object.assign({ order: "-last_modified", filters: {} }, params); + const results = await this.db.list(params); + let data = results; + if (!options.includeDeleted) { + data = results.filter(record => record._status !== "deleted"); + } + return { data, permissions: {} }; + } + /** + * Imports remote changes into the local database. + * This method is in charge of detecting the conflicts, and resolve them + * according to the specified strategy. + * @param {SyncResultObject} syncResultObject The sync result object. + * @param {Array} decodedChanges The list of changes to import in the local database. + * @param {String} strategy The {@link Collection.strategy} (default: MANUAL) + * @return {Promise} + */ + async importChanges(syncResultObject, decodedChanges, strategy = Collection.strategy.MANUAL) { + // Retrieve records matching change ids. + try { + for (let i = 0; i < decodedChanges.length; i += IMPORT_CHUNK_SIZE) { + const slice = decodedChanges.slice(i, i + IMPORT_CHUNK_SIZE); + const { imports, resolved } = await this.db.execute(transaction => { + const imports = slice.map(remote => { + // Store remote change into local database. + return importChange(transaction, remote, this.localFields, strategy); + }); + const conflicts = imports + .filter(i => i.type === "conflicts") + .map(i => i.data); + const resolved = this._handleConflicts(transaction, conflicts, strategy); + return { imports, resolved }; + }, { preload: slice.map(record => record.id) }); + // Lists of created/updated/deleted records + imports.forEach(({ type, data }) => syncResultObject.add(type, data)); + // Automatically resolved conflicts (if not manual) + if (resolved.length > 0) { + syncResultObject.reset("conflicts").add("resolved", resolved); + } + } + } + catch (err) { + const data = { + type: "incoming", + message: err.message, + stack: err.stack, + }; + // XXX one error of the whole transaction instead of per atomic op + syncResultObject.add("errors", data); + } + return syncResultObject; + } + /** + * Imports the responses of pushed changes into the local database. + * Basically it stores the timestamp assigned by the server into the local + * database. + * @param {SyncResultObject} syncResultObject The sync result object. + * @param {Array} toApplyLocally The list of changes to import in the local database. + * @param {Array} conflicts The list of conflicts that have to be resolved. + * @param {String} strategy The {@link Collection.strategy}. + * @return {Promise} + */ + async _applyPushedResults(syncResultObject, toApplyLocally, conflicts, strategy = Collection.strategy.MANUAL) { + const toDeleteLocally = toApplyLocally.filter(r => r.deleted); + const toUpdateLocally = toApplyLocally.filter(r => !r.deleted); + const { published, resolved } = await this.db.execute(transaction => { + const updated = toUpdateLocally.map(record => { + const synced = markSynced(record); + transaction.update(synced); + return synced; + }); + const deleted = toDeleteLocally.map(record => { + transaction.delete(record.id); + // Amend result data with the deleted attribute set + return { id: record.id, deleted: true }; + }); + const published = updated.concat(deleted); + // Handle conflicts, if any + const resolved = this._handleConflicts(transaction, conflicts, strategy); + return { published, resolved }; + }); + syncResultObject.add("published", published); + if (resolved.length > 0) { + syncResultObject + .reset("conflicts") + .reset("resolved") + .add("resolved", resolved); + } + return syncResultObject; + } + /** + * Handles synchronization conflicts according to specified strategy. + * + * @param {SyncResultObject} result The sync result object. + * @param {String} strategy The {@link Collection.strategy}. + * @return {Promise<Array<Object>>} The resolved conflicts, as an + * array of {accepted, rejected} objects + */ + _handleConflicts(transaction, conflicts, strategy) { + if (strategy === Collection.strategy.MANUAL) { + return []; + } + return conflicts.map(conflict => { + const resolution = strategy === Collection.strategy.CLIENT_WINS + ? conflict.local + : conflict.remote; + const rejected = strategy === Collection.strategy.CLIENT_WINS + ? conflict.remote + : conflict.local; + let accepted, status, id; + if (resolution === null) { + // We "resolved" with the server-side deletion. Delete locally. + // This only happens during SERVER_WINS because the local + // version of a record can never be null. + // We can get "null" from the remote side if we got a conflict + // and there is no remote version available; see kinto-http.js + // batch.js:aggregate. + transaction.delete(conflict.local.id); + accepted = null; + // The record was deleted, but that status is "synced" with + // the server, so we don't need to push the change. + status = "synced"; + id = conflict.local.id; + } + else { + const updated = this._resolveRaw(conflict, resolution); + transaction.update(updated); + accepted = updated; + status = updated._status; + id = updated.id; + } + return { rejected, accepted, id, _status: status }; + }); + } + /** + * Execute a bunch of operations in a transaction. + * + * This transaction should be atomic -- either all of its operations + * will succeed, or none will. + * + * The argument to this function is itself a function which will be + * called with a {@link CollectionTransaction}. Collection methods + * are available on this transaction, but instead of returning + * promises, they are synchronous. execute() returns a Promise whose + * value will be the return value of the provided function. + * + * Most operations will require access to the record itself, which + * must be preloaded by passing its ID in the preloadIds option. + * + * Options: + * - {Array} preloadIds: list of IDs to fetch at the beginning of + * the transaction + * + * @return {Promise} Resolves with the result of the given function + * when the transaction commits. + */ + execute(doOperations, { preloadIds = [] } = {}) { + for (const id of preloadIds) { + if (!this.idSchema.validate(id)) { + return Promise.reject(Error(`Invalid Id: ${id}`)); + } + } + return this.db.execute(transaction => { + const txn = new CollectionTransaction(this, transaction); + const result = doOperations(txn); + txn.emitEvents(); + return result; + }, { preload: preloadIds }); + } + /** + * Resets the local records as if they were never synced; existing records are + * marked as newly created, deleted records are dropped. + * + * A next call to {@link Collection.sync} will thus republish the whole + * content of the local collection to the server. + * + * @return {Promise} Resolves with the number of processed records. + */ + async resetSyncStatus() { + const unsynced = await this.list({ filters: { _status: ["deleted", "synced"] }, order: "" }, { includeDeleted: true }); + await this.db.execute(transaction => { + unsynced.data.forEach(record => { + if (record._status === "deleted") { + // Garbage collect deleted records. + transaction.delete(record.id); + } + else { + // Records that were synced become «created». + transaction.update(Object.assign(Object.assign({}, record), { last_modified: undefined, _status: "created" })); + } + }); + }); + this._lastModified = null; + await this.db.saveLastModified(null); + return unsynced.data.length; + } + /** + * Returns an object containing two lists: + * + * - `toDelete`: unsynced deleted records we can safely delete; + * - `toSync`: local updates to send to the server. + * + * @return {Promise} + */ + async gatherLocalChanges() { + const unsynced = await this.list({ + filters: { _status: ["created", "updated"] }, + order: "", + }); + const deleted = await this.list({ filters: { _status: "deleted" }, order: "" }, { includeDeleted: true }); + return await Promise.all(unsynced.data + .concat(deleted.data) + .map(this._encodeRecord.bind(this, "remote"))); + } + /** + * Fetch remote changes, import them to the local database, and handle + * conflicts according to `options.strategy`. Then, updates the passed + * {@link SyncResultObject} with import results. + * + * Options: + * - {String} strategy: The selected sync strategy. + * - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter. + * - {Array<String>} exclude: A list of record ids to exclude from pull. + * - {Object} headers: The HTTP headers to use in the request. + * - {int} retry: The number of retries to do if the HTTP request fails. + * - {int} lastModified: The timestamp to use in `?_since` query. + * + * @param {KintoClient.Collection} client Kinto client Collection instance. + * @param {SyncResultObject} syncResultObject The sync result object. + * @param {Object} options The options object. + * @return {Promise} + */ + async pullChanges(client, syncResultObject, options = {}) { + if (!syncResultObject.ok) { + return syncResultObject; + } + const since = this.lastModified + ? this.lastModified + : await this.db.getLastModified(); + options = Object.assign({ strategy: Collection.strategy.MANUAL, lastModified: since, headers: {} }, options); + // Optionally ignore some records when pulling for changes. + // (avoid redownloading our own changes on last step of #sync()) + let filters; + if (options.exclude) { + // Limit the list of excluded records to the first 50 records in order + // to remain under de-facto URL size limit (~2000 chars). + // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184#417184 + const exclude_id = options.exclude + .slice(0, 50) + .map(r => r.id) + .join(","); + filters = { exclude_id }; + } + if (options.expectedTimestamp) { + filters = Object.assign(Object.assign({}, filters), { _expected: options.expectedTimestamp }); + } + // First fetch remote changes from the server + const { data, last_modified } = await client.listRecords({ + // Since should be ETag (see https://github.com/Kinto/kinto.js/issues/356) + since: options.lastModified ? `${options.lastModified}` : undefined, + headers: options.headers, + retry: options.retry, + // Fetch every page by default (FIXME: option to limit pages, see #277) + pages: Infinity, + filters, + }); + // last_modified is the ETag header value (string). + // For retro-compatibility with first kinto.js versions + // parse it to integer. + const unquoted = last_modified ? parseInt(last_modified, 10) : undefined; + // Check if server was flushed. + // This is relevant for the Kinto demo server + // (and thus for many new comers). + const localSynced = options.lastModified; + const serverChanged = unquoted > options.lastModified; + const emptyCollection = data.length === 0; + if (!options.exclude && localSynced && serverChanged && emptyCollection) { + const e = new ServerWasFlushedError(localSynced, unquoted, "Server has been flushed. Client Side Timestamp: " + + localSynced + + " Server Side Timestamp: " + + unquoted); + throw e; + } + // Atomic updates are not sensible here because unquoted is not + // computed as a function of syncResultObject.lastModified. + // eslint-disable-next-line require-atomic-updates + syncResultObject.lastModified = unquoted; + // Decode incoming changes. + const decodedChanges = await Promise.all(data.map(change => { + return this._decodeRecord("remote", change); + })); + // Hook receives decoded records. + const payload = { lastModified: unquoted, changes: decodedChanges }; + const afterHooks = await this.applyHook("incoming-changes", payload); + // No change, nothing to import. + if (afterHooks.changes.length > 0) { + // Reflect these changes locally + await this.importChanges(syncResultObject, afterHooks.changes, options.strategy); + } + return syncResultObject; + } + applyHook(hookName, payload) { + if (typeof this.hooks[hookName] == "undefined") { + return Promise.resolve(payload); + } + return waterfall(this.hooks[hookName].map(hook => { + return record => { + const result = hook(payload, this); + const resultThenable = result && typeof result.then === "function"; + const resultChanges = result && Object.prototype.hasOwnProperty.call(result, "changes"); + if (!(resultThenable || resultChanges)) { + throw new Error(`Invalid return value for hook: ${JSON.stringify(result)} has no 'then()' or 'changes' properties`); + } + return result; + }; + }), payload); + } + /** + * Publish local changes to the remote server and updates the passed + * {@link SyncResultObject} with publication results. + * + * Options: + * - {String} strategy: The selected sync strategy. + * - {Object} headers: The HTTP headers to use in the request. + * - {int} retry: The number of retries to do if the HTTP request fails. + * + * @param {KintoClient.Collection} client Kinto client Collection instance. + * @param {SyncResultObject} syncResultObject The sync result object. + * @param {Object} changes The change object. + * @param {Array} changes.toDelete The list of records to delete. + * @param {Array} changes.toSync The list of records to create/update. + * @param {Object} options The options object. + * @return {Promise} + */ + async pushChanges(client, changes, syncResultObject, options = {}) { + if (!syncResultObject.ok) { + return syncResultObject; + } + const safe = !options.strategy || options.strategy !== Collection.CLIENT_WINS; + const toDelete = changes.filter(r => r._status == "deleted"); + const toSync = changes.filter(r => r._status != "deleted"); + // Perform a batch request with every changes. + const synced = await client.batch(batch => { + toDelete.forEach(r => { + // never published locally deleted records should not be pusblished + if (r.last_modified) { + batch.deleteRecord(r); + } + }); + toSync.forEach(r => { + // Clean local fields (like _status) before sending to server. + const published = this.cleanLocalFields(r); + if (r._status === "created") { + batch.createRecord(published); + } + else { + batch.updateRecord(published); + } + }); + }, { + headers: options.headers, + retry: options.retry, + safe, + aggregate: true, + }); + // Store outgoing errors into sync result object + syncResultObject.add("errors", synced.errors.map(e => (Object.assign(Object.assign({}, e), { type: "outgoing" })))); + // Store outgoing conflicts into sync result object + const conflicts = []; + for (const { type, local, remote } of synced.conflicts) { + // Note: we ensure that local data are actually available, as they may + // be missing in the case of a published deletion. + const safeLocal = (local && local.data) || { id: remote.id }; + const realLocal = await this._decodeRecord("remote", safeLocal); + // We can get "null" from the remote side if we got a conflict + // and there is no remote version available; see kinto-http.js + // batch.js:aggregate. + const realRemote = remote && (await this._decodeRecord("remote", remote)); + const conflict = { type, local: realLocal, remote: realRemote }; + conflicts.push(conflict); + } + syncResultObject.add("conflicts", conflicts); + // Records that must be deleted are either deletions that were pushed + // to server (published) or deleted records that were never pushed (skipped). + const missingRemotely = synced.skipped.map(r => (Object.assign(Object.assign({}, r), { deleted: true }))); + // For created and updated records, the last_modified coming from server + // will be stored locally. + // Reflect publication results locally using the response from + // the batch request. + const published = synced.published.map(c => c.data); + const toApplyLocally = published.concat(missingRemotely); + // Apply the decode transformers, if any + const decoded = await Promise.all(toApplyLocally.map(record => { + return this._decodeRecord("remote", record); + })); + // We have to update the local records with the responses of the server + // (eg. last_modified values etc.). + if (decoded.length > 0 || conflicts.length > 0) { + await this._applyPushedResults(syncResultObject, decoded, conflicts, options.strategy); + } + return syncResultObject; + } + /** + * Return a copy of the specified record without the local fields. + * + * @param {Object} record A record with potential local fields. + * @return {Object} + */ + cleanLocalFields(record) { + const localKeys = RECORD_FIELDS_TO_CLEAN.concat(this.localFields); + return omitKeys(record, localKeys); + } + /** + * Resolves a conflict, updating local record according to proposed + * resolution — keeping remote record `last_modified` value as a reference for + * further batch sending. + * + * @param {Object} conflict The conflict object. + * @param {Object} resolution The proposed record. + * @return {Promise} + */ + resolve(conflict, resolution) { + return this.db.execute(transaction => { + const updated = this._resolveRaw(conflict, resolution); + transaction.update(updated); + return { data: updated, permissions: {} }; + }); + } + /** + * @private + */ + _resolveRaw(conflict, resolution) { + const resolved = Object.assign(Object.assign({}, resolution), { + // Ensure local record has the latest authoritative timestamp + last_modified: conflict.remote && conflict.remote.last_modified }); + // If the resolution object is strictly equal to the + // remote record, then we can mark it as synced locally. + // Otherwise, mark it as updated (so that the resolution is pushed). + const synced = deepEqual(resolved, conflict.remote); + return markStatus(resolved, synced ? "synced" : "updated"); + } + /** + * Synchronize remote and local data. The promise will resolve with a + * {@link SyncResultObject}, though will reject: + * + * - if the server is currently backed off; + * - if the server has been detected flushed. + * + * Options: + * - {Object} headers: HTTP headers to attach to outgoing requests. + * - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter. + * - {Number} retry: Number of retries when server fails to process the request (default: 1). + * - {Collection.strategy} strategy: See {@link Collection.strategy}. + * - {Boolean} ignoreBackoff: Force synchronization even if server is currently + * backed off. + * - {String} bucket: The remove bucket id to use (default: null) + * - {String} collection: The remove collection id to use (default: null) + * - {String} remote The remote Kinto server endpoint to use (default: null). + * + * @param {Object} options Options. + * @return {Promise} + * @throws {Error} If an invalid remote option is passed. + */ + async sync(options = { + strategy: Collection.strategy.MANUAL, + headers: {}, + retry: 1, + ignoreBackoff: false, + bucket: null, + collection: null, + remote: null, + expectedTimestamp: null, + }) { + options = Object.assign(Object.assign({}, options), { bucket: options.bucket || this.bucket, collection: options.collection || this.name }); + const previousRemote = this.api.remote; + if (options.remote) { + // Note: setting the remote ensures it's valid, throws when invalid. + this.api.remote = options.remote; + } + if (!options.ignoreBackoff && this.api.backoff > 0) { + const seconds = Math.ceil(this.api.backoff / 1000); + return Promise.reject(new Error(`Server is asking clients to back off; retry in ${seconds}s or use the ignoreBackoff option.`)); + } + const client = this.api + .bucket(options.bucket) + .collection(options.collection); + const result = new SyncResultObject(); + try { + // Fetch collection metadata. + await this.pullMetadata(client, options); + // Fetch last changes from the server. + await this.pullChanges(client, result, options); + const { lastModified } = result; + if (options.strategy != Collection.strategy.PULL_ONLY) { + // Fetch local changes + const toSync = await this.gatherLocalChanges(); + // Publish local changes and pull local resolutions + await this.pushChanges(client, toSync, result, options); + // Publish local resolution of push conflicts to server (on CLIENT_WINS) + const resolvedUnsynced = result.resolved.filter(r => r._status !== "synced"); + if (resolvedUnsynced.length > 0) { + const resolvedEncoded = await Promise.all(resolvedUnsynced.map(resolution => { + let record = resolution.accepted; + if (record === null) { + record = { id: resolution.id, _status: resolution._status }; + } + return this._encodeRecord("remote", record); + })); + await this.pushChanges(client, resolvedEncoded, result, options); + } + // Perform a last pull to catch changes that occured after the last pull, + // while local changes were pushed. Do not do it nothing was pushed. + if (result.published.length > 0) { + // Avoid redownloading our own changes during the last pull. + const pullOpts = Object.assign(Object.assign({}, options), { lastModified, exclude: result.published }); + await this.pullChanges(client, result, pullOpts); + } + } + // Don't persist lastModified value if any conflict or error occured + if (result.ok) { + // No conflict occured, persist collection's lastModified value + this._lastModified = await this.db.saveLastModified(result.lastModified); + } + } + catch (e) { + this.events.emit("sync:error", Object.assign(Object.assign({}, options), { error: e })); + throw e; + } + finally { + // Ensure API default remote is reverted if a custom one's been used + this.api.remote = previousRemote; + } + this.events.emit("sync:success", Object.assign(Object.assign({}, options), { result })); + return result; + } + /** + * Load a list of records already synced with the remote server. + * + * The local records which are unsynced or whose timestamp is either missing + * or superior to those being loaded will be ignored. + * + * @deprecated Use {@link importBulk} instead. + * @param {Array} records The previously exported list of records to load. + * @return {Promise} with the effectively imported records. + */ + async loadDump(records) { + return this.importBulk(records); + } + /** + * Load a list of records already synced with the remote server. + * + * The local records which are unsynced or whose timestamp is either missing + * or superior to those being loaded will be ignored. + * + * @param {Array} records The previously exported list of records to load. + * @return {Promise} with the effectively imported records. + */ + async importBulk(records) { + if (!Array.isArray(records)) { + throw new Error("Records is not an array."); + } + for (const record of records) { + if (!Object.prototype.hasOwnProperty.call(record, "id") || + !this.idSchema.validate(record.id)) { + throw new Error("Record has invalid ID: " + JSON.stringify(record)); + } + if (!record.last_modified) { + throw new Error("Record has no last_modified value: " + JSON.stringify(record)); + } + } + // Fetch all existing records from local database, + // and skip those who are newer or not marked as synced. + // XXX filter by status / ids in records + const { data } = await this.list({}, { includeDeleted: true }); + const existingById = data.reduce((acc, record) => { + acc[record.id] = record; + return acc; + }, {}); + const newRecords = records.filter(record => { + const localRecord = existingById[record.id]; + const shouldKeep = + // No local record with this id. + localRecord === undefined || + // Or local record is synced + (localRecord._status === "synced" && + // And was synced from server + localRecord.last_modified !== undefined && + // And is older than imported one. + record.last_modified > localRecord.last_modified); + return shouldKeep; + }); + return await this.db.importBulk(newRecords.map(markSynced)); + } + async pullMetadata(client, options = {}) { + const { expectedTimestamp, headers } = options; + const query = expectedTimestamp + ? { query: { _expected: expectedTimestamp } } + : undefined; + const metadata = await client.getData(Object.assign(Object.assign({}, query), { headers })); + return this.db.saveMetadata(metadata); + } + async metadata() { + return this.db.getMetadata(); + } + } + /** + * A Collection-oriented wrapper for an adapter's transaction. + * + * This defines the high-level functions available on a collection. + * The collection itself offers functions of the same name. These will + * perform just one operation in its own transaction. + */ + class CollectionTransaction { + constructor(collection, adapterTransaction) { + this.collection = collection; + this.adapterTransaction = adapterTransaction; + this._events = []; + } + _queueEvent(action, payload) { + this._events.push({ action, payload }); + } + /** + * Emit queued events, to be called once every transaction operations have + * been executed successfully. + */ + emitEvents() { + for (const { action, payload } of this._events) { + this.collection.events.emit(action, payload); + } + if (this._events.length > 0) { + const targets = this._events.map(({ action, payload }) => (Object.assign({ action }, payload))); + this.collection.events.emit("change", { targets }); + } + this._events = []; + } + /** + * Retrieve a record by its id from the local database, or + * undefined if none exists. + * + * This will also return virtually deleted records. + * + * @param {String} id + * @return {Object} + */ + getAny(id) { + const record = this.adapterTransaction.get(id); + return { data: record, permissions: {} }; + } + /** + * Retrieve a record by its id from the local database. + * + * Options: + * - {Boolean} includeDeleted: Include virtually deleted records. + * + * @param {String} id + * @param {Object} options + * @return {Object} + */ + get(id, options = { includeDeleted: false }) { + const res = this.getAny(id); + if (!res.data || + (!options.includeDeleted && res.data._status === "deleted")) { + throw new Error(`Record with id=${id} not found.`); + } + return res; + } + /** + * Deletes a record from the local database. + * + * Options: + * - {Boolean} virtual: When set to `true`, doesn't actually delete the record, + * update its `_status` attribute to `deleted` instead (default: true) + * + * @param {String} id The record's Id. + * @param {Object} options The options object. + * @return {Object} + */ + delete(id, options = { virtual: true }) { + // Ensure the record actually exists. + const existing = this.adapterTransaction.get(id); + const alreadyDeleted = existing && existing._status == "deleted"; + if (!existing || (alreadyDeleted && options.virtual)) { + throw new Error(`Record with id=${id} not found.`); + } + // Virtual updates status. + if (options.virtual) { + this.adapterTransaction.update(markDeleted(existing)); + } + else { + // Delete for real. + this.adapterTransaction.delete(id); + } + this._queueEvent("delete", { data: existing }); + return { data: existing, permissions: {} }; + } + /** + * Soft delete all records from the local database. + * + * @param {Array} ids Array of non-deleted Record Ids. + * @return {Object} + */ + deleteAll(ids) { + const existingRecords = []; + ids.forEach(id => { + existingRecords.push(this.adapterTransaction.get(id)); + this.delete(id); + }); + this._queueEvent("deleteAll", { data: existingRecords }); + return { data: existingRecords, permissions: {} }; + } + /** + * Deletes a record from the local database, if any exists. + * Otherwise, do nothing. + * + * @param {String} id The record's Id. + * @return {Object} + */ + deleteAny(id) { + const existing = this.adapterTransaction.get(id); + if (existing) { + this.adapterTransaction.update(markDeleted(existing)); + this._queueEvent("delete", { data: existing }); + } + return { data: Object.assign({ id }, existing), deleted: !!existing, permissions: {} }; + } + /** + * Adds a record to the local database, asserting that none + * already exist with this ID. + * + * @param {Object} record, which must contain an ID + * @return {Object} + */ + create(record) { + if (typeof record !== "object") { + throw new Error("Record is not an object."); + } + if (!Object.prototype.hasOwnProperty.call(record, "id")) { + throw new Error("Cannot create a record missing id"); + } + if (!this.collection.idSchema.validate(record.id)) { + throw new Error(`Invalid Id: ${record.id}`); + } + this.adapterTransaction.create(record); + this._queueEvent("create", { data: record }); + return { data: record, permissions: {} }; + } + /** + * Updates a record from the local database. + * + * Options: + * - {Boolean} synced: Sets record status to "synced" (default: false) + * - {Boolean} patch: Extends the existing record instead of overwriting it + * (default: false) + * + * @param {Object} record + * @param {Object} options + * @return {Object} + */ + update(record, options = { synced: false, patch: false }) { + if (typeof record !== "object") { + throw new Error("Record is not an object."); + } + if (!Object.prototype.hasOwnProperty.call(record, "id")) { + throw new Error("Cannot update a record missing id."); + } + if (!this.collection.idSchema.validate(record.id)) { + throw new Error(`Invalid Id: ${record.id}`); + } + const oldRecord = this.adapterTransaction.get(record.id); + if (!oldRecord) { + throw new Error(`Record with id=${record.id} not found.`); + } + const newRecord = options.patch ? Object.assign(Object.assign({}, oldRecord), record) : record; + const updated = this._updateRaw(oldRecord, newRecord, options); + this.adapterTransaction.update(updated); + this._queueEvent("update", { data: updated, oldRecord }); + return { data: updated, oldRecord, permissions: {} }; + } + /** + * Lower-level primitive for updating a record while respecting + * _status and last_modified. + * + * @param {Object} oldRecord: the record retrieved from the DB + * @param {Object} newRecord: the record to replace it with + * @return {Object} + */ + _updateRaw(oldRecord, newRecord, { synced = false } = {}) { + const updated = Object.assign({}, newRecord); + // Make sure to never loose the existing timestamp. + if (oldRecord && oldRecord.last_modified && !updated.last_modified) { + updated.last_modified = oldRecord.last_modified; + } + // If only local fields have changed, then keep record as synced. + // If status is created, keep record as created. + // If status is deleted, mark as updated. + const isIdentical = oldRecord && + recordsEqual(oldRecord, updated, this.collection.localFields); + const keepSynced = isIdentical && oldRecord._status == "synced"; + const neverSynced = !oldRecord || (oldRecord && oldRecord._status == "created"); + const newStatus = keepSynced || synced ? "synced" : neverSynced ? "created" : "updated"; + return markStatus(updated, newStatus); + } + /** + * Upsert a record into the local database. + * + * This record must have an ID. + * + * If a record with this ID already exists, it will be replaced. + * Otherwise, this record will be inserted. + * + * @param {Object} record + * @return {Object} + */ + upsert(record) { + if (typeof record !== "object") { + throw new Error("Record is not an object."); + } + if (!Object.prototype.hasOwnProperty.call(record, "id")) { + throw new Error("Cannot update a record missing id."); + } + if (!this.collection.idSchema.validate(record.id)) { + throw new Error(`Invalid Id: ${record.id}`); + } + let oldRecord = this.adapterTransaction.get(record.id); + const updated = this._updateRaw(oldRecord, record); + this.adapterTransaction.update(updated); + // Don't return deleted records -- pretend they are gone + if (oldRecord && oldRecord._status == "deleted") { + oldRecord = undefined; + } + if (oldRecord) { + this._queueEvent("update", { data: updated, oldRecord }); + } + else { + this._queueEvent("create", { data: updated }); + } + return { data: updated, oldRecord, permissions: {} }; + } + } + + const DEFAULT_BUCKET_NAME = "default"; + const DEFAULT_REMOTE = "http://localhost:8888/v1"; + const DEFAULT_RETRY = 1; + /** + * KintoBase class. + */ + class KintoBase { + /** + * Provides a public access to the base adapter class. Users can create a + * custom DB adapter by extending {@link BaseAdapter}. + * + * @type {Object} + */ + static get adapters() { + return { + BaseAdapter: BaseAdapter, + }; + } + /** + * Synchronization strategies. Available strategies are: + * + * - `MANUAL`: Conflicts will be reported in a dedicated array. + * - `SERVER_WINS`: Conflicts are resolved using remote data. + * - `CLIENT_WINS`: Conflicts are resolved using local data. + * + * @type {Object} + */ + static get syncStrategy() { + return Collection.strategy; + } + /** + * Constructor. + * + * Options: + * - `{String}` `remote` The server URL to use. + * - `{String}` `bucket` The collection bucket name. + * - `{EventEmitter}` `events` Events handler. + * - `{BaseAdapter}` `adapter` The base DB adapter class. + * - `{Object}` `adapterOptions` Options given to the adapter. + * - `{Object}` `headers` The HTTP headers to use. + * - `{Object}` `retry` Number of retries when the server fails to process the request (default: `1`) + * - `{String}` `requestMode` The HTTP CORS mode to use. + * - `{Number}` `timeout` The requests timeout in ms (default: `5000`). + * + * @param {Object} options The options object. + */ + constructor(options = {}) { + const defaults = { + bucket: DEFAULT_BUCKET_NAME, + remote: DEFAULT_REMOTE, + retry: DEFAULT_RETRY, + }; + this._options = Object.assign(Object.assign({}, defaults), options); + if (!this._options.adapter) { + throw new Error("No adapter provided"); + } + this._api = null; + /** + * The event emitter instance. + * @type {EventEmitter} + */ + this.events = this._options.events; + } + /** + * The kinto HTTP client instance. + * @type {KintoClient} + */ + get api() { + const { events, headers, remote, requestMode, retry, timeout, } = this._options; + if (!this._api) { + this._api = new this.ApiClass(remote, { + events, + headers, + requestMode, + retry, + timeout, + }); + } + return this._api; + } + /** + * Creates a {@link Collection} instance. The second (optional) parameter + * will set collection-level options like e.g. `remoteTransformers`. + * + * @param {String} collName The collection name. + * @param {Object} [options={}] Extra options or override client's options. + * @param {Object} [options.idSchema] IdSchema instance (default: UUID) + * @param {Object} [options.remoteTransformers] Array<RemoteTransformer> (default: `[]`]) + * @param {Object} [options.hooks] Array<Hook> (default: `[]`]) + * @param {Object} [options.localFields] Array<Field> (default: `[]`]) + * @return {Collection} + */ + collection(collName, options = {}) { + if (!collName) { + throw new Error("missing collection name"); + } + const { bucket, events, adapter, adapterOptions } = Object.assign(Object.assign({}, this._options), options); + const { idSchema, remoteTransformers, hooks, localFields } = options; + return new Collection(bucket, collName, this, { + events, + adapter, + adapterOptions, + idSchema, + remoteTransformers, + hooks, + localFields, + }); + } + } + + /* + * + * 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. + */ + const { setTimeout, clearTimeout } = ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs"); + const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs"); + XPCOMUtils.defineLazyGlobalGetters(global, ["fetch", "indexedDB"]); + ChromeUtils.defineESModuleGetters(global, { + EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", + // Use standalone kinto-http module landed in FFx. + KintoHttpClient: "resource://services-common/kinto-http-client.sys.mjs" + }); + ChromeUtils.defineLazyGetter(global, "generateUUID", () => { + const { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); + return generateUUID; + }); + class Kinto extends KintoBase { + static get adapters() { + return { + BaseAdapter, + IDB, + }; + } + get ApiClass() { + return KintoHttpClient; + } + constructor(options = {}) { + const events = {}; + EventEmitter.decorate(events); + const defaults = { + adapter: IDB, + events, + }; + super(Object.assign(Object.assign({}, defaults), options)); + } + collection(collName, options = {}) { + const idSchema = { + validate(id) { + return typeof id == "string" && RE_RECORD_ID.test(id); + }, + generate() { + return generateUUID() + .toString() + .replace(/[{}]/g, ""); + }, + }; + return super.collection(collName, Object.assign({ idSchema }, options)); + } + } + + return Kinto; + +}))); diff --git a/services/common/kinto-storage-adapter.sys.mjs b/services/common/kinto-storage-adapter.sys.mjs new file mode 100644 index 0000000000..75c6c06a26 --- /dev/null +++ b/services/common/kinto-storage-adapter.sys.mjs @@ -0,0 +1,553 @@ +/* + * 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. + */ +import { Sqlite } from "resource://gre/modules/Sqlite.sys.mjs"; + +const { Kinto } = ChromeUtils.import( + "resource://services-common/kinto-offline-client.js" +); + +/** + * Filter and sort list against provided filters and order. + * + * @param {Object} filters The filters to apply. + * @param {String} order The order to apply. + * @param {Array} list The list to reduce. + * @return {Array} + */ +function reduceRecords(filters, order, list) { + const filtered = filters ? filterObjects(filters, list) : list; + return order ? sortObjects(order, filtered) : filtered; +} + +/** + * Checks if a value is undefined. + * + * This is a copy of `_isUndefined` from kinto.js/src/utils.js. + * @param {Any} value + * @return {Boolean} + */ +function _isUndefined(value) { + return typeof value === "undefined"; +} + +/** + * Sorts records in a list according to a given ordering. + * + * This is a copy of `sortObjects` from kinto.js/src/utils.js. + * + * @param {String} order The ordering, eg. `-last_modified`. + * @param {Array} list The collection to order. + * @return {Array} + */ +function sortObjects(order, list) { + const hasDash = order[0] === "-"; + const field = hasDash ? order.slice(1) : order; + const direction = hasDash ? -1 : 1; + return list.slice().sort((a, b) => { + if (a[field] && _isUndefined(b[field])) { + return direction; + } + if (b[field] && _isUndefined(a[field])) { + return -direction; + } + if (_isUndefined(a[field]) && _isUndefined(b[field])) { + return 0; + } + return a[field] > b[field] ? direction : -direction; + }); +} + +/** + * Test if a single object matches all given filters. + * + * This is a copy of `filterObject` from kinto.js/src/utils.js. + * + * @param {Object} filters The filters object. + * @param {Object} entry The object to filter. + * @return {Function} + */ +function filterObject(filters, entry) { + return Object.keys(filters).every(filter => { + const value = filters[filter]; + if (Array.isArray(value)) { + return value.some(candidate => candidate === entry[filter]); + } + return entry[filter] === value; + }); +} + +/** + * Filters records in a list matching all given filters. + * + * This is a copy of `filterObjects` from kinto.js/src/utils.js. + * + * @param {Object} filters The filters object. + * @param {Array} list The collection to filter. + * @return {Array} + */ +function filterObjects(filters, list) { + return list.filter(entry => { + return filterObject(filters, entry); + }); +} + +const statements = { + createCollectionData: ` + CREATE TABLE collection_data ( + collection_name TEXT, + record_id TEXT, + record TEXT + );`, + + createCollectionMetadata: ` + CREATE TABLE collection_metadata ( + collection_name TEXT PRIMARY KEY, + last_modified INTEGER, + metadata TEXT + ) WITHOUT ROWID;`, + + createCollectionDataRecordIdIndex: ` + CREATE UNIQUE INDEX unique_collection_record + ON collection_data(collection_name, record_id);`, + + clearData: ` + DELETE FROM collection_data + WHERE collection_name = :collection_name;`, + + createData: ` + INSERT INTO collection_data (collection_name, record_id, record) + VALUES (:collection_name, :record_id, :record);`, + + updateData: ` + INSERT OR REPLACE INTO collection_data (collection_name, record_id, record) + VALUES (:collection_name, :record_id, :record);`, + + deleteData: ` + DELETE FROM collection_data + WHERE collection_name = :collection_name + AND record_id = :record_id;`, + + saveLastModified: ` + INSERT INTO collection_metadata(collection_name, last_modified) + VALUES(:collection_name, :last_modified) + ON CONFLICT(collection_name) DO UPDATE SET last_modified = :last_modified`, + + getLastModified: ` + SELECT last_modified + FROM collection_metadata + WHERE collection_name = :collection_name;`, + + saveMetadata: ` + INSERT INTO collection_metadata(collection_name, metadata) + VALUES(:collection_name, :metadata) + ON CONFLICT(collection_name) DO UPDATE SET metadata = :metadata`, + + getMetadata: ` + SELECT metadata + FROM collection_metadata + WHERE collection_name = :collection_name;`, + + getRecord: ` + SELECT record + FROM collection_data + WHERE collection_name = :collection_name + AND record_id = :record_id;`, + + listRecords: ` + SELECT record + FROM collection_data + WHERE collection_name = :collection_name;`, + + // N.B. we have to have a dynamic number of placeholders, which you + // can't do without building your own statement. See `execute` for details + listRecordsById: ` + SELECT record_id, record + FROM collection_data + WHERE collection_name = ? + AND record_id IN `, + + importData: ` + REPLACE INTO collection_data (collection_name, record_id, record) + VALUES (:collection_name, :record_id, :record);`, + + scanAllRecords: `SELECT * FROM collection_data;`, + + clearCollectionMetadata: `DELETE FROM collection_metadata;`, + + calculateStorage: ` + SELECT collection_name, SUM(LENGTH(record)) as size, COUNT(record) as num_records + FROM collection_data + GROUP BY collection_name;`, + + addMetadataColumn: ` + ALTER TABLE collection_metadata + ADD COLUMN metadata TEXT;`, +}; + +const createStatements = [ + "createCollectionData", + "createCollectionMetadata", + "createCollectionDataRecordIdIndex", +]; + +const currentSchemaVersion = 2; + +/** + * Firefox adapter. + * + * Uses Sqlite as a backing store. + * + * Options: + * - sqliteHandle: a handle to the Sqlite database this adapter will + * use as its backing store. To open such a handle, use the + * static openConnection() method. + */ +export class FirefoxAdapter extends Kinto.adapters.BaseAdapter { + constructor(collection, options = {}) { + super(); + const { sqliteHandle = null } = options; + this.collection = collection; + this._connection = sqliteHandle; + this._options = options; + } + + /** + * Initialize a Sqlite connection to be suitable for use with Kinto. + * + * This will be called automatically by open(). + */ + static async _init(connection) { + await connection.executeTransaction(async function doSetup() { + const schema = await connection.getSchemaVersion(); + + if (schema == 0) { + for (let statementName of createStatements) { + await connection.execute(statements[statementName]); + } + await connection.setSchemaVersion(currentSchemaVersion); + } else if (schema == 1) { + await connection.execute(statements.addMetadataColumn); + await connection.setSchemaVersion(currentSchemaVersion); + } else if (schema != 2) { + throw new Error("Unknown database schema: " + schema); + } + }); + return connection; + } + + _executeStatement(statement, params) { + return this._connection.executeCached(statement, params); + } + + /** + * Open and initialize a Sqlite connection to a database that Kinto + * can use. When you are done with this connection, close it by + * calling close(). + * + * Options: + * - path: The path for the Sqlite database + * + * @returns SqliteConnection + */ + static async openConnection(options) { + const opts = Object.assign({}, { sharedMemoryCache: false }, options); + const conn = await Sqlite.openConnection(opts).then(this._init); + try { + Sqlite.shutdown.addBlocker( + "Kinto storage adapter connection closing", + () => conn.close() + ); + } catch (e) { + // It's too late to block shutdown, just close the connection. + await conn.close(); + throw e; + } + return conn; + } + + clear() { + const params = { collection_name: this.collection }; + return this._executeStatement(statements.clearData, params); + } + + execute(callback, options = { preload: [] }) { + let result; + const conn = this._connection; + const collection = this.collection; + + return conn + .executeTransaction(async function doExecuteTransaction() { + // Preload specified records from DB, within transaction. + + // if options.preload has more elements than the sqlite variable + // limit, split it up. + const limit = 100; + let preloaded = {}; + let preload; + let more = options.preload; + + while (more.length) { + preload = more.slice(0, limit); + more = more.slice(limit, more.length); + + const parameters = [collection, ...preload]; + const placeholders = preload.map(_ => "?"); + const stmt = + statements.listRecordsById + "(" + placeholders.join(",") + ");"; + const rows = await conn.execute(stmt, parameters); + + rows.reduce((acc, row) => { + const record = JSON.parse(row.getResultByName("record")); + acc[row.getResultByName("record_id")] = record; + return acc; + }, preloaded); + } + const proxy = transactionProxy(collection, preloaded); + result = callback(proxy); + + for (let { statement, params } of proxy.operations) { + await conn.executeCached(statement, params); + } + }, conn.TRANSACTION_EXCLUSIVE) + .then(_ => result); + } + + get(id) { + const params = { + collection_name: this.collection, + record_id: id, + }; + return this._executeStatement(statements.getRecord, params).then(result => { + if (!result.length) { + return null; + } + return JSON.parse(result[0].getResultByName("record")); + }); + } + + list(params = { filters: {}, order: "" }) { + const parameters = { + collection_name: this.collection, + }; + return this._executeStatement(statements.listRecords, parameters) + .then(result => { + const records = []; + for (let k = 0; k < result.length; k++) { + const row = result[k]; + records.push(JSON.parse(row.getResultByName("record"))); + } + return records; + }) + .then(results => { + // The resulting list of records is filtered and sorted. + // XXX: with some efforts, this could be implemented using SQL. + return reduceRecords(params.filters, params.order, results); + }); + } + + async loadDump(records) { + return this.importBulk(records); + } + + /** + * Load a list of records into the local database. + * + * Note: The adapter is not in charge of filtering the already imported + * records. This is done in `Collection#loadDump()`, as a common behaviour + * between every adapters. + * + * @param {Array} records. + * @return {Array} imported records. + */ + async importBulk(records) { + const connection = this._connection; + const collection_name = this.collection; + await connection.executeTransaction(async function doImport() { + for (let record of records) { + const params = { + collection_name, + record_id: record.id, + record: JSON.stringify(record), + }; + await connection.execute(statements.importData, params); + } + const lastModified = Math.max( + ...records.map(record => record.last_modified) + ); + const params = { + collection_name, + }; + const previousLastModified = await connection + .execute(statements.getLastModified, params) + .then(result => { + return result.length + ? result[0].getResultByName("last_modified") + : -1; + }); + if (lastModified > previousLastModified) { + const params = { + collection_name, + last_modified: lastModified, + }; + await connection.execute(statements.saveLastModified, params); + } + }); + return records; + } + + saveLastModified(lastModified) { + const parsedLastModified = parseInt(lastModified, 10) || null; + const params = { + collection_name: this.collection, + last_modified: parsedLastModified, + }; + return this._executeStatement(statements.saveLastModified, params).then( + () => parsedLastModified + ); + } + + getLastModified() { + const params = { + collection_name: this.collection, + }; + return this._executeStatement(statements.getLastModified, params).then( + result => { + if (!result.length) { + return 0; + } + return result[0].getResultByName("last_modified"); + } + ); + } + + async saveMetadata(metadata) { + const params = { + collection_name: this.collection, + metadata: JSON.stringify(metadata), + }; + await this._executeStatement(statements.saveMetadata, params); + return metadata; + } + + async getMetadata() { + const params = { + collection_name: this.collection, + }; + const result = await this._executeStatement(statements.getMetadata, params); + if (!result.length) { + return null; + } + return JSON.parse(result[0].getResultByName("metadata")); + } + + calculateStorage() { + return this._executeStatement(statements.calculateStorage, {}).then( + result => { + return Array.from(result, row => ({ + collectionName: row.getResultByName("collection_name"), + size: row.getResultByName("size"), + numRecords: row.getResultByName("num_records"), + })); + } + ); + } + + /** + * Reset the sync status of every record and collection we have + * access to. + */ + resetSyncStatus() { + // We're going to use execute instead of executeCached, so build + // in our own sanity check + if (!this._connection) { + throw new Error("The storage adapter is not open"); + } + + return this._connection.executeTransaction(async function (conn) { + const promises = []; + await conn.execute(statements.scanAllRecords, null, function (row) { + const record = JSON.parse(row.getResultByName("record")); + const record_id = row.getResultByName("record_id"); + const collection_name = row.getResultByName("collection_name"); + if (record._status === "deleted") { + // Garbage collect deleted records. + promises.push( + conn.execute(statements.deleteData, { collection_name, record_id }) + ); + } else { + const newRecord = Object.assign({}, record, { + _status: "created", + last_modified: undefined, + }); + promises.push( + conn.execute(statements.updateData, { + record: JSON.stringify(newRecord), + record_id, + collection_name, + }) + ); + } + }); + await Promise.all(promises); + await conn.execute(statements.clearCollectionMetadata); + }); + } +} + +function transactionProxy(collection, preloaded) { + const _operations = []; + + return { + get operations() { + return _operations; + }, + + create(record) { + _operations.push({ + statement: statements.createData, + params: { + collection_name: collection, + record_id: record.id, + record: JSON.stringify(record), + }, + }); + }, + + update(record) { + _operations.push({ + statement: statements.updateData, + params: { + collection_name: collection, + record_id: record.id, + record: JSON.stringify(record), + }, + }); + }, + + delete(id) { + _operations.push({ + statement: statements.deleteData, + params: { + collection_name: collection, + record_id: id, + }, + }); + }, + + get(id) { + // Gecko JS engine outputs undesired warnings if id is not in preloaded. + return id in preloaded ? preloaded[id] : undefined; + }, + }; +} diff --git a/services/common/logmanager.sys.mjs b/services/common/logmanager.sys.mjs new file mode 100644 index 0000000000..724cfde38b --- /dev/null +++ b/services/common/logmanager.sys.mjs @@ -0,0 +1,447 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict;"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + NetUtil: "resource://gre/modules/NetUtil.sys.mjs", +}); + +const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days + +// "shared" logs (ie, where the same log name is used by multiple LogManager +// instances) are a fact of life here - eg, FirefoxAccounts logs are used by +// both Sync and Reading List. +// However, different instances have different pref branches, so we need to +// handle when one pref branch says "Debug" and the other says "Error" +// So we (a) keep singleton console and dump appenders and (b) keep track +// of the minimum (ie, most verbose) level and use that. +// This avoids (a) the most recent setter winning (as that is indeterminate) +// and (b) multiple dump/console appenders being added to the same log multiple +// times, which would cause messages to appear twice. + +// Singletons used by each instance. +var formatter; +var dumpAppender; +var consoleAppender; + +// A set of all preference roots used by all instances. +var allBranches = new Set(); + +const STREAM_SEGMENT_SIZE = 4096; +const PR_UINT32_MAX = 0xffffffff; + +/** + * Append to an nsIStorageStream + * + * This writes logging output to an in-memory stream which can later be read + * back as an nsIInputStream. It can be used to avoid expensive I/O operations + * during logging. Instead, one can periodically consume the input stream and + * e.g. write it to disk asynchronously. + */ +class StorageStreamAppender extends Log.Appender { + constructor(formatter) { + super(formatter); + this._name = "StorageStreamAppender"; + + this._converterStream = null; // holds the nsIConverterOutputStream + this._outputStream = null; // holds the underlying nsIOutputStream + + this._ss = null; + } + + get outputStream() { + if (!this._outputStream) { + // First create a raw stream. We can bail out early if that fails. + this._outputStream = this.newOutputStream(); + if (!this._outputStream) { + return null; + } + + // Wrap the raw stream in an nsIConverterOutputStream. We can reuse + // the instance if we already have one. + if (!this._converterStream) { + this._converterStream = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + } + this._converterStream.init(this._outputStream, "UTF-8"); + } + return this._converterStream; + } + + newOutputStream() { + let ss = (this._ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + )); + ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null); + return ss.getOutputStream(0); + } + + getInputStream() { + if (!this._ss) { + return null; + } + return this._ss.newInputStream(0); + } + + reset() { + if (!this._outputStream) { + return; + } + this.outputStream.close(); + this._outputStream = null; + this._ss = null; + } + + doAppend(formatted) { + if (!formatted) { + return; + } + try { + this.outputStream.writeString(formatted + "\n"); + } catch (ex) { + if (ex.result == Cr.NS_BASE_STREAM_CLOSED) { + // The underlying output stream is closed, so let's open a new one + // and try again. + this._outputStream = null; + } + try { + this.outputStream.writeString(formatted + "\n"); + } catch (ex) { + // Ah well, we tried, but something seems to be hosed permanently. + } + } + } +} + +// A storage appender that is flushable to a file on disk. Policies for +// when to flush, to what file, log rotation etc are up to the consumer +// (although it does maintain a .sawError property to help the consumer decide +// based on its policies) +class FlushableStorageAppender extends StorageStreamAppender { + constructor(formatter) { + super(formatter); + this.sawError = false; + } + + append(message) { + if (message.level >= Log.Level.Error) { + this.sawError = true; + } + StorageStreamAppender.prototype.append.call(this, message); + } + + reset() { + super.reset(); + this.sawError = false; + } + + // Flush the current stream to a file. Somewhat counter-intuitively, you + // must pass a log which will be written to with details of the operation. + async flushToFile(subdirArray, filename, log) { + let inStream = this.getInputStream(); + this.reset(); + if (!inStream) { + log.debug("Failed to flush log to a file - no input stream"); + return; + } + log.debug("Flushing file log"); + log.trace("Beginning stream copy to " + filename + ": " + Date.now()); + try { + await this._copyStreamToFile(inStream, subdirArray, filename, log); + log.trace("onCopyComplete", Date.now()); + } catch (ex) { + log.error("Failed to copy log stream to file", ex); + } + } + + /** + * Copy an input stream to the named file, doing everything off the main + * thread. + * subDirArray is an array of path components, relative to the profile + * directory, where the file will be created. + * outputFileName is the filename to create. + * Returns a promise that is resolved on completion or rejected with an error. + */ + async _copyStreamToFile(inputStream, subdirArray, outputFileName, log) { + let outputDirectory = PathUtils.join(PathUtils.profileDir, ...subdirArray); + await IOUtils.makeDirectory(outputDirectory); + let fullOutputFileName = PathUtils.join(outputDirectory, outputFileName); + + let outputStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + + outputStream.init( + new lazy.FileUtils.File(fullOutputFileName), + -1, + -1, + Ci.nsIFileOutputStream.DEFER_OPEN + ); + + await new Promise(resolve => + lazy.NetUtil.asyncCopy(inputStream, outputStream, () => resolve()) + ); + + outputStream.close(); + log.trace("finished copy to", fullOutputFileName); + } +} + +// The public LogManager object. +export function LogManager(prefRoot, logNames, logFilePrefix) { + this._prefObservers = []; + this.init(prefRoot, logNames, logFilePrefix); +} + +LogManager.StorageStreamAppender = StorageStreamAppender; + +LogManager.prototype = { + _cleaningUpFileLogs: false, + + init(prefRoot, logNames, logFilePrefix) { + this._prefs = Services.prefs.getBranch(prefRoot); + this._prefsBranch = prefRoot; + + this.logFilePrefix = logFilePrefix; + if (!formatter) { + // Create a formatter and various appenders to attach to the logs. + formatter = new Log.BasicFormatter(); + consoleAppender = new Log.ConsoleAppender(formatter); + dumpAppender = new Log.DumpAppender(formatter); + } + + allBranches.add(this._prefsBranch); + // We create a preference observer for all our prefs so they are magically + // reflected if the pref changes after creation. + let setupAppender = ( + appender, + prefName, + defaultLevel, + findSmallest = false + ) => { + let observer = newVal => { + let level = Log.Level[newVal] || defaultLevel; + if (findSmallest) { + // As some of our appenders have global impact (ie, there is only one + // place 'dump' goes to), we need to find the smallest value from all + // prefs controlling this appender. + // For example, if consumerA has dump=Debug then consumerB sets + // dump=Error, we need to keep dump=Debug so consumerA is respected. + for (let branch of allBranches) { + let lookPrefBranch = Services.prefs.getBranch(branch); + let lookVal = + Log.Level[lookPrefBranch.getStringPref(prefName, null)]; + if (lookVal && lookVal < level) { + level = lookVal; + } + } + } + appender.level = level; + }; + this._prefs.addObserver(prefName, observer); + this._prefObservers.push([prefName, observer]); + // and call the observer now with the current pref value. + observer(this._prefs.getStringPref(prefName, null)); + return observer; + }; + + this._observeConsolePref = setupAppender( + consoleAppender, + "log.appender.console", + Log.Level.Fatal, + true + ); + this._observeDumpPref = setupAppender( + dumpAppender, + "log.appender.dump", + Log.Level.Error, + true + ); + + // The file appender doesn't get the special singleton behaviour. + let fapp = (this._fileAppender = new FlushableStorageAppender(formatter)); + // the stream gets a default of Debug as the user must go out of their way + // to see the stuff spewed to it. + this._observeStreamPref = setupAppender( + fapp, + "log.appender.file.level", + Log.Level.Debug + ); + + // now attach the appenders to all our logs. + for (let logName of logNames) { + let log = Log.repository.getLogger(logName); + for (let appender of [fapp, dumpAppender, consoleAppender]) { + log.addAppender(appender); + } + } + // and use the first specified log as a "root" for our log. + this._log = Log.repository.getLogger(logNames[0] + ".LogManager"); + }, + + /** + * Cleanup this instance + */ + finalize() { + for (let [prefName, observer] of this._prefObservers) { + this._prefs.removeObserver(prefName, observer); + } + this._prefObservers = []; + try { + allBranches.delete(this._prefsBranch); + } catch (e) {} + this._prefs = null; + }, + + get _logFileSubDirectoryEntries() { + // At this point we don't allow a custom directory for the logs, nor allow + // it to be outside the profile directory. + // This returns an array of the the relative directory entries below the + // profile dir, and is the directory about:sync-log uses. + return ["weave", "logs"]; + }, + + get sawError() { + return this._fileAppender.sawError; + }, + + // Result values for resetFileLog. + SUCCESS_LOG_WRITTEN: "success-log-written", + ERROR_LOG_WRITTEN: "error-log-written", + + /** + * Possibly generate a log file for all accumulated log messages and refresh + * the input & output streams. + * Whether a "success" or "error" log is written is determined based on + * whether an "Error" log entry was written to any of the logs. + * Returns a promise that resolves on completion with either null (for no + * file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was + * written, or ERROR_LOG_WRITTEN if an "error" log was written. + */ + async resetFileLog() { + try { + let flushToFile; + let reasonPrefix; + let reason; + if (this._fileAppender.sawError) { + reason = this.ERROR_LOG_WRITTEN; + flushToFile = this._prefs.getBoolPref( + "log.appender.file.logOnError", + true + ); + reasonPrefix = "error"; + } else { + reason = this.SUCCESS_LOG_WRITTEN; + flushToFile = this._prefs.getBoolPref( + "log.appender.file.logOnSuccess", + false + ); + reasonPrefix = "success"; + } + + // might as well avoid creating an input stream if we aren't going to use it. + if (!flushToFile) { + this._fileAppender.reset(); + return null; + } + + // We have reasonPrefix at the start of the filename so all "error" + // logs are grouped in about:sync-log. + let filename = + reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt"; + await this._fileAppender.flushToFile( + this._logFileSubDirectoryEntries, + filename, + this._log + ); + // It's not completely clear to markh why we only do log cleanups + // for errors, but for now the Sync semantics have been copied... + // (one theory is that only cleaning up on error makes it less + // likely old error logs would be removed, but that's not true if + // there are occasional errors - let's address this later!) + if (reason == this.ERROR_LOG_WRITTEN && !this._cleaningUpFileLogs) { + this._log.trace("Running cleanup."); + try { + await this.cleanupLogs(); + } catch (err) { + this._log.error("Failed to cleanup logs", err); + } + } + return reason; + } catch (ex) { + this._log.error("Failed to resetFileLog", ex); + return null; + } + }, + + /** + * Finds all logs older than maxErrorAge and deletes them using async I/O. + */ + cleanupLogs() { + let maxAge = this._prefs.getIntPref( + "log.appender.file.maxErrorAge", + DEFAULT_MAX_ERROR_AGE + ); + let threshold = Date.now() - 1000 * maxAge; + this._log.debug("Log cleanup threshold time: " + threshold); + + let shouldDelete = fileInfo => { + return fileInfo.lastModified < threshold; + }; + return this._deleteLogFiles(shouldDelete); + }, + + /** + * Finds all logs and removes them. + */ + removeAllLogs() { + return this._deleteLogFiles(() => true); + }, + + // Delete some log files. A callback is invoked for each found log file to + // determine if that file should be removed. + async _deleteLogFiles(cbShouldDelete) { + this._cleaningUpFileLogs = true; + let logDir = lazy.FileUtils.getDir( + "ProfD", + this._logFileSubDirectoryEntries + ); + for (const path of await IOUtils.getChildren(logDir.path)) { + const name = PathUtils.filename(path); + + if (!name.startsWith("error-") && !name.startsWith("success-")) { + continue; + } + + try { + const info = await IOUtils.stat(path); + if (!cbShouldDelete(info)) { + continue; + } + + this._log.trace(` > Cleanup removing ${name} (${info.lastModified})`); + await IOUtils.remove(path); + this._log.trace(`Deleted ${name}`); + } catch (ex) { + this._log.debug( + `Encountered error trying to clean up old log file ${name}`, + ex + ); + } + } + this._cleaningUpFileLogs = false; + this._log.debug("Done deleting files."); + // This notification is used only for tests. + Services.obs.notifyObservers( + null, + "services-tests:common:log-manager:cleanup-logs" + ); + }, +}; diff --git a/services/common/modules-testing/logging.sys.mjs b/services/common/modules-testing/logging.sys.mjs new file mode 100644 index 0000000000..63bd306b67 --- /dev/null +++ b/services/common/modules-testing/logging.sys.mjs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +export function initTestLogging(level) { + function LogStats() { + this.errorsLogged = 0; + } + LogStats.prototype = { + format: function format(message) { + if (message.level == Log.Level.Error) { + this.errorsLogged += 1; + } + + return ( + message.time + + "\t" + + message.loggerName + + "\t" + + message.levelDesc + + "\t" + + this.formatText(message) + + "\n" + ); + }, + }; + Object.setPrototypeOf(LogStats.prototype, new Log.BasicFormatter()); + + let log = Log.repository.rootLogger; + let logStats = new LogStats(); + let appender = new Log.DumpAppender(logStats); + + if (typeof level == "undefined") { + level = "Debug"; + } + getTestLogger().level = Log.Level[level]; + Log.repository.getLogger("Services").level = Log.Level[level]; + + log.level = Log.Level.Trace; + appender.level = Log.Level.Trace; + // Overwrite any other appenders (e.g. from previous incarnations) + log.ownAppenders = [appender]; + log.updateAppenders(); + + // SQLite logging is noisy in these tests - we make it quiet by default + // (although individual tests are free to bump it later) + Log.repository.getLogger("Sqlite").level = Log.Level.Info; + + return logStats; +} + +export function getTestLogger(component) { + return Log.repository.getLogger("Testing"); +} diff --git a/services/common/moz.build b/services/common/moz.build new file mode 100644 index 0000000000..3585dc5426 --- /dev/null +++ b/services/common/moz.build @@ -0,0 +1,47 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Cloud Services", "Firefox: Common") + +TEST_DIRS += ["tests"] + +EXPORTS.mozilla.appservices += [ + "app_services_logger/AppServicesLoggerComponents.h", +] + +EXTRA_COMPONENTS += [ + "servicesComponents.manifest", +] + +EXTRA_JS_MODULES["services-common"] += [ + "async.sys.mjs", + "kinto-http-client.sys.mjs", + "kinto-offline-client.js", + "kinto-storage-adapter.sys.mjs", + "logmanager.sys.mjs", + "observers.sys.mjs", + "rest.sys.mjs", + "uptake-telemetry.sys.mjs", + "utils.sys.mjs", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + EXTRA_JS_MODULES["services-common"] += [ + "hawkclient.sys.mjs", + "hawkrequest.sys.mjs", + "tokenserverclient.sys.mjs", + ] + +TESTING_JS_MODULES.services.common += [ + "modules-testing/logging.sys.mjs", +] + +SPHINX_TREES["/services/common"] = "docs" + +XPCOM_MANIFESTS += [ + "app_services_logger/components.conf", +] diff --git a/services/common/observers.sys.mjs b/services/common/observers.sys.mjs new file mode 100644 index 0000000000..c79b2b1bf2 --- /dev/null +++ b/services/common/observers.sys.mjs @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A service for adding, removing and notifying observers of notifications. + * Wraps the nsIObserverService interface. + * + * @version 0.2 + */ +export var Observers = { + /** + * Register the given callback as an observer of the given topic. + * + * @param topic {String} + * the topic to observe + * + * @param callback {Object} + * the callback; an Object that implements nsIObserver or a Function + * that gets called when the notification occurs + * + * @param thisObject {Object} [optional] + * the object to use as |this| when calling a Function callback + * + * @returns the observer + */ + add(topic, callback, thisObject) { + let observer = new Observer(topic, callback, thisObject); + this._cache.push(observer); + Services.obs.addObserver(observer, topic, true); + + return observer; + }, + + /** + * Unregister the given callback as an observer of the given topic. + * + * @param topic {String} + * the topic being observed + * + * @param callback {Object} + * the callback doing the observing + * + * @param thisObject {Object} [optional] + * the object being used as |this| when calling a Function callback + */ + remove(topic, callback, thisObject) { + // This seems fairly inefficient, but I'm not sure how much better + // we can make it. We could index by topic, but we can't index by callback + // or thisObject, as far as I know, since the keys to JavaScript hashes + // (a.k.a. objects) can apparently only be primitive values. + let [observer] = this._cache.filter( + v => + v.topic == topic && v.callback == callback && v.thisObject == thisObject + ); + if (observer) { + Services.obs.removeObserver(observer, topic); + this._cache.splice(this._cache.indexOf(observer), 1); + } else { + throw new Error("Attempt to remove non-existing observer"); + } + }, + + /** + * Notify observers about something. + * + * @param topic {String} + * the topic to notify observers about + * + * @param subject {Object} [optional] + * some information about the topic; can be any JS object or primitive + * + * @param data {String} [optional] [deprecated] + * some more information about the topic; deprecated as the subject + * is sufficient to pass all needed information to the JS observers + * that this module targets; if you have multiple values to pass to + * the observer, wrap them in an object and pass them via the subject + * parameter (i.e.: { foo: 1, bar: "some string", baz: myObject }) + */ + notify(topic, subject, data) { + subject = typeof subject == "undefined" ? null : new Subject(subject); + data = typeof data == "undefined" ? null : data; + Services.obs.notifyObservers(subject, topic, data); + }, + + /** + * A cache of observers that have been added. + * + * We use this to remove observers when a caller calls |remove|. + * + * XXX This might result in reference cycles, causing memory leaks, + * if we hold a reference to an observer that holds a reference to us. + * Could we fix that by making this an independent top-level object + * rather than a property of this object? + */ + _cache: [], +}; + +function Observer(topic, callback, thisObject) { + this.topic = topic; + this.callback = callback; + this.thisObject = thisObject; +} + +Observer.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + observe(subject, topic, data) { + // Extract the wrapped object for subjects that are one of our wrappers + // around a JS object. This way we support both wrapped subjects created + // using this module and those that are real XPCOM components. + if ( + subject && + typeof subject == "object" && + "wrappedJSObject" in subject && + "observersModuleSubjectWrapper" in subject.wrappedJSObject + ) { + subject = subject.wrappedJSObject.object; + } + + if (typeof this.callback == "function") { + if (this.thisObject) { + this.callback.call(this.thisObject, subject, data); + } else { + this.callback(subject, data); + } + } else { + // typeof this.callback == "object" (nsIObserver) + this.callback.observe(subject, topic, data); + } + }, +}; + +function Subject(object) { + // Double-wrap the object and set a property identifying the wrappedJSObject + // as one of our wrappers to distinguish between subjects that are one of our + // wrappers (which we should unwrap when notifying our observers) and those + // that are real JS XPCOM components (which we should pass through unaltered). + this.wrappedJSObject = { observersModuleSubjectWrapper: true, object }; +} + +Subject.prototype = { + QueryInterface: ChromeUtils.generateQI([]), + getScriptableHelper() {}, + getInterfaces() {}, +}; diff --git a/services/common/rest.sys.mjs b/services/common/rest.sys.mjs new file mode 100644 index 0000000000..ed32047bcd --- /dev/null +++ b/services/common/rest.sys.mjs @@ -0,0 +1,720 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CryptoUtils: "resource://services-crypto/utils.sys.mjs", +}); + +function decodeString(data, charset) { + if (!data || !charset) { + return data; + } + + // This could be simpler if we assumed the charset is only ever UTF-8. + // It's unclear to me how willing we are to assume this, though... + let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stringStream.setData(data, data.length); + + let converterStream = Cc[ + "@mozilla.org/intl/converter-input-stream;1" + ].createInstance(Ci.nsIConverterInputStream); + + converterStream.init( + stringStream, + charset, + 0, + converterStream.DEFAULT_REPLACEMENT_CHARACTER + ); + + let remaining = data.length; + let body = ""; + while (remaining > 0) { + let str = {}; + let num = converterStream.readString(remaining, str); + if (!num) { + break; + } + remaining -= num; + body += str.value; + } + return body; +} + +/** + * Single use HTTP requests to RESTish resources. + * + * @param uri + * URI for the request. This can be an nsIURI object or a string + * that can be used to create one. An exception will be thrown if + * the string is not a valid URI. + * + * Examples: + * + * (1) Quick GET request: + * + * let response = await new RESTRequest("http://server/rest/resource").get(); + * if (!response.success) { + * // Bail out if we're not getting an HTTP 2xx code. + * processHTTPError(response.status); + * return; + * } + * processData(response.body); + * + * (2) Quick PUT request (non-string data is automatically JSONified) + * + * let response = await new RESTRequest("http://server/rest/resource").put(data); + */ +export function RESTRequest(uri) { + this.status = this.NOT_SENT; + + // If we don't have an nsIURI object yet, make one. This will throw if + // 'uri' isn't a valid URI string. + if (!(uri instanceof Ci.nsIURI)) { + uri = Services.io.newURI(uri); + } + this.uri = uri; + + this._headers = {}; + this._deferred = Promise.withResolvers(); + this._log = Log.repository.getLogger(this._logName); + this._log.manageLevelFromPref("services.common.log.logger.rest.request"); +} + +RESTRequest.prototype = { + _logName: "Services.Common.RESTRequest", + + QueryInterface: ChromeUtils.generateQI([ + "nsIInterfaceRequestor", + "nsIChannelEventSink", + ]), + + /** Public API: **/ + + /** + * URI for the request (an nsIURI object). + */ + uri: null, + + /** + * HTTP method (e.g. "GET") + */ + method: null, + + /** + * RESTResponse object + */ + response: null, + + /** + * nsIRequest load flags. Don't do any caching by default. Don't send user + * cookies and such over the wire (Bug 644734). + */ + loadFlags: + Ci.nsIRequest.LOAD_BYPASS_CACHE | + Ci.nsIRequest.INHIBIT_CACHING | + Ci.nsIRequest.LOAD_ANONYMOUS, + + /** + * nsIHttpChannel + */ + channel: null, + + /** + * Flag to indicate the status of the request. + * + * One of NOT_SENT, SENT, IN_PROGRESS, COMPLETED, ABORTED. + */ + status: null, + + NOT_SENT: 0, + SENT: 1, + IN_PROGRESS: 2, + COMPLETED: 4, + ABORTED: 8, + + /** + * HTTP status text of response + */ + statusText: null, + + /** + * Request timeout (in seconds, though decimal values can be used for + * up to millisecond granularity.) + * + * 0 for no timeout. Default is 300 seconds (5 minutes), the same as Sync uses + * in resource.js. + */ + timeout: 300, + + /** + * The encoding with which the response to this request must be treated. + * If a charset parameter is available in the HTTP Content-Type header for + * this response, that will always be used, and this value is ignored. We + * default to UTF-8 because that is a reasonable default. + */ + charset: "utf-8", + + /** + * Set a request header. + */ + setHeader(name, value) { + this._headers[name.toLowerCase()] = value; + }, + + /** + * Perform an HTTP GET. + * + * @return Promise<RESTResponse> + */ + async get() { + return this.dispatch("GET", null); + }, + + /** + * Perform an HTTP PATCH. + * + * @param data + * Data to be used as the request body. If this isn't a string + * it will be JSONified automatically. + * + * @return Promise<RESTResponse> + */ + async patch(data) { + return this.dispatch("PATCH", data); + }, + + /** + * Perform an HTTP PUT. + * + * @param data + * Data to be used as the request body. If this isn't a string + * it will be JSONified automatically. + * + * @return Promise<RESTResponse> + */ + async put(data) { + return this.dispatch("PUT", data); + }, + + /** + * Perform an HTTP POST. + * + * @param data + * Data to be used as the request body. If this isn't a string + * it will be JSONified automatically. + * + * @return Promise<RESTResponse> + */ + async post(data) { + return this.dispatch("POST", data); + }, + + /** + * Perform an HTTP DELETE. + * + * @return Promise<RESTResponse> + */ + async delete() { + return this.dispatch("DELETE", null); + }, + + /** + * Abort an active request. + */ + abort(rejectWithError = null) { + if (this.status != this.SENT && this.status != this.IN_PROGRESS) { + throw new Error("Can only abort a request that has been sent."); + } + + this.status = this.ABORTED; + this.channel.cancel(Cr.NS_BINDING_ABORTED); + + if (this.timeoutTimer) { + // Clear the abort timer now that the channel is done. + this.timeoutTimer.clear(); + } + if (rejectWithError) { + this._deferred.reject(rejectWithError); + } + }, + + /** Implementation stuff **/ + + async dispatch(method, data) { + if (this.status != this.NOT_SENT) { + throw new Error("Request has already been sent!"); + } + + this.method = method; + + // Create and initialize HTTP channel. + let channel = NetUtil.newChannel({ + uri: this.uri, + loadUsingSystemPrincipal: true, + }) + .QueryInterface(Ci.nsIRequest) + .QueryInterface(Ci.nsIHttpChannel); + this.channel = channel; + channel.loadFlags |= this.loadFlags; + channel.notificationCallbacks = this; + + this._log.debug(`${method} request to ${this.uri.spec}`); + // Set request headers. + let headers = this._headers; + for (let key in headers) { + if (key == "authorization" || key == "x-client-state") { + this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); + } else { + this._log.trace("HTTP Header " + key + ": " + headers[key]); + } + channel.setRequestHeader(key, headers[key], false); + } + + // REST requests accept JSON by default + if (!headers.accept) { + channel.setRequestHeader( + "accept", + "application/json;q=0.9,*/*;q=0.2", + false + ); + } + + // Set HTTP request body. + if (method == "PUT" || method == "POST" || method == "PATCH") { + // Convert non-string bodies into JSON with utf-8 encoding. If a string + // is passed we assume they've already encoded it. + let contentType = headers["content-type"]; + if (typeof data != "string") { + data = JSON.stringify(data); + if (!contentType) { + contentType = "application/json"; + } + if (!contentType.includes("charset")) { + data = CommonUtils.encodeUTF8(data); + contentType += "; charset=utf-8"; + } else { + // If someone handed us an object but also a custom content-type + // it's probably confused. We could go to even further lengths to + // respect it, but this shouldn't happen in practice. + console.error( + "rest.js found an object to JSON.stringify but also a " + + "content-type header with a charset specification. " + + "This probably isn't going to do what you expect" + ); + } + } + if (!contentType) { + contentType = "text/plain"; + } + + this._log.debug(method + " Length: " + data.length); + if (this._log.level <= Log.Level.Trace) { + this._log.trace(method + " Body: " + data); + } + + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setData(data, data.length); + + channel.QueryInterface(Ci.nsIUploadChannel); + channel.setUploadStream(stream, contentType, data.length); + } + // We must set this after setting the upload stream, otherwise it + // will always be 'PUT'. Yeah, I know. + channel.requestMethod = method; + + // Before opening the channel, set the charset that serves as a hint + // as to what the response might be encoded as. + channel.contentCharset = this.charset; + + // Blast off! + try { + channel.asyncOpen(this); + } catch (ex) { + // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port. + this._log.warn("Caught an error in asyncOpen", ex); + this._deferred.reject(ex); + } + this.status = this.SENT; + this.delayTimeout(); + return this._deferred.promise; + }, + + /** + * Create or push back the abort timer that kills this request. + */ + delayTimeout() { + if (this.timeout) { + CommonUtils.namedTimer( + this.abortTimeout, + this.timeout * 1000, + this, + "timeoutTimer" + ); + } + }, + + /** + * Abort the request based on a timeout. + */ + abortTimeout() { + this.abort( + Components.Exception( + "Aborting due to channel inactivity.", + Cr.NS_ERROR_NET_TIMEOUT + ) + ); + }, + + /** nsIStreamListener **/ + + onStartRequest(channel) { + if (this.status == this.ABORTED) { + this._log.trace( + "Not proceeding with onStartRequest, request was aborted." + ); + // We might have already rejected, but just in case. + this._deferred.reject( + Components.Exception("Request aborted", Cr.NS_BINDING_ABORTED) + ); + return; + } + + try { + channel.QueryInterface(Ci.nsIHttpChannel); + } catch (ex) { + this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); + this.status = this.ABORTED; + channel.cancel(Cr.NS_BINDING_ABORTED); + this._deferred.reject(ex); + return; + } + + this.status = this.IN_PROGRESS; + + this._log.trace( + "onStartRequest: " + channel.requestMethod + " " + channel.URI.spec + ); + + // Create a new response object. + this.response = new RESTResponse(this); + + this.delayTimeout(); + }, + + onStopRequest(channel, statusCode) { + if (this.timeoutTimer) { + // Clear the abort timer now that the channel is done. + this.timeoutTimer.clear(); + } + + // We don't want to do anything for a request that's already been aborted. + if (this.status == this.ABORTED) { + this._log.trace( + "Not proceeding with onStopRequest, request was aborted." + ); + // We might not have already rejected if the user called reject() manually. + // If we have already rejected, then this is a no-op + this._deferred.reject( + Components.Exception("Request aborted", Cr.NS_BINDING_ABORTED) + ); + return; + } + + try { + channel.QueryInterface(Ci.nsIHttpChannel); + } catch (ex) { + this._log.error("Unexpected error: channel not nsIHttpChannel!"); + this.status = this.ABORTED; + this._deferred.reject(ex); + return; + } + + this.status = this.COMPLETED; + + try { + this.response.body = decodeString( + this.response._rawBody, + this.response.charset + ); + this.response._rawBody = null; + } catch (ex) { + this._log.warn( + `Exception decoding response - ${this.method} ${channel.URI.spec}`, + ex + ); + this._deferred.reject(ex); + return; + } + + let statusSuccess = Components.isSuccessCode(statusCode); + let uri = (channel && channel.URI && channel.URI.spec) || "<unknown>"; + this._log.trace( + "Channel for " + + channel.requestMethod + + " " + + uri + + " returned status code " + + statusCode + ); + + // Throw the failure code and stop execution. Use Components.Exception() + // instead of Error() so the exception is QI-able and can be passed across + // XPCOM borders while preserving the status code. + if (!statusSuccess) { + let message = Components.Exception("", statusCode).name; + let error = Components.Exception(message, statusCode); + this._log.debug( + this.method + " " + uri + " failed: " + statusCode + " - " + message + ); + // Additionally give the full response body when Trace logging. + if (this._log.level <= Log.Level.Trace) { + this._log.trace(this.method + " body", this.response.body); + } + this._deferred.reject(error); + return; + } + + this._log.debug(this.method + " " + uri + " " + this.response.status); + + // Note that for privacy/security reasons we don't log this response body + + delete this._inputStream; + + this._deferred.resolve(this.response); + }, + + onDataAvailable(channel, stream, off, count) { + // We get an nsIRequest, which doesn't have contentCharset. + try { + channel.QueryInterface(Ci.nsIHttpChannel); + } catch (ex) { + this._log.error("Unexpected error: channel not nsIHttpChannel!"); + this.abort(ex); + return; + } + + if (channel.contentCharset) { + this.response.charset = channel.contentCharset; + } else { + this.response.charset = null; + } + + if (!this._inputStream) { + this._inputStream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].createInstance(Ci.nsIScriptableInputStream); + } + this._inputStream.init(stream); + + this.response._rawBody += this._inputStream.read(count); + + this.delayTimeout(); + }, + + /** nsIInterfaceRequestor **/ + + getInterface(aIID) { + return this.QueryInterface(aIID); + }, + + /** + * Returns true if headers from the old channel should be + * copied to the new channel. Invoked when a channel redirect + * is in progress. + */ + shouldCopyOnRedirect(oldChannel, newChannel, flags) { + let isInternal = !!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL); + let isSameURI = newChannel.URI.equals(oldChannel.URI); + this._log.debug( + "Channel redirect: " + + oldChannel.URI.spec + + ", " + + newChannel.URI.spec + + ", internal = " + + isInternal + ); + return isInternal && isSameURI; + }, + + /** nsIChannelEventSink **/ + asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { + let oldSpec = + oldChannel && oldChannel.URI ? oldChannel.URI.spec : "<undefined>"; + let newSpec = + newChannel && newChannel.URI ? newChannel.URI.spec : "<undefined>"; + this._log.debug( + "Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags + ); + + try { + newChannel.QueryInterface(Ci.nsIHttpChannel); + } catch (ex) { + this._log.error("Unexpected error: channel not nsIHttpChannel!"); + callback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE); + return; + } + + // For internal redirects, copy the headers that our caller set. + try { + if (this.shouldCopyOnRedirect(oldChannel, newChannel, flags)) { + this._log.trace("Copying headers for safe internal redirect."); + for (let key in this._headers) { + newChannel.setRequestHeader(key, this._headers[key], false); + } + } + } catch (ex) { + this._log.error("Error copying headers", ex); + } + + this.channel = newChannel; + + // We let all redirects proceed. + callback.onRedirectVerifyCallback(Cr.NS_OK); + }, +}; + +/** + * Response object for a RESTRequest. This will be created automatically by + * the RESTRequest. + */ +export function RESTResponse(request = null) { + this.body = ""; + this._rawBody = ""; + this.request = request; + this._log = Log.repository.getLogger(this._logName); + this._log.manageLevelFromPref("services.common.log.logger.rest.response"); +} + +RESTResponse.prototype = { + _logName: "Services.Common.RESTResponse", + + /** + * Corresponding REST request + */ + request: null, + + /** + * HTTP status code + */ + get status() { + let status; + try { + status = this.request.channel.responseStatus; + } catch (ex) { + this._log.debug("Caught exception fetching HTTP status code", ex); + return null; + } + Object.defineProperty(this, "status", { value: status }); + return status; + }, + + /** + * HTTP status text + */ + get statusText() { + let statusText; + try { + statusText = this.request.channel.responseStatusText; + } catch (ex) { + this._log.debug("Caught exception fetching HTTP status text", ex); + return null; + } + Object.defineProperty(this, "statusText", { value: statusText }); + return statusText; + }, + + /** + * Boolean flag that indicates whether the HTTP status code is 2xx or not. + */ + get success() { + let success; + try { + success = this.request.channel.requestSucceeded; + } catch (ex) { + this._log.debug("Caught exception fetching HTTP success flag", ex); + return null; + } + Object.defineProperty(this, "success", { value: success }); + return success; + }, + + /** + * Object containing HTTP headers (keyed as lower case) + */ + get headers() { + let headers = {}; + try { + this._log.trace("Processing response headers."); + let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel); + channel.visitResponseHeaders(function (header, value) { + headers[header.toLowerCase()] = value; + }); + } catch (ex) { + this._log.debug("Caught exception processing response headers", ex); + return null; + } + + Object.defineProperty(this, "headers", { value: headers }); + return headers; + }, + + /** + * HTTP body (string) + */ + body: null, +}; + +/** + * Single use MAC authenticated HTTP requests to RESTish resources. + * + * @param uri + * URI going to the RESTRequest constructor. + * @param authToken + * (Object) An auth token of the form {id: (string), key: (string)} + * from which the MAC Authentication header for this request will be + * derived. A token as obtained from + * TokenServerClient.getTokenUsingOAuth is accepted. + * @param extra + * (Object) Optional extra parameters. Valid keys are: nonce_bytes, ts, + * nonce, and ext. See CrytoUtils.computeHTTPMACSHA1 for information on + * the purpose of these values. + */ +export function TokenAuthenticatedRESTRequest(uri, authToken, extra) { + RESTRequest.call(this, uri); + this.authToken = authToken; + this.extra = extra || {}; +} + +TokenAuthenticatedRESTRequest.prototype = { + async dispatch(method, data) { + let sig = await lazy.CryptoUtils.computeHTTPMACSHA1( + this.authToken.id, + this.authToken.key, + method, + this.uri, + this.extra + ); + + this.setHeader("Authorization", sig.getHeader()); + + return super.dispatch(method, data); + }, +}; + +Object.setPrototypeOf( + TokenAuthenticatedRESTRequest.prototype, + RESTRequest.prototype +); diff --git a/services/common/servicesComponents.manifest b/services/common/servicesComponents.manifest new file mode 100644 index 0000000000..fe2a52fab6 --- /dev/null +++ b/services/common/servicesComponents.manifest @@ -0,0 +1,2 @@ +# Register resource aliases +resource services-common resource://gre/modules/services-common/ diff --git a/services/common/tests/moz.build b/services/common/tests/moz.build new file mode 100644 index 0000000000..77d3622318 --- /dev/null +++ b/services/common/tests/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.toml"] + +TEST_DIRS += ["unit"] diff --git a/services/common/tests/unit/head_global.js b/services/common/tests/unit/head_global.js new file mode 100644 index 0000000000..bcb1c063a4 --- /dev/null +++ b/services/common/tests/unit/head_global.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var Cm = Components.manager; + +// Required to avoid failures. +do_get_profile(); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); +updateAppInfo({ + name: "XPCShell", + ID: "xpcshell@tests.mozilla.org", + version: "1", + platformVersion: "", +}); + +function addResourceAlias() { + const handler = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + + let modules = ["common", "crypto", "settings"]; + for (let module of modules) { + let uri = Services.io.newURI( + "resource://gre/modules/services-" + module + "/" + ); + handler.setSubstitution("services-" + module, uri); + } +} +addResourceAlias(); diff --git a/services/common/tests/unit/head_helpers.js b/services/common/tests/unit/head_helpers.js new file mode 100644 index 0000000000..bd994ee71a --- /dev/null +++ b/services/common/tests/unit/head_helpers.js @@ -0,0 +1,270 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from head_global.js */ + +var { Log } = ChromeUtils.importESModule("resource://gre/modules/Log.sys.mjs"); +var { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" +); +var { + HTTP_400, + HTTP_401, + HTTP_402, + HTTP_403, + HTTP_404, + HTTP_405, + HTTP_406, + HTTP_407, + HTTP_408, + HTTP_409, + HTTP_410, + HTTP_411, + HTTP_412, + HTTP_413, + HTTP_414, + HTTP_415, + HTTP_417, + HTTP_500, + HTTP_501, + HTTP_502, + HTTP_503, + HTTP_504, + HTTP_505, + HttpError, + HttpServer, +} = ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs"); +var { getTestLogger, initTestLogging } = ChromeUtils.importESModule( + "resource://testing-common/services/common/logging.sys.mjs" +); +var { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); +var { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" +); + +function do_check_empty(obj) { + do_check_attribute_count(obj, 0); +} + +function do_check_attribute_count(obj, c) { + Assert.equal(c, Object.keys(obj).length); +} + +function do_check_throws(aFunc, aResult) { + try { + aFunc(); + } catch (e) { + Assert.equal(e.result, aResult); + return; + } + do_throw("Expected result " + aResult + ", none thrown."); +} + +/** + * Test whether specified function throws exception with expected + * result. + * + * @param func + * Function to be tested. + * @param message + * Message of expected exception. <code>null</code> for no throws. + */ +function do_check_throws_message(aFunc, aResult) { + try { + aFunc(); + } catch (e) { + Assert.equal(e.message, aResult); + return; + } + do_throw("Expected an error, none thrown."); +} + +/** + * Print some debug message to the console. All arguments will be printed, + * separated by spaces. + * + * @param [arg0, arg1, arg2, ...] + * Any number of arguments to print out + * @usage _("Hello World") -> prints "Hello World" + * @usage _(1, 2, 3) -> prints "1 2 3" + */ +var _ = function (some, debug, text, to) { + print(Array.from(arguments).join(" ")); +}; + +function httpd_setup(handlers, port = -1) { + let server = new HttpServer(); + for (let path in handlers) { + server.registerPathHandler(path, handlers[path]); + } + try { + server.start(port); + } catch (ex) { + _("=========================================="); + _("Got exception starting HTTP server on port " + port); + _("Error: " + Log.exceptionStr(ex)); + _("Is there a process already listening on port " + port + "?"); + _("=========================================="); + do_throw(ex); + } + + // Set the base URI for convenience. + let i = server.identity; + server.baseURI = + i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort; + + return server; +} + +function httpd_handler(statusCode, status, body) { + return function handler(request, response) { + _("Processing request"); + // Allow test functions to inspect the request. + request.body = readBytesFromInputStream(request.bodyInputStream); + handler.request = request; + + response.setStatusLine(request.httpVersion, statusCode, status); + if (body) { + response.bodyOutputStream.write(body, body.length); + } + }; +} + +function promiseStopServer(server) { + return new Promise(resolve => server.stop(resolve)); +} + +/* + * Read bytes string from an nsIInputStream. If 'count' is omitted, + * all available input is read. + */ +function readBytesFromInputStream(inputStream, count) { + if (!count) { + count = inputStream.available(); + } + if (!count) { + return ""; + } + return NetUtil.readInputStreamToString(inputStream, count, { + charset: "UTF-8", + }); +} + +function writeBytesToOutputStream(outputStream, string) { + if (!string) { + return; + } + let converter = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + converter.init(outputStream, "UTF-8"); + converter.writeString(string); + converter.close(); +} + +/* + * Ensure exceptions from inside callbacks leads to test failures. + */ +function ensureThrows(func) { + return function () { + try { + func.apply(this, arguments); + } catch (ex) { + do_throw(ex); + } + }; +} + +/** + * Proxy auth helpers. + */ + +/** + * Fake a PAC to prompt a channel replacement. + */ +var PACSystemSettings = { + QueryInterface: ChromeUtils.generateQI(["nsISystemProxySettings"]), + + // Replace this URI for each test to avoid caching. We want to ensure that + // each test gets a completely fresh setup. + mainThreadOnly: true, + PACURI: null, + getProxyForURI: function getProxyForURI(aURI) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + }, +}; + +var fakePACCID; +function installFakePAC() { + _("Installing fake PAC."); + fakePACCID = MockRegistrar.register( + "@mozilla.org/system-proxy-settings;1", + PACSystemSettings + ); +} + +function uninstallFakePAC() { + _("Uninstalling fake PAC."); + MockRegistrar.unregister(fakePACCID); +} + +function getUptakeTelemetrySnapshot(component, source) { + const TELEMETRY_CATEGORY_ID = "uptake.remotecontent.result"; + const snapshot = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_ALL_CHANNELS, + true + ); + const parentEvents = snapshot.parent || []; + return ( + parentEvents + // Transform raw event data to objects. + .map(([i, category, method, object, value, extras]) => { + return { category, method, object, value, extras }; + }) + // Keep only for the specified component and source. + .filter( + e => + e.category == TELEMETRY_CATEGORY_ID && + e.object == component && + e.extras.source == source + ) + // Return total number of events received by status, to mimic histograms snapshots. + .reduce((acc, e) => { + acc[e.value] = (acc[e.value] || 0) + 1; + return acc; + }, {}) + ); +} + +function checkUptakeTelemetry(snapshot1, snapshot2, expectedIncrements) { + const { UptakeTelemetry } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" + ); + const STATUSES = Object.values(UptakeTelemetry.STATUS); + for (const status of STATUSES) { + const expected = expectedIncrements[status] || 0; + const previous = snapshot1[status] || 0; + const current = snapshot2[status] || previous; + Assert.equal(expected, current - previous, `check events for ${status}`); + } +} + +async function withFakeChannel(channel, f) { + const { Policy } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" + ); + let oldGetChannel = Policy.getChannel; + Policy.getChannel = () => channel; + try { + return await f(); + } finally { + Policy.getChannel = oldGetChannel; + } +} + +function arrayEqual(a, b) { + return JSON.stringify(a) == JSON.stringify(b); +} diff --git a/services/common/tests/unit/head_http.js b/services/common/tests/unit/head_http.js new file mode 100644 index 0000000000..6c2a0a3f0f --- /dev/null +++ b/services/common/tests/unit/head_http.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from head_global.js */ + +var { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" +); + +function basic_auth_header(user, password) { + return "Basic " + btoa(user + ":" + CommonUtils.encodeUTF8(password)); +} + +function basic_auth_matches(req, user, password) { + if (!req.hasHeader("Authorization")) { + return false; + } + + let expected = basic_auth_header(user, CommonUtils.encodeUTF8(password)); + return req.getHeader("Authorization") == expected; +} + +function httpd_basic_auth_handler(body, metadata, response) { + if (basic_auth_matches(metadata, "guest", "guest")) { + response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } else { + body = "This path exists and is protected - failed"; + response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } + response.bodyOutputStream.write(body, body.length); +} diff --git a/services/common/tests/unit/moz.build b/services/common/tests/unit/moz.build new file mode 100644 index 0000000000..568f361a54 --- /dev/null +++ b/services/common/tests/unit/moz.build @@ -0,0 +1,5 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. diff --git a/services/common/tests/unit/test_async_chain.js b/services/common/tests/unit/test_async_chain.js new file mode 100644 index 0000000000..3277c92f81 --- /dev/null +++ b/services/common/tests/unit/test_async_chain.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Async } = ChromeUtils.importESModule( + "resource://services-common/async.sys.mjs" +); + +function run_test() { + _("Chain a few async methods, making sure the 'this' object is correct."); + + let methods = { + save(x, callback) { + this.x = x; + callback(x); + }, + addX(x, callback) { + callback(x + this.x); + }, + double(x, callback) { + callback(x * 2); + }, + neg(x, callback) { + callback(-x); + }, + }; + methods.chain = Async.chain; + + // ((1 + 1 + 1) * (-1) + 1) * 2 + 1 = -3 + methods.chain( + methods.save, + methods.addX, + methods.addX, + methods.neg, + methods.addX, + methods.double, + methods.addX, + methods.save + )(1); + Assert.equal(methods.x, -3); +} diff --git a/services/common/tests/unit/test_async_foreach.js b/services/common/tests/unit/test_async_foreach.js new file mode 100644 index 0000000000..14a23e7ff7 --- /dev/null +++ b/services/common/tests/unit/test_async_foreach.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Async } = ChromeUtils.importESModule( + "resource://services-common/async.sys.mjs" +); +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); + +function makeArray(length) { + // Start at 1 so that we can just divide by yieldEvery to get the expected + // call count. (we exp) + return Array.from({ length }, (v, i) => i + 1); +} + +// Adjust if we ever change the default. +const DEFAULT_YIELD_EVERY = 50; + +add_task(async function testYields() { + let spy = sinon.spy(Async, "promiseYield"); + try { + await Async.yieldingForEach(makeArray(DEFAULT_YIELD_EVERY * 2), element => { + // The yield will happen *after* this function is ran. + Assert.equal( + spy.callCount, + Math.floor((element - 1) / DEFAULT_YIELD_EVERY) + ); + }); + } finally { + spy.restore(); + } +}); + +add_task(async function testExistingYieldState() { + const yieldState = Async.yieldState(DEFAULT_YIELD_EVERY); + + for (let i = 0; i < 15; i++) { + Assert.equal(yieldState.shouldYield(), false); + } + + let spy = sinon.spy(Async, "promiseYield"); + + try { + await Async.yieldingForEach( + makeArray(DEFAULT_YIELD_EVERY * 2), + element => { + Assert.equal( + spy.callCount, + Math.floor((element + 15 - 1) / DEFAULT_YIELD_EVERY) + ); + }, + yieldState + ); + } finally { + spy.restore(); + } +}); + +add_task(async function testEarlyReturn() { + let lastElement = 0; + await Async.yieldingForEach(makeArray(DEFAULT_YIELD_EVERY), element => { + lastElement = element; + return element === 10; + }); + + Assert.equal(lastElement, 10); +}); + +add_task(async function testEaryReturnAsync() { + let lastElement = 0; + await Async.yieldingForEach(makeArray(DEFAULT_YIELD_EVERY), async element => { + lastElement = element; + return element === 10; + }); + + Assert.equal(lastElement, 10); +}); + +add_task(async function testEarlyReturnPromise() { + let lastElement = 0; + await Async.yieldingForEach(makeArray(DEFAULT_YIELD_EVERY), element => { + lastElement = element; + return new Promise(resolve => resolve(element === 10)); + }); + + Assert.equal(lastElement, 10); +}); diff --git a/services/common/tests/unit/test_hawkclient.js b/services/common/tests/unit/test_hawkclient.js new file mode 100644 index 0000000000..33d125f42e --- /dev/null +++ b/services/common/tests/unit/test_hawkclient.js @@ -0,0 +1,515 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { HawkClient } = ChromeUtils.importESModule( + "resource://services-common/hawkclient.sys.mjs" +); + +const SECOND_MS = 1000; +const MINUTE_MS = SECOND_MS * 60; +const HOUR_MS = MINUTE_MS * 60; + +const TEST_CREDS = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", +}; + +initTestLogging("Trace"); + +add_task(function test_now() { + let client = new HawkClient("https://example.com"); + + Assert.ok(client.now() - Date.now() < SECOND_MS); +}); + +add_task(function test_updateClockOffset() { + let client = new HawkClient("https://example.com"); + + let now = new Date(); + let serverDate = now.toUTCString(); + + // Client's clock is off + client.now = () => { + return now.valueOf() + HOUR_MS; + }; + + client._updateClockOffset(serverDate); + + // Check that they're close; there will likely be a one-second rounding + // error, so checking strict equality will likely fail. + // + // localtimeOffsetMsec is how many milliseconds to add to the local clock so + // that it agrees with the server. We are one hour ahead of the server, so + // our offset should be -1 hour. + Assert.ok(Math.abs(client.localtimeOffsetMsec + HOUR_MS) <= SECOND_MS); +}); + +add_task(async function test_authenticated_get_request() { + let message = '{"msg": "Great Success!"}'; + let method = "GET"; + + let server = httpd_setup({ + "/foo": (request, response) => { + Assert.ok(request.hasHeader("Authorization")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + let response = await client.request("/foo", method, TEST_CREDS); + let result = JSON.parse(response.body); + + Assert.equal("Great Success!", result.msg); + + await promiseStopServer(server); +}); + +async function check_authenticated_request(method) { + let server = httpd_setup({ + "/foo": (request, response) => { + Assert.ok(request.hasHeader("Authorization")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + response.bodyOutputStream.writeFrom( + request.bodyInputStream, + request.bodyInputStream.available() + ); + }, + }); + + let client = new HawkClient(server.baseURI); + + let response = await client.request("/foo", method, TEST_CREDS, { + foo: "bar", + }); + let result = JSON.parse(response.body); + + Assert.equal("bar", result.foo); + + await promiseStopServer(server); +} + +add_task(async function test_authenticated_post_request() { + await check_authenticated_request("POST"); +}); + +add_task(async function test_authenticated_put_request() { + await check_authenticated_request("PUT"); +}); + +add_task(async function test_authenticated_patch_request() { + await check_authenticated_request("PATCH"); +}); + +add_task(async function test_extra_headers() { + let server = httpd_setup({ + "/foo": (request, response) => { + Assert.ok(request.hasHeader("Authorization")); + Assert.ok(request.hasHeader("myHeader")); + Assert.equal(request.getHeader("myHeader"), "fake"); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + response.bodyOutputStream.writeFrom( + request.bodyInputStream, + request.bodyInputStream.available() + ); + }, + }); + + let client = new HawkClient(server.baseURI); + + let response = await client.request( + "/foo", + "POST", + TEST_CREDS, + { foo: "bar" }, + { myHeader: "fake" } + ); + let result = JSON.parse(response.body); + + Assert.equal("bar", result.foo); + + await promiseStopServer(server); +}); + +add_task(async function test_credentials_optional() { + let method = "GET"; + let server = httpd_setup({ + "/foo": (request, response) => { + Assert.ok(!request.hasHeader("Authorization")); + + let message = JSON.stringify({ msg: "you're in the friend zone" }); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + let result = await client.request("/foo", method); // credentials undefined + Assert.equal(JSON.parse(result.body).msg, "you're in the friend zone"); + + await promiseStopServer(server); +}); + +add_task(async function test_server_error() { + let message = "Ohai!"; + let method = "GET"; + + let server = httpd_setup({ + "/foo": (request, response) => { + response.setStatusLine(request.httpVersion, 418, "I am a Teapot"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + try { + await client.request("/foo", method, TEST_CREDS); + do_throw("Expected an error"); + } catch (err) { + Assert.equal(418, err.code); + Assert.equal("I am a Teapot", err.message); + } + + await promiseStopServer(server); +}); + +add_task(async function test_server_error_json() { + let message = JSON.stringify({ error: "Cannot get ye flask." }); + let method = "GET"; + + let server = httpd_setup({ + "/foo": (request, response) => { + response.setStatusLine( + request.httpVersion, + 400, + "What wouldst thou deau?" + ); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + try { + await client.request("/foo", method, TEST_CREDS); + do_throw("Expected an error"); + } catch (err) { + Assert.equal("Cannot get ye flask.", err.error); + } + + await promiseStopServer(server); +}); + +add_task(async function test_offset_after_request() { + let message = "Ohai!"; + let method = "GET"; + + let server = httpd_setup({ + "/foo": (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + let now = Date.now(); + client.now = () => { + return now + HOUR_MS; + }; + + Assert.equal(client.localtimeOffsetMsec, 0); + + await client.request("/foo", method, TEST_CREDS); + // Should be about an hour off + Assert.ok(Math.abs(client.localtimeOffsetMsec + HOUR_MS) < SECOND_MS); + + await promiseStopServer(server); +}); + +add_task(async function test_offset_in_hawk_header() { + let message = "Ohai!"; + let method = "GET"; + + let server = httpd_setup({ + "/first": function (request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + + "/second": function (request, response) { + // We see a better date now in the ts component of the header + let delta = getTimestampDelta(request.getHeader("Authorization")); + + // We're now within HAWK's one-minute window. + // I hope this isn't a recipe for intermittent oranges ... + if (delta < MINUTE_MS) { + response.setStatusLine(request.httpVersion, 200, "OK"); + } else { + response.setStatusLine(request.httpVersion, 400, "Delta: " + delta); + } + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + client.now = () => { + return Date.now() + 12 * HOUR_MS; + }; + + // We begin with no offset + Assert.equal(client.localtimeOffsetMsec, 0); + await client.request("/first", method, TEST_CREDS); + + // After the first server response, our offset is updated to -12 hours. + // We should be safely in the window, now. + Assert.ok(Math.abs(client.localtimeOffsetMsec + 12 * HOUR_MS) < MINUTE_MS); + await client.request("/second", method, TEST_CREDS); + + await promiseStopServer(server); +}); + +add_task(async function test_2xx_success() { + // Just to ensure that we're not biased toward 200 OK for success + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/foo": (request, response) => { + response.setStatusLine(request.httpVersion, 202, "Accepted"); + }, + }); + + let client = new HawkClient(server.baseURI); + + let response = await client.request("/foo", method, credentials); + + // Shouldn't be any content in a 202 + Assert.equal(response.body, ""); + + await promiseStopServer(server); +}); + +add_task(async function test_retry_request_on_fail() { + let attempts = 0; + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/maybe": function (request, response) { + // This path should be hit exactly twice; once with a bad timestamp, and + // again when the client retries the request with a corrected timestamp. + attempts += 1; + Assert.ok(attempts <= 2); + + let delta = getTimestampDelta(request.getHeader("Authorization")); + + // First time through, we should have a bad timestamp + if (attempts === 1) { + Assert.ok(delta > MINUTE_MS); + let message = "never!!!"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.bodyOutputStream.write(message, message.length); + return; + } + + // Second time through, timestamp should be corrected by client + Assert.ok(delta < MINUTE_MS); + let message = "i love you!!!"; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + client.now = () => { + return Date.now() + 12 * HOUR_MS; + }; + + // We begin with no offset + Assert.equal(client.localtimeOffsetMsec, 0); + + // Request will have bad timestamp; client will retry once + let response = await client.request("/maybe", method, credentials); + Assert.equal(response.body, "i love you!!!"); + + await promiseStopServer(server); +}); + +add_task(async function test_multiple_401_retry_once() { + // Like test_retry_request_on_fail, but always return a 401 + // and ensure that the client only retries once. + let attempts = 0; + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/maybe": function (request, response) { + // This path should be hit exactly twice; once with a bad timestamp, and + // again when the client retries the request with a corrected timestamp. + attempts += 1; + + Assert.ok(attempts <= 2); + + let message = "never!!!"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + client.now = () => { + return Date.now() - 12 * HOUR_MS; + }; + + // We begin with no offset + Assert.equal(client.localtimeOffsetMsec, 0); + + // Request will have bad timestamp; client will retry once + try { + await client.request("/maybe", method, credentials); + do_throw("Expected an error"); + } catch (err) { + Assert.equal(err.code, 401); + } + Assert.equal(attempts, 2); + + await promiseStopServer(server); +}); + +add_task(async function test_500_no_retry() { + // If we get a 500 error, the client should not retry (as it would with a + // 401) + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/no-shutup": function (request, response) { + let message = "Cannot get ye flask."; + response.setStatusLine(request.httpVersion, 500, "Internal server error"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + // Throw off the clock so the HawkClient would want to retry the request if + // it could + client.now = () => { + return Date.now() - 12 * HOUR_MS; + }; + + // Request will 500; no retries + try { + await client.request("/no-shutup", method, credentials); + do_throw("Expected an error"); + } catch (err) { + Assert.equal(err.code, 500); + } + + await promiseStopServer(server); +}); + +add_task(async function test_401_then_500() { + // Like test_multiple_401_retry_once, but return a 500 to the + // second request, ensuring that the promise is properly rejected + // in client.request. + let attempts = 0; + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/maybe": function (request, response) { + // This path should be hit exactly twice; once with a bad timestamp, and + // again when the client retries the request with a corrected timestamp. + attempts += 1; + Assert.ok(attempts <= 2); + + let delta = getTimestampDelta(request.getHeader("Authorization")); + + // First time through, we should have a bad timestamp + // Client will retry + if (attempts === 1) { + Assert.ok(delta > MINUTE_MS); + let message = "never!!!"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.bodyOutputStream.write(message, message.length); + return; + } + + // Second time through, timestamp should be corrected by client + // And fail on the client + Assert.ok(delta < MINUTE_MS); + let message = "Cannot get ye flask."; + response.setStatusLine(request.httpVersion, 500, "Internal server error"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new HawkClient(server.baseURI); + + client.now = () => { + return Date.now() - 12 * HOUR_MS; + }; + + // We begin with no offset + Assert.equal(client.localtimeOffsetMsec, 0); + + // Request will have bad timestamp; client will retry once + try { + await client.request("/maybe", method, credentials); + } catch (err) { + Assert.equal(err.code, 500); + } + Assert.equal(attempts, 2); + + await promiseStopServer(server); +}); + +// End of tests. +// Utility functions follow + +function getTimestampDelta(authHeader, now = Date.now()) { + let tsMS = new Date( + parseInt(/ts="(\d+)"/.exec(authHeader)[1], 10) * SECOND_MS + ); + return Math.abs(tsMS - now); +} + +function run_test() { + initTestLogging("Trace"); + run_next_test(); +} diff --git a/services/common/tests/unit/test_hawkrequest.js b/services/common/tests/unit/test_hawkrequest.js new file mode 100644 index 0000000000..13ab6737de --- /dev/null +++ b/services/common/tests/unit/test_hawkrequest.js @@ -0,0 +1,231 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { HAWKAuthenticatedRESTRequest, deriveHawkCredentials } = + ChromeUtils.importESModule("resource://services-common/hawkrequest.sys.mjs"); +const { Async } = ChromeUtils.importESModule( + "resource://services-common/async.sys.mjs" +); + +// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc +var SESSION_KEYS = { + sessionToken: h( + // eslint-disable-next-line no-useless-concat + "a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf" + ), + + tokenID: h( + // eslint-disable-next-line no-useless-concat + "c0a29dcf46174973 da1378696e4c82ae" + "10f723cf4f4d9f75 e39f4ae3851595ab" + ), + + reqHMACkey: h( + // eslint-disable-next-line no-useless-concat + "9d8f22998ee7f579 8b887042466b72d5" + "3e56ab0c094388bf 65831f702d2febc0" + ), +}; + +function do_register_cleanup() { + Services.prefs.clearUserPref("intl.accept_languages"); + Services.prefs.clearUserPref("services.common.log.logger.rest.request"); + + // remove the pref change listener + let hawk = new HAWKAuthenticatedRESTRequest("https://example.com"); + hawk._intl.uninit(); +} + +function run_test() { + registerCleanupFunction(do_register_cleanup); + + Services.prefs.setStringPref( + "services.common.log.logger.rest.request", + "Trace" + ); + initTestLogging("Trace"); + + run_next_test(); +} + +add_test(function test_intl_accept_language() { + let testCount = 0; + let languages = [ + "zu-NP;vo", // Nepalese dialect of Zulu, defaulting to Volapük + "fa-CG;ik", // Congolese dialect of Farsei, defaulting to Inupiaq + ]; + + function setLanguagePref(lang) { + Services.prefs.setStringPref("intl.accept_languages", lang); + } + + let hawk = new HAWKAuthenticatedRESTRequest("https://example.com"); + + Services.prefs.addObserver("intl.accept_languages", checkLanguagePref); + setLanguagePref(languages[testCount]); + + function checkLanguagePref() { + CommonUtils.nextTick(function () { + // Ensure we're only called for the number of entries in languages[]. + Assert.ok(testCount < languages.length); + + Assert.equal(hawk._intl.accept_languages, languages[testCount]); + + testCount++; + if (testCount < languages.length) { + // Set next language in prefs; Pref service will call checkNextLanguage. + setLanguagePref(languages[testCount]); + return; + } + + // We've checked all the entries in languages[]. Cleanup and move on. + info( + "Checked " + + testCount + + " languages. Removing checkLanguagePref as pref observer." + ); + Services.prefs.removeObserver("intl.accept_languages", checkLanguagePref); + run_next_test(); + }); + } +}); + +add_task(async function test_hawk_authenticated_request() { + let postData = { your: "data" }; + + // An arbitrary date - Feb 2, 1971. It ends in a bunch of zeroes to make our + // computation with the hawk timestamp easier, since hawk throws away the + // millisecond values. + let then = 34329600000; + + let clockSkew = 120000; + let timeOffset = -1 * clockSkew; + let localTime = then + clockSkew; + + // Set the accept-languages pref to the Nepalese dialect of Zulu. + let acceptLanguage = "zu-NP"; // omit trailing ';', which our HTTP libs snip + Services.prefs.setStringPref("intl.accept_languages", acceptLanguage); + + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + + let server = httpd_setup({ + "/elysium": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + + // check that the header timestamp is our arbitrary system date, not + // today's date. Note that hawk header timestamps are in seconds, not + // milliseconds. + let authorization = request.getHeader("Authorization"); + let tsMS = parseInt(/ts="(\d+)"/.exec(authorization)[1], 10) * 1000; + Assert.equal(tsMS, then); + + // This testing can be a little wonky. In an environment where + // pref("intl.accept_languages") === 'en-US, en' + // the header is sent as: + // 'en-US,en;q=0.5' + // hence our fake value for acceptLanguage. + let lang = request.getHeader("Accept-Language"); + Assert.equal(lang, acceptLanguage); + + let message = "yay"; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let url = server.baseURI + "/elysium"; + let extra = { + now: localTime, + localtimeOffsetMsec: timeOffset, + }; + + let request = new HAWKAuthenticatedRESTRequest(url, credentials, extra); + + // Allow hawk._intl to respond to the language pref change + await Async.promiseYield(); + + await request.post(postData); + Assert.equal(200, request.response.status); + Assert.equal(request.response.body, "yay"); + + Services.prefs.clearUserPref("intl.accept_languages"); + let pref = Services.prefs.getComplexValue( + "intl.accept_languages", + Ci.nsIPrefLocalizedString + ); + Assert.notEqual(acceptLanguage, pref.data); + + await promiseStopServer(server); +}); + +add_task(async function test_hawk_language_pref_changed() { + let languages = [ + "zu-NP", // Nepalese dialect of Zulu + "fa-CG", // Congolese dialect of Farsi + ]; + + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + + function setLanguage(lang) { + Services.prefs.setStringPref("intl.accept_languages", lang); + } + + let server = httpd_setup({ + "/foo": function (request, response) { + Assert.equal(languages[1], request.getHeader("Accept-Language")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + }, + }); + + let url = server.baseURI + "/foo"; + let request; + + setLanguage(languages[0]); + + // A new request should create the stateful object for tracking the current + // language. + request = new HAWKAuthenticatedRESTRequest(url, credentials); + + // Wait for change to propagate + await Async.promiseYield(); + Assert.equal(languages[0], request._intl.accept_languages); + + // Change the language pref ... + setLanguage(languages[1]); + + await Async.promiseYield(); + + request = new HAWKAuthenticatedRESTRequest(url, credentials); + let response = await request.post({}); + + Assert.equal(200, response.status); + Services.prefs.clearUserPref("intl.accept_languages"); + + await promiseStopServer(server); +}); + +add_task(async function test_deriveHawkCredentials() { + let credentials = await deriveHawkCredentials( + SESSION_KEYS.sessionToken, + "sessionToken" + ); + Assert.equal(credentials.id, SESSION_KEYS.tokenID); + Assert.equal( + CommonUtils.bytesAsHex(credentials.key), + SESSION_KEYS.reqHMACkey + ); +}); + +// turn formatted test vectors into normal hex strings +function h(hexStr) { + return hexStr.replace(/\s+/g, ""); +} diff --git a/services/common/tests/unit/test_kinto.js b/services/common/tests/unit/test_kinto.js new file mode 100644 index 0000000000..4b5e8471b1 --- /dev/null +++ b/services/common/tests/unit/test_kinto.js @@ -0,0 +1,512 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Kinto } = ChromeUtils.import( + "resource://services-common/kinto-offline-client.js" +); +const { FirefoxAdapter } = ChromeUtils.importESModule( + "resource://services-common/kinto-storage-adapter.sys.mjs" +); + +var server; + +// set up what we need to make storage adapters +const kintoFilename = "kinto.sqlite"; + +function do_get_kinto_sqliteHandle() { + return FirefoxAdapter.openConnection({ path: kintoFilename }); +} + +function do_get_kinto_collection(sqliteHandle, collection = "test_collection") { + let config = { + remote: `http://localhost:${server.identity.primaryPort}/v1/`, + headers: { Authorization: "Basic " + btoa("user:pass") }, + adapter: FirefoxAdapter, + adapterOptions: { sqliteHandle }, + }; + return new Kinto(config).collection(collection); +} + +async function clear_collection() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + await collection.clear(); + } finally { + await sqliteHandle.close(); + } +} + +// test some operations on a local collection +add_task(async function test_kinto_add_get() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + + let newRecord = { foo: "bar" }; + // check a record is created + let createResult = await collection.create(newRecord); + Assert.equal(createResult.data.foo, newRecord.foo); + // check getting the record gets the same info + let getResult = await collection.get(createResult.data.id); + deepEqual(createResult.data, getResult.data); + // check what happens if we create the same item again (it should throw + // since you can't create with id) + try { + await collection.create(createResult.data); + do_throw("Creation of a record with an id should fail"); + } catch (err) {} + // try a few creates without waiting for the first few to resolve + let promises = []; + promises.push(collection.create(newRecord)); + promises.push(collection.create(newRecord)); + promises.push(collection.create(newRecord)); + await collection.create(newRecord); + await Promise.all(promises); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +// test some operations on multiple connections +add_task(async function test_kinto_add_get() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection1 = do_get_kinto_collection(sqliteHandle); + const collection2 = do_get_kinto_collection( + sqliteHandle, + "test_collection_2" + ); + + let newRecord = { foo: "bar" }; + + // perform several write operations alternately without waiting for promises + // to resolve + let promises = []; + for (let i = 0; i < 10; i++) { + promises.push(collection1.create(newRecord)); + promises.push(collection2.create(newRecord)); + } + + // ensure subsequent operations still work + await Promise.all([ + collection1.create(newRecord), + collection2.create(newRecord), + ]); + await Promise.all(promises); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_kinto_update() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const newRecord = { foo: "bar" }; + // check a record is created + let createResult = await collection.create(newRecord); + Assert.equal(createResult.data.foo, newRecord.foo); + Assert.equal(createResult.data._status, "created"); + // check we can update this OK + let copiedRecord = Object.assign(createResult.data, {}); + deepEqual(createResult.data, copiedRecord); + copiedRecord.foo = "wibble"; + let updateResult = await collection.update(copiedRecord); + // check the field was updated + Assert.equal(updateResult.data.foo, copiedRecord.foo); + // check the status is still "created", since we haven't synced + // the record + Assert.equal(updateResult.data._status, "created"); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_kinto_clear() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + + // create an expected number of records + const expected = 10; + const newRecord = { foo: "bar" }; + for (let i = 0; i < expected; i++) { + await collection.create(newRecord); + } + // check the collection contains the correct number + let list = await collection.list(); + Assert.equal(list.data.length, expected); + // clear the collection and check again - should be 0 + await collection.clear(); + list = await collection.list(); + Assert.equal(list.data.length, 0); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_kinto_delete() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const newRecord = { foo: "bar" }; + // check a record is created + let createResult = await collection.create(newRecord); + Assert.equal(createResult.data.foo, newRecord.foo); + // check getting the record gets the same info + let getResult = await collection.get(createResult.data.id); + deepEqual(createResult.data, getResult.data); + // delete that record + let deleteResult = await collection.delete(createResult.data.id); + // check the ID is set on the result + Assert.equal(getResult.data.id, deleteResult.data.id); + // and check that get no longer returns the record + try { + getResult = await collection.get(createResult.data.id); + do_throw("there should not be a result"); + } catch (e) {} + } finally { + await sqliteHandle.close(); + } +}); + +add_task(async function test_kinto_list() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const expected = 10; + const created = []; + for (let i = 0; i < expected; i++) { + let newRecord = { foo: "test " + i }; + let createResult = await collection.create(newRecord); + created.push(createResult.data); + } + // check the collection contains the correct number + let list = await collection.list(); + Assert.equal(list.data.length, expected); + + // check that all created records exist in the retrieved list + for (let createdRecord of created) { + let found = false; + for (let retrievedRecord of list.data) { + if (createdRecord.id == retrievedRecord.id) { + deepEqual(createdRecord, retrievedRecord); + found = true; + } + } + Assert.ok(found); + } + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_importBulk_ignores_already_imported_records() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const record = { + id: "41b71c13-17e9-4ee3-9268-6a41abf9730f", + title: "foo", + last_modified: 1457896541, + }; + await collection.importBulk([record]); + let impactedRecords = await collection.importBulk([record]); + Assert.equal(impactedRecords.length, 0); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_loadDump_should_overwrite_old_records() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const record = { + id: "41b71c13-17e9-4ee3-9268-6a41abf9730f", + title: "foo", + last_modified: 1457896541, + }; + await collection.loadDump([record]); + const updated = Object.assign({}, record, { last_modified: 1457896543 }); + let impactedRecords = await collection.loadDump([updated]); + Assert.equal(impactedRecords.length, 1); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task(async function test_loadDump_should_not_overwrite_unsynced_records() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f"; + await collection.create( + { id: recordId, title: "foo" }, + { useRecordId: true } + ); + const record = { id: recordId, title: "bar", last_modified: 1457896541 }; + let impactedRecords = await collection.loadDump([record]); + Assert.equal(impactedRecords.length, 0); + } finally { + await sqliteHandle.close(); + } +}); + +add_task(clear_collection); + +add_task( + async function test_loadDump_should_not_overwrite_records_without_last_modified() { + let sqliteHandle; + try { + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + const recordId = "41b71c13-17e9-4ee3-9268-6a41abf9730f"; + await collection.create({ id: recordId, title: "foo" }, { synced: true }); + const record = { id: recordId, title: "bar", last_modified: 1457896541 }; + let impactedRecords = await collection.loadDump([record]); + Assert.equal(impactedRecords.length, 0); + } finally { + await sqliteHandle.close(); + } + } +); + +add_task(clear_collection); + +// Now do some sanity checks against a server - we're not looking to test +// core kinto.js functionality here (there is excellent test coverage in +// kinto.js), more making sure things are basically working as expected. +add_task(async function test_kinto_sync() { + const configPath = "/v1/"; + const metadataPath = "/v1/buckets/default/collections/test_collection"; + const recordsPath = "/v1/buckets/default/collections/test_collection/records"; + // register a handler + function handleResponse(request, response) { + try { + const sampled = getSampleResponse(request, server.identity.primaryPort); + if (!sampled) { + do_throw( + `unexpected ${request.method} request for ${request.path}?${request.queryString}` + ); + } + + response.setStatusLine( + null, + sampled.status.status, + sampled.status.statusText + ); + // send the headers + for (let headerLine of sampled.sampleHeaders) { + let headerElements = headerLine.split(":"); + response.setHeader(headerElements[0], headerElements[1].trimLeft()); + } + response.setHeader("Date", new Date().toUTCString()); + + response.write(sampled.responseBody); + } catch (e) { + dump(`${e}\n`); + } + } + server.registerPathHandler(configPath, handleResponse); + server.registerPathHandler(metadataPath, handleResponse); + server.registerPathHandler(recordsPath, handleResponse); + + // create an empty collection, sync to populate + let sqliteHandle; + try { + let result; + sqliteHandle = await do_get_kinto_sqliteHandle(); + const collection = do_get_kinto_collection(sqliteHandle); + + result = await collection.sync(); + Assert.ok(result.ok); + + // our test data has a single record; it should be in the local collection + let list = await collection.list(); + Assert.equal(list.data.length, 1); + + // now sync again; we should now have 2 records + result = await collection.sync(); + Assert.ok(result.ok); + list = await collection.list(); + Assert.equal(list.data.length, 2); + + // sync again; the second records should have been modified + const before = list.data[0].title; + result = await collection.sync(); + Assert.ok(result.ok); + list = await collection.list(); + const after = list.data[1].title; + Assert.notEqual(before, after); + + const manualID = list.data[0].id; + Assert.equal(list.data.length, 3); + Assert.equal(manualID, "some-manually-chosen-id"); + } finally { + await sqliteHandle.close(); + } +}); + +function run_test() { + // Set up an HTTP Server + server = new HttpServer(); + server.start(-1); + + run_next_test(); + + registerCleanupFunction(function () { + server.stop(function () {}); + }); +} + +// get a response for a given request from sample data +function getSampleResponse(req, port) { + const responses = { + OPTIONS: { + sampleHeaders: [ + "Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page", + "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS", + "Access-Control-Allow-Origin: *", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 200, statusText: "OK" }, + responseBody: "null", + }, + "GET:/v1/?": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + settings: { + batch_max_requests: 25, + }, + url: `http://localhost:${port}/v1/`, + documentation: "https://kinto.readthedocs.org/", + version: "1.5.1", + commit: "cbc6f58", + hello: "kinto", + }), + }, + "GET:/v1/buckets/default/collections/test_collection": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "1234"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + data: { + id: "test_collection", + last_modified: 1234, + }, + }), + }, + "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "1445606341071"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + data: [ + { + last_modified: 1445606341071, + done: false, + id: "68db8313-686e-4fff-835e-07d78ad6f2af", + title: "New test", + }, + ], + }), + }, + "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445606341071": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "1445607941223"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + data: [ + { + last_modified: 1445607941223, + done: false, + id: "901967b0-f729-4b30-8d8d-499cba7f4b1d", + title: "Another new test", + }, + ], + }), + }, + "GET:/v1/buckets/default/collections/test_collection/records?_sort=-last_modified&_since=1445607941223": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "1445607541267"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + data: [ + { + last_modified: 1445607541265, + done: false, + id: "901967b0-f729-4b30-8d8d-499cba7f4b1d", + title: "Modified title", + }, + { + last_modified: 1445607541267, + done: true, + id: "some-manually-chosen-id", + title: "New record with custom ID", + }, + ], + }), + }, + }; + return ( + responses[`${req.method}:${req.path}?${req.queryString}`] || + responses[`${req.method}:${req.path}`] || + responses[req.method] + ); +} diff --git a/services/common/tests/unit/test_load_modules.js b/services/common/tests/unit/test_load_modules.js new file mode 100644 index 0000000000..d86165266f --- /dev/null +++ b/services/common/tests/unit/test_load_modules.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const MODULE_BASE = "resource://services-common/"; +const shared_modules = [ + "async.sys.mjs", + "logmanager.sys.mjs", + "rest.sys.mjs", + "utils.sys.mjs", +]; + +const non_android_modules = ["tokenserverclient.sys.mjs"]; + +const TEST_BASE = "resource://testing-common/services/common/"; +const shared_test_modules = ["logging.sys.mjs"]; + +function expectImportsToSucceed(mm, base = MODULE_BASE) { + for (let m of mm) { + let resource = base + m; + let succeeded = false; + try { + ChromeUtils.importESModule(resource); + succeeded = true; + } catch (e) {} + + if (!succeeded) { + throw new Error(`Importing ${resource} should have succeeded!`); + } + } +} + +function expectImportsToFail(mm, base = MODULE_BASE) { + for (let m of mm) { + let resource = base + m; + let succeeded = false; + try { + ChromeUtils.importESModule(resource); + succeeded = true; + } catch (e) {} + + if (succeeded) { + throw new Error(`Importing ${resource} should have failed!`); + } + } +} + +function run_test() { + expectImportsToSucceed(shared_modules); + expectImportsToSucceed(shared_test_modules, TEST_BASE); + + if (AppConstants.platform != "android") { + expectImportsToSucceed(non_android_modules); + } else { + expectImportsToFail(non_android_modules); + } +} diff --git a/services/common/tests/unit/test_logmanager.js b/services/common/tests/unit/test_logmanager.js new file mode 100644 index 0000000000..89ac274e61 --- /dev/null +++ b/services/common/tests/unit/test_logmanager.js @@ -0,0 +1,330 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// NOTE: The sync test_errorhandler_* tests have quite good coverage for +// other aspects of this. + +const { LogManager } = ChromeUtils.importESModule( + "resource://services-common/logmanager.sys.mjs" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +// Returns an array of [consoleAppender, dumpAppender, [fileAppenders]] for +// the specified log. Note that fileAppenders will usually have length=1 +function getAppenders(log) { + let capps = log.appenders.filter(app => app instanceof Log.ConsoleAppender); + equal(capps.length, 1, "should only have one console appender"); + let dapps = log.appenders.filter(app => app instanceof Log.DumpAppender); + equal(dapps.length, 1, "should only have one dump appender"); + let fapps = log.appenders.filter( + app => app instanceof LogManager.StorageStreamAppender + ); + return [capps[0], dapps[0], fapps]; +} + +// Test that the correct thing happens when no prefs exist for the log manager. +add_task(async function test_noPrefs() { + // tell the log manager to init with a pref branch that doesn't exist. + let lm = new LogManager("no-such-branch.", ["TestLog"], "test"); + + let log = Log.repository.getLogger("TestLog"); + let [capp, dapp, fapps] = getAppenders(log); + // The console appender gets "Fatal" while the "dump" appender gets "Error" levels + equal(capp.level, Log.Level.Fatal); + equal(dapp.level, Log.Level.Error); + // and the file (stream) appender gets Debug by default + equal(fapps.length, 1, "only 1 file appender"); + equal(fapps[0].level, Log.Level.Debug); + lm.finalize(); +}); + +// Test that changes to the prefs used by the log manager are updated dynamically. +add_task(async function test_PrefChanges() { + Services.prefs.setStringPref( + "log-manager.test.log.appender.console", + "Trace" + ); + Services.prefs.setStringPref("log-manager.test.log.appender.dump", "Trace"); + Services.prefs.setStringPref( + "log-manager.test.log.appender.file.level", + "Trace" + ); + let lm = new LogManager("log-manager.test.", ["TestLog2"], "test"); + + let log = Log.repository.getLogger("TestLog2"); + let [capp, dapp, [fapp]] = getAppenders(log); + equal(capp.level, Log.Level.Trace); + equal(dapp.level, Log.Level.Trace); + equal(fapp.level, Log.Level.Trace); + // adjust the prefs and they should magically be reflected in the appenders. + Services.prefs.setStringPref( + "log-manager.test.log.appender.console", + "Debug" + ); + Services.prefs.setStringPref("log-manager.test.log.appender.dump", "Debug"); + Services.prefs.setStringPref( + "log-manager.test.log.appender.file.level", + "Debug" + ); + equal(capp.level, Log.Level.Debug); + equal(dapp.level, Log.Level.Debug); + equal(fapp.level, Log.Level.Debug); + // and invalid values should cause them to fallback to their defaults. + Services.prefs.setStringPref("log-manager.test.log.appender.console", "xxx"); + Services.prefs.setStringPref("log-manager.test.log.appender.dump", "xxx"); + Services.prefs.setStringPref( + "log-manager.test.log.appender.file.level", + "xxx" + ); + equal(capp.level, Log.Level.Fatal); + equal(dapp.level, Log.Level.Error); + equal(fapp.level, Log.Level.Debug); + lm.finalize(); +}); + +// Test that the same log used by multiple log managers does the right thing. +add_task(async function test_SharedLogs() { + // create the prefs for the first instance. + Services.prefs.setStringPref( + "log-manager-1.test.log.appender.console", + "Trace" + ); + Services.prefs.setStringPref("log-manager-1.test.log.appender.dump", "Trace"); + Services.prefs.setStringPref( + "log-manager-1.test.log.appender.file.level", + "Trace" + ); + let lm1 = new LogManager("log-manager-1.test.", ["TestLog3"], "test"); + + // and the second. + Services.prefs.setStringPref( + "log-manager-2.test.log.appender.console", + "Debug" + ); + Services.prefs.setStringPref("log-manager-2.test.log.appender.dump", "Debug"); + Services.prefs.setStringPref( + "log-manager-2.test.log.appender.file.level", + "Debug" + ); + let lm2 = new LogManager("log-manager-2.test.", ["TestLog3"], "test"); + + let log = Log.repository.getLogger("TestLog3"); + let [capp, dapp] = getAppenders(log); + + // console and dump appenders should be "trace" as it is more verbose than + // "debug" + equal(capp.level, Log.Level.Trace); + equal(dapp.level, Log.Level.Trace); + + // Set the prefs on the -1 branch to "Error" - it should then end up with + // "Debug" from the -2 branch. + Services.prefs.setStringPref( + "log-manager-1.test.log.appender.console", + "Error" + ); + Services.prefs.setStringPref("log-manager-1.test.log.appender.dump", "Error"); + Services.prefs.setStringPref( + "log-manager-1.test.log.appender.file.level", + "Error" + ); + + equal(capp.level, Log.Level.Debug); + equal(dapp.level, Log.Level.Debug); + + lm1.finalize(); + lm2.finalize(); +}); + +// A little helper to test what log files exist. We expect exactly zero (if +// prefix is null) or exactly one with the specified prefix. +function checkLogFile(prefix) { + let logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]); + let entries = logsdir.directoryEntries; + if (!prefix) { + // expecting no files. + ok(!entries.hasMoreElements()); + } else { + // expecting 1 file. + ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + equal(logfile.leafName.slice(-4), ".txt"); + ok(logfile.leafName.startsWith(prefix + "-test-"), logfile.leafName); + // and remove it ready for the next check. + logfile.remove(false); + } +} + +// Test that we correctly write error logs by default +add_task(async function test_logFileErrorDefault() { + let lm = new LogManager("log-manager.test.", ["TestLog2"], "test"); + + let log = Log.repository.getLogger("TestLog2"); + log.error("an error message"); + await lm.resetFileLog(lm.REASON_ERROR); + // One error log file exists. + checkLogFile("error"); + + lm.finalize(); +}); + +// Test that we correctly write success logs. +add_task(async function test_logFileSuccess() { + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnError", + false + ); + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + false + ); + + let lm = new LogManager("log-manager.test.", ["TestLog2"], "test"); + + let log = Log.repository.getLogger("TestLog2"); + log.info("an info message"); + await lm.resetFileLog(); + // Zero log files exist. + checkLogFile(null); + + // Reset logOnSuccess and do it again - log should appear. + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + true + ); + log.info("an info message"); + await lm.resetFileLog(); + + checkLogFile("success"); + + // Now test with no "reason" specified and no "error" record. + log.info("an info message"); + await lm.resetFileLog(); + // should get a "success" entry. + checkLogFile("success"); + + // With no "reason" and an error record - should get no success log. + log.error("an error message"); + await lm.resetFileLog(); + // should get no entry + checkLogFile(null); + + // And finally now with no error, to ensure that the fact we had an error + // previously doesn't persist after the .resetFileLog call. + log.info("an info message"); + await lm.resetFileLog(); + checkLogFile("success"); + + lm.finalize(); +}); + +// Test that we correctly write error logs. +add_task(async function test_logFileError() { + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnError", + false + ); + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + false + ); + + let lm = new LogManager("log-manager.test.", ["TestLog2"], "test"); + + let log = Log.repository.getLogger("TestLog2"); + log.info("an info message"); + let reason = await lm.resetFileLog(); + Assert.equal(reason, null, "null returned when no file created."); + // Zero log files exist. + checkLogFile(null); + + // Reset logOnSuccess - success logs should appear if no error records. + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + true + ); + log.info("an info message"); + reason = await lm.resetFileLog(); + Assert.equal(reason, lm.SUCCESS_LOG_WRITTEN); + checkLogFile("success"); + + // Set logOnError and unset logOnSuccess - error logs should appear. + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + false + ); + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnError", + true + ); + log.error("an error message"); + reason = await lm.resetFileLog(); + Assert.equal(reason, lm.ERROR_LOG_WRITTEN); + checkLogFile("error"); + + // Now test with no "error" record. + log.info("an info message"); + reason = await lm.resetFileLog(); + // should get no file + Assert.equal(reason, null); + checkLogFile(null); + + // With an error record we should get an error log. + log.error("an error message"); + reason = await lm.resetFileLog(); + // should get en error log + Assert.equal(reason, lm.ERROR_LOG_WRITTEN); + checkLogFile("error"); + + // And finally now with success, to ensure that the fact we had an error + // previously doesn't persist after the .resetFileLog call. + log.info("an info message"); + await lm.resetFileLog(); + checkLogFile(null); + + lm.finalize(); +}); + +function countLogFiles() { + let logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]); + let count = 0; + for (let entry of logsdir.directoryEntries) { + void entry; + count += 1; + } + return count; +} + +// Test that removeAllLogs removes all log files. +add_task(async function test_logFileError() { + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnError", + true + ); + Services.prefs.setBoolPref( + "log-manager.test.log.appender.file.logOnSuccess", + true + ); + + let lm = new LogManager("log-manager.test.", ["TestLog2"], "test"); + + let log = Log.repository.getLogger("TestLog2"); + log.info("an info message"); + let reason = await lm.resetFileLog(); + Assert.equal(reason, lm.SUCCESS_LOG_WRITTEN, "success log was written."); + + log.error("an error message"); + reason = await lm.resetFileLog(); + Assert.equal(reason, lm.ERROR_LOG_WRITTEN); + + Assert.equal(countLogFiles(), 2, "expect 2 log files"); + await lm.removeAllLogs(); + Assert.equal( + countLogFiles(), + 0, + "should be no log files after removing them" + ); + + lm.finalize(); +}); diff --git a/services/common/tests/unit/test_observers.js b/services/common/tests/unit/test_observers.js new file mode 100644 index 0000000000..b0fce95e0b --- /dev/null +++ b/services/common/tests/unit/test_observers.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Observers } = ChromeUtils.importESModule( + "resource://services-common/observers.sys.mjs" +); + +var gSubject = {}; + +add_test(function test_function_observer() { + let foo = false; + + let onFoo = function (subject, data) { + foo = !foo; + Assert.equal(subject, gSubject); + Assert.equal(data, "some data"); + }; + + Observers.add("foo", onFoo); + Observers.notify("foo", gSubject, "some data"); + + // The observer was notified after being added. + Assert.ok(foo); + + Observers.remove("foo", onFoo); + Observers.notify("foo"); + + // The observer was not notified after being removed. + Assert.ok(foo); + + run_next_test(); +}); + +add_test(function test_method_observer() { + let obj = { + foo: false, + onFoo(subject, data) { + this.foo = !this.foo; + Assert.equal(subject, gSubject); + Assert.equal(data, "some data"); + }, + }; + + // The observer is notified after being added. + Observers.add("foo", obj.onFoo, obj); + Observers.notify("foo", gSubject, "some data"); + Assert.ok(obj.foo); + + // The observer is not notified after being removed. + Observers.remove("foo", obj.onFoo, obj); + Observers.notify("foo"); + Assert.ok(obj.foo); + + run_next_test(); +}); + +add_test(function test_object_observer() { + let obj = { + foo: false, + observe(subject, topic, data) { + this.foo = !this.foo; + + Assert.equal(subject, gSubject); + Assert.equal(topic, "foo"); + Assert.equal(data, "some data"); + }, + }; + + Observers.add("foo", obj); + Observers.notify("foo", gSubject, "some data"); + + // The observer is notified after being added. + Assert.ok(obj.foo); + + Observers.remove("foo", obj); + Observers.notify("foo"); + + // The observer is not notified after being removed. + Assert.ok(obj.foo); + + run_next_test(); +}); diff --git a/services/common/tests/unit/test_restrequest.js b/services/common/tests/unit/test_restrequest.js new file mode 100644 index 0000000000..9ae5e9429a --- /dev/null +++ b/services/common/tests/unit/test_restrequest.js @@ -0,0 +1,860 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { RESTRequest } = ChromeUtils.importESModule( + "resource://services-common/rest.sys.mjs" +); + +function run_test() { + Log.repository.getLogger("Services.Common.RESTRequest").level = + Log.Level.Trace; + initTestLogging("Trace"); + + run_next_test(); +} + +/** + * Initializing a RESTRequest with an invalid URI throws + * NS_ERROR_MALFORMED_URI. + */ +add_test(function test_invalid_uri() { + do_check_throws(function () { + new RESTRequest("an invalid URI"); + }, Cr.NS_ERROR_MALFORMED_URI); + run_next_test(); +}); + +/** + * Verify initial values for attributes. + */ +add_test(function test_attributes() { + let uri = "http://foo.com/bar/baz"; + let request = new RESTRequest(uri); + + Assert.ok(request.uri instanceof Ci.nsIURI); + Assert.equal(request.uri.spec, uri); + Assert.equal(request.response, null); + Assert.equal(request.status, request.NOT_SENT); + let expectedLoadFlags = + Ci.nsIRequest.LOAD_BYPASS_CACHE | + Ci.nsIRequest.INHIBIT_CACHING | + Ci.nsIRequest.LOAD_ANONYMOUS; + Assert.equal(request.loadFlags, expectedLoadFlags); + + run_next_test(); +}); + +/** + * Verify that a proxy auth redirect doesn't break us. This has to be the first + * request made in the file! + */ +add_task(async function test_proxy_auth_redirect() { + let pacFetched = false; + function pacHandler(metadata, response) { + pacFetched = true; + let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader( + "Content-Type", + "application/x-ns-proxy-autoconfig", + false + ); + response.bodyOutputStream.write(body, body.length); + } + + let fetched = false; + function original(metadata, response) { + fetched = true; + let body = "TADA!"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); + } + + let server = httpd_setup({ + "/original": original, + "/pac3": pacHandler, + }); + PACSystemSettings.PACURI = server.baseURI + "/pac3"; + installFakePAC(); + + let req = new RESTRequest(server.baseURI + "/original"); + await req.get(); + + Assert.ok(pacFetched); + Assert.ok(fetched); + + Assert.ok(req.response.success); + Assert.equal("TADA!", req.response.body); + uninstallFakePAC(); + await promiseStopServer(server); +}); + +/** + * Ensure that failures that cause asyncOpen to throw + * result in callbacks being invoked. + * Bug 826086. + */ +add_task(async function test_forbidden_port() { + let request = new RESTRequest("http://localhost:6000/"); + + await Assert.rejects( + request.get(), + error => error.result == Cr.NS_ERROR_PORT_ACCESS_NOT_ALLOWED + ); +}); + +/** + * Demonstrate API short-hand: create a request and dispatch it immediately. + */ +add_task(async function test_simple_get() { + let handler = httpd_handler(200, "OK", "Huzzah!"); + let server = httpd_setup({ "/resource": handler }); + let request = new RESTRequest(server.baseURI + "/resource"); + let promiseResponse = request.get(); + + Assert.equal(request.status, request.SENT); + Assert.equal(request.method, "GET"); + + let response = await promiseResponse; + Assert.equal(response, request.response); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(response.success); + Assert.equal(response.status, 200); + Assert.equal(response.body, "Huzzah!"); + await promiseStopServer(server); +}); + +/** + * Test HTTP GET with all bells and whistles. + */ +add_task(async function test_get() { + let handler = httpd_handler(200, "OK", "Huzzah!"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + Assert.equal(request.status, request.NOT_SENT); + + let promiseResponse = request.get(); + + Assert.equal(request.status, request.SENT); + Assert.equal(request.method, "GET"); + + Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.LOAD_BYPASS_CACHE)); + Assert.ok(!!(request.channel.loadFlags & Ci.nsIRequest.INHIBIT_CACHING)); + + let response = await promiseResponse; + + Assert.equal(response, request.response); + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, "Huzzah!"); + Assert.equal(handler.request.method, "GET"); + + await Assert.rejects(request.get(), /Request has already been sent/); + + await promiseStopServer(server); +}); + +/** + * Test HTTP GET with UTF-8 content, and custom Content-Type. + */ +add_task(async function test_get_utf8() { + let response = "Hello World or Καλημέρα κόσμε or こんにちは 世界 😺"; + + let contentType = "text/plain"; + let charset = true; + let charsetSuffix = "; charset=UTF-8"; + + let server = httpd_setup({ + "/resource": function (req, res) { + res.setStatusLine(req.httpVersion, 200, "OK"); + res.setHeader( + "Content-Type", + contentType + (charset ? charsetSuffix : "") + ); + + let converter = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + converter.init(res.bodyOutputStream, "UTF-8"); + converter.writeString(response); + converter.close(); + }, + }); + + // Check if charset in Content-Type is propertly interpreted. + let request1 = new RESTRequest(server.baseURI + "/resource"); + await request1.get(); + + Assert.equal(request1.response.status, 200); + Assert.equal(request1.response.body, response); + Assert.equal( + request1.response.headers["content-type"], + contentType + charsetSuffix + ); + + // Check that we default to UTF-8 if Content-Type doesn't have a charset + charset = false; + let request2 = new RESTRequest(server.baseURI + "/resource"); + await request2.get(); + Assert.equal(request2.response.status, 200); + Assert.equal(request2.response.body, response); + Assert.equal(request2.response.headers["content-type"], contentType); + Assert.equal(request2.response.charset, "utf-8"); + + let request3 = new RESTRequest(server.baseURI + "/resource"); + + // With the test server we tend to get onDataAvailable in chunks of 8192 (in + // real network requests there doesn't appear to be any pattern to the size of + // the data `onDataAvailable` is called with), the smiling cat emoji encodes as + // 4 bytes, and so when utf8 encoded, the `"a" + "😺".repeat(2048)` will not be + // aligned onto a codepoint. + // + // Since 8192 isn't guaranteed and could easily change, the following string is + // a) very long, and b) misaligned on roughly 3/4 of the bytes, as a safety + // measure. + response = ("a" + "😺".repeat(2048)).repeat(10); + + await request3.get(); + + Assert.equal(request3.response.status, 200); + + // Make sure it came through ok, despite the misalignment. + Assert.equal(request3.response.body, response); + + await promiseStopServer(server); +}); + +/** + * Test HTTP POST data is encoded as UTF-8 by default. + */ +add_task(async function test_post_utf8() { + // We setup a handler that responds with exactly what it received. + // Given we've already tested above that responses are correctly utf-8 + // decoded we can surmise that the correct response coming back means the + // input must also have been encoded. + let server = httpd_setup({ + "/echo": function (req, res) { + res.setStatusLine(req.httpVersion, 200, "OK"); + res.setHeader("Content-Type", req.getHeader("content-type")); + // Get the body as bytes and write them back without touching them + let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(req.bodyInputStream); + let body = sis.read(sis.available()); + sis.close(); + res.write(body); + }, + }); + + let data = { + copyright: "©", + // See the comment in test_get_utf8 about this string. + long: ("a" + "😺".repeat(2048)).repeat(10), + }; + let request1 = new RESTRequest(server.baseURI + "/echo"); + await request1.post(data); + + Assert.equal(request1.response.status, 200); + deepEqual(JSON.parse(request1.response.body), data); + Assert.equal( + request1.response.headers["content-type"], + "application/json; charset=utf-8" + ); + + await promiseStopServer(server); +}); + +/** + * Test more variations of charset handling. + */ +add_task(async function test_charsets() { + let response = "Hello World, I can't speak Russian"; + + let contentType = "text/plain"; + let charset = true; + let charsetSuffix = "; charset=us-ascii"; + + let server = httpd_setup({ + "/resource": function (req, res) { + res.setStatusLine(req.httpVersion, 200, "OK"); + res.setHeader( + "Content-Type", + contentType + (charset ? charsetSuffix : "") + ); + + let converter = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + converter.init(res.bodyOutputStream, "us-ascii"); + converter.writeString(response); + converter.close(); + }, + }); + + // Check that provided charset overrides hint. + let request1 = new RESTRequest(server.baseURI + "/resource"); + request1.charset = "not-a-charset"; + await request1.get(); + Assert.equal(request1.response.status, 200); + Assert.equal(request1.response.body, response); + Assert.equal( + request1.response.headers["content-type"], + contentType + charsetSuffix + ); + Assert.equal(request1.response.charset, "us-ascii"); + + // Check that hint is used if Content-Type doesn't have a charset. + charset = false; + let request2 = new RESTRequest(server.baseURI + "/resource"); + request2.charset = "us-ascii"; + await request2.get(); + + Assert.equal(request2.response.status, 200); + Assert.equal(request2.response.body, response); + Assert.equal(request2.response.headers["content-type"], contentType); + Assert.equal(request2.response.charset, "us-ascii"); + + await promiseStopServer(server); +}); + +/** + * Used for testing PATCH/PUT/POST methods. + */ +async function check_posting_data(method) { + let funcName = method.toLowerCase(); + let handler = httpd_handler(200, "OK", "Got it!"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + Assert.equal(request.status, request.NOT_SENT); + let responsePromise = request[funcName]("Hullo?"); + Assert.equal(request.status, request.SENT); + Assert.equal(request.method, method); + + let response = await responsePromise; + + Assert.equal(response, request.response); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, "Got it!"); + + Assert.equal(handler.request.method, method); + Assert.equal(handler.request.body, "Hullo?"); + Assert.equal(handler.request.getHeader("Content-Type"), "text/plain"); + + await Assert.rejects( + request[funcName]("Hai!"), + /Request has already been sent/ + ); + + await promiseStopServer(server); +} + +/** + * Test HTTP PATCH with a simple string argument and default Content-Type. + */ +add_task(async function test_patch() { + await check_posting_data("PATCH"); +}); + +/** + * Test HTTP PUT with a simple string argument and default Content-Type. + */ +add_task(async function test_put() { + await check_posting_data("PUT"); +}); + +/** + * Test HTTP POST with a simple string argument and default Content-Type. + */ +add_task(async function test_post() { + await check_posting_data("POST"); +}); + +/** + * Test HTTP DELETE. + */ +add_task(async function test_delete() { + let handler = httpd_handler(200, "OK", "Got it!"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + Assert.equal(request.status, request.NOT_SENT); + let responsePromise = request.delete(); + Assert.equal(request.status, request.SENT); + Assert.equal(request.method, "DELETE"); + + let response = await responsePromise; + Assert.equal(response, request.response); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, "Got it!"); + Assert.equal(handler.request.method, "DELETE"); + + await Assert.rejects(request.delete(), /Request has already been sent/); + + await promiseStopServer(server); +}); + +/** + * Test an HTTP response with a non-200 status code. + */ +add_task(async function test_get_404() { + let handler = httpd_handler(404, "Not Found", "Cannae find it!"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + await request.get(); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(!request.response.success); + Assert.equal(request.response.status, 404); + Assert.equal(request.response.body, "Cannae find it!"); + + await promiseStopServer(server); +}); + +/** + * The 'data' argument to PUT, if not a string already, is automatically + * stringified as JSON. + */ +add_task(async function test_put_json() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let sample_data = { + some: "sample_data", + injson: "format", + number: 42, + }; + let request = new RESTRequest(server.baseURI + "/resource"); + await request.put(sample_data); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.method, "PUT"); + Assert.equal(handler.request.body, JSON.stringify(sample_data)); + Assert.equal( + handler.request.getHeader("Content-Type"), + "application/json; charset=utf-8" + ); + + await promiseStopServer(server); +}); + +/** + * The 'data' argument to POST, if not a string already, is automatically + * stringified as JSON. + */ +add_task(async function test_post_json() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let sample_data = { + some: "sample_data", + injson: "format", + number: 42, + }; + let request = new RESTRequest(server.baseURI + "/resource"); + await request.post(sample_data); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.method, "POST"); + Assert.equal(handler.request.body, JSON.stringify(sample_data)); + Assert.equal( + handler.request.getHeader("Content-Type"), + "application/json; charset=utf-8" + ); + + await promiseStopServer(server); +}); + +/** + * The content-type will be text/plain without a charset if the 'data' argument + * to POST is already a string. + */ +add_task(async function test_post_json() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let sample_data = "hello"; + let request = new RESTRequest(server.baseURI + "/resource"); + await request.post(sample_data); + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.method, "POST"); + Assert.equal(handler.request.body, sample_data); + Assert.equal(handler.request.getHeader("Content-Type"), "text/plain"); + + await promiseStopServer(server); +}); + +/** + * HTTP PUT with a custom Content-Type header. + */ +add_task(async function test_put_override_content_type() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + request.setHeader("Content-Type", "application/lolcat"); + await request.put("O HAI!!1!"); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.method, "PUT"); + Assert.equal(handler.request.body, "O HAI!!1!"); + Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat"); + + await promiseStopServer(server); +}); + +/** + * HTTP POST with a custom Content-Type header. + */ +add_task(async function test_post_override_content_type() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + request.setHeader("Content-Type", "application/lolcat"); + await request.post("O HAI!!1!"); + + Assert.equal(request.status, request.COMPLETED); + Assert.ok(request.response.success); + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.method, "POST"); + Assert.equal(handler.request.body, "O HAI!!1!"); + Assert.equal(handler.request.getHeader("Content-Type"), "application/lolcat"); + + await promiseStopServer(server); +}); + +/** + * No special headers are sent by default on a GET request. + */ +add_task(async function test_get_no_headers() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let ignore_headers = [ + "host", + "user-agent", + "accept", + "accept-language", + "accept-encoding", + "accept-charset", + "keep-alive", + "connection", + "pragma", + "cache-control", + "content-length", + "sec-fetch-dest", + "sec-fetch-mode", + "sec-fetch-site", + "sec-fetch-user", + ]; + let request = new RESTRequest(server.baseURI + "/resource"); + await request.get(); + + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + let server_headers = handler.request.headers; + while (server_headers.hasMoreElements()) { + let header = server_headers.getNext().toString(); + if (!ignore_headers.includes(header)) { + do_throw("Got unexpected header!"); + } + } + + await promiseStopServer(server); +}); + +/** + * Client includes default Accept header in API requests + */ +add_task(async function test_default_accept_headers() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + await request.get(); + + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + let accept_header = handler.request.getHeader("accept"); + + Assert.ok(!accept_header.includes("text/html")); + Assert.ok(!accept_header.includes("application/xhtml+xml")); + Assert.ok(!accept_header.includes("applcation/xml")); + + Assert.ok( + accept_header.includes("application/json") || + accept_header.includes("application/newlines") + ); + + await promiseStopServer(server); +}); + +/** + * Test changing the URI after having created the request. + */ +add_task(async function test_changing_uri() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest("http://localhost:1234/the-wrong-resource"); + request.uri = CommonUtils.makeURI(server.baseURI + "/resource"); + let response = await request.get(); + Assert.equal(response.status, 200); + await promiseStopServer(server); +}); + +/** + * Test setting HTTP request headers. + */ +add_task(async function test_request_setHeader() { + let handler = httpd_handler(200, "OK"); + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + + request.setHeader("X-What-Is-Weave", "awesome"); + request.setHeader("X-WHAT-is-Weave", "more awesomer"); + request.setHeader("Another-Header", "Hello World"); + await request.get(); + + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(handler.request.getHeader("X-What-Is-Weave"), "more awesomer"); + Assert.equal(handler.request.getHeader("another-header"), "Hello World"); + + await promiseStopServer(server); +}); + +/** + * Test receiving HTTP response headers. + */ +add_task(async function test_response_headers() { + function handler(request, response) { + response.setHeader("X-What-Is-Weave", "awesome"); + response.setHeader("Another-Header", "Hello World"); + response.setStatusLine(request.httpVersion, 200, "OK"); + } + let server = httpd_setup({ "/resource": handler }); + let request = new RESTRequest(server.baseURI + "/resource"); + await request.get(); + + Assert.equal(request.response.status, 200); + Assert.equal(request.response.body, ""); + + Assert.equal(request.response.headers["x-what-is-weave"], "awesome"); + Assert.equal(request.response.headers["another-header"], "Hello World"); + + await promiseStopServer(server); +}); + +/** + * The onComplete() handler gets called in case of any network errors + * (e.g. NS_ERROR_CONNECTION_REFUSED). + */ +add_task(async function test_connection_refused() { + let request = new RESTRequest("http://localhost:1234/resource"); + + // Fail the test if we resolve, return the error if we reject + await Assert.rejects( + request.get(), + error => + error.result == Cr.NS_ERROR_CONNECTION_REFUSED && + error.message == "NS_ERROR_CONNECTION_REFUSED" + ); + + Assert.equal(request.status, request.COMPLETED); +}); + +/** + * Abort a request that just sent off. + */ +add_task(async function test_abort() { + function handler() { + do_throw("Shouldn't have gotten here!"); + } + let server = httpd_setup({ "/resource": handler }); + + let request = new RESTRequest(server.baseURI + "/resource"); + + // Aborting a request that hasn't been sent yet is pointless and will throw. + do_check_throws(function () { + request.abort(); + }); + + let responsePromise = request.get(); + request.abort(); + + // Aborting an already aborted request is pointless and will throw. + do_check_throws(function () { + request.abort(); + }); + + Assert.equal(request.status, request.ABORTED); + + await Assert.rejects(responsePromise, /NS_BINDING_ABORTED/); + + await promiseStopServer(server); +}); + +/** + * A non-zero 'timeout' property specifies the amount of seconds to wait after + * channel activity until the request is automatically canceled. + */ +add_task(async function test_timeout() { + let server = new HttpServer(); + let server_connection; + server._handler.handleResponse = function (connection) { + // This is a handler that doesn't do anything, just keeps the connection + // open, thereby mimicking a timing out connection. We keep a reference to + // the open connection for later so it can be properly disposed of. That's + // why you really only want to make one HTTP request to this server ever. + server_connection = connection; + }; + server.start(); + let identity = server.identity; + let uri = + identity.primaryScheme + + "://" + + identity.primaryHost + + ":" + + identity.primaryPort; + + let request = new RESTRequest(uri + "/resource"); + request.timeout = 0.1; // 100 milliseconds + + await Assert.rejects( + request.get(), + error => error.result == Cr.NS_ERROR_NET_TIMEOUT + ); + + Assert.equal(request.status, request.ABORTED); + + // server_connection is undefined on the Android emulator for reasons + // unknown. Yet, we still get here. If this test is refactored, we should + // investigate the reason why the above callback is behaving differently. + if (server_connection) { + _("Closing connection."); + server_connection.close(); + } + await promiseStopServer(server); +}); + +add_task(async function test_new_channel() { + _("Ensure a redirect to a new channel is handled properly."); + + function checkUA(metadata) { + let ua = metadata.getHeader("User-Agent"); + _("User-Agent is " + ua); + Assert.equal("foo bar", ua); + } + + let redirectRequested = false; + let redirectURL; + function redirectHandler(metadata, response) { + checkUA(metadata); + redirectRequested = true; + + let body = "Redirecting"; + response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT"); + response.setHeader("Location", redirectURL); + response.bodyOutputStream.write(body, body.length); + } + + let resourceRequested = false; + function resourceHandler(metadata, response) { + checkUA(metadata); + resourceRequested = true; + + let body = "Test"; + response.setHeader("Content-Type", "text/plain"); + response.bodyOutputStream.write(body, body.length); + } + + let server1 = httpd_setup({ "/redirect": redirectHandler }); + let server2 = httpd_setup({ "/resource": resourceHandler }); + redirectURL = server2.baseURI + "/resource"; + + let request = new RESTRequest(server1.baseURI + "/redirect"); + request.setHeader("User-Agent", "foo bar"); + + // Swizzle in our own fakery, because this redirect is neither + // internal nor URI-preserving. RESTRequest's policy is to only + // copy headers under certain circumstances. + let protoMethod = request.shouldCopyOnRedirect; + request.shouldCopyOnRedirect = function wrapped(o, n, f) { + // Check the default policy. + Assert.ok(!protoMethod.call(this, o, n, f)); + return true; + }; + + let response = await request.get(); + + Assert.equal(200, response.status); + Assert.equal("Test", response.body); + Assert.ok(redirectRequested); + Assert.ok(resourceRequested); + + await promiseStopServer(server1); + await promiseStopServer(server2); +}); + +add_task(async function test_not_sending_cookie() { + function handler(metadata, response) { + let body = "COOKIE!"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); + Assert.ok(!metadata.hasHeader("Cookie")); + } + let server = httpd_setup({ "/test": handler }); + + let uri = CommonUtils.makeURI(server.baseURI); + let channel = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }); + Services.cookies.setCookieStringFromHttp(uri, "test=test; path=/;", channel); + + let res = new RESTRequest(server.baseURI + "/test"); + let response = await res.get(); + + Assert.ok(response.success); + Assert.equal("COOKIE!", response.body); + + await promiseStopServer(server); +}); diff --git a/services/common/tests/unit/test_storage_adapter.js b/services/common/tests/unit/test_storage_adapter.js new file mode 100644 index 0000000000..1dda35ad12 --- /dev/null +++ b/services/common/tests/unit/test_storage_adapter.js @@ -0,0 +1,307 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Sqlite } = ChromeUtils.importESModule( + "resource://gre/modules/Sqlite.sys.mjs" +); +const { FirefoxAdapter } = ChromeUtils.importESModule( + "resource://services-common/kinto-storage-adapter.sys.mjs" +); + +// set up what we need to make storage adapters +const kintoFilename = "kinto.sqlite"; + +function do_get_kinto_connection() { + return FirefoxAdapter.openConnection({ path: kintoFilename }); +} + +function do_get_kinto_adapter(sqliteHandle) { + return new FirefoxAdapter("test", { sqliteHandle }); +} + +function do_get_kinto_db() { + let profile = do_get_profile(); + let kintoDB = profile.clone(); + kintoDB.append(kintoFilename); + return kintoDB; +} + +function cleanup_kinto() { + add_test(function cleanup_kinto_files() { + let kintoDB = do_get_kinto_db(); + // clean up the db + kintoDB.remove(false); + run_next_test(); + }); +} + +function test_collection_operations() { + add_task(async function test_kinto_clear() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.clear(); + await sqliteHandle.close(); + }); + + // test creating new records... and getting them again + add_task(async function test_kinto_create_new_get_existing() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + let record = { id: "test-id", foo: "bar" }; + await adapter.execute(transaction => transaction.create(record)); + let newRecord = await adapter.get("test-id"); + // ensure the record is the same as when it was added + deepEqual(record, newRecord); + await sqliteHandle.close(); + }); + + // test removing records + add_task(async function test_kinto_can_remove_some_records() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + // create a second record + let record = { id: "test-id-2", foo: "baz" }; + await adapter.execute(transaction => transaction.create(record)); + let newRecord = await adapter.get("test-id-2"); + deepEqual(record, newRecord); + // delete the record + await adapter.execute(transaction => transaction.delete(record.id)); + newRecord = await adapter.get(record.id); + // ... and ensure it's no longer there + Assert.equal(newRecord, undefined); + // ensure the other record still exists + newRecord = await adapter.get("test-id"); + Assert.notEqual(newRecord, undefined); + await sqliteHandle.close(); + }); + + // test getting records that don't exist + add_task(async function test_kinto_get_non_existant() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + // Kinto expects adapters to either: + let newRecord = await adapter.get("missing-test-id"); + // resolve with an undefined record + Assert.equal(newRecord, undefined); + await sqliteHandle.close(); + }); + + // test updating records... and getting them again + add_task(async function test_kinto_update_get_existing() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + let originalRecord = { id: "test-id", foo: "bar" }; + let updatedRecord = { id: "test-id", foo: "baz" }; + await adapter.clear(); + await adapter.execute(transaction => transaction.create(originalRecord)); + await adapter.execute(transaction => transaction.update(updatedRecord)); + // ensure the record exists + let newRecord = await adapter.get("test-id"); + // ensure the record is the same as when it was added + deepEqual(updatedRecord, newRecord); + await sqliteHandle.close(); + }); + + // test listing records + add_task(async function test_kinto_list() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + let originalRecord = { id: "test-id-1", foo: "bar" }; + let records = await adapter.list(); + Assert.equal(records.length, 1); + await adapter.execute(transaction => transaction.create(originalRecord)); + records = await adapter.list(); + Assert.equal(records.length, 2); + await sqliteHandle.close(); + }); + + // test aborting transaction + add_task(async function test_kinto_aborting_transaction() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.clear(); + let record = { id: 1, foo: "bar" }; + let error = null; + try { + await adapter.execute(transaction => { + transaction.create(record); + throw new Error("unexpected"); + }); + } catch (e) { + error = e; + } + Assert.notEqual(error, null); + let records = await adapter.list(); + Assert.equal(records.length, 0); + await sqliteHandle.close(); + }); + + // test save and get last modified + add_task(async function test_kinto_last_modified() { + const initialValue = 0; + const intendedValue = 12345678; + + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + let lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, initialValue); + let result = await adapter.saveLastModified(intendedValue); + Assert.equal(result, intendedValue); + lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, intendedValue); + + // test saveLastModified parses values correctly + result = await adapter.saveLastModified(" " + intendedValue + " blah"); + // should resolve with the parsed int + Assert.equal(result, intendedValue); + // and should have saved correctly + lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, intendedValue); + await sqliteHandle.close(); + }); + + // test loadDump(records) + add_task(async function test_kinto_import_records() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + let record1 = { id: 1, foo: "bar" }; + let record2 = { id: 2, foo: "baz" }; + let impactedRecords = await adapter.loadDump([record1, record2]); + Assert.equal(impactedRecords.length, 2); + let newRecord1 = await adapter.get("1"); + // ensure the record is the same as when it was added + deepEqual(record1, newRecord1); + let newRecord2 = await adapter.get("2"); + // ensure the record is the same as when it was added + deepEqual(record2, newRecord2); + await sqliteHandle.close(); + }); + + add_task(async function test_kinto_import_records_should_override_existing() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.clear(); + let records = await adapter.list(); + Assert.equal(records.length, 0); + let impactedRecords = await adapter.loadDump([ + { id: 1, foo: "bar" }, + { id: 2, foo: "baz" }, + ]); + Assert.equal(impactedRecords.length, 2); + await adapter.loadDump([ + { id: 1, foo: "baz" }, + { id: 3, foo: "bab" }, + ]); + records = await adapter.list(); + Assert.equal(records.length, 3); + let newRecord1 = await adapter.get("1"); + deepEqual(newRecord1.foo, "baz"); + await sqliteHandle.close(); + }); + + add_task(async function test_import_updates_lastModified() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.loadDump([ + { id: 1, foo: "bar", last_modified: 1457896541 }, + { id: 2, foo: "baz", last_modified: 1458796542 }, + ]); + let lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, 1458796542); + await sqliteHandle.close(); + }); + + add_task(async function test_import_preserves_older_lastModified() { + let sqliteHandle = await do_get_kinto_connection(); + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.saveLastModified(1458796543); + + await adapter.loadDump([ + { id: 1, foo: "bar", last_modified: 1457896541 }, + { id: 2, foo: "baz", last_modified: 1458796542 }, + ]); + let lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, 1458796543); + await sqliteHandle.close(); + }); + + add_task(async function test_save_metadata_preserves_lastModified() { + let sqliteHandle = await do_get_kinto_connection(); + + let adapter = do_get_kinto_adapter(sqliteHandle); + await adapter.saveLastModified(42); + + await adapter.saveMetadata({ id: "col" }); + + let lastModified = await adapter.getLastModified(); + Assert.equal(lastModified, 42); + await sqliteHandle.close(); + }); +} + +// test kinto db setup and operations in various scenarios +// test from scratch - no current existing database +add_test(function test_db_creation() { + add_test(function test_create_from_scratch() { + // ensure the file does not exist in the profile + let kintoDB = do_get_kinto_db(); + Assert.ok(!kintoDB.exists()); + run_next_test(); + }); + + test_collection_operations(); + + cleanup_kinto(); + run_next_test(); +}); + +// this is the closest we can get to a schema version upgrade at v1 - test an +// existing database +add_test(function test_creation_from_empty_db() { + add_test(function test_create_from_empty_db() { + // place an empty kinto db file in the profile + let profile = do_get_profile(); + + let emptyDB = do_get_file("test_storage_adapter/empty.sqlite"); + emptyDB.copyTo(profile, kintoFilename); + + run_next_test(); + }); + + test_collection_operations(); + + cleanup_kinto(); + run_next_test(); +}); + +// test schema version upgrade at v2 +add_test(function test_migration_from_v1_to_v2() { + add_test(function test_migrate_from_v1_to_v2() { + // place an empty kinto db file in the profile + let profile = do_get_profile(); + + let v1DB = do_get_file("test_storage_adapter/v1.sqlite"); + v1DB.copyTo(profile, kintoFilename); + + run_next_test(); + }); + + add_test(async function schema_is_update_from_1_to_2() { + // The `v1.sqlite` has schema version 1. + let sqliteHandle = await Sqlite.openConnection({ path: kintoFilename }); + Assert.equal(await sqliteHandle.getSchemaVersion(), 1); + await sqliteHandle.close(); + + // The `.openConnection()` migrates it to version 2. + sqliteHandle = await FirefoxAdapter.openConnection({ path: kintoFilename }); + Assert.equal(await sqliteHandle.getSchemaVersion(), 2); + await sqliteHandle.close(); + + run_next_test(); + }); + + test_collection_operations(); + + cleanup_kinto(); + run_next_test(); +}); diff --git a/services/common/tests/unit/test_storage_adapter/empty.sqlite b/services/common/tests/unit/test_storage_adapter/empty.sqlite Binary files differnew file mode 100644 index 0000000000..7f295b4146 --- /dev/null +++ b/services/common/tests/unit/test_storage_adapter/empty.sqlite diff --git a/services/common/tests/unit/test_storage_adapter/v1.sqlite b/services/common/tests/unit/test_storage_adapter/v1.sqlite Binary files differnew file mode 100644 index 0000000000..8482b8b31d --- /dev/null +++ b/services/common/tests/unit/test_storage_adapter/v1.sqlite diff --git a/services/common/tests/unit/test_storage_adapter_shutdown.js b/services/common/tests/unit/test_storage_adapter_shutdown.js new file mode 100644 index 0000000000..dce26ce842 --- /dev/null +++ b/services/common/tests/unit/test_storage_adapter_shutdown.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AsyncShutdown } = ChromeUtils.importESModule( + "resource://gre/modules/AsyncShutdown.sys.mjs" +); + +const { FirefoxAdapter } = ChromeUtils.importESModule( + "resource://services-common/kinto-storage-adapter.sys.mjs" +); + +add_task(async function test_sqlite_shutdown() { + const sqliteHandle = await FirefoxAdapter.openConnection({ + path: "kinto.sqlite", + }); + + // Shutdown Sqlite.sys.mjs synchronously. + Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); + AsyncShutdown.profileBeforeChange._trigger(); + Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); + + try { + sqliteHandle.execute("SELECT 1;"); + equal("Should not succeed, connection should be closed.", false); + } catch (e) { + equal(e.message, "Connection is not open."); + } +}); diff --git a/services/common/tests/unit/test_tokenauthenticatedrequest.js b/services/common/tests/unit/test_tokenauthenticatedrequest.js new file mode 100644 index 0000000000..70735894e4 --- /dev/null +++ b/services/common/tests/unit/test_tokenauthenticatedrequest.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); +const { TokenAuthenticatedRESTRequest } = ChromeUtils.importESModule( + "resource://services-common/rest.sys.mjs" +); + +function run_test() { + initTestLogging("Trace"); + run_next_test(); +} + +add_task(async function test_authenticated_request() { + _("Ensure that sending a MAC authenticated GET request works as expected."); + + let message = "Great Success!"; + + let id = "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x"; + let key = "qTZf4ZFpAMpMoeSsX3zVRjiqmNs="; + let method = "GET"; + + let nonce = btoa(CryptoUtils.generateRandomBytesLegacy(16)); + let ts = Math.floor(Date.now() / 1000); + let extra = { ts, nonce }; + + let auth; + + let server = httpd_setup({ + "/foo": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + Assert.equal(auth, request.getHeader("Authorization")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + let uri = CommonUtils.makeURI(server.baseURI + "/foo"); + let sig = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra); + auth = sig.getHeader(); + + let req = new TokenAuthenticatedRESTRequest(uri, { id, key }, extra); + await req.get(); + + Assert.equal(message, req.response.body); + + await promiseStopServer(server); +}); diff --git a/services/common/tests/unit/test_tokenserverclient.js b/services/common/tests/unit/test_tokenserverclient.js new file mode 100644 index 0000000000..c1c47f6e1a --- /dev/null +++ b/services/common/tests/unit/test_tokenserverclient.js @@ -0,0 +1,382 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { + TokenServerClient, + TokenServerClientError, + TokenServerClientServerError, +} = ChromeUtils.importESModule( + "resource://services-common/tokenserverclient.sys.mjs" +); + +initTestLogging("Trace"); + +add_task(async function test_working_token_exchange() { + _("Ensure that working OAuth token exchange works as expected."); + + let service = "http://example.com/foo"; + let duration = 300; + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + Assert.ok(request.hasHeader("accept")); + Assert.equal("application/json", request.getHeader("accept")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + + let body = JSON.stringify({ + id: "id", + key: "key", + api_endpoint: service, + uid: "uid", + duration, + }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + let result = await client.getTokenUsingOAuth(url, "access_token"); + Assert.equal("object", typeof result); + do_check_attribute_count(result, 7); + Assert.equal(service, result.endpoint); + Assert.equal("id", result.id); + Assert.equal("key", result.key); + Assert.equal("uid", result.uid); + Assert.equal(duration, result.duration); + Assert.deepEqual(undefined, result.node_type); + await promiseStopServer(server); +}); + +add_task(async function test_working_token_exchange_with_nodetype() { + _("Ensure that a token response with a node type as expected."); + + let service = "http://example.com/foo"; + let duration = 300; + let nodeType = "the-node-type"; + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + Assert.ok(request.hasHeader("accept")); + Assert.equal("application/json", request.getHeader("accept")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + + let body = JSON.stringify({ + id: "id", + key: "key", + api_endpoint: service, + uid: "uid", + duration, + node_type: nodeType, + }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + let result = await client.getTokenUsingOAuth(url, "access_token"); + Assert.equal("object", typeof result); + do_check_attribute_count(result, 7); + Assert.equal(service, result.endpoint); + Assert.equal("id", result.id); + Assert.equal("key", result.key); + Assert.equal("uid", result.uid); + Assert.equal(duration, result.duration); + Assert.equal(nodeType, result.node_type); + await promiseStopServer(server); +}); + +add_task(async function test_invalid_arguments() { + _("Ensure invalid arguments to APIs are rejected."); + + let args = [ + [null, "access_token"], + ["http://example.com/", null], + ]; + + for (let arg of args) { + let client = new TokenServerClient(); + await Assert.rejects(client.getTokenUsingOAuth(arg[0], arg[1]), ex => { + Assert.ok(ex instanceof TokenServerClientError); + return true; + }); + } +}); + +add_task(async function test_invalid_403_no_content_type() { + _("Ensure that a 403 without content-type is handled properly."); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 403, "Forbidden"); + // No Content-Type header by design. + + let body = JSON.stringify({ + errors: [{ description: "irrelevant", location: "body", name: "" }], + urls: { foo: "http://bar" }, + }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.ok(error instanceof TokenServerClientServerError); + Assert.equal(error.cause, "malformed-response"); + + Assert.equal(null, error.urls); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_send_extra_headers() { + _("Ensures that the condition acceptance header is sent when asked."); + + let duration = 300; + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + Assert.ok(request.hasHeader("x-foo")); + Assert.equal(request.getHeader("x-foo"), "42"); + + Assert.ok(request.hasHeader("x-bar")); + Assert.equal(request.getHeader("x-bar"), "17"); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + + let body = JSON.stringify({ + id: "id", + key: "key", + api_endpoint: "http://example.com/", + uid: "uid", + duration, + }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + let extra = { + "X-Foo": 42, + "X-Bar": 17, + }; + + await client.getTokenUsingOAuth(url, "access_token", extra); + // Other tests validate other things. + + await promiseStopServer(server); +}); + +add_task(async function test_error_404_empty() { + _("Ensure that 404 responses without proper response are handled properly."); + + let server = httpd_setup(); + + let client = new TokenServerClient(); + let url = server.baseURI + "/foo"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.ok(error instanceof TokenServerClientServerError); + Assert.equal(error.cause, "malformed-response"); + + Assert.notEqual(null, error.response); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_error_404_proper_response() { + _("Ensure that a Cornice error report for 404 is handled properly."); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 404, "Not Found"); + response.setHeader("Content-Type", "application/json; charset=utf-8"); + + let body = JSON.stringify({ + status: 404, + errors: [{ description: "No service", location: "body", name: "" }], + }); + + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.ok(error instanceof TokenServerClientServerError); + Assert.equal(error.cause, "unknown-service"); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_bad_json() { + _("Ensure that malformed JSON is handled properly."); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + + let body = '{"id": "id", baz}'; + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.notEqual(null, error); + Assert.equal("TokenServerClientServerError", error.name); + Assert.equal(error.cause, "malformed-response"); + Assert.notEqual(null, error.response); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_400_response() { + _("Ensure HTTP 400 is converted to malformed-request."); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 400, "Bad Request"); + response.setHeader("Content-Type", "application/json; charset=utf-8"); + + let body = "{}"; // Actual content may not be used. + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.notEqual(null, error); + Assert.equal("TokenServerClientServerError", error.name); + Assert.notEqual(null, error.response); + Assert.equal(error.cause, "malformed-request"); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_401_with_error_cause() { + _("Ensure 401 cause is specified in body.status"); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.setHeader("Content-Type", "application/json; charset=utf-8"); + + let body = JSON.stringify({ status: "no-soup-for-you" }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let client = new TokenServerClient(); + let url = server.baseURI + "/1.0/foo/1.0"; + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.notEqual(null, error); + Assert.equal("TokenServerClientServerError", error.name); + Assert.notEqual(null, error.response); + Assert.equal(error.cause, "no-soup-for-you"); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_unhandled_media_type() { + _("Ensure that unhandled media types throw an error."); + + let server = httpd_setup({ + "/1.0/foo/1.0": function (request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain"); + + let body = "hello, world"; + response.bodyOutputStream.write(body, body.length); + }, + }); + + let url = server.baseURI + "/1.0/foo/1.0"; + let client = new TokenServerClient(); + + await Assert.rejects( + client.getTokenUsingOAuth(url, "access_token"), + error => { + Assert.notEqual(null, error); + Assert.equal("TokenServerClientServerError", error.name); + Assert.notEqual(null, error.response); + return true; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_rich_media_types() { + _("Ensure that extra tokens in the media type aren't rejected."); + + let duration = 300; + let server = httpd_setup({ + "/foo": function (request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json; foo=bar; bar=foo"); + + let body = JSON.stringify({ + id: "id", + key: "key", + api_endpoint: "foo", + uid: "uid", + duration, + }); + response.bodyOutputStream.write(body, body.length); + }, + }); + + let url = server.baseURI + "/foo"; + let client = new TokenServerClient(); + + await client.getTokenUsingOAuth(url, "access_token"); + await promiseStopServer(server); +}); diff --git a/services/common/tests/unit/test_uptake_telemetry.js b/services/common/tests/unit/test_uptake_telemetry.js new file mode 100644 index 0000000000..24967b641f --- /dev/null +++ b/services/common/tests/unit/test_uptake_telemetry.js @@ -0,0 +1,121 @@ +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); +const { UptakeTelemetry } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" +); + +const COMPONENT = "remotesettings"; + +async function withFakeClientID(uuid, f) { + const { Policy } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" + ); + let oldGetClientID = Policy.getClientID; + Policy._clientIDHash = null; + Policy.getClientID = () => Promise.resolve(uuid); + try { + return await f(); + } finally { + Policy.getClientID = oldGetClientID; + } +} + +add_task(async function test_unknown_status_is_not_reported() { + const source = "update-source"; + const startSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + + try { + await UptakeTelemetry.report(COMPONENT, "unknown-status", { source }); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + Assert.deepEqual(startSnapshot, endSnapshot); +}); + +add_task(async function test_age_is_converted_to_string_and_reported() { + const status = UptakeTelemetry.STATUS.SUCCESS; + const age = 42; + + await withFakeChannel("nightly", async () => { + await UptakeTelemetry.report(COMPONENT, status, { source: "s", age }); + }); + + TelemetryTestUtils.assertEvents([ + [ + "uptake.remotecontent.result", + "uptake", + COMPONENT, + status, + { source: "s", age: `${age}` }, + ], + ]); +}); + +add_task(async function test_each_status_can_be_caught_in_snapshot() { + const source = "some-source"; + const startSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + + const expectedIncrements = {}; + await withFakeChannel("nightly", async () => { + for (const status of Object.values(UptakeTelemetry.STATUS)) { + expectedIncrements[status] = 1; + await UptakeTelemetry.report(COMPONENT, status, { source }); + } + }); + + const endSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); + +add_task(async function test_events_are_sent_when_hash_is_mod_0() { + const source = "some-source"; + const startSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + const startSuccess = startSnapshot.success || 0; + const uuid = "d81bbfad-d741-41f5-a7e6-29f6bde4972a"; // hash % 100 = 0 + await withFakeClientID(uuid, async () => { + await withFakeChannel("release", async () => { + await UptakeTelemetry.report(COMPONENT, UptakeTelemetry.STATUS.SUCCESS, { + source, + }); + }); + }); + const endSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + Assert.equal(endSnapshot.success, startSuccess + 1); +}); + +add_task( + async function test_events_are_not_sent_when_hash_is_greater_than_pref() { + const source = "some-source"; + const startSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + const startSuccess = startSnapshot.success || 0; + const uuid = "d81bbfad-d741-41f5-a7e6-29f6bde49721"; // hash % 100 = 1 + await withFakeClientID(uuid, async () => { + await withFakeChannel("release", async () => { + await UptakeTelemetry.report( + COMPONENT, + UptakeTelemetry.STATUS.SUCCESS, + { source } + ); + }); + }); + const endSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + Assert.equal(endSnapshot.success || 0, startSuccess); + } +); + +add_task(async function test_events_are_sent_when_nightly() { + const source = "some-source"; + const startSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + const startSuccess = startSnapshot.success || 0; + const uuid = "d81bbfad-d741-41f5-a7e6-29f6bde49721"; // hash % 100 = 1 + await withFakeClientID(uuid, async () => { + await withFakeChannel("nightly", async () => { + await UptakeTelemetry.report(COMPONENT, UptakeTelemetry.STATUS.SUCCESS, { + source, + }); + }); + }); + const endSnapshot = getUptakeTelemetrySnapshot(COMPONENT, source); + Assert.equal(endSnapshot.success, startSuccess + 1); +}); diff --git a/services/common/tests/unit/test_utils_atob.js b/services/common/tests/unit/test_utils_atob.js new file mode 100644 index 0000000000..9e24b56f6f --- /dev/null +++ b/services/common/tests/unit/test_utils_atob.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let data = ["Zm9vYmE=", "Zm9vYmE==", "Zm9vYmE==="]; + for (let d in data) { + Assert.equal(CommonUtils.safeAtoB(data[d]), "fooba"); + } +} diff --git a/services/common/tests/unit/test_utils_convert_string.js b/services/common/tests/unit/test_utils_convert_string.js new file mode 100644 index 0000000000..fcfd874994 --- /dev/null +++ b/services/common/tests/unit/test_utils_convert_string.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// A wise line of Greek verse, and the utf-8 byte encoding. +// N.b., Greek begins at utf-8 ce 91 +const TEST_STR = "πόλλ' οἶδ' ἀλώπηξ, ἀλλ' ἐχῖνος ἓν μέγα"; +const TEST_HEX = h( + "cf 80 cf 8c ce bb ce bb 27 20 ce bf e1 bc b6 ce" + + "b4 27 20 e1 bc 80 ce bb cf 8e cf 80 ce b7 ce be" + + "2c 20 e1 bc 80 ce bb ce bb 27 20 e1 bc 90 cf 87" + + "e1 bf 96 ce bd ce bf cf 82 20 e1 bc 93 ce bd 20" + + "ce bc ce ad ce b3 ce b1" +); +// Integer byte values for the above +const TEST_BYTES = [ + 207, 128, 207, 140, 206, 187, 206, 187, 39, 32, 206, 191, 225, 188, 182, 206, + 180, 39, 32, 225, 188, 128, 206, 187, 207, 142, 207, 128, 206, 183, 206, 190, + 44, 32, 225, 188, 128, 206, 187, 206, 187, 39, 32, 225, 188, 144, 207, 135, + 225, 191, 150, 206, 189, 206, 191, 207, 130, 32, 225, 188, 147, 206, 189, 32, + 206, 188, 206, 173, 206, 179, 206, 177, +]; + +add_test(function test_compress_string() { + const INPUT = "hello"; + + let result = CommonUtils.convertString(INPUT, "uncompressed", "deflate"); + Assert.equal(result.length, 13); + + let result2 = CommonUtils.convertString(INPUT, "uncompressed", "deflate"); + Assert.equal(result, result2); + + let result3 = CommonUtils.convertString(result, "deflate", "uncompressed"); + Assert.equal(result3, INPUT); + + run_next_test(); +}); + +add_test(function test_compress_utf8() { + const INPUT = + "Árvíztűrő tükörfúrógép いろはにほへとちりぬるを Pijamalı hasta, yağız şoföre çabucak güvendi."; + let inputUTF8 = CommonUtils.encodeUTF8(INPUT); + + let compressed = CommonUtils.convertString( + inputUTF8, + "uncompressed", + "deflate" + ); + let uncompressed = CommonUtils.convertString( + compressed, + "deflate", + "uncompressed" + ); + + Assert.equal(uncompressed, inputUTF8); + + let outputUTF8 = CommonUtils.decodeUTF8(uncompressed); + Assert.equal(outputUTF8, INPUT); + + run_next_test(); +}); + +add_test(function test_bad_argument() { + let failed = false; + try { + CommonUtils.convertString(null, "uncompressed", "deflate"); + } catch (ex) { + failed = true; + Assert.ok(ex.message.startsWith("Input string must be defined")); + } finally { + Assert.ok(failed); + } + + run_next_test(); +}); + +add_task(function test_stringAsHex() { + Assert.equal(TEST_HEX, CommonUtils.stringAsHex(TEST_STR)); +}); + +add_task(function test_hexAsString() { + Assert.equal(TEST_STR, CommonUtils.hexAsString(TEST_HEX)); +}); + +add_task(function test_hexToBytes() { + let bytes = CommonUtils.hexToBytes(TEST_HEX); + Assert.equal(TEST_BYTES.length, bytes.length); + // Ensure that the decimal values of each byte are correct + Assert.ok(arraysEqual(TEST_BYTES, CommonUtils.stringToByteArray(bytes))); +}); + +add_task(function test_bytesToHex() { + // Create a list of our character bytes from the reference int values + let bytes = CommonUtils.byteArrayToString(TEST_BYTES); + Assert.equal(TEST_HEX, CommonUtils.bytesAsHex(bytes)); +}); + +add_task(function test_stringToBytes() { + Assert.ok( + arraysEqual( + TEST_BYTES, + CommonUtils.stringToByteArray(CommonUtils.stringToBytes(TEST_STR)) + ) + ); +}); + +add_task(function test_stringRoundTrip() { + Assert.equal( + TEST_STR, + CommonUtils.hexAsString(CommonUtils.stringAsHex(TEST_STR)) + ); +}); + +add_task(function test_hexRoundTrip() { + Assert.equal( + TEST_HEX, + CommonUtils.stringAsHex(CommonUtils.hexAsString(TEST_HEX)) + ); +}); + +add_task(function test_byteArrayRoundTrip() { + Assert.ok( + arraysEqual( + TEST_BYTES, + CommonUtils.stringToByteArray(CommonUtils.byteArrayToString(TEST_BYTES)) + ) + ); +}); + +// turn formatted test vectors into normal hex strings +function h(hexStr) { + return hexStr.replace(/\s+/g, ""); +} + +function arraysEqual(a1, a2) { + if (a1.length !== a2.length) { + return false; + } + for (let i = 0; i < a1.length; i++) { + if (a1[i] !== a2[i]) { + return false; + } + } + return true; +} diff --git a/services/common/tests/unit/test_utils_dateprefs.js b/services/common/tests/unit/test_utils_dateprefs.js new file mode 100644 index 0000000000..35b0c26470 --- /dev/null +++ b/services/common/tests/unit/test_utils_dateprefs.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var prefs = Services.prefs.getBranch("servicescommon.tests."); + +function DummyLogger() { + this.messages = []; +} +DummyLogger.prototype.warn = function warn(message) { + this.messages.push(message); +}; + +add_test(function test_set_basic() { + let now = new Date(); + + CommonUtils.setDatePref(prefs, "test00", now); + let value = prefs.getStringPref("test00"); + Assert.equal(value, "" + now.getTime()); + + let now2 = CommonUtils.getDatePref(prefs, "test00"); + + Assert.equal(now.getTime(), now2.getTime()); + + run_next_test(); +}); + +add_test(function test_set_bounds_checking() { + let d = new Date(2342354); + + let failed = false; + try { + CommonUtils.setDatePref(prefs, "test01", d); + } catch (ex) { + Assert.ok(ex.message.startsWith("Trying to set")); + failed = true; + } + + Assert.ok(failed); + run_next_test(); +}); + +add_test(function test_get_bounds_checking() { + prefs.setStringPref("test_bounds_checking", "13241431"); + + let log = new DummyLogger(); + let d = CommonUtils.getDatePref(prefs, "test_bounds_checking", 0, log); + Assert.equal(d.getTime(), 0); + Assert.equal(log.messages.length, 1); + + run_next_test(); +}); + +add_test(function test_get_bad_default() { + let failed = false; + try { + CommonUtils.getDatePref(prefs, "get_bad_default", new Date()); + } catch (ex) { + Assert.ok(ex.message.startsWith("Default value is not a number")); + failed = true; + } + + Assert.ok(failed); + run_next_test(); +}); + +add_test(function test_get_invalid_number() { + prefs.setStringPref("get_invalid_number", "hello world"); + + let log = new DummyLogger(); + let d = CommonUtils.getDatePref(prefs, "get_invalid_number", 42, log); + Assert.equal(d.getTime(), 42); + Assert.equal(log.messages.length, 1); + + run_next_test(); +}); diff --git a/services/common/tests/unit/test_utils_encodeBase32.js b/services/common/tests/unit/test_utils_encodeBase32.js new file mode 100644 index 0000000000..3299ac8d05 --- /dev/null +++ b/services/common/tests/unit/test_utils_encodeBase32.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + // Testing byte array manipulation. + Assert.equal( + "FOOBAR", + CommonUtils.byteArrayToString([70, 79, 79, 66, 65, 82]) + ); + Assert.equal("", CommonUtils.byteArrayToString([])); + + _("Testing encoding..."); + // Test vectors from RFC 4648 + Assert.equal(CommonUtils.encodeBase32(""), ""); + Assert.equal(CommonUtils.encodeBase32("f"), "MY======"); + Assert.equal(CommonUtils.encodeBase32("fo"), "MZXQ===="); + Assert.equal(CommonUtils.encodeBase32("foo"), "MZXW6==="); + Assert.equal(CommonUtils.encodeBase32("foob"), "MZXW6YQ="); + Assert.equal(CommonUtils.encodeBase32("fooba"), "MZXW6YTB"); + Assert.equal(CommonUtils.encodeBase32("foobar"), "MZXW6YTBOI======"); + + Assert.equal( + CommonUtils.encodeBase32("Bacon is a vegetable."), + "IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======" + ); + + _("Checking assumptions..."); + for (let i = 0; i <= 255; ++i) { + Assert.equal(undefined | i, i); + } + + _("Testing decoding..."); + Assert.equal(CommonUtils.decodeBase32(""), ""); + Assert.equal(CommonUtils.decodeBase32("MY======"), "f"); + Assert.equal(CommonUtils.decodeBase32("MZXQ===="), "fo"); + Assert.equal(CommonUtils.decodeBase32("MZXW6YTB"), "fooba"); + Assert.equal(CommonUtils.decodeBase32("MZXW6YTBOI======"), "foobar"); + + // Same with incorrect or missing padding. + Assert.equal(CommonUtils.decodeBase32("MZXW6YTBOI=="), "foobar"); + Assert.equal(CommonUtils.decodeBase32("MZXW6YTBOI"), "foobar"); + + let encoded = CommonUtils.encodeBase32("Bacon is a vegetable."); + _("Encoded to " + JSON.stringify(encoded)); + Assert.equal(CommonUtils.decodeBase32(encoded), "Bacon is a vegetable."); + + // Test failure. + let err; + try { + CommonUtils.decodeBase32("000"); + } catch (ex) { + err = ex; + } + Assert.equal(err.message, "Unknown character in base32: 0"); +} diff --git a/services/common/tests/unit/test_utils_encodeBase64URL.js b/services/common/tests/unit/test_utils_encodeBase64URL.js new file mode 100644 index 0000000000..84d98b06f1 --- /dev/null +++ b/services/common/tests/unit/test_utils_encodeBase64URL.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_test(function test_simple() { + let expected = { + hello: "aGVsbG8=", + "<>?": "PD4_", + }; + + for (let [k, v] of Object.entries(expected)) { + Assert.equal(CommonUtils.encodeBase64URL(k), v); + } + + run_next_test(); +}); + +add_test(function test_no_padding() { + Assert.equal(CommonUtils.encodeBase64URL("hello", false), "aGVsbG8"); + + run_next_test(); +}); diff --git a/services/common/tests/unit/test_utils_ensureMillisecondsTimestamp.js b/services/common/tests/unit/test_utils_ensureMillisecondsTimestamp.js new file mode 100644 index 0000000000..f334364b2d --- /dev/null +++ b/services/common/tests/unit/test_utils_ensureMillisecondsTimestamp.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + Assert.equal(null, CommonUtils.ensureMillisecondsTimestamp(null)); + Assert.equal(null, CommonUtils.ensureMillisecondsTimestamp(0)); + Assert.equal(null, CommonUtils.ensureMillisecondsTimestamp("0")); + Assert.equal(null, CommonUtils.ensureMillisecondsTimestamp("000")); + + Assert.equal( + null, + CommonUtils.ensureMillisecondsTimestamp(999 * 10000000000) + ); + + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp(-1); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp(1); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp(1.5); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp(999 * 10000000000 + 0.5); + }); + + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp("-1"); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp("1"); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp("1.5"); + }); + do_check_throws(function err() { + CommonUtils.ensureMillisecondsTimestamp("" + (999 * 10000000000 + 0.5)); + }); +} diff --git a/services/common/tests/unit/test_utils_makeURI.js b/services/common/tests/unit/test_utils_makeURI.js new file mode 100644 index 0000000000..d6d7e19db9 --- /dev/null +++ b/services/common/tests/unit/test_utils_makeURI.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Make sure uri strings are converted to nsIURIs"); + +function run_test() { + _test_makeURI(); +} + +function _test_makeURI() { + _("Check http uris"); + let uri1 = "http://mozillalabs.com/"; + Assert.equal(CommonUtils.makeURI(uri1).spec, uri1); + let uri2 = "http://www.mozillalabs.com/"; + Assert.equal(CommonUtils.makeURI(uri2).spec, uri2); + let uri3 = "http://mozillalabs.com/path"; + Assert.equal(CommonUtils.makeURI(uri3).spec, uri3); + let uri4 = "http://mozillalabs.com/multi/path"; + Assert.equal(CommonUtils.makeURI(uri4).spec, uri4); + let uri5 = "http://mozillalabs.com/?query"; + Assert.equal(CommonUtils.makeURI(uri5).spec, uri5); + let uri6 = "http://mozillalabs.com/#hash"; + Assert.equal(CommonUtils.makeURI(uri6).spec, uri6); + + _("Check https uris"); + let uris1 = "https://mozillalabs.com/"; + Assert.equal(CommonUtils.makeURI(uris1).spec, uris1); + let uris2 = "https://www.mozillalabs.com/"; + Assert.equal(CommonUtils.makeURI(uris2).spec, uris2); + let uris3 = "https://mozillalabs.com/path"; + Assert.equal(CommonUtils.makeURI(uris3).spec, uris3); + let uris4 = "https://mozillalabs.com/multi/path"; + Assert.equal(CommonUtils.makeURI(uris4).spec, uris4); + let uris5 = "https://mozillalabs.com/?query"; + Assert.equal(CommonUtils.makeURI(uris5).spec, uris5); + let uris6 = "https://mozillalabs.com/#hash"; + Assert.equal(CommonUtils.makeURI(uris6).spec, uris6); + + _("Check chrome uris"); + let uric1 = "chrome://browser/content/browser.xhtml"; + Assert.equal(CommonUtils.makeURI(uric1).spec, uric1); + let uric2 = "chrome://browser/skin/browser.css"; + Assert.equal(CommonUtils.makeURI(uric2).spec, uric2); + + _("Check about uris"); + let uria1 = "about:weave"; + Assert.equal(CommonUtils.makeURI(uria1).spec, uria1); + let uria2 = "about:weave/"; + Assert.equal(CommonUtils.makeURI(uria2).spec, uria2); + let uria3 = "about:weave/path"; + Assert.equal(CommonUtils.makeURI(uria3).spec, uria3); + let uria4 = "about:weave/multi/path"; + Assert.equal(CommonUtils.makeURI(uria4).spec, uria4); + let uria5 = "about:weave/?query"; + Assert.equal(CommonUtils.makeURI(uria5).spec, uria5); + let uria6 = "about:weave/#hash"; + Assert.equal(CommonUtils.makeURI(uria6).spec, uria6); + + _("Invalid uris are undefined"); + Assert.equal(CommonUtils.makeURI("mozillalabs.com"), undefined); + Assert.equal(CommonUtils.makeURI("this is a test"), undefined); +} diff --git a/services/common/tests/unit/test_utils_namedTimer.js b/services/common/tests/unit/test_utils_namedTimer.js new file mode 100644 index 0000000000..47ac0b9dc1 --- /dev/null +++ b/services/common/tests/unit/test_utils_namedTimer.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_test(function test_required_args() { + try { + CommonUtils.namedTimer(function callback() { + do_throw("Shouldn't fire."); + }, 0); + do_throw("Should have thrown!"); + } catch (ex) { + run_next_test(); + } +}); + +add_test(function test_simple() { + _("Test basic properties of CommonUtils.namedTimer."); + + const delay = 200; + let that = {}; + let t0 = Date.now(); + CommonUtils.namedTimer( + function callback(timer) { + Assert.equal(this, that); + Assert.equal(this._zetimer, null); + Assert.ok(timer instanceof Ci.nsITimer); + // Difference should be ~delay, but hard to predict on all platforms, + // particularly Windows XP. + Assert.ok(Date.now() > t0); + run_next_test(); + }, + delay, + that, + "_zetimer" + ); +}); + +add_test(function test_delay() { + _("Test delaying a timer that hasn't fired yet."); + + const delay = 100; + let that = {}; + let t0 = Date.now(); + function callback(timer) { + // Difference should be ~2*delay, but hard to predict on all platforms, + // particularly Windows XP. + Assert.ok(Date.now() - t0 > delay); + run_next_test(); + } + CommonUtils.namedTimer(callback, delay, that, "_zetimer"); + CommonUtils.namedTimer(callback, 2 * delay, that, "_zetimer"); + run_next_test(); +}); + +add_test(function test_clear() { + _("Test clearing a timer that hasn't fired yet."); + + const delay = 0; + let that = {}; + CommonUtils.namedTimer( + function callback(timer) { + do_throw("Shouldn't fire!"); + }, + delay, + that, + "_zetimer" + ); + + that._zetimer.clear(); + Assert.equal(that._zetimer, null); + CommonUtils.nextTick(run_next_test); + + run_next_test(); +}); diff --git a/services/common/tests/unit/test_utils_sets.js b/services/common/tests/unit/test_utils_sets.js new file mode 100644 index 0000000000..c15d48528f --- /dev/null +++ b/services/common/tests/unit/test_utils_sets.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const EMPTY = new Set(); +const A = new Set(["a"]); +const ABC = new Set(["a", "b", "c"]); +const ABCD = new Set(["a", "b", "c", "d"]); +const BC = new Set(["b", "c"]); +const BCD = new Set(["b", "c", "d"]); +const FGH = new Set(["f", "g", "h"]); +const BCDFGH = new Set(["b", "c", "d", "f", "g", "h"]); + +var union = CommonUtils.union; +var difference = CommonUtils.difference; +var intersection = CommonUtils.intersection; +var setEqual = CommonUtils.setEqual; + +function do_check_setEqual(a, b) { + Assert.ok(setEqual(a, b)); +} + +function do_check_not_setEqual(a, b) { + Assert.ok(!setEqual(a, b)); +} + +add_test(function test_setEqual() { + do_check_setEqual(EMPTY, EMPTY); + do_check_setEqual(EMPTY, new Set()); + do_check_setEqual(A, A); + do_check_setEqual(A, new Set(["a"])); + do_check_setEqual(new Set(["a"]), A); + do_check_not_setEqual(A, EMPTY); + do_check_not_setEqual(EMPTY, A); + do_check_not_setEqual(ABC, A); + run_next_test(); +}); + +add_test(function test_union() { + do_check_setEqual(EMPTY, union(EMPTY, EMPTY)); + do_check_setEqual(ABC, union(EMPTY, ABC)); + do_check_setEqual(ABC, union(ABC, ABC)); + do_check_setEqual(ABCD, union(ABC, BCD)); + do_check_setEqual(ABCD, union(BCD, ABC)); + do_check_setEqual(BCDFGH, union(BCD, FGH)); + run_next_test(); +}); + +add_test(function test_difference() { + do_check_setEqual(EMPTY, difference(EMPTY, EMPTY)); + do_check_setEqual(EMPTY, difference(EMPTY, A)); + do_check_setEqual(EMPTY, difference(A, A)); + do_check_setEqual(ABC, difference(ABC, EMPTY)); + do_check_setEqual(ABC, difference(ABC, FGH)); + do_check_setEqual(A, difference(ABC, BCD)); + run_next_test(); +}); + +add_test(function test_intersection() { + do_check_setEqual(EMPTY, intersection(EMPTY, EMPTY)); + do_check_setEqual(EMPTY, intersection(ABC, EMPTY)); + do_check_setEqual(EMPTY, intersection(ABC, FGH)); + do_check_setEqual(BC, intersection(ABC, BCD)); + run_next_test(); +}); diff --git a/services/common/tests/unit/test_utils_utf8.js b/services/common/tests/unit/test_utils_utf8.js new file mode 100644 index 0000000000..aa075873b9 --- /dev/null +++ b/services/common/tests/unit/test_utils_utf8.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let str = "Umlaute: \u00FC \u00E4\n"; // Umlaute: ü ä + let encoded = CommonUtils.encodeUTF8(str); + let decoded = CommonUtils.decodeUTF8(encoded); + Assert.equal(decoded, str); +} diff --git a/services/common/tests/unit/test_utils_uuid.js b/services/common/tests/unit/test_utils_uuid.js new file mode 100644 index 0000000000..33b407e92d --- /dev/null +++ b/services/common/tests/unit/test_utils_uuid.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function run_test() { + let uuid = CommonUtils.generateUUID(); + Assert.equal(uuid.length, 36); + Assert.equal(uuid[8], "-"); + + run_next_test(); +} diff --git a/services/common/tests/unit/xpcshell.toml b/services/common/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..e4035f66b2 --- /dev/null +++ b/services/common/tests/unit/xpcshell.toml @@ -0,0 +1,63 @@ +[DEFAULT] +head = "head_global.js head_helpers.js head_http.js" +firefox-appdir = "browser" +support-files = ["test_storage_adapter/**"] + +# Test load modules first so syntax failures are caught early. + +["test_async_chain.js"] + +["test_async_foreach.js"] + +["test_hawkclient.js"] +skip-if = ["os == 'android'"] + +["test_hawkrequest.js"] +skip-if = ["os == 'android'"] + +["test_kinto.js"] +tags = "blocklist" + +["test_load_modules.js"] + +["test_logmanager.js"] + +["test_observers.js"] + +["test_restrequest.js"] + +["test_storage_adapter.js"] +tags = "remote-settings blocklist" + +["test_storage_adapter_shutdown.js"] +tags = "remote-settings blocklist" + +["test_tokenauthenticatedrequest.js"] + +["test_tokenserverclient.js"] +skip-if = ["os == 'android'"] + +["test_uptake_telemetry.js"] +tags = "remote-settings" + +["test_utils_atob.js"] + +["test_utils_convert_string.js"] + +["test_utils_dateprefs.js"] + +["test_utils_encodeBase32.js"] + +["test_utils_encodeBase64URL.js"] + +["test_utils_ensureMillisecondsTimestamp.js"] + +["test_utils_makeURI.js"] + +["test_utils_namedTimer.js"] + +["test_utils_sets.js"] + +["test_utils_utf8.js"] + +["test_utils_uuid.js"] diff --git a/services/common/tokenserverclient.sys.mjs b/services/common/tokenserverclient.sys.mjs new file mode 100644 index 0000000000..c23e3d779c --- /dev/null +++ b/services/common/tokenserverclient.sys.mjs @@ -0,0 +1,392 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { RESTRequest } from "resource://services-common/rest.sys.mjs"; +import { Observers } from "resource://services-common/observers.sys.mjs"; + +const PREF_LOG_LEVEL = "services.common.log.logger.tokenserverclient"; + +/** + * Represents a TokenServerClient error that occurred on the client. + * + * This is the base type for all errors raised by client operations. + * + * @param message + * (string) Error message. + */ +export function TokenServerClientError(message) { + this.name = "TokenServerClientError"; + this.message = message || "Client error."; + // Without explicitly setting .stack, all stacks from these errors will point + // to the "new Error()" call a few lines down, which isn't helpful. + this.stack = Error().stack; +} + +TokenServerClientError.prototype = new Error(); +TokenServerClientError.prototype.constructor = TokenServerClientError; +TokenServerClientError.prototype._toStringFields = function () { + return { message: this.message }; +}; +TokenServerClientError.prototype.toString = function () { + return this.name + "(" + JSON.stringify(this._toStringFields()) + ")"; +}; +TokenServerClientError.prototype.toJSON = function () { + let result = this._toStringFields(); + result.name = this.name; + return result; +}; + +/** + * Represents a TokenServerClient error that occurred in the network layer. + * + * @param error + * The underlying error thrown by the network layer. + */ +export function TokenServerClientNetworkError(error) { + this.name = "TokenServerClientNetworkError"; + this.error = error; + this.stack = Error().stack; +} + +TokenServerClientNetworkError.prototype = new TokenServerClientError(); +TokenServerClientNetworkError.prototype.constructor = + TokenServerClientNetworkError; +TokenServerClientNetworkError.prototype._toStringFields = function () { + return { error: this.error }; +}; + +/** + * Represents a TokenServerClient error that occurred on the server. + * + * This type will be encountered for all non-200 response codes from the + * server. The type of error is strongly enumerated and is stored in the + * `cause` property. This property can have the following string values: + * + * invalid-credentials -- A token could not be obtained because + * the credentials presented by the client were invalid. + * + * unknown-service -- The requested service was not found. + * + * malformed-request -- The server rejected the request because it + * was invalid. If you see this, code in this file is likely wrong. + * + * malformed-response -- The response from the server was not what was + * expected. + * + * general -- A general server error has occurred. Clients should + * interpret this as an opaque failure. + * + * @param message + * (string) Error message. + */ +export function TokenServerClientServerError(message, cause = "general") { + this.now = new Date().toISOString(); // may be useful to diagnose time-skew issues. + this.name = "TokenServerClientServerError"; + this.message = message || "Server error."; + this.cause = cause; + this.stack = Error().stack; +} + +TokenServerClientServerError.prototype = new TokenServerClientError(); +TokenServerClientServerError.prototype.constructor = + TokenServerClientServerError; + +TokenServerClientServerError.prototype._toStringFields = function () { + let fields = { + now: this.now, + message: this.message, + cause: this.cause, + }; + if (this.response) { + fields.response_body = this.response.body; + fields.response_headers = this.response.headers; + fields.response_status = this.response.status; + } + return fields; +}; + +/** + * Represents a client to the Token Server. + * + * http://docs.services.mozilla.com/token/index.html + * + * The Token Server was designed to support obtaining tokens for arbitrary apps by + * constructing URI paths of the form <app>/<app_version>. In practice this was + * never used and it only supports an <app> value of `sync`, and the API presented + * here reflects that. + * + * Areas to Improve: + * + * - The server sends a JSON response on error. The client does not currently + * parse this. It might be convenient if it did. + * - Currently most non-200 status codes are rolled into one error type. It + * might be helpful if callers had a richer API that communicated who was + * at fault (e.g. differentiating a 503 from a 401). + */ +export function TokenServerClient() { + this._log = Log.repository.getLogger("Services.Common.TokenServerClient"); + this._log.manageLevelFromPref(PREF_LOG_LEVEL); +} + +TokenServerClient.prototype = { + /** + * Logger instance. + */ + _log: null, + + /** + * Obtain a token from a provided OAuth token against a specific URL. + * + * This asynchronously obtains the token. + * It returns a Promise that resolves or rejects: + * + * Rejects with: + * (TokenServerClientError) If no token could be obtained, this + * will be a TokenServerClientError instance describing why. The + * type seen defines the type of error encountered. If an HTTP response + * was seen, a RESTResponse instance will be stored in the `response` + * property of this object. If there was no error and a token is + * available, this will be null. + * + * Resolves with: + * (map) On success, this will be a map containing the results from + * the server. If there was an error, this will be null. The map has the + * following properties: + * + * id (string) HTTP MAC public key identifier. + * key (string) HTTP MAC shared symmetric key. + * endpoint (string) URL where service can be connected to. + * uid (string) user ID for requested service. + * duration (string) the validity duration of the issued token. + * + * Example Usage + * ------------- + * + * let client = new TokenServerClient(); + * let access_token = getOAuthAccessTokenFromSomewhere(); + * let url = "https://token.services.mozilla.com/1.0/sync/2.0"; + * + * try { + * const result = await client.getTokenUsingOAuth(url, access_token); + * let {id, key, uid, endpoint, duration} = result; + * // Do stuff with data and carry on. + * } catch (error) { + * // Handle errors. + * } + * Obtain a token from a provided OAuth token against a specific URL. + * + * @param url + * (string) URL to fetch token from. + * @param oauthToken + * (string) FxA OAuth Token to exchange token for. + * @param addHeaders + * (object) Extra headers for the request. + */ + async getTokenUsingOAuth(url, oauthToken, addHeaders = {}) { + this._log.debug("Beginning OAuth token exchange: " + url); + + if (!oauthToken) { + throw new TokenServerClientError("oauthToken argument is not valid."); + } + + return this._tokenServerExchangeRequest( + url, + `Bearer ${oauthToken}`, + addHeaders + ); + }, + + /** + * Performs the exchange request to the token server to + * produce a token based on the authorizationHeader input. + * + * @param url + * (string) URL to fetch token from. + * @param authorizationHeader + * (string) The auth header string that populates the 'Authorization' header. + * @param addHeaders + * (object) Extra headers for the request. + */ + async _tokenServerExchangeRequest(url, authorizationHeader, addHeaders = {}) { + if (!url) { + throw new TokenServerClientError("url argument is not valid."); + } + + if (!authorizationHeader) { + throw new TokenServerClientError( + "authorizationHeader argument is not valid." + ); + } + + let req = this.newRESTRequest(url); + req.setHeader("Accept", "application/json"); + req.setHeader("Authorization", authorizationHeader); + + for (let header in addHeaders) { + req.setHeader(header, addHeaders[header]); + } + let response; + try { + response = await req.get(); + } catch (err) { + throw new TokenServerClientNetworkError(err); + } + + try { + return this._processTokenResponse(response); + } catch (ex) { + if (ex instanceof TokenServerClientServerError) { + throw ex; + } + this._log.warn("Error processing token server response", ex); + let error = new TokenServerClientError(ex); + error.response = response; + throw error; + } + }, + + /** + * Handler to process token request responses. + * + * @param response + * RESTResponse from token HTTP request. + */ + _processTokenResponse(response) { + this._log.debug("Got token response: " + response.status); + + // Responses should *always* be JSON, even in the case of 4xx and 5xx + // errors. If we don't see JSON, the server is likely very unhappy. + let ct = response.headers["content-type"] || ""; + if (ct != "application/json" && !ct.startsWith("application/json;")) { + this._log.warn("Did not receive JSON response. Misconfigured server?"); + this._log.debug("Content-Type: " + ct); + this._log.debug("Body: " + response.body); + + let error = new TokenServerClientServerError( + "Non-JSON response.", + "malformed-response" + ); + error.response = response; + throw error; + } + + let result; + try { + result = JSON.parse(response.body); + } catch (ex) { + this._log.warn("Invalid JSON returned by server: " + response.body); + let error = new TokenServerClientServerError( + "Malformed JSON.", + "malformed-response" + ); + error.response = response; + throw error; + } + + // Any response status can have X-Backoff or X-Weave-Backoff headers. + this._maybeNotifyBackoff(response, "x-weave-backoff"); + this._maybeNotifyBackoff(response, "x-backoff"); + + // The service shouldn't have any 3xx, so we don't need to handle those. + if (response.status != 200) { + // We /should/ have a Cornice error report in the JSON. We log that to + // help with debugging. + if ("errors" in result) { + // This could throw, but this entire function is wrapped in a try. If + // the server is sending something not an array of objects, it has + // failed to keep its contract with us and there is little we can do. + for (let error of result.errors) { + this._log.info("Server-reported error: " + JSON.stringify(error)); + } + } + + let error = new TokenServerClientServerError(); + error.response = response; + + if (response.status == 400) { + error.message = "Malformed request."; + error.cause = "malformed-request"; + } else if (response.status == 401) { + // Cause can be invalid-credentials, invalid-timestamp, or + // invalid-generation. + error.message = "Authentication failed."; + error.cause = result.status; + } else if (response.status == 404) { + error.message = "Unknown service."; + error.cause = "unknown-service"; + } + + // A Retry-After header should theoretically only appear on a 503, but + // we'll look for it on any error response. + this._maybeNotifyBackoff(response, "retry-after"); + + throw error; + } + + for (let k of ["id", "key", "api_endpoint", "uid", "duration"]) { + if (!(k in result)) { + let error = new TokenServerClientServerError( + "Expected key not present in result: " + k + ); + error.cause = "malformed-response"; + error.response = response; + throw error; + } + } + + this._log.debug("Successful token response"); + return { + id: result.id, + key: result.key, + endpoint: result.api_endpoint, + uid: result.uid, + duration: result.duration, + hashed_fxa_uid: result.hashed_fxa_uid, + node_type: result.node_type, + }; + }, + + /* + * The prefix used for all notifications sent by this module. This + * allows the handler of notifications to be sure they are handling + * notifications for the service they expect. + * + * If not set, no notifications will be sent. + */ + observerPrefix: null, + + // Given an optional header value, notify that a backoff has been requested. + _maybeNotifyBackoff(response, headerName) { + if (!this.observerPrefix) { + return; + } + let headerVal = response.headers[headerName]; + if (!headerVal) { + return; + } + let backoffInterval; + try { + backoffInterval = parseInt(headerVal, 10); + } catch (ex) { + this._log.error( + "TokenServer response had invalid backoff value in '" + + headerName + + "' header: " + + headerVal + ); + return; + } + Observers.notify( + this.observerPrefix + ":backoff:interval", + backoffInterval + ); + }, + + // override points for testing. + newRESTRequest(url) { + return new RESTRequest(url); + }, +}; diff --git a/services/common/uptake-telemetry.sys.mjs b/services/common/uptake-telemetry.sys.mjs new file mode 100644 index 0000000000..9b456b137f --- /dev/null +++ b/services/common/uptake-telemetry.sys.mjs @@ -0,0 +1,193 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + ClientID: "resource://gre/modules/ClientID.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "CryptoHash", () => { + return Components.Constructor( + "@mozilla.org/security/hash;1", + "nsICryptoHash", + "initWithString" + ); +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gSampleRate", + "services.common.uptake.sampleRate" +); + +// Telemetry events id (see Events.yaml). +const TELEMETRY_EVENTS_ID = "uptake.remotecontent.result"; + +/** + * A wrapper around certain low-level operations that can be substituted for testing. + */ +export var Policy = { + _clientIDHash: null, + + getClientID() { + return lazy.ClientID.getClientID(); + }, + + /** + * Compute an integer in the range [0, 100) using a hash of the + * client ID. + * + * This is useful for sampling clients when trying to report + * telemetry only for a sample of clients. + */ + async getClientIDHash() { + if (this._clientIDHash === null) { + this._clientIDHash = this._doComputeClientIDHash(); + } + return this._clientIDHash; + }, + + async _doComputeClientIDHash() { + const clientID = await this.getClientID(); + let byteArr = new TextEncoder().encode(clientID); + let hash = new lazy.CryptoHash("sha256"); + hash.update(byteArr, byteArr.length); + const bytes = hash.finish(false); + let rem = 0; + for (let i = 0, len = bytes.length; i < len; i++) { + rem = ((rem << 8) + (bytes[i].charCodeAt(0) & 0xff)) % 100; + } + return rem; + }, + + getChannel() { + return AppConstants.MOZ_UPDATE_CHANNEL; + }, +}; + +/** + * A Telemetry helper to report uptake of remote content. + */ +export class UptakeTelemetry { + /** + * Supported uptake statuses: + * + * - `UP_TO_DATE`: Local content was already up-to-date with remote content. + * - `SUCCESS`: Local content was updated successfully. + * - `BACKOFF`: Remote server asked clients to backoff. + * - `PARSE_ERROR`: Parsing server response has failed. + * - `CONTENT_ERROR`: Server response has unexpected content. + * - `PREF_DISABLED`: Update is disabled in user preferences. + * - `SIGNATURE_ERROR`: Signature verification after diff-based sync has failed. + * - `SIGNATURE_RETRY_ERROR`: Signature verification after full fetch has failed. + * - `CONFLICT_ERROR`: Some remote changes are in conflict with local changes. + * - `CORRUPTION_ERROR`: Error related to corrupted local data. + * - `SYNC_ERROR`: Synchronization of remote changes has failed. + * - `APPLY_ERROR`: Application of changes locally has failed. + * - `SERVER_ERROR`: Server failed to respond. + * - `CERTIFICATE_ERROR`: Server certificate verification has failed. + * - `DOWNLOAD_ERROR`: Data could not be fully retrieved. + * - `TIMEOUT_ERROR`: Server response has timed out. + * - `NETWORK_ERROR`: Communication with server has failed. + * - `NETWORK_OFFLINE_ERROR`: Network not available. + * - `SHUTDOWN_ERROR`: Error occuring during shutdown. + * - `UNKNOWN_ERROR`: Uncategorized error. + * - `CLEANUP_ERROR`: Clean-up of temporary files has failed. + * - `SYNC_BROKEN_ERROR`: Synchronization is broken. + * - `CUSTOM_1_ERROR`: Update source specific error #1. + * - `CUSTOM_2_ERROR`: Update source specific error #2. + * - `CUSTOM_3_ERROR`: Update source specific error #3. + * - `CUSTOM_4_ERROR`: Update source specific error #4. + * - `CUSTOM_5_ERROR`: Update source specific error #5. + * + * @type {Object} + */ + static get STATUS() { + return { + UP_TO_DATE: "up_to_date", + SUCCESS: "success", + BACKOFF: "backoff", + PARSE_ERROR: "parse_error", + CONTENT_ERROR: "content_error", + PREF_DISABLED: "pref_disabled", + SIGNATURE_ERROR: "sign_error", + SIGNATURE_RETRY_ERROR: "sign_retry_error", + CONFLICT_ERROR: "conflict_error", + CORRUPTION_ERROR: "corruption_error", + SYNC_ERROR: "sync_error", + APPLY_ERROR: "apply_error", + SERVER_ERROR: "server_error", + CERTIFICATE_ERROR: "certificate_error", + DOWNLOAD_ERROR: "download_error", + TIMEOUT_ERROR: "timeout_error", + NETWORK_ERROR: "network_error", + NETWORK_OFFLINE_ERROR: "offline_error", + SHUTDOWN_ERROR: "shutdown_error", + UNKNOWN_ERROR: "unknown_error", + CLEANUP_ERROR: "cleanup_error", + SYNC_BROKEN_ERROR: "sync_broken_error", + CUSTOM_1_ERROR: "custom_1_error", + CUSTOM_2_ERROR: "custom_2_error", + CUSTOM_3_ERROR: "custom_3_error", + CUSTOM_4_ERROR: "custom_4_error", + CUSTOM_5_ERROR: "custom_5_error", + }; + } + + static get Policy() { + return Policy; + } + + /** + * Reports the uptake status for the specified source. + * + * @param {string} component the component reporting the uptake (eg. "normandy"). + * @param {string} status the uptake status (eg. "network_error") + * @param {Object} extra extra values to report + * @param {string} extra.source the update source (eg. "recipe-42"). + * @param {string} extra.trigger what triggered the polling/fetching (eg. "broadcast", "timer"). + * @param {int} extra.age age of pulled data in seconds + */ + static async report(component, status, extra = {}) { + const { source } = extra; + + if (!source) { + throw new Error("`source` value is mandatory."); + } + + if (!Object.values(UptakeTelemetry.STATUS).includes(status)) { + throw new Error(`Unknown status '${status}'`); + } + + // Report event for real-time monitoring. See Events.yaml for registration. + // Contrary to histograms, Telemetry Events are not enabled by default. + // Enable them on first call to `report()`. + if (!this._eventsEnabled) { + Services.telemetry.setEventRecordingEnabled(TELEMETRY_EVENTS_ID, true); + this._eventsEnabled = true; + } + + const hash = await UptakeTelemetry.Policy.getClientIDHash(); + const channel = UptakeTelemetry.Policy.getChannel(); + const shouldSendEvent = + !["release", "esr"].includes(channel) || hash < lazy.gSampleRate; + if (shouldSendEvent) { + // The Event API requires `extra` values to be of type string. Force it! + const extraStr = Object.keys(extra).reduce((acc, k) => { + acc[k] = extra[k].toString(); + return acc; + }, {}); + Services.telemetry.recordEvent( + TELEMETRY_EVENTS_ID, + "uptake", + component, + status, + extraStr + ); + } + } +} diff --git a/services/common/utils.sys.mjs b/services/common/utils.sys.mjs new file mode 100644 index 0000000000..3b427c0ad4 --- /dev/null +++ b/services/common/utils.sys.mjs @@ -0,0 +1,693 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +export var CommonUtils = { + /* + * Set manipulation methods. These should be lifted into toolkit, or added to + * `Set` itself. + */ + + /** + * Return elements of `a` or `b`. + */ + union(a, b) { + let out = new Set(a); + for (let x of b) { + out.add(x); + } + return out; + }, + + /** + * Return elements of `a` that are not present in `b`. + */ + difference(a, b) { + let out = new Set(a); + for (let x of b) { + out.delete(x); + } + return out; + }, + + /** + * Return elements of `a` that are also in `b`. + */ + intersection(a, b) { + let out = new Set(); + for (let x of a) { + if (b.has(x)) { + out.add(x); + } + } + return out; + }, + + /** + * Return true if `a` and `b` are the same size, and + * every element of `a` is in `b`. + */ + setEqual(a, b) { + if (a.size != b.size) { + return false; + } + for (let x of a) { + if (!b.has(x)) { + return false; + } + } + return true; + }, + + /** + * Checks elements in two arrays for equality, as determined by the `===` + * operator. This function does not perform a deep comparison; see Sync's + * `Util.deepEquals` for that. + */ + arrayEqual(a, b) { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; + }, + + /** + * Encode byte string as base64URL (RFC 4648). + * + * @param bytes + * (string) Raw byte string to encode. + * @param pad + * (bool) Whether to include padding characters (=). Defaults + * to true for historical reasons. + */ + encodeBase64URL: function encodeBase64URL(bytes, pad = true) { + let s = btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_"); + + if (!pad) { + return s.replace(/=+$/, ""); + } + + return s; + }, + + /** + * Create a nsIURI instance from a string. + */ + makeURI: function makeURI(URIString) { + if (!URIString) { + return null; + } + try { + return Services.io.newURI(URIString); + } catch (e) { + let log = Log.repository.getLogger("Common.Utils"); + log.debug("Could not create URI", e); + return null; + } + }, + + /** + * Execute a function on the next event loop tick. + * + * @param callback + * Function to invoke. + * @param thisObj [optional] + * Object to bind the callback to. + */ + nextTick: function nextTick(callback, thisObj) { + if (thisObj) { + callback = callback.bind(thisObj); + } + Services.tm.dispatchToMainThread(callback); + }, + + /** + * Return a timer that is scheduled to call the callback after waiting the + * provided time or as soon as possible. The timer will be set as a property + * of the provided object with the given timer name. + */ + namedTimer: function namedTimer(callback, wait, thisObj, name) { + if (!thisObj || !name) { + throw new Error( + "You must provide both an object and a property name for the timer!" + ); + } + + // Delay an existing timer if it exists + if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) { + thisObj[name].delay = wait; + return thisObj[name]; + } + + // Create a special timer that we can add extra properties + let timer = Object.create( + Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer) + ); + + // Provide an easy way to clear out the timer + timer.clear = function () { + thisObj[name] = null; + timer.cancel(); + }; + + // Initialize the timer with a smart callback + timer.initWithCallback( + { + notify: function notify() { + // Clear out the timer once it's been triggered + timer.clear(); + callback.call(thisObj, timer); + }, + }, + wait, + timer.TYPE_ONE_SHOT + ); + + return (thisObj[name] = timer); + }, + + encodeUTF8: function encodeUTF8(str) { + try { + str = this._utf8Converter.ConvertFromUnicode(str); + return str + this._utf8Converter.Finish(); + } catch (ex) { + return null; + } + }, + + decodeUTF8: function decodeUTF8(str) { + try { + str = this._utf8Converter.ConvertToUnicode(str); + return str + this._utf8Converter.Finish(); + } catch (ex) { + return null; + } + }, + + byteArrayToString: function byteArrayToString(bytes) { + return bytes.map(byte => String.fromCharCode(byte)).join(""); + }, + + stringToByteArray: function stringToByteArray(bytesString) { + return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0)); + }, + + // A lot of Util methods work with byte strings instead of ArrayBuffers. + // A patch should address this problem, but in the meantime let's provide + // helpers method to convert byte strings to Uint8Array. + byteStringToArrayBuffer(byteString) { + if (byteString === undefined) { + return new Uint8Array(); + } + const bytes = new Uint8Array(byteString.length); + for (let i = 0; i < byteString.length; ++i) { + bytes[i] = byteString.charCodeAt(i) & 0xff; + } + return bytes; + }, + + arrayBufferToByteString(buffer) { + return CommonUtils.byteArrayToString([...buffer]); + }, + + bufferToHex(buffer) { + return Array.prototype.map + .call(buffer, x => ("00" + x.toString(16)).slice(-2)) + .join(""); + }, + + bytesAsHex: function bytesAsHex(bytes) { + let s = ""; + for (let i = 0, len = bytes.length; i < len; i++) { + let c = (bytes[i].charCodeAt(0) & 0xff).toString(16); + if (c.length == 1) { + c = "0" + c; + } + s += c; + } + return s; + }, + + stringAsHex: function stringAsHex(str) { + return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str)); + }, + + stringToBytes: function stringToBytes(str) { + return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str)); + }, + + hexToBytes: function hexToBytes(str) { + let bytes = []; + for (let i = 0; i < str.length - 1; i += 2) { + bytes.push(parseInt(str.substr(i, 2), 16)); + } + return String.fromCharCode.apply(String, bytes); + }, + + hexToArrayBuffer(str) { + const octString = CommonUtils.hexToBytes(str); + return CommonUtils.byteStringToArrayBuffer(octString); + }, + + hexAsString: function hexAsString(hex) { + return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex)); + }, + + base64urlToHex(b64str) { + return CommonUtils.bufferToHex( + new Uint8Array(ChromeUtils.base64URLDecode(b64str, { padding: "reject" })) + ); + }, + + /** + * Base32 encode (RFC 4648) a string + */ + encodeBase32: function encodeBase32(bytes) { + const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + let leftover = bytes.length % 5; + + // Pad the last quantum with zeros so the length is a multiple of 5. + if (leftover) { + for (let i = leftover; i < 5; i++) { + bytes += "\0"; + } + } + + // Chop the string into quanta of 5 bytes (40 bits). Each quantum + // is turned into 8 characters from the 32 character base. + let ret = ""; + for (let i = 0; i < bytes.length; i += 5) { + let c = Array.prototype.slice + .call(bytes.slice(i, i + 5)) + .map(byte => byte.charCodeAt(0)); + ret += + key[c[0] >> 3] + + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)] + + key[(c[1] >> 1) & 0x1f] + + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)] + + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)] + + key[(c[3] >> 2) & 0x1f] + + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)] + + key[c[4] & 0x1f]; + } + + switch (leftover) { + case 1: + return ret.slice(0, -6) + "======"; + case 2: + return ret.slice(0, -4) + "===="; + case 3: + return ret.slice(0, -3) + "==="; + case 4: + return ret.slice(0, -1) + "="; + default: + return ret; + } + }, + + /** + * Base32 decode (RFC 4648) a string. + */ + decodeBase32: function decodeBase32(str) { + const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + let padChar = str.indexOf("="); + let chars = padChar == -1 ? str.length : padChar; + let bytes = Math.floor((chars * 5) / 8); + let blocks = Math.ceil(chars / 8); + + // Process a chunk of 5 bytes / 8 characters. + // The processing of this is known in advance, + // so avoid arithmetic! + function processBlock(ret, cOffset, rOffset) { + let c, val; + + // N.B., this relies on + // undefined | foo == foo. + function accumulate(val) { + ret[rOffset] |= val; + } + + function advance() { + c = str[cOffset++]; + if (!c || c == "" || c == "=") { + // Easier than range checking. + throw new Error("Done"); + } // Will be caught far away. + val = key.indexOf(c); + if (val == -1) { + throw new Error(`Unknown character in base32: ${c}`); + } + } + + // Handle a left shift, restricted to bytes. + function left(octet, shift) { + return (octet << shift) & 0xff; + } + + advance(); + accumulate(left(val, 3)); + advance(); + accumulate(val >> 2); + ++rOffset; + accumulate(left(val, 6)); + advance(); + accumulate(left(val, 1)); + advance(); + accumulate(val >> 4); + ++rOffset; + accumulate(left(val, 4)); + advance(); + accumulate(val >> 1); + ++rOffset; + accumulate(left(val, 7)); + advance(); + accumulate(left(val, 2)); + advance(); + accumulate(val >> 3); + ++rOffset; + accumulate(left(val, 5)); + advance(); + accumulate(val); + ++rOffset; + } + + // Our output. Define to be explicit (and maybe the compiler will be smart). + let ret = new Array(bytes); + let i = 0; + let cOff = 0; + let rOff = 0; + + for (; i < blocks; ++i) { + try { + processBlock(ret, cOff, rOff); + } catch (ex) { + // Handle the detection of padding. + if (ex.message == "Done") { + break; + } + throw ex; + } + cOff += 8; + rOff += 5; + } + + // Slice in case our shift overflowed to the right. + return CommonUtils.byteArrayToString(ret.slice(0, bytes)); + }, + + /** + * Trim excess padding from a Base64 string and atob(). + * + * See bug 562431 comment 4. + */ + safeAtoB: function safeAtoB(b64) { + let len = b64.length; + let over = len % 4; + return over ? atob(b64.substr(0, len - over)) : atob(b64); + }, + + /** + * Ensure that the specified value is defined in integer milliseconds since + * UNIX epoch. + * + * This throws an error if the value is not an integer, is negative, or looks + * like seconds, not milliseconds. + * + * If the value is null or 0, no exception is raised. + * + * @param value + * Value to validate. + */ + ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) { + if (!value) { + return; + } + + if (!/^[0-9]+$/.test(value)) { + throw new Error("Timestamp value is not a positive integer: " + value); + } + + let intValue = parseInt(value, 10); + + if (!intValue) { + return; + } + + // Catch what looks like seconds, not milliseconds. + if (intValue < 10000000000) { + throw new Error("Timestamp appears to be in seconds: " + intValue); + } + }, + + /** + * Read bytes from an nsIInputStream into a string. + * + * @param stream + * (nsIInputStream) Stream to read from. + * @param count + * (number) Integer number of bytes to read. If not defined, or + * 0, all available input is read. + */ + readBytesFromInputStream: function readBytesFromInputStream(stream, count) { + let BinaryInputStream = Components.Constructor( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" + ); + if (!count) { + count = stream.available(); + } + + return new BinaryInputStream(stream).readBytes(count); + }, + + /** + * Generate a new UUID using nsIUUIDGenerator. + * + * Example value: "1e00a2e2-1570-443e-bf5e-000354124234" + * + * @return string A hex-formatted UUID string. + */ + generateUUID: function generateUUID() { + let uuid = Services.uuid.generateUUID().toString(); + + return uuid.substring(1, uuid.length - 1); + }, + + /** + * Obtain an epoch value from a preference. + * + * This reads a string preference and returns an integer. The string + * preference is expected to contain the integer milliseconds since epoch. + * For best results, only read preferences that have been saved with + * setDatePref(). + * + * We need to store times as strings because integer preferences are only + * 32 bits and likely overflow most dates. + * + * If the pref contains a non-integer value, the specified default value will + * be returned. + * + * @param branch + * (Preferences) Branch from which to retrieve preference. + * @param pref + * (string) The preference to read from. + * @param def + * (Number) The default value to use if the preference is not defined. + * @param log + * (Log.Logger) Logger to write warnings to. + */ + getEpochPref: function getEpochPref(branch, pref, def = 0, log = null) { + if (!Number.isInteger(def)) { + throw new Error("Default value is not a number: " + def); + } + + let valueStr = branch.getStringPref(pref, null); + + if (valueStr !== null) { + let valueInt = parseInt(valueStr, 10); + if (Number.isNaN(valueInt)) { + if (log) { + log.warn( + "Preference value is not an integer. Using default. " + + pref + + "=" + + valueStr + + " -> " + + def + ); + } + + return def; + } + + return valueInt; + } + + return def; + }, + + /** + * Obtain a Date from a preference. + * + * This is a wrapper around getEpochPref. It converts the value to a Date + * instance and performs simple range checking. + * + * The range checking ensures the date is newer than the oldestYear + * parameter. + * + * @param branch + * (Preferences) Branch from which to read preference. + * @param pref + * (string) The preference from which to read. + * @param def + * (Number) The default value (in milliseconds) if the preference is + * not defined or invalid. + * @param log + * (Log.Logger) Logger to write warnings to. + * @param oldestYear + * (Number) Oldest year to accept in read values. + */ + getDatePref: function getDatePref( + branch, + pref, + def = 0, + log = null, + oldestYear = 2010 + ) { + let valueInt = this.getEpochPref(branch, pref, def, log); + let date = new Date(valueInt); + + if (valueInt == def || date.getFullYear() >= oldestYear) { + return date; + } + + if (log) { + log.warn( + "Unexpected old date seen in pref. Returning default: " + + pref + + "=" + + date + + " -> " + + def + ); + } + + return new Date(def); + }, + + /** + * Store a Date in a preference. + * + * This is the opposite of getDatePref(). The same notes apply. + * + * If the range check fails, an Error will be thrown instead of a default + * value silently being used. + * + * @param branch + * (Preference) Branch from which to read preference. + * @param pref + * (string) Name of preference to write to. + * @param date + * (Date) The value to save. + * @param oldestYear + * (Number) The oldest year to accept for values. + */ + setDatePref: function setDatePref(branch, pref, date, oldestYear = 2010) { + if (date.getFullYear() < oldestYear) { + throw new Error( + "Trying to set " + + pref + + " to a very old time: " + + date + + ". The current time is " + + new Date() + + ". Is the system clock wrong?" + ); + } + + branch.setStringPref(pref, "" + date.getTime()); + }, + + /** + * Convert a string between two encodings. + * + * Output is only guaranteed if the input stream is composed of octets. If + * the input string has characters with values larger than 255, data loss + * will occur. + * + * The returned string is guaranteed to consist of character codes no greater + * than 255. + * + * @param s + * (string) The source string to convert. + * @param source + * (string) The current encoding of the string. + * @param dest + * (string) The target encoding of the string. + * + * @return string + */ + convertString: function convertString(s, source, dest) { + if (!s) { + throw new Error("Input string must be defined."); + } + + let is = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + is.setData(s, s.length); + + let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance( + Ci.nsIStreamLoader + ); + + let result; + + listener.init({ + onStreamComplete: function onStreamComplete( + loader, + context, + status, + length, + data + ) { + result = String.fromCharCode.apply(this, data); + }, + }); + + let converter = this._converterService.asyncConvertData( + source, + dest, + listener, + null + ); + converter.onStartRequest(null, null); + converter.onDataAvailable(null, is, 0, s.length); + converter.onStopRequest(null, null, null); + + return result; + }, +}; + +ChromeUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function () { + let converter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + return converter; +}); + +ChromeUtils.defineLazyGetter(CommonUtils, "_converterService", function () { + return Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); +}); diff --git a/services/crypto/cryptoComponents.manifest b/services/crypto/cryptoComponents.manifest new file mode 100644 index 0000000000..f9f47bb42a --- /dev/null +++ b/services/crypto/cryptoComponents.manifest @@ -0,0 +1 @@ +resource services-crypto resource://gre/modules/services-crypto/ diff --git a/services/crypto/modules/WeaveCrypto.sys.mjs b/services/crypto/modules/WeaveCrypto.sys.mjs new file mode 100644 index 0000000000..63ee51a1a2 --- /dev/null +++ b/services/crypto/modules/WeaveCrypto.sys.mjs @@ -0,0 +1,232 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const CRYPT_ALGO = "AES-CBC"; +const CRYPT_ALGO_LENGTH = 256; +const CRYPT_ALGO_USAGES = ["encrypt", "decrypt"]; +const AES_CBC_IV_SIZE = 16; +const OPERATIONS = { ENCRYPT: 0, DECRYPT: 1 }; +const UTF_LABEL = "utf-8"; + +export function WeaveCrypto() { + this.init(); +} + +WeaveCrypto.prototype = { + prefBranch: null, + debug: true, // services.sync.log.cryptoDebug + + observer: { + _self: null, + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + + observe(subject, topic, data) { + let self = this._self; + self.log("Observed " + topic + " topic."); + if (topic == "nsPref:changed") { + self.debug = self.prefBranch.getBoolPref("cryptoDebug"); + } + }, + }, + + init() { + // Preferences. Add observer so we get notified of changes. + this.prefBranch = Services.prefs.getBranch("services.sync.log."); + this.prefBranch.addObserver("cryptoDebug", this.observer); + this.observer._self = this; + this.debug = this.prefBranch.getBoolPref("cryptoDebug", false); + ChromeUtils.defineLazyGetter( + this, + "encoder", + () => new TextEncoder(UTF_LABEL) + ); + ChromeUtils.defineLazyGetter( + this, + "decoder", + () => new TextDecoder(UTF_LABEL, { fatal: true }) + ); + }, + + log(message) { + if (!this.debug) { + return; + } + dump("WeaveCrypto: " + message + "\n"); + Services.console.logStringMessage("WeaveCrypto: " + message); + }, + + // /!\ Only use this for tests! /!\ + _getCrypto() { + return crypto; + }, + + async encrypt(clearTextUCS2, symmetricKey, iv) { + this.log("encrypt() called"); + let clearTextBuffer = this.encoder.encode(clearTextUCS2).buffer; + let encrypted = await this._commonCrypt( + clearTextBuffer, + symmetricKey, + iv, + OPERATIONS.ENCRYPT + ); + return this.encodeBase64(encrypted); + }, + + async decrypt(cipherText, symmetricKey, iv) { + this.log("decrypt() called"); + if (cipherText.length) { + cipherText = atob(cipherText); + } + let cipherTextBuffer = this.byteCompressInts(cipherText); + let decrypted = await this._commonCrypt( + cipherTextBuffer, + symmetricKey, + iv, + OPERATIONS.DECRYPT + ); + return this.decoder.decode(decrypted); + }, + + /** + * _commonCrypt + * + * @args + * data: data to encrypt/decrypt (ArrayBuffer) + * symKeyStr: symmetric key (Base64 String) + * ivStr: initialization vector (Base64 String) + * operation: operation to apply (either OPERATIONS.ENCRYPT or OPERATIONS.DECRYPT) + * @returns + * the encrypted/decrypted data (ArrayBuffer) + */ + async _commonCrypt(data, symKeyStr, ivStr, operation) { + this.log("_commonCrypt() called"); + ivStr = atob(ivStr); + + if (operation !== OPERATIONS.ENCRYPT && operation !== OPERATIONS.DECRYPT) { + throw new Error("Unsupported operation in _commonCrypt."); + } + // We never want an IV longer than the block size, which is 16 bytes + // for AES, neither do we want one smaller; throw in both cases. + if (ivStr.length !== AES_CBC_IV_SIZE) { + throw new Error(`Invalid IV size; must be ${AES_CBC_IV_SIZE} bytes.`); + } + + let iv = this.byteCompressInts(ivStr); + let symKey = await this.importSymKey(symKeyStr, operation); + let cryptMethod = ( + operation === OPERATIONS.ENCRYPT + ? crypto.subtle.encrypt + : crypto.subtle.decrypt + ).bind(crypto.subtle); + let algo = { name: CRYPT_ALGO, iv }; + + let keyBytes = await cryptMethod.call(crypto.subtle, algo, symKey, data); + return new Uint8Array(keyBytes); + }, + + async generateRandomKey() { + this.log("generateRandomKey() called"); + let algo = { + name: CRYPT_ALGO, + length: CRYPT_ALGO_LENGTH, + }; + let key = await crypto.subtle.generateKey(algo, true, CRYPT_ALGO_USAGES); + let keyBytes = await crypto.subtle.exportKey("raw", key); + return this.encodeBase64(new Uint8Array(keyBytes)); + }, + + generateRandomIV() { + return this.generateRandomBytes(AES_CBC_IV_SIZE); + }, + + generateRandomBytes(byteCount) { + this.log("generateRandomBytes() called"); + + let randBytes = new Uint8Array(byteCount); + crypto.getRandomValues(randBytes); + + return this.encodeBase64(randBytes); + }, + + // + // SymKey CryptoKey memoization. + // + + // Memoize the import of symmetric keys. We do this by using the base64 + // string itself as a key. + _encryptionSymKeyMemo: {}, + _decryptionSymKeyMemo: {}, + async importSymKey(encodedKeyString, operation) { + let memo; + + // We use two separate memos for thoroughness: operation is an input to + // key import. + switch (operation) { + case OPERATIONS.ENCRYPT: + memo = this._encryptionSymKeyMemo; + break; + case OPERATIONS.DECRYPT: + memo = this._decryptionSymKeyMemo; + break; + default: + throw new Error("Unsupported operation in importSymKey."); + } + + if (encodedKeyString in memo) { + return memo[encodedKeyString]; + } + + let symmetricKeyBuffer = this.makeUint8Array(encodedKeyString, true); + let algo = { name: CRYPT_ALGO }; + let usages = [operation === OPERATIONS.ENCRYPT ? "encrypt" : "decrypt"]; + let symKey = await crypto.subtle.importKey( + "raw", + symmetricKeyBuffer, + algo, + false, + usages + ); + memo[encodedKeyString] = symKey; + return symKey; + }, + + // + // Utility functions + // + + /** + * Returns an Uint8Array filled with a JS string, + * which means we only keep utf-16 characters from 0x00 to 0xFF. + */ + byteCompressInts(str) { + let arrayBuffer = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + arrayBuffer[i] = str.charCodeAt(i) & 0xff; + } + return arrayBuffer; + }, + + expandData(data) { + let expanded = ""; + for (let i = 0; i < data.length; i++) { + expanded += String.fromCharCode(data[i]); + } + return expanded; + }, + + encodeBase64(data) { + return btoa(this.expandData(data)); + }, + + makeUint8Array(input, isEncoded) { + if (isEncoded) { + input = atob(input); + } + return this.byteCompressInts(input); + }, +}; diff --git a/services/crypto/modules/jwcrypto.sys.mjs b/services/crypto/modules/jwcrypto.sys.mjs new file mode 100644 index 0000000000..d1f37eaa58 --- /dev/null +++ b/services/crypto/modules/jwcrypto.sys.mjs @@ -0,0 +1,214 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const ECDH_PARAMS = { + name: "ECDH", + namedCurve: "P-256", +}; +const AES_PARAMS = { + name: "AES-GCM", + length: 256, +}; +const AES_TAG_LEN = 128; +const AES_GCM_IV_SIZE = 12; +const UTF8_ENCODER = new TextEncoder(); +const UTF8_DECODER = new TextDecoder(); + +class JWCrypto { + /** + * Encrypts the given data into a JWE using AES-256-GCM content encryption. + * + * This function implements a very small subset of the JWE encryption standard + * from https://tools.ietf.org/html/rfc7516. The only supported content encryption + * algorithm is enc="A256GCM" [1] and the only supported key encryption algorithm + * is alg="ECDH-ES" [2]. + * + * @param {Object} key Peer Public JWK. + * @param {ArrayBuffer} data + * + * [1] https://tools.ietf.org/html/rfc7518#section-5.3 + * [2] https://tools.ietf.org/html/rfc7518#section-4.6 + * + * @returns {Promise<String>} + */ + async generateJWE(key, data) { + // Generate an ephemeral key to use just for this encryption. + // The public component gets embedded in the JWE header. + const epk = await crypto.subtle.generateKey(ECDH_PARAMS, true, [ + "deriveKey", + ]); + const ownPublicJWK = await crypto.subtle.exportKey("jwk", epk.publicKey); + // Remove properties added by our WebCrypto implementation but that aren't typically + // used with JWE in the wild. This saves space in the resulting JWE, and makes it easier + // to re-import the resulting JWK. + delete ownPublicJWK.key_ops; + delete ownPublicJWK.ext; + let header = { alg: "ECDH-ES", enc: "A256GCM", epk: ownPublicJWK }; + // Import the peer's public key. + const peerPublicKey = await crypto.subtle.importKey( + "jwk", + key, + ECDH_PARAMS, + false, + ["deriveKey"] + ); + if (key.hasOwnProperty("kid")) { + header.kid = key.kid; + } + // Do ECDH agreement to get the content encryption key. + const contentKey = await deriveECDHSharedAESKey( + epk.privateKey, + peerPublicKey, + ["encrypt"] + ); + // Encrypt with AES-GCM using the generated key. + // Note that the IV is generated randomly, which *in general* is not safe to do with AES-GCM because + // it's too short to guarantee uniqueness. But we know that the AES-GCM key itself is unique and will + // only be used for this single encryption, making a random IV safe to use for this particular use-case. + let iv = crypto.getRandomValues(new Uint8Array(AES_GCM_IV_SIZE)); + // Yes, additionalData is the byte representation of the base64 representation of the stringified header. + const additionalData = UTF8_ENCODER.encode( + ChromeUtils.base64URLEncode(UTF8_ENCODER.encode(JSON.stringify(header)), { + pad: false, + }) + ); + const encrypted = await crypto.subtle.encrypt( + { + name: "AES-GCM", + iv, + additionalData, + tagLength: AES_TAG_LEN, + }, + contentKey, + data + ); + // JWE needs the authentication tag as a separate string. + const tagIdx = encrypted.byteLength - ((AES_TAG_LEN + 7) >> 3); + let ciphertext = encrypted.slice(0, tagIdx); + let tag = encrypted.slice(tagIdx); + // JWE serialization in compact format. + header = UTF8_ENCODER.encode(JSON.stringify(header)); + header = ChromeUtils.base64URLEncode(header, { pad: false }); + tag = ChromeUtils.base64URLEncode(tag, { pad: false }); + ciphertext = ChromeUtils.base64URLEncode(ciphertext, { pad: false }); + iv = ChromeUtils.base64URLEncode(iv, { pad: false }); + return `${header}..${iv}.${ciphertext}.${tag}`; // No CEK + } + + /** + * Decrypts the given JWE using AES-256-GCM content encryption into a byte array. + * This function does the opposite of `JWCrypto.generateJWE`. + * The only supported content encryption algorithm is enc="A256GCM" [1] + * and the only supported key encryption algorithm is alg="ECDH-ES" [2]. + * + * @param {"ECDH-ES"} algorithm + * @param {CryptoKey} key Local private key + * + * [1] https://tools.ietf.org/html/rfc7518#section-5.3 + * [2] https://tools.ietf.org/html/rfc7518#section-4.6 + * + * @returns {Promise<Uint8Array>} + */ + async decryptJWE(jwe, key) { + let [header, cek, iv, ciphertext, authTag] = jwe.split("."); + const additionalData = UTF8_ENCODER.encode(header); + header = JSON.parse( + UTF8_DECODER.decode( + ChromeUtils.base64URLDecode(header, { padding: "reject" }) + ) + ); + if (!!cek.length || header.enc !== "A256GCM" || header.alg !== "ECDH-ES") { + throw new Error("Unknown algorithm."); + } + if ("apu" in header || "apv" in header) { + throw new Error("apu and apv header values are not supported."); + } + const peerPublicKey = await crypto.subtle.importKey( + "jwk", + header.epk, + ECDH_PARAMS, + false, + ["deriveKey"] + ); + // Do ECDH agreement to get the content encryption key. + const contentKey = await deriveECDHSharedAESKey(key, peerPublicKey, [ + "decrypt", + ]); + iv = ChromeUtils.base64URLDecode(iv, { padding: "reject" }); + ciphertext = new Uint8Array( + ChromeUtils.base64URLDecode(ciphertext, { padding: "reject" }) + ); + authTag = new Uint8Array( + ChromeUtils.base64URLDecode(authTag, { padding: "reject" }) + ); + const bundle = new Uint8Array([...ciphertext, ...authTag]); + + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-GCM", + iv, + tagLength: AES_TAG_LEN, + additionalData, + }, + contentKey, + bundle + ); + return new Uint8Array(decrypted); + } +} + +/** + * Do an ECDH agreement between a public and private key, + * returning the derived encryption key as specced by + * JWA RFC. + * The raw ECDH secret is derived into a key using + * Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A]. + * @param {CryptoKey} privateKey + * @param {CryptoKey} publicKey + * @param {String[]} keyUsages See `SubtleCrypto.deriveKey` 5th paramater documentation. + * @returns {Promise<CryptoKey>} + */ +async function deriveECDHSharedAESKey(privateKey, publicKey, keyUsages) { + const params = { ...ECDH_PARAMS, ...{ public: publicKey } }; + const sharedKey = await crypto.subtle.deriveKey( + params, + privateKey, + AES_PARAMS, + true, + keyUsages + ); + // This is the NIST Concat KDF specialized to a specific set of parameters, + // which basically turn it into a single application of SHA256. + // The details are from the JWA RFC. + let sharedKeyBytes = await crypto.subtle.exportKey("raw", sharedKey); + sharedKeyBytes = new Uint8Array(sharedKeyBytes); + const info = [ + "\x00\x00\x00\x07A256GCM", // 7-byte algorithm identifier + "\x00\x00\x00\x00", // empty PartyUInfo + "\x00\x00\x00\x00", // empty PartyVInfo + "\x00\x00\x01\x00", // keylen == 256 + ].join(""); + const pkcs = `\x00\x00\x00\x01${String.fromCharCode.apply( + null, + sharedKeyBytes + )}${info}`; + const pkcsBuf = Uint8Array.from( + Array.prototype.map.call(pkcs, c => c.charCodeAt(0)) + ); + const derivedKeyBytes = await crypto.subtle.digest( + { + name: "SHA-256", + }, + pkcsBuf + ); + return crypto.subtle.importKey( + "raw", + derivedKeyBytes, + AES_PARAMS, + false, + keyUsages + ); +} + +export const jwcrypto = new JWCrypto(); diff --git a/services/crypto/modules/utils.sys.mjs b/services/crypto/modules/utils.sys.mjs new file mode 100644 index 0000000000..3aa3db32d1 --- /dev/null +++ b/services/crypto/modules/utils.sys.mjs @@ -0,0 +1,539 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Observers } from "resource://services-common/observers.sys.mjs"; + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "textEncoder", function () { + return new TextEncoder(); +}); + +/** + * A number of `Legacy` suffixed functions are exposed by CryptoUtils. + * They work with octet strings, which were used before Javascript + * got ArrayBuffer and friends. + */ +export var CryptoUtils = { + xor(a, b) { + let bytes = []; + + if (a.length != b.length) { + throw new Error( + "can't xor unequal length strings: " + a.length + " vs " + b.length + ); + } + + for (let i = 0; i < a.length; i++) { + bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i); + } + + return String.fromCharCode.apply(String, bytes); + }, + + /** + * Generate a string of random bytes. + * @returns {String} Octet string + */ + generateRandomBytesLegacy(length) { + let bytes = CryptoUtils.generateRandomBytes(length); + return CommonUtils.arrayBufferToByteString(bytes); + }, + + generateRandomBytes(length) { + return crypto.getRandomValues(new Uint8Array(length)); + }, + + /** + * UTF8-encode a message and hash it with the given hasher. Returns a + * string containing bytes. + */ + digestUTF8(message, hasher) { + let data = lazy.textEncoder.encode(message); + hasher.update(data, data.length); + let result = hasher.finish(false); + return result; + }, + + /** + * Treat the given message as a bytes string (if necessary) and hash it with + * the given hasher. Returns a string containing bytes. + */ + digestBytes(bytes, hasher) { + if (typeof bytes == "string" || bytes instanceof String) { + bytes = CommonUtils.byteStringToArrayBuffer(bytes); + } + return CryptoUtils.digestBytesArray(bytes, hasher); + }, + + digestBytesArray(bytes, hasher) { + hasher.update(bytes, bytes.length); + let result = hasher.finish(false); + return result; + }, + + /** + * Encode the message into UTF-8 and feed the resulting bytes into the + * given hasher. Does not return a hash. This can be called multiple times + * with a single hasher, but eventually you must extract the result + * yourself. + */ + updateUTF8(message, hasher) { + let bytes = lazy.textEncoder.encode(message); + hasher.update(bytes, bytes.length); + }, + + sha256(message) { + let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(hasher.SHA256); + return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher)); + }, + + sha256Base64(message) { + let data = lazy.textEncoder.encode(message); + let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + return hasher.finish(true); + }, + + /** + * @param {string} alg Hash algorithm (common values are SHA-1 or SHA-256) + * @param {string} key Key as an octet string. + * @param {string} data Data as an octet string. + */ + async hmacLegacy(alg, key, data) { + if (!key || !key.length) { + key = "\0"; + } + data = CommonUtils.byteStringToArrayBuffer(data); + key = CommonUtils.byteStringToArrayBuffer(key); + const result = await CryptoUtils.hmac(alg, key, data); + return CommonUtils.arrayBufferToByteString(result); + }, + + /** + * @param {string} ikm IKM as an octet string. + * @param {string} salt Salt as an Hex string. + * @param {string} info Info as a regular string. + * @param {Number} len Desired output length in bytes. + */ + async hkdfLegacy(ikm, xts, info, len) { + ikm = CommonUtils.byteStringToArrayBuffer(ikm); + xts = CommonUtils.byteStringToArrayBuffer(xts); + info = lazy.textEncoder.encode(info); + const okm = await CryptoUtils.hkdf(ikm, xts, info, len); + return CommonUtils.arrayBufferToByteString(okm); + }, + + /** + * @param {String} alg Hash algorithm (common values are SHA-1 or SHA-256) + * @param {ArrayBuffer} key + * @param {ArrayBuffer} data + * @param {Number} len Desired output length in bytes. + * @returns {Uint8Array} + */ + async hmac(alg, key, data) { + const hmacKey = await crypto.subtle.importKey( + "raw", + key, + { name: "HMAC", hash: alg }, + false, + ["sign"] + ); + const result = await crypto.subtle.sign("HMAC", hmacKey, data); + return new Uint8Array(result); + }, + + /** + * @param {ArrayBuffer} ikm + * @param {ArrayBuffer} salt + * @param {ArrayBuffer} info + * @param {Number} len Desired output length in bytes. + * @returns {Uint8Array} + */ + async hkdf(ikm, salt, info, len) { + const key = await crypto.subtle.importKey( + "raw", + ikm, + { name: "HKDF" }, + false, + ["deriveBits"] + ); + const okm = await crypto.subtle.deriveBits( + { + name: "HKDF", + hash: "SHA-256", + salt, + info, + }, + key, + len * 8 + ); + return new Uint8Array(okm); + }, + + /** + * PBKDF2 password stretching with SHA-256 hmac. + * + * @param {string} passphrase Passphrase as an octet string. + * @param {string} salt Salt as an octet string. + * @param {string} iterations Number of iterations, a positive integer. + * @param {string} len Desired output length in bytes. + */ + async pbkdf2Generate(passphrase, salt, iterations, len) { + passphrase = CommonUtils.byteStringToArrayBuffer(passphrase); + salt = CommonUtils.byteStringToArrayBuffer(salt); + const key = await crypto.subtle.importKey( + "raw", + passphrase, + { name: "PBKDF2" }, + false, + ["deriveBits"] + ); + const output = await crypto.subtle.deriveBits( + { + name: "PBKDF2", + hash: "SHA-256", + salt, + iterations, + }, + key, + len * 8 + ); + return CommonUtils.arrayBufferToByteString(new Uint8Array(output)); + }, + + /** + * Compute the HTTP MAC SHA-1 for an HTTP request. + * + * @param identifier + * (string) MAC Key Identifier. + * @param key + * (string) MAC Key. + * @param method + * (string) HTTP request method. + * @param URI + * (nsIURI) HTTP request URI. + * @param extra + * (object) Optional extra parameters. Valid keys are: + * nonce_bytes - How many bytes the nonce should be. This defaults + * to 8. Note that this many bytes are Base64 encoded, so the + * string length of the nonce will be longer than this value. + * ts - Timestamp to use. Should only be defined for testing. + * nonce - String nonce. Should only be defined for testing as this + * function will generate a cryptographically secure random one + * if not defined. + * ext - Extra string to be included in MAC. Per the HTTP MAC spec, + * the format is undefined and thus application specific. + * @returns + * (object) Contains results of operation and input arguments (for + * symmetry). The object has the following keys: + * + * identifier - (string) MAC Key Identifier (from arguments). + * key - (string) MAC Key (from arguments). + * method - (string) HTTP request method (from arguments). + * hostname - (string) HTTP hostname used (derived from arguments). + * port - (string) HTTP port number used (derived from arguments). + * mac - (string) Raw HMAC digest bytes. + * getHeader - (function) Call to obtain the string Authorization + * header value for this invocation. + * nonce - (string) Nonce value used. + * ts - (number) Integer seconds since Unix epoch that was used. + */ + async computeHTTPMACSHA1(identifier, key, method, uri, extra) { + let ts = extra && extra.ts ? extra.ts : Math.floor(Date.now() / 1000); + let nonce_bytes = extra && extra.nonce_bytes > 0 ? extra.nonce_bytes : 8; + + // We are allowed to use more than the Base64 alphabet if we want. + let nonce = + extra && extra.nonce + ? extra.nonce + : btoa(CryptoUtils.generateRandomBytesLegacy(nonce_bytes)); + + let host = uri.asciiHost; + let port; + let usedMethod = method.toUpperCase(); + + if (uri.port != -1) { + port = uri.port; + } else if (uri.scheme == "http") { + port = "80"; + } else if (uri.scheme == "https") { + port = "443"; + } else { + throw new Error("Unsupported URI scheme: " + uri.scheme); + } + + let ext = extra && extra.ext ? extra.ext : ""; + + let requestString = + ts.toString(10) + + "\n" + + nonce + + "\n" + + usedMethod + + "\n" + + uri.pathQueryRef + + "\n" + + host + + "\n" + + port + + "\n" + + ext + + "\n"; + + const mac = await CryptoUtils.hmacLegacy("SHA-1", key, requestString); + + function getHeader() { + return CryptoUtils.getHTTPMACSHA1Header( + this.identifier, + this.ts, + this.nonce, + this.mac, + this.ext + ); + } + + return { + identifier, + key, + method: usedMethod, + hostname: host, + port, + mac, + nonce, + ts, + ext, + getHeader, + }; + }, + + /** + * Obtain the HTTP MAC Authorization header value from fields. + * + * @param identifier + * (string) MAC key identifier. + * @param ts + * (number) Integer seconds since Unix epoch. + * @param nonce + * (string) Nonce value. + * @param mac + * (string) Computed HMAC digest (raw bytes). + * @param ext + * (optional) (string) Extra string content. + * @returns + * (string) Value to put in Authorization header. + */ + getHTTPMACSHA1Header: function getHTTPMACSHA1Header( + identifier, + ts, + nonce, + mac, + ext + ) { + let header = + 'MAC id="' + + identifier + + '", ' + + 'ts="' + + ts + + '", ' + + 'nonce="' + + nonce + + '", ' + + 'mac="' + + btoa(mac) + + '"'; + + if (!ext) { + return header; + } + + return (header += ', ext="' + ext + '"'); + }, + + /** + * Given an HTTP header value, strip out any attributes. + */ + + stripHeaderAttributes(value) { + value = value || ""; + let i = value.indexOf(";"); + return value + .substring(0, i >= 0 ? i : undefined) + .trim() + .toLowerCase(); + }, + + /** + * Compute the HAWK client values (mostly the header) for an HTTP request. + * + * @param URI + * (nsIURI) HTTP request URI. + * @param method + * (string) HTTP request method. + * @param options + * (object) extra parameters (all but "credentials" are optional): + * credentials - (object, mandatory) HAWK credentials object. + * All three keys are required: + * id - (string) key identifier + * key - (string) raw key bytes + * ext - (string) application-specific data, included in MAC + * localtimeOffsetMsec - (number) local clock offset (vs server) + * payload - (string) payload to include in hash, containing the + * HTTP request body. If not provided, the HAWK hash + * will not cover the request body, and the server + * should not check it either. This will be UTF-8 + * encoded into bytes before hashing. This function + * cannot handle arbitrary binary data, sorry (the + * UTF-8 encoding process will corrupt any codepoints + * between U+0080 and U+00FF). Callers must be careful + * to use an HTTP client function which encodes the + * payload exactly the same way, otherwise the hash + * will not match. + * contentType - (string) payload Content-Type. This is included + * (without any attributes like "charset=") in the + * HAWK hash. It does *not* affect interpretation + * of the "payload" property. + * hash - (base64 string) pre-calculated payload hash. If + * provided, "payload" is ignored. + * ts - (number) pre-calculated timestamp, secs since epoch + * now - (number) current time, ms-since-epoch, for tests + * nonce - (string) pre-calculated nonce. Should only be defined + * for testing as this function will generate a + * cryptographically secure random one if not defined. + * @returns + * Promise<Object> Contains results of operation. The object has the + * following keys: + * field - (string) HAWK header, to use in Authorization: header + * artifacts - (object) other generated values: + * ts - (number) timestamp, in seconds since epoch + * nonce - (string) + * method - (string) + * resource - (string) path plus querystring + * host - (string) + * port - (number) + * hash - (string) payload hash (base64) + * ext - (string) app-specific data + * MAC - (string) request MAC (base64) + */ + async computeHAWK(uri, method, options) { + let credentials = options.credentials; + let ts = + options.ts || + Math.floor( + ((options.now || Date.now()) + (options.localtimeOffsetMsec || 0)) / + 1000 + ); + let port; + if (uri.port != -1) { + port = uri.port; + } else if (uri.scheme == "http") { + port = 80; + } else if (uri.scheme == "https") { + port = 443; + } else { + throw new Error("Unsupported URI scheme: " + uri.scheme); + } + + let artifacts = { + ts, + nonce: options.nonce || btoa(CryptoUtils.generateRandomBytesLegacy(8)), + method: method.toUpperCase(), + resource: uri.pathQueryRef, // This includes both path and search/queryarg. + host: uri.asciiHost.toLowerCase(), // This includes punycoding. + port: port.toString(10), + hash: options.hash, + ext: options.ext, + }; + + let contentType = CryptoUtils.stripHeaderAttributes(options.contentType); + + if ( + !artifacts.hash && + options.hasOwnProperty("payload") && + options.payload + ) { + const buffer = lazy.textEncoder.encode( + `hawk.1.payload\n${contentType}\n${options.payload}\n` + ); + const hash = await crypto.subtle.digest("SHA-256", buffer); + // HAWK specifies this .hash to use +/ (not _-) and include the + // trailing "==" padding. + artifacts.hash = ChromeUtils.base64URLEncode(hash, { pad: true }) + .replace(/-/g, "+") + .replace(/_/g, "/"); + } + + let requestString = + "hawk.1.header\n" + + artifacts.ts.toString(10) + + "\n" + + artifacts.nonce + + "\n" + + artifacts.method + + "\n" + + artifacts.resource + + "\n" + + artifacts.host + + "\n" + + artifacts.port + + "\n" + + (artifacts.hash || "") + + "\n"; + if (artifacts.ext) { + requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n"); + } + requestString += "\n"; + + const hash = await CryptoUtils.hmacLegacy( + "SHA-256", + credentials.key, + requestString + ); + artifacts.mac = btoa(hash); + // The output MAC uses "+" and "/", and padded== . + + function escape(attribute) { + // This is used for "x=y" attributes inside HTTP headers. + return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"'); + } + let header = + 'Hawk id="' + + credentials.id + + '", ' + + 'ts="' + + artifacts.ts + + '", ' + + 'nonce="' + + artifacts.nonce + + '", ' + + (artifacts.hash ? 'hash="' + artifacts.hash + '", ' : "") + + (artifacts.ext ? 'ext="' + escape(artifacts.ext) + '", ' : "") + + 'mac="' + + artifacts.mac + + '"'; + return { + artifacts, + field: header, + }; + }, +}; + +var Svc = {}; + +Observers.add("xpcom-shutdown", function unloadServices() { + Observers.remove("xpcom-shutdown", unloadServices); + + for (let k in Svc) { + delete Svc[k]; + } +}); diff --git a/services/crypto/moz.build b/services/crypto/moz.build new file mode 100644 index 0000000000..4a1650cb6d --- /dev/null +++ b/services/crypto/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Sync") + +XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"] + +EXTRA_JS_MODULES["services-crypto"] += [ + "modules/jwcrypto.sys.mjs", + "modules/utils.sys.mjs", + "modules/WeaveCrypto.sys.mjs", +] + +EXTRA_COMPONENTS += [ + "cryptoComponents.manifest", +] diff --git a/services/crypto/tests/unit/head_helpers.js b/services/crypto/tests/unit/head_helpers.js new file mode 100644 index 0000000000..322f6761a9 --- /dev/null +++ b/services/crypto/tests/unit/head_helpers.js @@ -0,0 +1,78 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +try { + // In the context of xpcshell tests, there won't be a default AppInfo + // eslint-disable-next-line mozilla/use-services + Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); +} catch (ex) { + // Make sure to provide the right OS so crypto loads the right binaries + var OS = "XPCShell"; + if (mozinfo.os == "win") { + OS = "WINNT"; + } else if (mozinfo.os == "mac") { + OS = "Darwin"; + } else { + OS = "Linux"; + } + + const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" + ); + updateAppInfo({ + name: "XPCShell", + ID: "{3e3ba16c-1675-4e88-b9c8-afef81b3d2ef}", + version: "1", + platformVersion: "", + OS, + }); +} + +function base64UrlDecode(s) { + s = s.replace(/-/g, "+"); + s = s.replace(/_/g, "/"); + + // Replace padding if it was stripped by the sender. + // See http://tools.ietf.org/html/rfc4648#section-4 + switch (s.length % 4) { + case 0: + break; // No pad chars in this case + case 2: + s += "=="; + break; // Two pad chars + case 3: + s += "="; + break; // One pad char + default: + throw new Error("Illegal base64url string!"); + } + + // With correct padding restored, apply the standard base64 decoder + return atob(s); +} + +// Register resource alias. Normally done in SyncComponents.manifest. +function addResourceAlias() { + const resProt = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + let uri = Services.io.newURI("resource://gre/modules/services-crypto/"); + resProt.setSubstitution("services-crypto", uri); +} +addResourceAlias(); + +/** + * Print some debug message to the console. All arguments will be printed, + * separated by spaces. + * + * @param [arg0, arg1, arg2, ...] + * Any number of arguments to print out + * @usage _("Hello World") -> prints "Hello World" + * @usage _(1, 2, 3) -> prints "1 2 3" + */ +var _ = function (some, debug, text, to) { + print(Array.from(arguments).join(" ")); +}; diff --git a/services/crypto/tests/unit/test_crypto_crypt.js b/services/crypto/tests/unit/test_crypto_crypt.js new file mode 100644 index 0000000000..8fadd307c7 --- /dev/null +++ b/services/crypto/tests/unit/test_crypto_crypt.js @@ -0,0 +1,226 @@ +const { WeaveCrypto } = ChromeUtils.importESModule( + "resource://services-crypto/WeaveCrypto.sys.mjs" +); + +var cryptoSvc = new WeaveCrypto(); + +add_task(async function test_key_memoization() { + let cryptoGlobal = cryptoSvc._getCrypto(); + let oldImport = cryptoGlobal.subtle.importKey; + if (!oldImport) { + _("Couldn't swizzle crypto.subtle.importKey; returning."); + return; + } + + let iv = cryptoSvc.generateRandomIV(); + let key = await cryptoSvc.generateRandomKey(); + let c = 0; + cryptoGlobal.subtle.importKey = function ( + format, + keyData, + algo, + extractable, + usages + ) { + c++; + return oldImport.call( + cryptoGlobal.subtle, + format, + keyData, + algo, + extractable, + usages + ); + }; + + // Encryption should cause a single counter increment. + Assert.equal(c, 0); + let cipherText = await cryptoSvc.encrypt("Hello, world.", key, iv); + Assert.equal(c, 1); + cipherText = await cryptoSvc.encrypt("Hello, world.", key, iv); + Assert.equal(c, 1); + + // ... as should decryption. + await cryptoSvc.decrypt(cipherText, key, iv); + await cryptoSvc.decrypt(cipherText, key, iv); + await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(c, 2); + + // Un-swizzle. + cryptoGlobal.subtle.importKey = oldImport; +}); + +// Just verify that it gets populated with the correct bytes. +add_task(async function test_makeUint8Array() { + ChromeUtils.importESModule("resource://gre/modules/ctypes.sys.mjs"); + + let item1 = cryptoSvc.makeUint8Array("abcdefghi", false); + Assert.ok(item1); + for (let i = 0; i < 8; ++i) { + Assert.equal(item1[i], "abcdefghi".charCodeAt(i)); + } +}); + +add_task(async function test_encrypt_decrypt() { + // First, do a normal run with expected usage... Generate a random key and + // iv, encrypt and decrypt a string. + var iv = cryptoSvc.generateRandomIV(); + Assert.equal(iv.length, 24); + + var key = await cryptoSvc.generateRandomKey(); + Assert.equal(key.length, 44); + + var mySecret = "bacon is a vegetable"; + var cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + Assert.equal(cipherText.length, 44); + + var clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(clearText.length, 20); + + // Did the text survive the encryption round-trip? + Assert.equal(clearText, mySecret); + Assert.notEqual(cipherText, mySecret); // just to be explicit + + // Do some more tests with a fixed key/iv, to check for reproducable results. + key = "St1tFCor7vQEJNug/465dQ=="; + iv = "oLjkfrLIOnK2bDRvW4kXYA=="; + + _("Testing small IV."); + mySecret = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo="; + let shortiv = "YWJj"; + let err; + try { + await cryptoSvc.encrypt(mySecret, key, shortiv); + } catch (ex) { + err = ex; + } + Assert.ok(!!err); + + _("Testing long IV."); + let longiv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk="; + try { + await cryptoSvc.encrypt(mySecret, key, longiv); + } catch (ex) { + err = ex; + } + Assert.ok(!!err); + + // Test small input sizes + mySecret = ""; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "OGQjp6mK1a3fs9k9Ml4L3w=="); + Assert.equal(clearText, mySecret); + + mySecret = "x"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "96iMl4vhOxFUW/lVHHzVqg=="); + Assert.equal(clearText, mySecret); + + mySecret = "xx"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "olpPbETRYROCSqFWcH2SWg=="); + Assert.equal(clearText, mySecret); + + mySecret = "xxx"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "rRbpHGyVSZizLX/x43Wm+Q=="); + Assert.equal(clearText, mySecret); + + mySecret = "xxxx"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "HeC7miVGDcpxae9RmiIKAw=="); + Assert.equal(clearText, mySecret); + + // Test non-ascii input + // ("testuser1" using similar-looking glyphs) + mySecret = String.fromCharCode(355, 277, 349, 357, 533, 537, 101, 345, 185); + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "Pj4ixByXoH3SU3JkOXaEKPgwRAWplAWFLQZkpJd5Kr4="); + Assert.equal(clearText, mySecret); + + // Tests input spanning a block boundary (AES block size is 16 bytes) + mySecret = "123456789012345"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "e6c5hwphe45/3VN/M0bMUA=="); + Assert.equal(clearText, mySecret); + + mySecret = "1234567890123456"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "V6aaOZw8pWlYkoIHNkhsP1JOIQF87E2vTUvBUQnyV04="); + Assert.equal(clearText, mySecret); + + mySecret = "12345678901234567"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "V6aaOZw8pWlYkoIHNkhsP5GvxWJ9+GIAS6lXw+5fHTI="); + Assert.equal(clearText, mySecret); + + key = "iz35tuIMq4/H+IYw2KTgow=="; + iv = "TJYrvva2KxvkM8hvOIvWp3=="; + mySecret = "i like pie"; + + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "DLGx8BWqSCLGG7i/xwvvxg=="); + Assert.equal(clearText, mySecret); + + key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI="; + iv = "gsgLRDaxWvIfKt75RjuvFW=="; + mySecret = "i like pie"; + + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + clearText = await cryptoSvc.decrypt(cipherText, key, iv); + Assert.equal(cipherText, "o+ADtdMd8ubzNWurS6jt0Q=="); + Assert.equal(clearText, mySecret); + + key = "St1tFCor7vQEJNug/465dQ=="; + iv = "oLjkfrLIOnK2bDRvW4kXYA=="; + mySecret = "does thunder read testcases?"; + cipherText = await cryptoSvc.encrypt(mySecret, key, iv); + Assert.equal(cipherText, "T6fik9Ros+DB2ablH9zZ8FWZ0xm/szSwJjIHZu7sjPs="); + + var badkey = "badkeybadkeybadkeybadk=="; + var badiv = "badivbadivbadivbadivbad="; + var badcipher = "crapinputcrapinputcrapinputcrapinputcrapinp="; + var failure; + + try { + failure = false; + clearText = await cryptoSvc.decrypt(cipherText, badkey, iv); + } catch (e) { + failure = true; + } + Assert.ok(failure); + + try { + failure = false; + clearText = await cryptoSvc.decrypt(cipherText, key, badiv); + } catch (e) { + failure = true; + } + Assert.ok(failure); + + try { + failure = false; + clearText = await cryptoSvc.decrypt(cipherText, badkey, badiv); + } catch (e) { + failure = true; + } + Assert.ok(failure); + + try { + failure = false; + clearText = await cryptoSvc.decrypt(badcipher, key, iv); + } catch (e) { + failure = true; + } + Assert.ok(failure); +}); diff --git a/services/crypto/tests/unit/test_crypto_random.js b/services/crypto/tests/unit/test_crypto_random.js new file mode 100644 index 0000000000..bd913c81e8 --- /dev/null +++ b/services/crypto/tests/unit/test_crypto_random.js @@ -0,0 +1,52 @@ +const { WeaveCrypto } = ChromeUtils.importESModule( + "resource://services-crypto/WeaveCrypto.sys.mjs" +); + +var cryptoSvc = new WeaveCrypto(); + +add_task(async function test_crypto_random() { + if (this.gczeal) { + _("Running crypto random tests with gczeal(2)."); + gczeal(2); + } + + // Test salt generation. + var salt; + + salt = cryptoSvc.generateRandomBytes(0); + Assert.equal(salt.length, 0); + salt = cryptoSvc.generateRandomBytes(1); + Assert.equal(salt.length, 4); + salt = cryptoSvc.generateRandomBytes(2); + Assert.equal(salt.length, 4); + salt = cryptoSvc.generateRandomBytes(3); + Assert.equal(salt.length, 4); + salt = cryptoSvc.generateRandomBytes(4); + Assert.equal(salt.length, 8); + salt = cryptoSvc.generateRandomBytes(8); + Assert.equal(salt.length, 12); + + // sanity check to make sure salts seem random + var salt2 = cryptoSvc.generateRandomBytes(8); + Assert.equal(salt2.length, 12); + Assert.notEqual(salt, salt2); + + salt = cryptoSvc.generateRandomBytes(1024); + Assert.equal(salt.length, 1368); + salt = cryptoSvc.generateRandomBytes(16); + Assert.equal(salt.length, 24); + + // Test random key generation + var keydata, keydata2, iv; + + keydata = await cryptoSvc.generateRandomKey(); + Assert.equal(keydata.length, 44); + keydata2 = await cryptoSvc.generateRandomKey(); + Assert.notEqual(keydata, keydata2); // sanity check for randomness + iv = cryptoSvc.generateRandomIV(); + Assert.equal(iv.length, 24); + + if (this.gczeal) { + gczeal(0); + } +}); diff --git a/services/crypto/tests/unit/test_jwcrypto.js b/services/crypto/tests/unit/test_jwcrypto.js new file mode 100644 index 0000000000..02f064d431 --- /dev/null +++ b/services/crypto/tests/unit/test_jwcrypto.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", +}); + +// Enable logging from jwcrypto.jsm. +Services.prefs.setStringPref("services.crypto.jwcrypto.log.level", "Debug"); + +add_task(async function test_jwe_roundtrip_ecdh_es_encryption() { + const plaintext = crypto.getRandomValues(new Uint8Array(123)); + const remoteKey = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey"] + ); + const remoteJWK = await crypto.subtle.exportKey("jwk", remoteKey.publicKey); + delete remoteJWK.key_ops; + const jwe = await jwcrypto.generateJWE(remoteJWK, plaintext); + const decrypted = await jwcrypto.decryptJWE(jwe, remoteKey.privateKey); + Assert.deepEqual(plaintext, decrypted); +}); + +add_task(async function test_jwe_header_includes_key_id() { + const plaintext = crypto.getRandomValues(new Uint8Array(123)); + const remoteKey = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve: "P-256", + }, + true, + ["deriveKey"] + ); + const remoteJWK = await crypto.subtle.exportKey("jwk", remoteKey.publicKey); + delete remoteJWK.key_ops; + remoteJWK.kid = "key identifier"; + const jwe = await jwcrypto.generateJWE(remoteJWK, plaintext); + let [header /* other items deliberately ignored */] = jwe.split("."); + header = JSON.parse( + new TextDecoder().decode( + ChromeUtils.base64URLDecode(header, { padding: "reject" }) + ) + ); + Assert.equal(header.kid, "key identifier"); +}); diff --git a/services/crypto/tests/unit/test_load_modules.js b/services/crypto/tests/unit/test_load_modules.js new file mode 100644 index 0000000000..2c850d3dab --- /dev/null +++ b/services/crypto/tests/unit/test_load_modules.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const modules = ["utils.sys.mjs", "WeaveCrypto.sys.mjs"]; + +function run_test() { + for (let m of modules) { + let resource = "resource://services-crypto/" + m; + _("Attempting to import: " + resource); + ChromeUtils.importESModule(resource); + } +} diff --git a/services/crypto/tests/unit/test_utils_hawk.js b/services/crypto/tests/unit/test_utils_hawk.js new file mode 100644 index 0000000000..71702b7349 --- /dev/null +++ b/services/crypto/tests/unit/test_utils_hawk.js @@ -0,0 +1,346 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); + +function run_test() { + initTestLogging(); + + run_next_test(); +} + +add_task(async function test_hawk() { + let compute = CryptoUtils.computeHAWK; + + let method = "POST"; + let ts = 1353809207; + let nonce = "Ygvqdz"; + + let credentials = { + id: "123456", + key: "2983d45yun89q", + }; + + let uri_https = CommonUtils.makeURI( + "https://example.net/somewhere/over/the/rainbow" + ); + let opts = { + credentials, + ext: "Bazinga!", + ts, + nonce, + payload: "something to write about", + contentType: "text/plain", + }; + + let result = await compute(uri_https, method, opts); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' + + 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' + + 'ext="Bazinga!", ' + + 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="' + ); + Assert.equal(result.artifacts.ts, ts); + Assert.equal(result.artifacts.nonce, nonce); + Assert.equal(result.artifacts.method, method); + Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow"); + Assert.equal(result.artifacts.host, "example.net"); + Assert.equal(result.artifacts.port, 443); + Assert.equal( + result.artifacts.hash, + "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=" + ); + Assert.equal(result.artifacts.ext, "Bazinga!"); + + let opts_noext = { + credentials, + ts, + nonce, + payload: "something to write about", + contentType: "text/plain", + }; + result = await compute(uri_https, method, opts_noext); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' + + 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' + + 'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="' + ); + Assert.equal(result.artifacts.ts, ts); + Assert.equal(result.artifacts.nonce, nonce); + Assert.equal(result.artifacts.method, method); + Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow"); + Assert.equal(result.artifacts.host, "example.net"); + Assert.equal(result.artifacts.port, 443); + Assert.equal( + result.artifacts.hash, + "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=" + ); + + /* Leaving optional fields out should work, although of course then we can't + * assert much about the resulting hashes. The resulting header should look + * roughly like: + * Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U=" + */ + + result = await compute(uri_https, method, { credentials }); + let fields = result.field.split(" "); + Assert.equal(fields[0], "Hawk"); + Assert.equal(fields[1], 'id="123456",'); // from creds.id + Assert.ok(fields[2].startsWith('ts="')); + /* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch. + * Warning: this test will fail in the year 33658, and for time travellers + * who journey earlier than 2001. Please plan accordingly. */ + Assert.ok(result.artifacts.ts > 1000 * 1000 * 1000); + Assert.ok(result.artifacts.ts < 1000 * 1000 * 1000 * 1000); + Assert.ok(fields[3].startsWith('nonce="')); + Assert.equal(fields[3].length, 'nonce="12345678901=",'.length); + Assert.equal(result.artifacts.nonce.length, "12345678901=".length); + + let result2 = await compute(uri_https, method, { credentials }); + Assert.notEqual(result.artifacts.nonce, result2.artifacts.nonce); + + /* Using an upper-case URI hostname shouldn't affect the hash. */ + + let uri_https_upper = CommonUtils.makeURI( + "https://EXAMPLE.NET/somewhere/over/the/rainbow" + ); + result = await compute(uri_https_upper, method, opts); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' + + 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' + + 'ext="Bazinga!", ' + + 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="' + ); + + /* Using a lower-case method name shouldn't affect the hash. */ + result = await compute(uri_https_upper, method.toLowerCase(), opts); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' + + 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' + + 'ext="Bazinga!", ' + + 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="' + ); + + /* The localtimeOffsetMsec field should be honored. HAWK uses this to + * compensate for clock skew between client and server: if the request is + * rejected with a timestamp out-of-range error, the error includes the + * server's time, and the client computes its clock offset and tries again. + * Clients can remember this offset for a while. + */ + + result = await compute(uri_https, method, { + credentials, + now: 1378848968650, + }); + Assert.equal(result.artifacts.ts, 1378848968); + + result = await compute(uri_https, method, { + credentials, + now: 1378848968650, + localtimeOffsetMsec: 1000 * 1000, + }); + Assert.equal(result.artifacts.ts, 1378848968 + 1000); + + /* Search/query-args in URIs should be included in the hash. */ + let makeURI = CommonUtils.makeURI; + result = await compute(makeURI("http://example.net/path"), method, opts); + Assert.equal(result.artifacts.resource, "/path"); + Assert.equal( + result.artifacts.mac, + "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=" + ); + + result = await compute(makeURI("http://example.net/path/"), method, opts); + Assert.equal(result.artifacts.resource, "/path/"); + Assert.equal( + result.artifacts.mac, + "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=" + ); + + result = await compute( + makeURI("http://example.net/path?query=search"), + method, + opts + ); + Assert.equal(result.artifacts.resource, "/path?query=search"); + Assert.equal( + result.artifacts.mac, + "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=" + ); + + /* Test handling of the payload, which is supposed to be a bytestring + (String with codepoints from U+0000 to U+00FF, pre-encoded). */ + + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + }); + Assert.equal(result.artifacts.hash, undefined); + Assert.equal( + result.artifacts.mac, + "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=" + ); + + // Empty payload changes nothing. + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + payload: null, + }); + Assert.equal(result.artifacts.hash, undefined); + Assert.equal( + result.artifacts.mac, + "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=" + ); + + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + payload: "hello", + }); + Assert.equal( + result.artifacts.hash, + "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=" + ); + Assert.equal( + result.artifacts.mac, + "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=" + ); + + // update, utf-8 payload + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + payload: "andré@example.org", // non-ASCII + }); + Assert.equal( + result.artifacts.hash, + "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=" + ); + Assert.equal( + result.artifacts.mac, + "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=" + ); + + /* If "hash" is provided, "payload" is ignored. */ + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=", + payload: "something else", + }); + Assert.equal( + result.artifacts.hash, + "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=" + ); + Assert.equal( + result.artifacts.mac, + "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=" + ); + + // the payload "hash" is also non-urlsafe base64 (+/) + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + payload: "something else", + }); + Assert.equal( + result.artifacts.hash, + "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=" + ); + Assert.equal( + result.artifacts.mac, + "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=" + ); + + /* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes + * "ëxample.net" into "xn--xample-ova.net" before hashing. I still think + * punycode was a bad joke that got out of the lab and into a spec. + */ + + result = await compute(makeURI("http://ëxample.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + }); + Assert.equal( + result.artifacts.mac, + "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=" + ); + Assert.equal(result.artifacts.host, "xn--xample-ova.net"); + + result = await compute(makeURI("http://example.net/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + ext: 'backslash=\\ quote=" EOF', + }); + Assert.equal( + result.artifacts.mac, + "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=" + ); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="' + ); + + result = await compute(makeURI("http://example.net:1234/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + }); + Assert.equal( + result.artifacts.mac, + "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=" + ); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="' + ); + + /* HAWK (the node.js library) uses a URL parser which stores the "port" + * field as a string, but makeURI() gives us an integer. So we'll diverge + * on ports with a leading zero. This test vector would fail on the node.js + * library (HAWK-1.1.1), where they get a MAC of + * "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be + * updated to do what we do here, so port="01234" should get the same hash + * as port="1234". + */ + result = await compute(makeURI("http://example.net:01234/path"), method, { + credentials, + ts: 1353809207, + nonce: "Ygvqdz", + }); + Assert.equal( + result.artifacts.mac, + "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=" + ); + Assert.equal( + result.field, + 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="' + ); +}); + +add_test(function test_strip_header_attributes() { + let strip = CryptoUtils.stripHeaderAttributes; + + Assert.equal(strip(undefined), ""); + Assert.equal(strip("text/plain"), "text/plain"); + Assert.equal(strip("TEXT/PLAIN"), "text/plain"); + Assert.equal(strip(" text/plain "), "text/plain"); + Assert.equal(strip("text/plain ; charset=utf-8 "), "text/plain"); + + run_next_test(); +}); diff --git a/services/crypto/tests/unit/test_utils_httpmac.js b/services/crypto/tests/unit/test_utils_httpmac.js new file mode 100644 index 0000000000..4831683e70 --- /dev/null +++ b/services/crypto/tests/unit/test_utils_httpmac.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); + +add_test(function setup() { + initTestLogging(); + run_next_test(); +}); + +add_task(async function test_sha1() { + _("Ensure HTTP MAC SHA1 generation works as expected."); + + let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7"; + let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz"; + let ts = 1329181221; + let method = "GET"; + let nonce = "wGX71"; + let uri = CommonUtils.makeURI("http://10.250.2.176/alias/"); + + let result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, { + ts, + nonce, + }); + + Assert.equal(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck="); + + Assert.equal( + result.getHeader(), + 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' + + 'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="' + ); + + let ext = "EXTRA DATA; foo,bar=1"; + + result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, { + ts, + nonce, + ext, + }); + Assert.equal(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68="); + Assert.equal( + result.getHeader(), + 'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' + + 'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' + + 'ext="EXTRA DATA; foo,bar=1"' + ); +}); + +add_task(async function test_nonce_length() { + _("Ensure custom nonce lengths are honoured."); + + function get_mac(length) { + let uri = CommonUtils.makeURI("http://example.com/"); + return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, { + nonce_bytes: length, + }); + } + + let result = await get_mac(12); + Assert.equal(12, atob(result.nonce).length); + + result = await get_mac(2); + Assert.equal(2, atob(result.nonce).length); + + result = await get_mac(0); + Assert.equal(8, atob(result.nonce).length); + + result = await get_mac(-1); + Assert.equal(8, atob(result.nonce).length); +}); diff --git a/services/crypto/tests/unit/xpcshell.toml b/services/crypto/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..9ce7748357 --- /dev/null +++ b/services/crypto/tests/unit/xpcshell.toml @@ -0,0 +1,18 @@ +[DEFAULT] +head = "head_helpers.js ../../../common/tests/unit/head_helpers.js" +firefox-appdir = "browser" +support-files = ["!/services/common/tests/unit/head_helpers.js"] + +["test_crypto_crypt.js"] + +["test_crypto_random.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_jwcrypto.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_load_modules.js"] + +["test_utils_hawk.js"] + +["test_utils_httpmac.js"] diff --git a/services/docs/index.rst b/services/docs/index.rst new file mode 100644 index 0000000000..93b9506cb6 --- /dev/null +++ b/services/docs/index.rst @@ -0,0 +1,11 @@ +======== +Services +======== + +This is the nascent documentation of the Firefox services. + +.. toctree:: + :maxdepth: 2 + + settings/index + sync/index diff --git a/services/docs/moz.build b/services/docs/moz.build new file mode 100644 index 0000000000..8eba15268d --- /dev/null +++ b/services/docs/moz.build @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Sync") diff --git a/services/fxaccounts/Credentials.sys.mjs b/services/fxaccounts/Credentials.sys.mjs new file mode 100644 index 0000000000..30c88fafdc --- /dev/null +++ b/services/fxaccounts/Credentials.sys.mjs @@ -0,0 +1,134 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This module implements client-side key stretching for use in Firefox + * Accounts account creation and login. + * + * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol + */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/"; +const PBKDF2_ROUNDS = 1000; +const STRETCHED_PW_LENGTH_BYTES = 32; +const HKDF_SALT = CommonUtils.hexToBytes("00"); +const HKDF_LENGTH = 32; + +// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO", +// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by +// default. +const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; +let LOG_LEVEL = Log.Level.Error; +try { + LOG_LEVEL = + Services.prefs.getPrefType(PREF_LOG_LEVEL) == + Ci.nsIPrefBranch.PREF_STRING && + Services.prefs.getStringPref(PREF_LOG_LEVEL); +} catch (e) {} + +var log = Log.repository.getLogger("Identity.FxAccounts"); +log.level = LOG_LEVEL; +log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); + +export var Credentials = Object.freeze({ + /** + * Make constants accessible to tests + */ + constants: { + PROTOCOL_VERSION, + PBKDF2_ROUNDS, + STRETCHED_PW_LENGTH_BYTES, + HKDF_SALT, + HKDF_LENGTH, + }, + + /** + * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol + * + * keyWord derivation for use as a salt. + * + * + * @param {String} context String for use in generating salt + * + * @return {bitArray} the salt + * + * Note that PROTOCOL_VERSION does not refer in any way to the version of the + * Firefox Accounts API. + */ + keyWord(context) { + return CommonUtils.stringToBytes(PROTOCOL_VERSION + context); + }, + + /** + * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol + * + * keyWord extended with a name and an email. + * + * @param {String} name The name of the salt + * @param {String} email The email of the user. + * + * @return {bitArray} the salt combination with the namespace + * + * Note that PROTOCOL_VERSION does not refer in any way to the version of the + * Firefox Accounts API. + */ + keyWordExtended(name, email) { + return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ":" + email); + }, + + setup(emailInput, passwordInput, options = {}) { + return new Promise(resolve => { + log.debug("setup credentials for " + emailInput); + + let hkdfSalt = options.hkdfSalt || HKDF_SALT; + let hkdfLength = options.hkdfLength || HKDF_LENGTH; + let stretchedPWLength = + options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES; + let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS; + + let result = {}; + + let password = CommonUtils.encodeUTF8(passwordInput); + let salt = this.keyWordExtended("quickStretch", emailInput); + + let runnable = async () => { + let start = Date.now(); + let quickStretchedPW = await CryptoUtils.pbkdf2Generate( + password, + salt, + pbkdf2Rounds, + stretchedPWLength + ); + + result.quickStretchedPW = quickStretchedPW; + + result.authPW = await CryptoUtils.hkdfLegacy( + quickStretchedPW, + hkdfSalt, + this.keyWord("authPW"), + hkdfLength + ); + + result.unwrapBKey = await CryptoUtils.hkdfLegacy( + quickStretchedPW, + hkdfSalt, + this.keyWord("unwrapBkey"), + hkdfLength + ); + + log.debug("Credentials set up after " + (Date.now() - start) + " ms"); + resolve(result); + }; + + Services.tm.dispatchToMainThread(runnable); + log.debug("Dispatched thread for credentials setup crypto work"); + }); + }, +}); diff --git a/services/fxaccounts/FxAccounts.sys.mjs b/services/fxaccounts/FxAccounts.sys.mjs new file mode 100644 index 0000000000..18169c6b2d --- /dev/null +++ b/services/fxaccounts/FxAccounts.sys.mjs @@ -0,0 +1,1657 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +import { FxAccountsStorageManager } from "resource://gre/modules/FxAccountsStorage.sys.mjs"; + +import { + ERRNO_INVALID_AUTH_TOKEN, + ERROR_AUTH_ERROR, + ERROR_INVALID_PARAMETER, + ERROR_NO_ACCOUNT, + ERROR_TO_GENERAL_ERROR_CLASS, + ERROR_UNKNOWN, + ERROR_UNVERIFIED_ACCOUNT, + FXA_PWDMGR_PLAINTEXT_FIELDS, + FXA_PWDMGR_REAUTH_ALLOWLIST, + FXA_PWDMGR_SECURE_FIELDS, + FX_OAUTH_CLIENT_ID, + ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, + ONLOGIN_NOTIFICATION, + ONLOGOUT_NOTIFICATION, + ON_PRELOGOUT_NOTIFICATION, + ONVERIFIED_NOTIFICATION, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + POLL_SESSION, + PREF_ACCOUNT_ROOT, + PREF_LAST_FXA_USER, + SERVER_ERRNO_TO_ERROR, + log, + logPII, + logManager, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FxAccountsClient: "resource://gre/modules/FxAccountsClient.sys.mjs", + FxAccountsCommands: "resource://gre/modules/FxAccountsCommands.sys.mjs", + FxAccountsConfig: "resource://gre/modules/FxAccountsConfig.sys.mjs", + FxAccountsDevice: "resource://gre/modules/FxAccountsDevice.sys.mjs", + FxAccountsKeys: "resource://gre/modules/FxAccountsKeys.sys.mjs", + FxAccountsOAuth: "resource://gre/modules/FxAccountsOAuth.sys.mjs", + FxAccountsProfile: "resource://gre/modules/FxAccountsProfile.sys.mjs", + FxAccountsTelemetry: "resource://gre/modules/FxAccountsTelemetry.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "mpLocked", () => { + return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs") + .Utils.mpLocked; +}); + +ChromeUtils.defineLazyGetter(lazy, "ensureMPUnlocked", () => { + return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs") + .Utils.ensureMPUnlocked; +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "FXA_ENABLED", + "identity.fxaccounts.enabled", + true +); + +// An AccountState object holds all state related to one specific account. +// It is considered "private" to the FxAccounts modules. +// Only one AccountState is ever "current" in the FxAccountsInternal object - +// whenever a user logs out or logs in, the current AccountState is discarded, +// making it impossible for the wrong state or state data to be accidentally +// used. +// In addition, it has some promise-related helpers to ensure that if an +// attempt is made to resolve a promise on a "stale" state (eg, if an +// operation starts, but a different user logs in before the operation +// completes), the promise will be rejected. +// It is intended to be used thusly: +// somePromiseBasedFunction: function() { +// let currentState = this.currentAccountState; +// return someOtherPromiseFunction().then( +// data => currentState.resolve(data) +// ); +// } +// If the state has changed between the function being called and the promise +// being resolved, the .resolve() call will actually be rejected. +export function AccountState(storageManager) { + this.storageManager = storageManager; + this.inFlightTokenRequests = new Map(); + this.promiseInitialized = this.storageManager + .getAccountData() + .then(data => { + this.oauthTokens = data && data.oauthTokens ? data.oauthTokens : {}; + }) + .catch(err => { + log.error("Failed to initialize the storage manager", err); + // Things are going to fall apart, but not much we can do about it here. + }); +} + +AccountState.prototype = { + oauthTokens: null, + whenVerifiedDeferred: null, + whenKeysReadyDeferred: null, + + // If the storage manager has been nuked then we are no longer current. + get isCurrent() { + return this.storageManager != null; + }, + + abort() { + if (this.whenVerifiedDeferred) { + this.whenVerifiedDeferred.reject( + new Error("Verification aborted; Another user signing in") + ); + this.whenVerifiedDeferred = null; + } + if (this.whenKeysReadyDeferred) { + this.whenKeysReadyDeferred.reject( + new Error("Key fetching aborted; Another user signing in") + ); + this.whenKeysReadyDeferred = null; + } + this.inFlightTokenRequests.clear(); + return this.signOut(); + }, + + // Clobber all cached data and write that empty data to storage. + async signOut() { + this.cert = null; + this.keyPair = null; + this.oauthTokens = null; + this.inFlightTokenRequests.clear(); + + // Avoid finalizing the storageManager multiple times (ie, .signOut() + // followed by .abort()) + if (!this.storageManager) { + return; + } + const storageManager = this.storageManager; + this.storageManager = null; + + await storageManager.deleteAccountData(); + await storageManager.finalize(); + }, + + // Get user account data. Optionally specify explicit field names to fetch + // (and note that if you require an in-memory field you *must* specify the + // field name(s).) + getUserAccountData(fieldNames = null) { + if (!this.isCurrent) { + return Promise.reject(new Error("Another user has signed in")); + } + return this.storageManager.getAccountData(fieldNames).then(result => { + return this.resolve(result); + }); + }, + + async updateUserAccountData(updatedFields) { + if ("uid" in updatedFields) { + const existing = await this.getUserAccountData(["uid"]); + if (existing.uid != updatedFields.uid) { + throw new Error( + "The specified credentials aren't for the current user" + ); + } + // We need to nuke uid as storage will complain if we try and + // update it (even when the value is the same) + updatedFields = Cu.cloneInto(updatedFields, {}); // clone it first + delete updatedFields.uid; + } + if (!this.isCurrent) { + return Promise.reject(new Error("Another user has signed in")); + } + return this.storageManager.updateAccountData(updatedFields); + }, + + resolve(result) { + if (!this.isCurrent) { + log.info( + "An accountState promise was resolved, but was actually rejected" + + " due to a different user being signed in. Originally resolved" + + " with", + result + ); + return Promise.reject(new Error("A different user signed in")); + } + return Promise.resolve(result); + }, + + reject(error) { + // It could be argued that we should just let it reject with the original + // error - but this runs the risk of the error being (eg) a 401, which + // might cause the consumer to attempt some remediation and cause other + // problems. + if (!this.isCurrent) { + log.info( + "An accountState promise was rejected, but we are ignoring that " + + "reason and rejecting it due to a different user being signed in. " + + "Originally rejected with", + error + ); + return Promise.reject(new Error("A different user signed in")); + } + return Promise.reject(error); + }, + + // Abstractions for storage of cached tokens - these are all sync, and don't + // handle revocation etc - it's just storage (and the storage itself is async, + // but we don't return the storage promises, so it *looks* sync) + // These functions are sync simply so we can handle "token races" - when there + // are multiple in-flight requests for the same scope, we can detect this + // and revoke the redundant token. + + // A preamble for the cache helpers... + _cachePreamble() { + if (!this.isCurrent) { + throw new Error("Another user has signed in"); + } + }, + + // Set a cached token. |tokenData| must have a 'token' element, but may also + // have additional fields. + // The 'get' functions below return the entire |tokenData| value. + setCachedToken(scopeArray, tokenData) { + this._cachePreamble(); + if (!tokenData.token) { + throw new Error("No token"); + } + let key = getScopeKey(scopeArray); + this.oauthTokens[key] = tokenData; + // And a background save... + this._persistCachedTokens(); + }, + + // Return data for a cached token or null (or throws on bad state etc) + getCachedToken(scopeArray) { + this._cachePreamble(); + let key = getScopeKey(scopeArray); + let result = this.oauthTokens[key]; + if (result) { + // later we might want to check an expiry date - but we currently + // have no such concept, so just return it. + log.trace("getCachedToken returning cached token"); + return result; + } + return null; + }, + + // Remove a cached token from the cache. Does *not* revoke it from anywhere. + // Returns the entire token entry if found, null otherwise. + removeCachedToken(token) { + this._cachePreamble(); + let data = this.oauthTokens; + for (let [key, tokenValue] of Object.entries(data)) { + if (tokenValue.token == token) { + delete data[key]; + // And a background save... + this._persistCachedTokens(); + return tokenValue; + } + } + return null; + }, + + // A hook-point for tests. Returns a promise that's ignored in most cases + // (notable exceptions are tests and when we explicitly are saving the entire + // set of user data.) + _persistCachedTokens() { + this._cachePreamble(); + return this.updateUserAccountData({ oauthTokens: this.oauthTokens }).catch( + err => { + log.error("Failed to update cached tokens", err); + } + ); + }, +}; + +/* Given an array of scopes, make a string key by normalizing. */ +function getScopeKey(scopeArray) { + let normalizedScopes = scopeArray.map(item => item.toLowerCase()); + return normalizedScopes.sort().join("|"); +} + +function getPropertyDescriptor(obj, prop) { + return ( + Object.getOwnPropertyDescriptor(obj, prop) || + getPropertyDescriptor(Object.getPrototypeOf(obj), prop) + ); +} + +/** + * Copies properties from a given object to another object. + * + * @param from (object) + * The object we read property descriptors from. + * @param to (object) + * The object that we set property descriptors on. + * @param thisObj (object) + * The object that will be used to .bind() all function properties we find to. + * @param keys ([...]) + * The names of all properties to be copied. + */ +function copyObjectProperties(from, to, thisObj, keys) { + for (let prop of keys) { + // Look for the prop in the prototype chain. + let desc = getPropertyDescriptor(from, prop); + + if (typeof desc.value == "function") { + desc.value = desc.value.bind(thisObj); + } + + if (desc.get) { + desc.get = desc.get.bind(thisObj); + } + + if (desc.set) { + desc.set = desc.set.bind(thisObj); + } + + Object.defineProperty(to, prop, desc); + } +} + +/** + * The public API. + * + * TODO - *all* non-underscore stuff here should have sphinx docstrings so + * that docs magically appear on https://firefox-source-docs.mozilla.org/ + * (although |./mach doc| is broken on windows (bug 1232403) and on Linux for + * markh (some obscure npm issue he gave up on) - so later...) + */ +export class FxAccounts { + constructor(mocks = null) { + this._internal = new FxAccountsInternal(); + if (mocks) { + // it's slightly unfortunate that we need to mock the main "internal" object + // before calling initialize, primarily so a mock `newAccountState` is in + // place before initialize calls it, but we need to initialize the + // "sub-object" mocks after. This can probably be fixed, but whatever... + copyObjectProperties( + mocks, + this._internal, + this._internal, + Object.keys(mocks).filter(key => !["device", "commands"].includes(key)) + ); + } + this._internal.initialize(); + // allow mocking our "sub-objects" too. + if (mocks) { + for (let subobject of [ + "currentAccountState", + "keys", + "fxaPushService", + "device", + "commands", + ]) { + if (typeof mocks[subobject] == "object") { + copyObjectProperties( + mocks[subobject], + this._internal[subobject], + this._internal[subobject], + Object.keys(mocks[subobject]) + ); + } + } + } + } + + get commands() { + return this._internal.commands; + } + + static get config() { + return lazy.FxAccountsConfig; + } + + get device() { + return this._internal.device; + } + + get keys() { + return this._internal.keys; + } + + get telemetry() { + return this._internal.telemetry; + } + + _withCurrentAccountState(func) { + return this._internal.withCurrentAccountState(func); + } + + _withVerifiedAccountState(func) { + return this._internal.withVerifiedAccountState(func); + } + + _withSessionToken(func, mustBeVerified = true) { + return this._internal.withSessionToken(func, mustBeVerified); + } + + /** + * Returns an array listing all the OAuth clients connected to the + * authenticated user's account. This includes browsers and web sessions - no + * filtering is done of the set returned by the FxA server. + * + * @typedef {Object} AttachedClient + * @property {String} id - OAuth `client_id` of the client. + * @property {Number} lastAccessedDaysAgo - How many days ago the client last + * accessed the FxA server APIs. + * + * @returns {Array.<AttachedClient>} A list of attached clients. + */ + async listAttachedOAuthClients() { + // We expose last accessed times in 'days ago' + const ONE_DAY = 24 * 60 * 60 * 1000; + + return this._withSessionToken(async sessionToken => { + const response = await this._internal.fxAccountsClient.attachedClients( + sessionToken + ); + const attachedClients = response.body; + const timestamp = response.headers["x-timestamp"]; + const now = + timestamp !== undefined + ? new Date(parseInt(timestamp, 10)) + : Date.now(); + return attachedClients.map(client => { + const daysAgo = client.lastAccessTime + ? Math.max(Math.floor((now - client.lastAccessTime) / ONE_DAY), 0) + : null; + return { + id: client.clientId, + lastAccessedDaysAgo: daysAgo, + }; + }); + }); + } + + /** + * Get an OAuth token for the user. + * + * @param options + * { + * scope: (string/array) the oauth scope(s) being requested. As a + * convenience, you may pass a string if only one scope is + * required, or an array of strings if multiple are needed. + * ttl: (number) OAuth token TTL in seconds. + * } + * + * @return Promise.<string | Error> + * The promise resolves the oauth token as a string or rejects with + * an error object ({error: ERROR, details: {}}) of the following: + * INVALID_PARAMETER + * NO_ACCOUNT + * UNVERIFIED_ACCOUNT + * NETWORK_ERROR + * AUTH_ERROR + * UNKNOWN_ERROR + */ + async getOAuthToken(options = {}) { + try { + return await this._internal.getOAuthToken(options); + } catch (err) { + throw this._internal._errorToErrorClass(err); + } + } + + /** + * Remove an OAuth token from the token cache. Callers should call this + * after they determine a token is invalid, so a new token will be fetched + * on the next call to getOAuthToken(). + * + * @param options + * { + * token: (string) A previously fetched token. + * } + * @return Promise.<undefined> This function will always resolve, even if + * an unknown token is passed. + */ + removeCachedOAuthToken(options) { + return this._internal.removeCachedOAuthToken(options); + } + + /** + * Get details about the user currently signed in to Firefox Accounts. + * + * @return Promise + * The promise resolves to the credentials object of the signed-in user: + * { + * email: String: The user's email address + * uid: String: The user's unique id + * verified: Boolean: email verification status + * displayName: String or null if not known. + * avatar: URL of the avatar for the user. May be the default + * avatar, or null in edge-cases (eg, if there's an account + * issue, etc + * avatarDefault: boolean - whether `avatar` is specific to the user + * or the default avatar. + * } + * + * or null if no user is signed in. This function never fails except + * in pathological cases (eg, file-system errors, etc) + */ + getSignedInUser() { + // Note we don't return the session token, but use it to see if we + // should fetch the profile. + const ACCT_DATA_FIELDS = ["email", "uid", "verified", "sessionToken"]; + const PROFILE_FIELDS = ["displayName", "avatar", "avatarDefault"]; + return this._withCurrentAccountState(async currentState => { + const data = await currentState.getUserAccountData(ACCT_DATA_FIELDS); + if (!data) { + return null; + } + if (!lazy.FXA_ENABLED) { + await this.signOut(); + return null; + } + if (!this._internal.isUserEmailVerified(data)) { + // If the email is not verified, start polling for verification, + // but return null right away. We don't want to return a promise + // that might not be fulfilled for a long time. + this._internal.startVerifiedCheck(data); + } + + let profileData = null; + if (data.sessionToken) { + delete data.sessionToken; + try { + profileData = await this._internal.profile.getProfile(); + } catch (error) { + log.error("Could not retrieve profile data", error); + } + } + for (let field of PROFILE_FIELDS) { + data[field] = profileData ? profileData[field] : null; + } + // and email is a special case - if we have profile data we prefer the + // email from that, as the email we stored for the account itself might + // not have been updated if the email changed since the user signed in. + if (profileData && profileData.email) { + data.email = profileData.email; + } + return data; + }); + } + + /** + * Checks the status of the account. Resolves with Promise<boolean>, where + * true indicates the account status is OK and false indicates there's some + * issue with the account - either that there's no user currently signed in, + * the entire account has been deleted (in which case there will be no user + * signed in after this call returns), or that the user must reauthenticate (in + * which case `this.hasLocalSession()` will return `false` after this call + * returns). + * + * Typically used when some external code which uses, for example, oauth tokens + * received a 401 error using the token, or that this external code has some + * other reason to believe the account status may be bad. Note that this will + * be called automatically in many cases - for example, if calls to fetch the + * profile, or fetch keys, etc return a 401, there's no need to call this + * function. + * + * Because this hits the server, you should only call this method when you have + * good reason to believe the session very recently became invalid (eg, because + * you saw an auth related exception from a remote service.) + */ + checkAccountStatus() { + // Note that we don't use _withCurrentAccountState here because that will + // cause an exception to be thrown if we end up signing out due to the + // account not existing, which isn't what we want here. + let state = this._internal.currentAccountState; + return this._internal.checkAccountStatus(state); + } + + /** + * Checks if we have a valid local session state for the current account. + * + * @return Promise + * Resolves with a boolean, with true indicating that we appear to + * have a valid local session, or false if we need to reauthenticate + * with the content server to obtain one. + * Note that this only checks local state, although typically that's + * OK, because we drop the local session information whenever we detect + * we are in this state. However, see checkAccountStatus() for a way to + * check the account and session status with the server, which can be + * considered the canonical, albiet expensive, way to determine the + * status of the account. + */ + hasLocalSession() { + return this._withCurrentAccountState(async state => { + let data = await state.getUserAccountData(["sessionToken"]); + return !!(data && data.sessionToken); + }); + } + + /** Returns a promise that resolves to true if we can currently connect (ie, + * sign in, or re-connect after a password change) to a Firefox Account. + * If this returns false, the caller can assume that some UI was shown + * which tells the user why we could not connect. + * + * Currently, the primary password being locked is the only reason why + * this returns false, and in this scenario, the primary password unlock + * dialog will have been shown. + * + * This currently doesn't need to return a promise, but does so that + * future enhancements, such as other explanatory UI which requires + * async can work without modification of the call-sites. + */ + static canConnectAccount() { + return Promise.resolve(!lazy.mpLocked() || lazy.ensureMPUnlocked()); + } + + /** + * Send a message to a set of devices in the same account + * + * @param deviceIds: (null/string/array) The device IDs to send the message to. + * If null, will be sent to all devices. + * + * @param excludedIds: (null/string/array) If deviceIds is null, this may + * list device IDs which should not receive the message. + * + * @param payload: (object) The payload, which will be JSON.stringified. + * + * @param TTL: How long the message should be retained before it is discarded. + */ + // XXX - used only by sync to tell other devices that the clients collection + // has changed so they should sync asap. The API here is somewhat vague (ie, + // "an object"), but to be useful across devices, the payload really needs + // formalizing. We should try and do something better here. + notifyDevices(deviceIds, excludedIds, payload, TTL) { + return this._internal.notifyDevices(deviceIds, excludedIds, payload, TTL); + } + + /** + * Resend the verification email for the currently signed-in user. + * + */ + resendVerificationEmail() { + return this._withSessionToken((token, currentState) => { + this._internal.startPollEmailStatus(currentState, token, "start"); + return this._internal.fxAccountsClient.resendVerificationEmail(token); + }, false); + } + + async signOut(localOnly) { + // Note that we do not use _withCurrentAccountState here, otherwise we + // end up with an exception due to the user signing out before the call is + // complete - but that's the entire point of this method :) + return this._internal.signOut(localOnly); + } + + // XXX - we should consider killing this - the only reason it is public is + // so that sync can change it when it notices the device name being changed, + // and that could probably be replaced with a pref observer. + updateDeviceRegistration() { + return this._withCurrentAccountState(_ => { + return this._internal.updateDeviceRegistration(); + }); + } + + // we should try and kill this too. + whenVerified(data) { + return this._withCurrentAccountState(_ => { + return this._internal.whenVerified(data); + }); + } + + /** + * Generate a log file for the FxA action that just completed + * and refresh the input & output streams. + */ + async flushLogFile() { + const logType = await logManager.resetFileLog(); + if (logType == logManager.ERROR_LOG_WRITTEN) { + console.error( + "FxA encountered an error - see about:sync-log for the log file." + ); + } + Services.obs.notifyObservers(null, "service:log-manager:flush-log-file"); + } +} + +var FxAccountsInternal = function () {}; + +/** + * The internal API's prototype. + */ +FxAccountsInternal.prototype = { + // Make a local copy of this constant so we can mock it in testing + POLL_SESSION, + + // The timeout (in ms) we use to poll for a verified mail for the first + // VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD minutes if the user has + // logged-in in this session. + VERIFICATION_POLL_TIMEOUT_INITIAL: 60000, // 1 minute. + // All the other cases (> 5 min, on restart etc). + VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 5 * 60000, // 5 minutes. + // After X minutes, the polling will slow down to _SUBSEQUENT if we have + // logged-in in this session. + VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD: 5, + + _fxAccountsClient: null, + + // All significant initialization should be done in this initialize() method + // to help with our mocking story. + initialize() { + ChromeUtils.defineLazyGetter(this, "fxaPushService", function () { + return Cc["@mozilla.org/fxaccounts/push;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + }); + + this.keys = new lazy.FxAccountsKeys(this); + + if (!this.observerPreloads) { + // A registry of promise-returning functions that `notifyObservers` should + // call before sending notifications. Primarily used so parts of Firefox + // which have yet to load for performance reasons can be force-loaded, and + // thus not miss notifications. + this.observerPreloads = [ + // Sync + () => { + let { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" + ); + return Weave.Service.promiseInitialized; + }, + ]; + } + + this.currentTimer = null; + // This object holds details about, and storage for, the current user. It + // is replaced when a different user signs in. Instead of using it directly, + // you should try and use `withCurrentAccountState`. + this.currentAccountState = this.newAccountState(); + }, + + async withCurrentAccountState(func) { + const state = this.currentAccountState; + let result; + try { + result = await func(state); + } catch (ex) { + return state.reject(ex); + } + return state.resolve(result); + }, + + async withVerifiedAccountState(func) { + return this.withCurrentAccountState(async state => { + let data = await state.getUserAccountData(); + if (!data) { + // No signed-in user + throw this._error(ERROR_NO_ACCOUNT); + } + + if (!this.isUserEmailVerified(data)) { + // Signed-in user has not verified email + throw this._error(ERROR_UNVERIFIED_ACCOUNT); + } + return func(state); + }); + }, + + async withSessionToken(func, mustBeVerified = true) { + const state = this.currentAccountState; + let data = await state.getUserAccountData(); + if (!data) { + // No signed-in user + throw this._error(ERROR_NO_ACCOUNT); + } + + if (mustBeVerified && !this.isUserEmailVerified(data)) { + // Signed-in user has not verified email + throw this._error(ERROR_UNVERIFIED_ACCOUNT); + } + + if (!data.sessionToken) { + throw this._error(ERROR_AUTH_ERROR, "no session token"); + } + try { + // Anyone who needs the session token is going to send it to the server, + // so there's a chance we'll see an auth related error - so handle that + // here rather than requiring each caller to remember to. + let result = await func(data.sessionToken, state); + return state.resolve(result); + } catch (err) { + return this._handleTokenError(err); + } + }, + + get fxAccountsClient() { + if (!this._fxAccountsClient) { + this._fxAccountsClient = new lazy.FxAccountsClient(); + } + return this._fxAccountsClient; + }, + + // The profile object used to fetch the actual user profile. + _profile: null, + get profile() { + if (!this._profile) { + let profileServerUrl = Services.urlFormatter.formatURLPref( + "identity.fxaccounts.remote.profile.uri" + ); + this._profile = new lazy.FxAccountsProfile({ + fxa: this, + profileServerUrl, + }); + } + return this._profile; + }, + + _commands: null, + get commands() { + if (!this._commands) { + this._commands = new lazy.FxAccountsCommands(this); + } + return this._commands; + }, + + _device: null, + get device() { + if (!this._device) { + this._device = new lazy.FxAccountsDevice(this); + } + return this._device; + }, + + _oauth: null, + get oauth() { + if (!this._oauth) { + this._oauth = new lazy.FxAccountsOAuth(this.fxAccountsClient); + } + return this._oauth; + }, + + _telemetry: null, + get telemetry() { + if (!this._telemetry) { + this._telemetry = new lazy.FxAccountsTelemetry(this); + } + return this._telemetry; + }, + + beginOAuthFlow(scopes) { + return this.oauth.beginOAuthFlow(scopes); + }, + + completeOAuthFlow(sessionToken, code, state) { + return this.oauth.completeOAuthFlow(sessionToken, code, state); + }, + + setScopedKeys(scopedKeys) { + return this.keys.setScopedKeys(scopedKeys); + }, + + // A hook-point for tests who may want a mocked AccountState or mocked storage. + newAccountState(credentials) { + let storage = new FxAccountsStorageManager(); + storage.initialize(credentials); + return new AccountState(storage); + }, + + notifyDevices(deviceIds, excludedIds, payload, TTL) { + if (typeof deviceIds == "string") { + deviceIds = [deviceIds]; + } + return this.withSessionToken(sessionToken => { + return this.fxAccountsClient.notifyDevices( + sessionToken, + deviceIds, + excludedIds, + payload, + TTL + ); + }); + }, + + /** + * Return the current time in milliseconds as an integer. Allows tests to + * manipulate the date to simulate token expiration. + */ + now() { + return this.fxAccountsClient.now(); + }, + + /** + * Return clock offset in milliseconds, as reported by the fxAccountsClient. + * This can be overridden for testing. + * + * The offset is the number of milliseconds that must be added to the client + * clock to make it equal to the server clock. For example, if the client is + * five minutes ahead of the server, the localtimeOffsetMsec will be -300000. + */ + get localtimeOffsetMsec() { + return this.fxAccountsClient.localtimeOffsetMsec; + }, + + /** + * Ask the server whether the user's email has been verified + */ + checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) { + if (!sessionToken) { + return Promise.reject( + new Error("checkEmailStatus called without a session token") + ); + } + return this.fxAccountsClient + .recoveryEmailStatus(sessionToken, options) + .catch(error => this._handleTokenError(error)); + }, + + // set() makes sure that polling is happening, if necessary. + // get() does not wait for verification, and returns an object even if + // unverified. The caller of get() must check .verified . + // The "fxaccounts:onverified" event will fire only when the verified + // state goes from false to true, so callers must register their observer + // and then call get(). In particular, it will not fire when the account + // was found to be verified in a previous boot: if our stored state says + // the account is verified, the event will never fire. So callers must do: + // register notification observer (go) + // userdata = get() + // if (userdata.verified()) {go()} + + /** + * Set the current user signed in to Firefox Accounts. + * + * @param credentials + * The credentials object obtained by logging in or creating + * an account on the FxA server: + * { + * authAt: The time (seconds since epoch) that this record was + * authenticated + * email: The users email address + * keyFetchToken: a keyFetchToken which has not yet been used + * sessionToken: Session for the FxA server + * uid: The user's unique id + * unwrapBKey: used to unwrap kB, derived locally from the + * password (not revealed to the FxA server) + * verified: true/false + * } + * @return Promise + * The promise resolves to null when the data is saved + * successfully and is rejected on error. + */ + async setSignedInUser(credentials) { + if (!lazy.FXA_ENABLED) { + throw new Error("Cannot call setSignedInUser when FxA is disabled."); + } + for (const pref of Services.prefs.getChildList(PREF_ACCOUNT_ROOT)) { + Services.prefs.clearUserPref(pref); + } + log.debug("setSignedInUser - aborting any existing flows"); + const signedInUser = await this.currentAccountState.getUserAccountData(); + if (signedInUser) { + await this._signOutServer( + signedInUser.sessionToken, + signedInUser.oauthTokens + ); + } + await this.abortExistingFlow(); + let currentAccountState = (this.currentAccountState = this.newAccountState( + Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object. + )); + // This promise waits for storage, but not for verification. + // We're telling the caller that this is durable now (although is that + // really something we should commit to? Why not let the write happen in + // the background? Already does for updateAccountData ;) + await currentAccountState.promiseInitialized; + // Starting point for polling if new user + if (!this.isUserEmailVerified(credentials)) { + this.startVerifiedCheck(credentials); + } + await this.notifyObservers(ONLOGIN_NOTIFICATION); + await this.updateDeviceRegistration(); + return currentAccountState.resolve(); + }, + + /** + * Update account data for the currently signed in user. + * + * @param credentials + * The credentials object containing the fields to be updated. + * This object must contain the |uid| field and it must + * match the currently signed in user. + */ + updateUserAccountData(credentials) { + log.debug( + "updateUserAccountData called with fields", + Object.keys(credentials) + ); + if (logPII()) { + log.debug("updateUserAccountData called with data", credentials); + } + let currentAccountState = this.currentAccountState; + return currentAccountState.promiseInitialized.then(() => { + if (!credentials.uid) { + throw new Error("The specified credentials have no uid"); + } + return currentAccountState.updateUserAccountData(credentials); + }); + }, + + /* + * Reset state such that any previous flow is canceled. + */ + abortExistingFlow() { + if (this.currentTimer) { + log.debug("Polling aborted; Another user signing in"); + clearTimeout(this.currentTimer); + this.currentTimer = 0; + } + if (this._profile) { + this._profile.tearDown(); + this._profile = null; + } + if (this._commands) { + this._commands = null; + } + if (this._device) { + this._device.reset(); + } + // We "abort" the accountState and assume our caller is about to throw it + // away and replace it with a new one. + return this.currentAccountState.abort(); + }, + + async checkVerificationStatus() { + log.trace("checkVerificationStatus"); + let state = this.currentAccountState; + let data = await state.getUserAccountData(); + if (!data) { + log.trace("checkVerificationStatus - no user data"); + return null; + } + + // Always check the verification status, even if the local state indicates + // we're already verified. If the user changed their password, the check + // will fail, and we'll enter the reauth state. + log.trace("checkVerificationStatus - forcing verification status check"); + return this.startPollEmailStatus(state, data.sessionToken, "push"); + }, + + /** Destroyes an OAuth Token by sending a request to the FxA server + * @param { Object } tokenData: The token's data, with `tokenData.token` being the token itself + **/ + destroyOAuthToken(tokenData) { + return this.fxAccountsClient.oauthDestroy( + FX_OAUTH_CLIENT_ID, + tokenData.token + ); + }, + + _destroyAllOAuthTokens(tokenInfos) { + if (!tokenInfos) { + return Promise.resolve(); + } + // let's just destroy them all in parallel... + let promises = []; + for (let tokenInfo of Object.values(tokenInfos)) { + promises.push(this.destroyOAuthToken(tokenInfo)); + } + return Promise.all(promises); + }, + + async signOut(localOnly) { + let sessionToken; + let tokensToRevoke; + const data = await this.currentAccountState.getUserAccountData(); + // Save the sessionToken, tokens before resetting them in _signOutLocal(). + if (data) { + sessionToken = data.sessionToken; + tokensToRevoke = data.oauthTokens; + } + await this.notifyObservers(ON_PRELOGOUT_NOTIFICATION); + await this._signOutLocal(); + if (!localOnly) { + // Do this in the background so *any* slow request won't + // block the local sign out. + Services.tm.dispatchToMainThread(async () => { + await this._signOutServer(sessionToken, tokensToRevoke); + lazy.FxAccountsConfig.resetConfigURLs(); + this.notifyObservers("testhelper-fxa-signout-complete"); + }); + } else { + // We want to do this either way -- but if we're signing out remotely we + // need to wait until we destroy the oauth tokens if we want that to succeed. + lazy.FxAccountsConfig.resetConfigURLs(); + } + return this.notifyObservers(ONLOGOUT_NOTIFICATION); + }, + + async _signOutLocal() { + for (const pref of Services.prefs.getChildList(PREF_ACCOUNT_ROOT)) { + Services.prefs.clearUserPref(pref); + } + await this.currentAccountState.signOut(); + // this "aborts" this.currentAccountState but doesn't make a new one. + await this.abortExistingFlow(); + this.currentAccountState = this.newAccountState(); + return this.currentAccountState.promiseInitialized; + }, + + async _signOutServer(sessionToken, tokensToRevoke) { + log.debug("Unsubscribing from FxA push."); + try { + await this.fxaPushService.unsubscribe(); + } catch (err) { + log.error("Could not unsubscribe from push.", err); + } + if (sessionToken) { + log.debug("Destroying session and device."); + try { + await this.fxAccountsClient.signOut(sessionToken, { service: "sync" }); + } catch (err) { + log.error("Error during remote sign out of Firefox Accounts", err); + } + } else { + log.warn("Missing session token; skipping remote sign out"); + } + log.debug("Destroying all OAuth tokens."); + try { + await this._destroyAllOAuthTokens(tokensToRevoke); + } catch (err) { + log.error("Error during destruction of oauth tokens during signout", err); + } + }, + + getUserAccountData(fieldNames = null) { + return this.currentAccountState.getUserAccountData(fieldNames); + }, + + isUserEmailVerified: function isUserEmailVerified(data) { + return !!(data && data.verified); + }, + + /** + * Setup for and if necessary do email verification polling. + */ + loadAndPoll() { + let currentState = this.currentAccountState; + return currentState.getUserAccountData().then(data => { + if (data) { + if (!this.isUserEmailVerified(data)) { + this.startPollEmailStatus( + currentState, + data.sessionToken, + "browser-startup" + ); + } + } + return data; + }); + }, + + startVerifiedCheck(data) { + log.debug("startVerifiedCheck", data && data.verified); + if (logPII()) { + log.debug("startVerifiedCheck with user data", data); + } + + // Get us to the verified state. This returns a promise that will fire when + // verification is complete. + + // The callers of startVerifiedCheck never consume a returned promise (ie, + // this is simply kicking off a background fetch) so we must add a rejection + // handler to avoid runtime warnings about the rejection not being handled. + this.whenVerified(data).catch(err => + log.info("startVerifiedCheck promise was rejected: " + err) + ); + }, + + whenVerified(data) { + let currentState = this.currentAccountState; + if (data.verified) { + log.debug("already verified"); + return currentState.resolve(data); + } + if (!currentState.whenVerifiedDeferred) { + log.debug("whenVerified promise starts polling for verified email"); + this.startPollEmailStatus(currentState, data.sessionToken, "start"); + } + return currentState.whenVerifiedDeferred.promise.then(result => + currentState.resolve(result) + ); + }, + + async notifyObservers(topic, data) { + for (let f of this.observerPreloads) { + try { + await f(); + } catch (O_o) {} + } + log.debug("Notifying observers of " + topic); + Services.obs.notifyObservers(null, topic, data); + }, + + startPollEmailStatus(currentState, sessionToken, why) { + log.debug("entering startPollEmailStatus: " + why); + // If we were already polling, stop and start again. This could happen + // if the user requested the verification email to be resent while we + // were already polling for receipt of an earlier email. + if (this.currentTimer) { + log.debug( + "startPollEmailStatus starting while existing timer is running" + ); + clearTimeout(this.currentTimer); + this.currentTimer = null; + } + + this.pollStartDate = Date.now(); + if (!currentState.whenVerifiedDeferred) { + currentState.whenVerifiedDeferred = Promise.withResolvers(); + // This deferred might not end up with any handlers (eg, if sync + // is yet to start up.) This might cause "A promise chain failed to + // handle a rejection" messages, so add an error handler directly + // on the promise to log the error. + currentState.whenVerifiedDeferred.promise.then( + () => { + log.info("the user became verified"); + // We are now ready for business. This should only be invoked once + // per setSignedInUser(), regardless of whether we've rebooted since + // setSignedInUser() was called. + this.notifyObservers(ONVERIFIED_NOTIFICATION); + }, + err => { + log.info("the wait for user verification was stopped: " + err); + } + ); + } + return this.pollEmailStatus(currentState, sessionToken, why); + }, + + // We return a promise for testing only. Other callers can ignore this, + // since verification polling continues in the background. + async pollEmailStatus(currentState, sessionToken, why) { + log.debug("entering pollEmailStatus: " + why); + let nextPollMs; + try { + const response = await this.checkEmailStatus(sessionToken, { + reason: why, + }); + log.debug("checkEmailStatus -> " + JSON.stringify(response)); + if (response && response.verified) { + await this.onPollEmailSuccess(currentState); + return; + } + } catch (error) { + if (error && error.code && error.code == 401) { + let error = new Error("Verification status check failed"); + this._rejectWhenVerified(currentState, error); + return; + } + if (error && error.retryAfter) { + // If the server told us to back off, back off the requested amount. + nextPollMs = (error.retryAfter + 3) * 1000; + log.warn( + `the server rejected our email status check and told us to try again in ${nextPollMs}ms` + ); + } else { + log.error(`checkEmailStatus failed to poll`, error); + } + } + if (why == "push") { + return; + } + let pollDuration = Date.now() - this.pollStartDate; + // Polling session expired. + if (pollDuration >= this.POLL_SESSION) { + if (currentState.whenVerifiedDeferred) { + let error = new Error("User email verification timed out."); + this._rejectWhenVerified(currentState, error); + } + log.debug("polling session exceeded, giving up"); + return; + } + // Poll email status again after a short delay. + if (nextPollMs === undefined) { + let currentMinute = Math.ceil(pollDuration / 60000); + nextPollMs = + why == "start" && + currentMinute < this.VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD + ? this.VERIFICATION_POLL_TIMEOUT_INITIAL + : this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT; + } + this._scheduleNextPollEmailStatus( + currentState, + sessionToken, + nextPollMs, + why + ); + }, + + // Easy-to-mock testable method + _scheduleNextPollEmailStatus(currentState, sessionToken, nextPollMs, why) { + log.debug("polling with timeout = " + nextPollMs); + this.currentTimer = setTimeout(() => { + this.pollEmailStatus(currentState, sessionToken, why); + }, nextPollMs); + }, + + async onPollEmailSuccess(currentState) { + try { + await currentState.updateUserAccountData({ verified: true }); + const accountData = await currentState.getUserAccountData(); + this._setLastUserPref(accountData.email); + // Now that the user is verified, we can proceed to fetch keys + if (currentState.whenVerifiedDeferred) { + currentState.whenVerifiedDeferred.resolve(accountData); + delete currentState.whenVerifiedDeferred; + } + } catch (e) { + log.error(e); + } + }, + + _rejectWhenVerified(currentState, error) { + currentState.whenVerifiedDeferred.reject(error); + delete currentState.whenVerifiedDeferred; + }, + + /** + * Does the actual fetch of an oauth token for getOAuthToken() + * using the account session token. + * + * It's split out into a separate method so that we can easily + * stash in-flight calls in a cache. + * + * @param {String} scopeString + * @param {Number} ttl + * @returns {Promise<string>} + * @private + */ + async _doTokenFetchWithSessionToken(sessionToken, scopeString, ttl) { + const result = await this.fxAccountsClient.accessTokenWithSessionToken( + sessionToken, + FX_OAUTH_CLIENT_ID, + scopeString, + ttl + ); + return result.access_token; + }, + + getOAuthToken(options = {}) { + log.debug("getOAuthToken enter"); + let scope = options.scope; + if (typeof scope === "string") { + scope = [scope]; + } + + if (!scope || !scope.length) { + return Promise.reject( + this._error( + ERROR_INVALID_PARAMETER, + "Missing or invalid 'scope' option" + ) + ); + } + + return this.withSessionToken(async (sessionToken, currentState) => { + // Early exit for a cached token. + let cached = currentState.getCachedToken(scope); + if (cached) { + log.debug("getOAuthToken returning a cached token"); + return cached.token; + } + + // Build the string we use in our "inflight" map and that we send to the + // server. Because it's used as a key in the map we sort the scopes. + let scopeString = scope.sort().join(" "); + + // We keep a map of in-flight requests to avoid multiple promise-based + // consumers concurrently requesting the same token. + let maybeInFlight = currentState.inFlightTokenRequests.get(scopeString); + if (maybeInFlight) { + log.debug("getOAuthToken has an in-flight request for this scope"); + return maybeInFlight; + } + + // We need to start a new fetch and stick the promise in our in-flight map + // and remove it when it resolves. + let promise = this._doTokenFetchWithSessionToken( + sessionToken, + scopeString, + options.ttl + ) + .then(token => { + // As a sanity check, ensure something else hasn't raced getting a token + // of the same scope. If something has we just make noise rather than + // taking any concrete action because it should never actually happen. + if (currentState.getCachedToken(scope)) { + log.error(`detected a race for oauth token with scope ${scope}`); + } + // If we got one, cache it. + if (token) { + let entry = { token }; + currentState.setCachedToken(scope, entry); + } + return token; + }) + .finally(() => { + // Remove ourself from the in-flight map. There's no need to check the + // result of .delete() to handle a signout race, because setCachedToken + // above will fail in that case and cause the entire call to fail. + currentState.inFlightTokenRequests.delete(scopeString); + }); + + currentState.inFlightTokenRequests.set(scopeString, promise); + return promise; + }); + }, + + /** + * Remove an OAuth token from the token cache + * and makes a network request to FxA server to destroy the token. + * + * @param options + * { + * token: (string) A previously fetched token. + * } + * @return Promise.<undefined> This function will always resolve, even if + * an unknown token is passed. + */ + removeCachedOAuthToken(options) { + if (!options.token || typeof options.token !== "string") { + throw this._error( + ERROR_INVALID_PARAMETER, + "Missing or invalid 'token' option" + ); + } + return this.withCurrentAccountState(currentState => { + let existing = currentState.removeCachedToken(options.token); + if (existing) { + // background destroy. + this.destroyOAuthToken(existing).catch(err => { + log.warn("FxA failed to revoke a cached token", err); + }); + } + }); + }, + + /** Sets the user to be verified in the account state, + * This prevents any polling for the user's verification state from the FxA server + **/ + setUserVerified() { + return this.withCurrentAccountState(async currentState => { + const userData = await currentState.getUserAccountData(); + if (!userData.verified) { + await currentState.updateAccountData({ verified: true }); + } + }); + }, + + async _getVerifiedAccountOrReject() { + let data = await this.currentAccountState.getUserAccountData(); + if (!data) { + // No signed-in user + throw this._error(ERROR_NO_ACCOUNT); + } + if (!this.isUserEmailVerified(data)) { + // Signed-in user has not verified email + throw this._error(ERROR_UNVERIFIED_ACCOUNT); + } + return data; + }, + + // _handle* methods used by push, used when the account/device status is + // changed on a different device. + async _handleAccountDestroyed(uid) { + let state = this.currentAccountState; + const accountData = await state.getUserAccountData(); + const localUid = accountData ? accountData.uid : null; + if (!localUid) { + log.info( + `Account destroyed push notification received, but we're already logged-out` + ); + return null; + } + if (uid == localUid) { + const data = JSON.stringify({ isLocalDevice: true }); + await this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, data); + return this.signOut(true); + } + log.info( + `The destroyed account uid doesn't match with the local uid. ` + + `Local: ${localUid}, account uid destroyed: ${uid}` + ); + return null; + }, + + async _handleDeviceDisconnection(deviceId) { + let state = this.currentAccountState; + const accountData = await state.getUserAccountData(); + if (!accountData || !accountData.device) { + // Nothing we can do here. + return; + } + const localDeviceId = accountData.device.id; + const isLocalDevice = deviceId == localDeviceId; + if (isLocalDevice) { + this.signOut(true); + } + const data = JSON.stringify({ isLocalDevice }); + await this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, data); + }, + + _setLastUserPref(newEmail) { + Services.prefs.setStringPref( + PREF_LAST_FXA_USER, + CryptoUtils.sha256Base64(newEmail) + ); + }, + + async _handleEmailUpdated(newEmail) { + this._setLastUserPref(newEmail); + await this.currentAccountState.updateUserAccountData({ email: newEmail }); + }, + + /* + * Coerce an error into one of the general error cases: + * NETWORK_ERROR + * AUTH_ERROR + * UNKNOWN_ERROR + * + * These errors will pass through: + * INVALID_PARAMETER + * NO_ACCOUNT + * UNVERIFIED_ACCOUNT + */ + _errorToErrorClass(aError) { + if (aError.errno) { + let error = SERVER_ERRNO_TO_ERROR[aError.errno]; + return this._error( + ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN, + aError + ); + } else if ( + aError.message && + (aError.message === "INVALID_PARAMETER" || + aError.message === "NO_ACCOUNT" || + aError.message === "UNVERIFIED_ACCOUNT" || + aError.message === "AUTH_ERROR") + ) { + return aError; + } + return this._error(ERROR_UNKNOWN, aError); + }, + + _error(aError, aDetails) { + log.error("FxA rejecting with error ${aError}, details: ${aDetails}", { + aError, + aDetails, + }); + let reason = new Error(aError); + if (aDetails) { + reason.details = aDetails; + } + return reason; + }, + + // Attempt to update the auth server with whatever device details are stored + // in the account data. Returns a promise that always resolves, never rejects. + // If the promise resolves to a value, that value is the device id. + updateDeviceRegistration() { + return this.device.updateDeviceRegistration(); + }, + + /** + * Delete all the persisted credentials we store for FxA. After calling + * this, the user will be forced to re-authenticate to continue. + * + * @return Promise resolves when the user data has been persisted + */ + dropCredentials(state) { + // Delete all fields except those required for the user to + // reauthenticate. + let updateData = {}; + let clearField = field => { + if (!FXA_PWDMGR_REAUTH_ALLOWLIST.has(field)) { + updateData[field] = null; + } + }; + FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField); + FXA_PWDMGR_SECURE_FIELDS.forEach(clearField); + + return state.updateUserAccountData(updateData); + }, + + async checkAccountStatus(state) { + log.info("checking account status..."); + let data = await state.getUserAccountData(["uid", "sessionToken"]); + if (!data) { + log.info("account status: no user"); + return false; + } + // If we have a session token, then check if that remains valid - if this + // works we know the account must also be OK. + if (data.sessionToken) { + if (await this.fxAccountsClient.sessionStatus(data.sessionToken)) { + log.info("account status: ok"); + return true; + } + } + let exists = await this.fxAccountsClient.accountStatus(data.uid); + if (!exists) { + // Delete all local account data. Since the account no longer + // exists, we can skip the remote calls. + log.info("account status: deleted"); + await this._handleAccountDestroyed(data.uid); + } else { + // Note that we may already have been in a "needs reauth" state (ie, if + // this function was called when we already had no session token), but + // that's OK - re-notifying etc should cause no harm. + log.info("account status: needs reauthentication"); + await this.dropCredentials(this.currentAccountState); + // Notify the account state has changed so the UI updates. + await this.notifyObservers(ON_ACCOUNT_STATE_CHANGE_NOTIFICATION); + } + return false; + }, + + async _handleTokenError(err) { + if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) { + throw err; + } + log.warn("handling invalid token error", err); + // Note that we don't use `withCurrentAccountState` here as that will cause + // an error to be thrown if we sign out due to the account not existing. + let state = this.currentAccountState; + let ok = await this.checkAccountStatus(state); + if (ok) { + log.warn("invalid token error, but account state appears ok?"); + } + // always re-throw the error. + throw err; + }, +}; + +let fxAccountsSingleton = null; + +export function getFxAccountsSingleton() { + if (fxAccountsSingleton) { + return fxAccountsSingleton; + } + + fxAccountsSingleton = new FxAccounts(); + + // XXX Bug 947061 - We need a strategy for resuming email verification after + // browser restart + fxAccountsSingleton._internal.loadAndPoll(); + + return fxAccountsSingleton; +} + +// `AccountState` is exported for tests. diff --git a/services/fxaccounts/FxAccountsClient.sys.mjs b/services/fxaccounts/FxAccountsClient.sys.mjs new file mode 100644 index 0000000000..9dc80ff419 --- /dev/null +++ b/services/fxaccounts/FxAccountsClient.sys.mjs @@ -0,0 +1,839 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { HawkClient } from "resource://services-common/hawkclient.sys.mjs"; +import { deriveHawkCredentials } from "resource://services-common/hawkrequest.sys.mjs"; +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; + +import { + ERRNO_ACCOUNT_DOES_NOT_EXIST, + ERRNO_INCORRECT_EMAIL_CASE, + ERRNO_INCORRECT_PASSWORD, + ERRNO_INVALID_AUTH_NONCE, + ERRNO_INVALID_AUTH_TIMESTAMP, + ERRNO_INVALID_AUTH_TOKEN, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { Credentials } from "resource://gre/modules/Credentials.sys.mjs"; + +const HOST_PREF = "identity.fxaccounts.auth.uri"; + +const SIGNIN = "/account/login"; +const SIGNUP = "/account/create"; +// Devices older than this many days will not appear in the devices list +const DEVICES_FILTER_DAYS = 21; + +export var FxAccountsClient = function ( + host = Services.prefs.getStringPref(HOST_PREF) +) { + this.host = host; + + // The FxA auth server expects requests to certain endpoints to be authorized + // using Hawk. + this.hawk = new HawkClient(host); + this.hawk.observerPrefix = "FxA:hawk"; + + // Manage server backoff state. C.f. + // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol + this.backoffError = null; +}; + +FxAccountsClient.prototype = { + /** + * Return client clock offset, in milliseconds, as determined by hawk client. + * Provided because callers should not have to know about hawk + * implementation. + * + * The offset is the number of milliseconds that must be added to the client + * clock to make it equal to the server clock. For example, if the client is + * five minutes ahead of the server, the localtimeOffsetMsec will be -300000. + */ + get localtimeOffsetMsec() { + return this.hawk.localtimeOffsetMsec; + }, + + /* + * Return current time in milliseconds + * + * Not used by this module, but made available to the FxAccounts.jsm + * that uses this client. + */ + now() { + return this.hawk.now(); + }, + + /** + * Common code from signIn and signUp. + * + * @param path + * Request URL path. Can be /account/create or /account/login + * @param email + * The email address for the account (utf8) + * @param password + * The user's password + * @param [getKeys=false] + * If set to true the keyFetchToken will be retrieved + * @param [retryOK=true] + * If capitalization of the email is wrong and retryOK is set to true, + * we will retry with the suggested capitalization from the server + * @return Promise + * Returns a promise that resolves to an object: + * { + * authAt: authentication time for the session (seconds since epoch) + * email: the primary email for this account + * keyFetchToken: a key fetch token (hex) + * sessionToken: a session token (hex) + * uid: the user's unique ID (hex) + * unwrapBKey: used to unwrap kB, derived locally from the + * password (not revealed to the FxA server) + * verified (optional): flag indicating verification status of the + * email + * } + */ + _createSession(path, email, password, getKeys = false, retryOK = true) { + return Credentials.setup(email, password).then(creds => { + let data = { + authPW: CommonUtils.bytesAsHex(creds.authPW), + email, + }; + let keys = getKeys ? "?keys=true" : ""; + + return this._request(path + keys, "POST", null, data).then( + // Include the canonical capitalization of the email in the response so + // the caller can set its signed-in user state accordingly. + result => { + result.email = data.email; + result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey); + + return result; + }, + error => { + log.debug("Session creation failed", error); + // If the user entered an email with different capitalization from + // what's stored in the database (e.g., Greta.Garbo@gmail.COM as + // opposed to greta.garbo@gmail.com), the server will respond with a + // errno 120 (code 400) and the expected capitalization of the email. + // We retry with this email exactly once. If successful, we use the + // server's version of the email as the signed-in-user's email. This + // is necessary because the email also serves as salt; so we must be + // in agreement with the server on capitalization. + // + // API reference: + // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md + if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) { + if (!error.email) { + log.error("Server returned errno 120 but did not provide email"); + throw error; + } + return this._createSession( + path, + error.email, + password, + getKeys, + false + ); + } + throw error; + } + ); + }); + }, + + /** + * Create a new Firefox Account and authenticate + * + * @param email + * The email address for the account (utf8) + * @param password + * The user's password + * @param [getKeys=false] + * If set to true the keyFetchToken will be retrieved + * @return Promise + * Returns a promise that resolves to an object: + * { + * uid: the user's unique ID (hex) + * sessionToken: a session token (hex) + * keyFetchToken: a key fetch token (hex), + * unwrapBKey: used to unwrap kB, derived locally from the + * password (not revealed to the FxA server) + * } + */ + signUp(email, password, getKeys = false) { + return this._createSession( + SIGNUP, + email, + password, + getKeys, + false /* no retry */ + ); + }, + + /** + * Authenticate and create a new session with the Firefox Account API server + * + * @param email + * The email address for the account (utf8) + * @param password + * The user's password + * @param [getKeys=false] + * If set to true the keyFetchToken will be retrieved + * @return Promise + * Returns a promise that resolves to an object: + * { + * authAt: authentication time for the session (seconds since epoch) + * email: the primary email for this account + * keyFetchToken: a key fetch token (hex) + * sessionToken: a session token (hex) + * uid: the user's unique ID (hex) + * unwrapBKey: used to unwrap kB, derived locally from the + * password (not revealed to the FxA server) + * verified: flag indicating verification status of the email + * } + */ + signIn: function signIn(email, password, getKeys = false) { + return this._createSession( + SIGNIN, + email, + password, + getKeys, + true /* retry */ + ); + }, + + /** + * Check the status of a session given a session token + * + * @param sessionTokenHex + * The session token encoded in hex + * @return Promise + * Resolves with a boolean indicating if the session is still valid + */ + async sessionStatus(sessionTokenHex) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + return this._request("/session/status", "GET", credentials).then( + () => Promise.resolve(true), + error => { + if (isInvalidTokenError(error)) { + return Promise.resolve(false); + } + throw error; + } + ); + }, + + /** + * List all the clients connected to the authenticated user's account, + * including devices, OAuth clients, and web sessions. + * + * @param sessionTokenHex + * The session token encoded in hex + * @return Promise + */ + async attachedClients(sessionTokenHex) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + return this._requestWithHeaders( + "/account/attached_clients", + "GET", + credentials + ); + }, + + /** + * Retrieves an OAuth authorization code. + * + * @param String sessionTokenHex + * The session token encoded in hex + * @param {Object} options + * @param options.client_id + * @param options.state + * @param options.scope + * @param options.access_type + * @param options.code_challenge_method + * @param options.code_challenge + * @param [options.keys_jwe] + * @returns {Promise<Object>} Object containing `code` and `state`. + */ + async oauthAuthorize(sessionTokenHex, options) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + const body = { + client_id: options.client_id, + response_type: "code", + state: options.state, + scope: options.scope, + access_type: options.access_type, + code_challenge: options.code_challenge, + code_challenge_method: options.code_challenge_method, + }; + if (options.keys_jwe) { + body.keys_jwe = options.keys_jwe; + } + return this._request("/oauth/authorization", "POST", credentials, body); + }, + /** + * Exchanges an OAuth authorization code with a refresh token, access tokens and an optional JWE representing scoped keys + * Takes in the sessionToken to tie the device record associated with the session, with the device record associated with the refreshToken + * + * @param string sessionTokenHex: The session token encoded in hex + * @param String code: OAuth authorization code + * @param String verifier: OAuth PKCE verifier + * @param String clientId: OAuth client ID + * + * @returns { Object } object containing `refresh_token`, `access_token` and `keys_jwe` + **/ + async oauthToken(sessionTokenHex, code, verifier, clientId) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + const body = { + grant_type: "authorization_code", + code, + client_id: clientId, + code_verifier: verifier, + }; + return this._request("/oauth/token", "POST", credentials, body); + }, + /** + * Destroy an OAuth access token or refresh token. + * + * @param String clientId + * @param String token The token to be revoked. + */ + async oauthDestroy(clientId, token) { + const body = { + client_id: clientId, + token, + }; + return this._request("/oauth/destroy", "POST", null, body); + }, + + /** + * Query for the information required to derive + * scoped encryption keys requested by the specified OAuth client. + * + * @param sessionTokenHex + * The session token encoded in hex + * @param clientId + * @param scope + * Space separated list of scopes + * @return Promise + */ + async getScopedKeyData(sessionTokenHex, clientId, scope) { + if (!clientId) { + throw new Error("Missing 'clientId' parameter"); + } + if (!scope) { + throw new Error("Missing 'scope' parameter"); + } + const params = { + client_id: clientId, + scope, + }; + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + return this._request( + "/account/scoped-key-data", + "POST", + credentials, + params + ); + }, + + /** + * Destroy the current session with the Firefox Account API server and its + * associated device. + * + * @param sessionTokenHex + * The session token encoded in hex + * @return Promise + */ + async signOut(sessionTokenHex, options = {}) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + let path = "/session/destroy"; + if (options.service) { + path += "?service=" + encodeURIComponent(options.service); + } + return this._request(path, "POST", credentials); + }, + + /** + * Check the verification status of the user's FxA email address + * + * @param sessionTokenHex + * The current session token encoded in hex + * @return Promise + */ + async recoveryEmailStatus(sessionTokenHex, options = {}) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + let path = "/recovery_email/status"; + if (options.reason) { + path += "?reason=" + encodeURIComponent(options.reason); + } + + return this._request(path, "GET", credentials); + }, + + /** + * Resend the verification email for the user + * + * @param sessionTokenHex + * The current token encoded in hex + * @return Promise + */ + async resendVerificationEmail(sessionTokenHex) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + return this._request("/recovery_email/resend_code", "POST", credentials); + }, + + /** + * Retrieve encryption keys + * + * @param keyFetchTokenHex + * A one-time use key fetch token encoded in hex + * @return Promise + * Returns a promise that resolves to an object: + * { + * kA: an encryption key for recevorable data (bytes) + * wrapKB: an encryption key that requires knowledge of the + * user's password (bytes) + * } + */ + async accountKeys(keyFetchTokenHex) { + let creds = await deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken"); + let keyRequestKey = creds.extra.slice(0, 32); + let morecreds = await CryptoUtils.hkdfLegacy( + keyRequestKey, + undefined, + Credentials.keyWord("account/keys"), + 3 * 32 + ); + let respHMACKey = morecreds.slice(0, 32); + let respXORKey = morecreds.slice(32, 96); + + const resp = await this._request("/account/keys", "GET", creds); + if (!resp.bundle) { + throw new Error("failed to retrieve keys"); + } + + let bundle = CommonUtils.hexToBytes(resp.bundle); + let mac = bundle.slice(-32); + let key = CommonUtils.byteStringToArrayBuffer(respHMACKey); + // CryptoUtils.hmac takes ArrayBuffers as inputs for the key and data and + // returns an ArrayBuffer. + let bundleMAC = await CryptoUtils.hmac( + "SHA-256", + key, + CommonUtils.byteStringToArrayBuffer(bundle.slice(0, -32)) + ); + if (mac !== CommonUtils.arrayBufferToByteString(bundleMAC)) { + throw new Error("error unbundling encryption keys"); + } + + let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64)); + + return { + kA: keyAWrapB.slice(0, 32), + wrapKB: keyAWrapB.slice(32), + }; + }, + + /** + * Obtain an OAuth access token by authenticating using a session token. + * + * @param {String} sessionTokenHex + * The session token encoded in hex + * @param {String} clientId + * @param {String} scope + * List of space-separated scopes. + * @param {Number} ttl + * Token time to live. + * @return {Promise<Object>} Object containing an `access_token`. + */ + async accessTokenWithSessionToken(sessionTokenHex, clientId, scope, ttl) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + const body = { + client_id: clientId, + grant_type: "fxa-credentials", + scope, + ttl, + }; + return this._request("/oauth/token", "POST", credentials, body); + }, + + /** + * Determine if an account exists + * + * @param email + * The email address to check + * @return Promise + * The promise resolves to true if the account exists, or false + * if it doesn't. The promise is rejected on other errors. + */ + accountExists(email) { + return this.signIn(email, "").then( + cantHappen => { + throw new Error("How did I sign in with an empty password?"); + }, + expectedError => { + switch (expectedError.errno) { + case ERRNO_ACCOUNT_DOES_NOT_EXIST: + return false; + case ERRNO_INCORRECT_PASSWORD: + return true; + default: + // not so expected, any more ... + throw expectedError; + } + } + ); + }, + + /** + * Given the uid of an existing account (not an arbitrary email), ask + * the server if it still exists via /account/status. + * + * Used for differentiating between password change and account deletion. + */ + accountStatus(uid) { + return this._request("/account/status?uid=" + uid, "GET").then( + result => { + return result.exists; + }, + error => { + log.error("accountStatus failed", error); + return Promise.reject(error); + } + ); + }, + + /** + * Register a new device + * + * @method registerDevice + * @param sessionTokenHex + * Session token obtained from signIn + * @param name + * Device name + * @param type + * Device type (mobile|desktop) + * @param [options] + * Extra device options + * @param [options.availableCommands] + * Available commands for this device + * @param [options.pushCallback] + * `pushCallback` push endpoint callback + * @param [options.pushPublicKey] + * `pushPublicKey` push public key (URLSafe Base64 string) + * @param [options.pushAuthKey] + * `pushAuthKey` push auth secret (URLSafe Base64 string) + * @return Promise + * Resolves to an object: + * { + * id: Device identifier + * createdAt: Creation time (milliseconds since epoch) + * name: Name of device + * type: Type of device (mobile|desktop) + * } + */ + async registerDevice(sessionTokenHex, name, type, options = {}) { + let path = "/account/device"; + + let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken"); + let body = { name, type }; + + if (options.pushCallback) { + body.pushCallback = options.pushCallback; + } + if (options.pushPublicKey && options.pushAuthKey) { + body.pushPublicKey = options.pushPublicKey; + body.pushAuthKey = options.pushAuthKey; + } + body.availableCommands = options.availableCommands; + + return this._request(path, "POST", creds, body); + }, + + /** + * Sends a message to other devices. Must conform with the push payload schema: + * https://github.com/mozilla/fxa-auth-server/blob/master/docs/pushpayloads.schema.json + * + * @method notifyDevice + * @param sessionTokenHex + * Session token obtained from signIn + * @param deviceIds + * Devices to send the message to. If null, will be sent to all devices. + * @param excludedIds + * Devices to exclude when sending to all devices (deviceIds must be null). + * @param payload + * Data to send with the message + * @return Promise + * Resolves to an empty object: + * {} + */ + async notifyDevices( + sessionTokenHex, + deviceIds, + excludedIds, + payload, + TTL = 0 + ) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + if (deviceIds && excludedIds) { + throw new Error( + "You cannot specify excluded devices if deviceIds is set." + ); + } + const body = { + to: deviceIds || "all", + payload, + TTL, + }; + if (excludedIds) { + body.excluded = excludedIds; + } + return this._request("/account/devices/notify", "POST", credentials, body); + }, + + /** + * Retrieves pending commands for our device. + * + * @method getCommands + * @param sessionTokenHex - Session token obtained from signIn + * @param [index] - If specified, only messages received after the one who + * had that index will be retrieved. + * @param [limit] - Maximum number of messages to retrieve. + */ + async getCommands(sessionTokenHex, { index, limit }) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + const params = new URLSearchParams(); + if (index != undefined) { + params.set("index", index); + } + if (limit != undefined) { + params.set("limit", limit); + } + const path = `/account/device/commands?${params.toString()}`; + return this._request(path, "GET", credentials); + }, + + /** + * Invokes a command on another device. + * + * @method invokeCommand + * @param sessionTokenHex - Session token obtained from signIn + * @param command - Name of the command to invoke + * @param target - Recipient device ID. + * @param payload + * @return Promise + * Resolves to the request's response, (which should be an empty object) + */ + async invokeCommand(sessionTokenHex, command, target, payload) { + const credentials = await deriveHawkCredentials( + sessionTokenHex, + "sessionToken" + ); + const body = { + command, + target, + payload, + }; + return this._request( + "/account/devices/invoke_command", + "POST", + credentials, + body + ); + }, + + /** + * Update the session or name for an existing device + * + * @method updateDevice + * @param sessionTokenHex + * Session token obtained from signIn + * @param id + * Device identifier + * @param name + * Device name + * @param [options] + * Extra device options + * @param [options.availableCommands] + * Available commands for this device + * @param [options.pushCallback] + * `pushCallback` push endpoint callback + * @param [options.pushPublicKey] + * `pushPublicKey` push public key (URLSafe Base64 string) + * @param [options.pushAuthKey] + * `pushAuthKey` push auth secret (URLSafe Base64 string) + * @return Promise + * Resolves to an object: + * { + * id: Device identifier + * name: Device name + * } + */ + async updateDevice(sessionTokenHex, id, name, options = {}) { + let path = "/account/device"; + + let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken"); + let body = { id, name }; + if (options.pushCallback) { + body.pushCallback = options.pushCallback; + } + if (options.pushPublicKey && options.pushAuthKey) { + body.pushPublicKey = options.pushPublicKey; + body.pushAuthKey = options.pushAuthKey; + } + body.availableCommands = options.availableCommands; + + return this._request(path, "POST", creds, body); + }, + + /** + * Get a list of currently registered devices that have been accessed + * in the last `DEVICES_FILTER_DAYS` days + * + * @method getDeviceList + * @param sessionTokenHex + * Session token obtained from signIn + * @return Promise + * Resolves to an array of objects: + * [ + * { + * id: Device id + * isCurrentDevice: Boolean indicating whether the item + * represents the current device + * name: Device name + * type: Device type (mobile|desktop) + * }, + * ... + * ] + */ + async getDeviceList(sessionTokenHex) { + let timestamp = Date.now() - 1000 * 60 * 60 * 24 * DEVICES_FILTER_DAYS; + let path = `/account/devices?filterIdleDevicesTimestamp=${timestamp}`; + let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken"); + return this._request(path, "GET", creds, {}); + }, + + _clearBackoff() { + this.backoffError = null; + }, + + /** + * A general method for sending raw API calls to the FxA auth server. + * All request bodies and responses are JSON. + * + * @param path + * API endpoint path + * @param method + * The HTTP request method + * @param credentials + * Hawk credentials + * @param jsonPayload + * A JSON payload + * @return Promise + * Returns a promise that resolves to the JSON response of the API call, + * or is rejected with an error. Error responses have the following properties: + * { + * "code": 400, // matches the HTTP status code + * "errno": 107, // stable application-level error number + * "error": "Bad Request", // string description of the error type + * "message": "the value of salt is not allowed to be undefined", + * "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error + * } + */ + async _requestWithHeaders(path, method, credentials, jsonPayload) { + // We were asked to back off. + if (this.backoffError) { + log.debug("Received new request during backoff, re-rejecting."); + throw this.backoffError; + } + let response; + try { + response = await this.hawk.request( + path, + method, + credentials, + jsonPayload + ); + } catch (error) { + log.error(`error ${method}ing ${path}`, error); + if (error.retryAfter) { + log.debug("Received backoff response; caching error as flag."); + this.backoffError = error; + // Schedule clearing of cached-error-as-flag. + CommonUtils.namedTimer( + this._clearBackoff, + error.retryAfter * 1000, + this, + "fxaBackoffTimer" + ); + } + throw error; + } + try { + return { body: JSON.parse(response.body), headers: response.headers }; + } catch (error) { + log.error("json parse error on response: " + response.body); + // eslint-disable-next-line no-throw-literal + throw { error }; + } + }, + + async _request(path, method, credentials, jsonPayload) { + const response = await this._requestWithHeaders( + path, + method, + credentials, + jsonPayload + ); + return response.body; + }, +}; + +function isInvalidTokenError(error) { + if (error.code != 401) { + return false; + } + switch (error.errno) { + case ERRNO_INVALID_AUTH_TOKEN: + case ERRNO_INVALID_AUTH_TIMESTAMP: + case ERRNO_INVALID_AUTH_NONCE: + return true; + } + return false; +} diff --git a/services/fxaccounts/FxAccountsCommands.sys.mjs b/services/fxaccounts/FxAccountsCommands.sys.mjs new file mode 100644 index 0000000000..40fcc7f925 --- /dev/null +++ b/services/fxaccounts/FxAccountsCommands.sys.mjs @@ -0,0 +1,467 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + COMMAND_SENDTAB, + COMMAND_SENDTAB_TAIL, + SCOPE_OLD_SYNC, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { Observers } from "resource://services-common/observers.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + BulkKeyBundle: "resource://services-sync/keys.sys.mjs", + CryptoWrapper: "resource://services-sync/record.sys.mjs", + PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "INVALID_SHAREABLE_SCHEMES", + "services.sync.engine.tabs.filteredSchemes", + "", + null, + val => { + return new Set(val.split("|")); + } +); + +export class FxAccountsCommands { + constructor(fxAccountsInternal) { + this._fxai = fxAccountsInternal; + this.sendTab = new SendTab(this, fxAccountsInternal); + this._invokeRateLimitExpiry = 0; + } + + async availableCommands() { + const encryptedSendTabKeys = await this.sendTab.getEncryptedSendTabKeys(); + if (!encryptedSendTabKeys) { + // This will happen if the account is not verified yet. + return {}; + } + return { + [COMMAND_SENDTAB]: encryptedSendTabKeys, + }; + } + + async invoke(command, device, payload) { + const { sessionToken } = await this._fxai.getUserAccountData([ + "sessionToken", + ]); + const client = this._fxai.fxAccountsClient; + const now = Date.now(); + if (now < this._invokeRateLimitExpiry) { + const remaining = (this._invokeRateLimitExpiry - now) / 1000; + throw new Error( + `Invoke for ${command} is rate-limited for ${remaining} seconds.` + ); + } + try { + let info = await client.invokeCommand( + sessionToken, + command, + device.id, + payload + ); + if (!info.enqueued || !info.notified) { + // We want an error log here to help diagnose users who report failure. + log.error("Sending was only partially successful", info); + } else { + log.info("Successfully sent", info); + } + } catch (err) { + if (err.code && err.code === 429 && err.retryAfter) { + this._invokeRateLimitExpiry = Date.now() + err.retryAfter * 1000; + } + throw err; + } + log.info(`Payload sent to device ${device.id}.`); + } + + /** + * Poll and handle device commands for the current device. + * This method can be called either in response to a Push message, + * or by itself as a "commands recovery" mechanism. + * + * @param {Number} notifiedIndex "Command received" push messages include + * the index of the command that triggered the message. We use it as a + * hint when we have no "last command index" stored. + */ + async pollDeviceCommands(notifiedIndex = 0) { + // Whether the call to `pollDeviceCommands` was initiated by a Push message from the FxA + // servers in response to a message being received or simply scheduled in order + // to fetch missed messages. + log.info(`Polling device commands.`); + await this._fxai.withCurrentAccountState(async state => { + const { device } = await state.getUserAccountData(["device"]); + if (!device) { + throw new Error("No device registration."); + } + // We increment lastCommandIndex by 1 because the server response includes the current index. + // If we don't have a `lastCommandIndex` stored, we fall back on the index from the push message we just got. + const lastCommandIndex = device.lastCommandIndex + 1 || notifiedIndex; + // We have already received this message before. + if (notifiedIndex > 0 && notifiedIndex < lastCommandIndex) { + return; + } + const { index, messages } = await this._fetchDeviceCommands( + lastCommandIndex + ); + if (messages.length) { + await state.updateUserAccountData({ + device: { ...device, lastCommandIndex: index }, + }); + log.info(`Handling ${messages.length} messages`); + await this._handleCommands(messages, notifiedIndex); + } + }); + return true; + } + + async _fetchDeviceCommands(index, limit = null) { + const userData = await this._fxai.getUserAccountData(); + if (!userData) { + throw new Error("No user."); + } + const { sessionToken } = userData; + if (!sessionToken) { + throw new Error("No session token."); + } + const client = this._fxai.fxAccountsClient; + const opts = { index }; + if (limit != null) { + opts.limit = limit; + } + return client.getCommands(sessionToken, opts); + } + + _getReason(notifiedIndex, messageIndex) { + // The returned reason value represents an explanation for why the command associated with the + // message of the given `messageIndex` is being handled. If `notifiedIndex` is zero the command + // is a part of a fallback polling process initiated by "Sync Now" ["poll"]. If `notifiedIndex` is + // greater than `messageIndex` this is a push command that was previously missed ["push-missed"], + // otherwise we assume this is a push command with no missed messages ["push"]. + if (notifiedIndex == 0) { + return "poll"; + } else if (notifiedIndex > messageIndex) { + return "push-missed"; + } + // Note: The returned reason might be "push" in the case where a user sends multiple tabs + // in quick succession. We are not attempting to distinguish this from other push cases at + // present. + return "push"; + } + + async _handleCommands(messages, notifiedIndex) { + try { + await this._fxai.device.refreshDeviceList(); + } catch (e) { + log.warn("Error refreshing device list", e); + } + // We debounce multiple incoming tabs so we show a single notification. + const tabsReceived = []; + for (const { index, data } of messages) { + const { command, payload, sender: senderId } = data; + const reason = this._getReason(notifiedIndex, index); + const sender = + senderId && this._fxai.device.recentDeviceList + ? this._fxai.device.recentDeviceList.find(d => d.id == senderId) + : null; + if (!sender) { + log.warn( + "Incoming command is from an unknown device (maybe disconnected?)" + ); + } + switch (command) { + case COMMAND_SENDTAB: + try { + const { title, uri } = await this.sendTab.handle( + senderId, + payload, + reason + ); + log.info( + `Tab received with FxA commands: "${ + title || "<no title>" + }" from ${sender ? sender.name : "Unknown device"}.` + ); + // URLs are PII, so only logged at trace. + log.trace(`Tab received URL: ${uri}`); + // This should eventually be rare to hit as all platforms will be using the same + // scheme filter list, but we have this here in the case other platforms + // haven't caught up and/or trying to send invalid uris using older versions + const scheme = Services.io.newURI(uri).scheme; + if (lazy.INVALID_SHAREABLE_SCHEMES.has(scheme)) { + throw new Error("Invalid scheme found for received URI."); + } + tabsReceived.push({ title, uri, sender }); + } catch (e) { + log.error(`Error while handling incoming Send Tab payload.`, e); + } + break; + default: + log.info(`Unknown command: ${command}.`); + } + } + if (tabsReceived.length) { + this._notifyFxATabsReceived(tabsReceived); + } + } + + _notifyFxATabsReceived(tabsReceived) { + Observers.notify("fxaccounts:commands:open-uri", tabsReceived); + } +} + +/** + * Send Tab is built on top of FxA commands. + * + * Devices exchange keys wrapped in the oldsync key between themselves (getEncryptedSendTabKeys) + * during the device registration flow. The FxA server can theoretically never + * retrieve the send tab keys since it doesn't know the oldsync key. + * + * Note about the keys: + * The server has the `pushPublicKey`. The FxA server encrypt the send-tab payload again using the + * push keys - after the client has encrypted the payload using the send-tab keys. + * The push keys are different from the send-tab keys. The FxA server uses + * the push keys to deliver the tabs using same mechanism we use for web-push. + * However, clients use the send-tab keys for end-to-end encryption. + */ +export class SendTab { + constructor(commands, fxAccountsInternal) { + this._commands = commands; + this._fxai = fxAccountsInternal; + } + /** + * @param {Device[]} to - Device objects (typically returned by fxAccounts.getDevicesList()). + * @param {Object} tab + * @param {string} tab.url + * @param {string} tab.title + * @returns A report object, in the shape of + * {succeded: [Device], error: [{device: Device, error: Exception}]} + */ + async send(to, tab) { + log.info(`Sending a tab to ${to.length} devices.`); + const flowID = this._fxai.telemetry.generateFlowID(); + const encoder = new TextEncoder(); + const data = { entries: [{ title: tab.title, url: tab.url }] }; + const report = { + succeeded: [], + failed: [], + }; + for (let device of to) { + try { + const streamID = this._fxai.telemetry.generateFlowID(); + const targetData = Object.assign({ flowID, streamID }, data); + const bytes = encoder.encode(JSON.stringify(targetData)); + const encrypted = await this._encrypt(bytes, device); + // FxA expects an object as the payload, but we only have a single encrypted string; wrap it. + // If you add any plaintext items to this payload, please carefully consider the privacy implications + // of revealing that data to the FxA server. + const payload = { encrypted }; + await this._commands.invoke(COMMAND_SENDTAB, device, payload); + this._fxai.telemetry.recordEvent( + "command-sent", + COMMAND_SENDTAB_TAIL, + this._fxai.telemetry.sanitizeDeviceId(device.id), + { flowID, streamID } + ); + report.succeeded.push(device); + } catch (error) { + log.error("Error while invoking a send tab command.", error); + report.failed.push({ device, error }); + } + } + return report; + } + + // Returns true if the target device is compatible with FxA Commands Send tab. + isDeviceCompatible(device) { + return ( + device.availableCommands && device.availableCommands[COMMAND_SENDTAB] + ); + } + + // Handle incoming send tab payload, called by FxAccountsCommands. + async handle(senderID, { encrypted }, reason) { + const bytes = await this._decrypt(encrypted); + const decoder = new TextDecoder("utf8"); + const data = JSON.parse(decoder.decode(bytes)); + const { flowID, streamID, entries } = data; + const current = data.hasOwnProperty("current") + ? data.current + : entries.length - 1; + const { title, url: uri } = entries[current]; + // `flowID` and `streamID` are in the top-level of the JSON, `entries` is + // an array of "tabs" with `current` being what index is the one we care + // about, or the last one if not specified. + this._fxai.telemetry.recordEvent( + "command-received", + COMMAND_SENDTAB_TAIL, + this._fxai.telemetry.sanitizeDeviceId(senderID), + { flowID, streamID, reason } + ); + + return { + title, + uri, + }; + } + + async _encrypt(bytes, device) { + let bundle = device.availableCommands[COMMAND_SENDTAB]; + if (!bundle) { + throw new Error(`Device ${device.id} does not have send tab keys.`); + } + const oldsyncKey = await this._fxai.keys.getKeyForScope(SCOPE_OLD_SYNC); + // Older clients expect this to be hex, due to pre-JWK sync key ids :-( + const ourKid = this._fxai.keys.kidAsHex(oldsyncKey); + const { kid: theirKid } = JSON.parse( + device.availableCommands[COMMAND_SENDTAB] + ); + if (theirKid != ourKid) { + throw new Error("Target Send Tab key ID is different from ours"); + } + const json = JSON.parse(bundle); + const wrapper = new lazy.CryptoWrapper(); + wrapper.deserialize({ payload: json }); + const syncKeyBundle = lazy.BulkKeyBundle.fromJWK(oldsyncKey); + let { publicKey, authSecret } = await wrapper.decrypt(syncKeyBundle); + authSecret = urlsafeBase64Decode(authSecret); + publicKey = urlsafeBase64Decode(publicKey); + + const { ciphertext: encrypted } = await lazy.PushCrypto.encrypt( + bytes, + publicKey, + authSecret + ); + return urlsafeBase64Encode(encrypted); + } + + async _getPersistedSendTabKeys() { + const { device } = await this._fxai.getUserAccountData(["device"]); + return device && device.sendTabKeys; + } + + async _decrypt(ciphertext) { + let { privateKey, publicKey, authSecret } = + await this._getPersistedSendTabKeys(); + publicKey = urlsafeBase64Decode(publicKey); + authSecret = urlsafeBase64Decode(authSecret); + ciphertext = new Uint8Array(urlsafeBase64Decode(ciphertext)); + return lazy.PushCrypto.decrypt( + privateKey, + publicKey, + authSecret, + // The only Push encoding we support. + { encoding: "aes128gcm" }, + ciphertext + ); + } + + async _generateAndPersistSendTabKeys() { + let [publicKey, privateKey] = await lazy.PushCrypto.generateKeys(); + publicKey = urlsafeBase64Encode(publicKey); + let authSecret = lazy.PushCrypto.generateAuthenticationSecret(); + authSecret = urlsafeBase64Encode(authSecret); + const sendTabKeys = { + publicKey, + privateKey, + authSecret, + }; + await this._fxai.withCurrentAccountState(async state => { + const { device } = await state.getUserAccountData(["device"]); + await state.updateUserAccountData({ + device: { + ...device, + sendTabKeys, + }, + }); + }); + return sendTabKeys; + } + + async _getPersistedEncryptedSendTabKey() { + const { encryptedSendTabKeys } = await this._fxai.getUserAccountData([ + "encryptedSendTabKeys", + ]); + return encryptedSendTabKeys; + } + + async _generateAndPersistEncryptedSendTabKey() { + let sendTabKeys = await this._getPersistedSendTabKeys(); + if (!sendTabKeys) { + log.info("Could not find sendtab keys, generating them"); + sendTabKeys = await this._generateAndPersistSendTabKeys(); + } + // Strip the private key from the bundle to encrypt. + const keyToEncrypt = { + publicKey: sendTabKeys.publicKey, + authSecret: sendTabKeys.authSecret, + }; + if (!(await this._fxai.keys.canGetKeyForScope(SCOPE_OLD_SYNC))) { + log.info("Can't fetch keys, so unable to determine sendtab keys"); + return null; + } + let oldsyncKey; + try { + oldsyncKey = await this._fxai.keys.getKeyForScope(SCOPE_OLD_SYNC); + } catch (ex) { + log.warn("Failed to fetch keys, so unable to determine sendtab keys", ex); + return null; + } + const wrapper = new lazy.CryptoWrapper(); + wrapper.cleartext = keyToEncrypt; + const keyBundle = lazy.BulkKeyBundle.fromJWK(oldsyncKey); + await wrapper.encrypt(keyBundle); + const encryptedSendTabKeys = JSON.stringify({ + // This is expected in hex, due to pre-JWK sync key ids :-( + kid: this._fxai.keys.kidAsHex(oldsyncKey), + IV: wrapper.IV, + hmac: wrapper.hmac, + ciphertext: wrapper.ciphertext, + }); + await this._fxai.withCurrentAccountState(async state => { + await state.updateUserAccountData({ + encryptedSendTabKeys, + }); + }); + return encryptedSendTabKeys; + } + + async getEncryptedSendTabKeys() { + let encryptedSendTabKeys = await this._getPersistedEncryptedSendTabKey(); + const sendTabKeys = await this._getPersistedSendTabKeys(); + if (!encryptedSendTabKeys || !sendTabKeys) { + log.info("Generating and persisting encrypted sendtab keys"); + // `_generateAndPersistEncryptedKeys` requires the sync key + // which cannot be accessed if the login manager is locked + // (i.e when the primary password is locked) or if the sync keys + // aren't accessible (account isn't verified) + // so this function could fail to retrieve the keys + // however, device registration will trigger when the account + // is verified, so it's OK + // Note that it's okay to persist those keys, because they are + // already persisted in plaintext and the encrypted bundle + // does not include the sync-key (the sync key is used to encrypt + // it though) + encryptedSendTabKeys = + await this._generateAndPersistEncryptedSendTabKey(); + } + return encryptedSendTabKeys; + } +} + +function urlsafeBase64Encode(buffer) { + return ChromeUtils.base64URLEncode(new Uint8Array(buffer), { pad: false }); +} + +function urlsafeBase64Decode(str) { + return ChromeUtils.base64URLDecode(str, { padding: "reject" }); +} diff --git a/services/fxaccounts/FxAccountsCommon.sys.mjs b/services/fxaccounts/FxAccountsCommon.sys.mjs new file mode 100644 index 0000000000..2688fc3c0a --- /dev/null +++ b/services/fxaccounts/FxAccountsCommon.sys.mjs @@ -0,0 +1,393 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; +import { LogManager } from "resource://services-common/logmanager.sys.mjs"; + +// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config", +// "Debug", "Trace" or "All". If none is specified, "Debug" will be used by +// default. Note "Debug" is usually appropriate so that when this log is +// included in the Sync file logs we get verbose output. +const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel"; + +// A pref that can be set so "sensitive" information (eg, personally +// identifiable info, credentials, etc) will be logged. +const PREF_LOG_SENSITIVE_DETAILS = "identity.fxaccounts.log.sensitive"; + +export let log = Log.repository.getLogger("FirefoxAccounts"); +log.manageLevelFromPref(PREF_LOG_LEVEL); + +let logs = [ + "Sync", + "Services.Common", + "FirefoxAccounts", + "Hawk", + "browserwindow.syncui", + "BookmarkSyncUtils", + "addons.xpi", +]; + +// For legacy reasons, the log manager still thinks it's part of sync. +export let logManager = new LogManager("services.sync.", logs, "sync"); + +// A boolean to indicate if personally identifiable information (or anything +// else sensitive, such as credentials) should be logged. +export let logPII = () => + Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS, false); + +export let FXACCOUNTS_PERMISSION = "firefox-accounts"; + +export let DATA_FORMAT_VERSION = 1; +export let DEFAULT_STORAGE_FILENAME = "signedInUser.json"; + +export let OAUTH_TOKEN_FOR_SYNC_LIFETIME_SECONDS = 3600 * 6; // 6 hours + +// After we start polling for account verification, we stop polling when this +// many milliseconds have elapsed. +export let POLL_SESSION = 1000 * 60 * 20; // 20 minutes + +// Observer notifications. +export let ONLOGIN_NOTIFICATION = "fxaccounts:onlogin"; +export let ONVERIFIED_NOTIFICATION = "fxaccounts:onverified"; +export let ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout"; +export let ON_PRELOGOUT_NOTIFICATION = "fxaccounts:on_pre_logout"; +// Internal to services/fxaccounts only +export let ON_DEVICE_CONNECTED_NOTIFICATION = "fxaccounts:device_connected"; +export let ON_DEVICE_DISCONNECTED_NOTIFICATION = + "fxaccounts:device_disconnected"; +export let ON_PROFILE_UPDATED_NOTIFICATION = "fxaccounts:profile_updated"; // Push +export let ON_PASSWORD_CHANGED_NOTIFICATION = "fxaccounts:password_changed"; +export let ON_PASSWORD_RESET_NOTIFICATION = "fxaccounts:password_reset"; +export let ON_ACCOUNT_DESTROYED_NOTIFICATION = "fxaccounts:account_destroyed"; +export let ON_COLLECTION_CHANGED_NOTIFICATION = "sync:collection_changed"; +export let ON_VERIFY_LOGIN_NOTIFICATION = "fxaccounts:verify_login"; +export let ON_COMMAND_RECEIVED_NOTIFICATION = "fxaccounts:command_received"; + +export let FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update"; + +export let ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChannel +export let ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange"; +export let ON_NEW_DEVICE_ID = "fxaccounts:new_device_id"; +export let ON_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated"; + +// The common prefix for all commands. +export let COMMAND_PREFIX = "https://identity.mozilla.com/cmd/"; + +// The commands we support - only the _TAIL values are recorded in telemetry. +export let COMMAND_SENDTAB_TAIL = "open-uri"; +export let COMMAND_SENDTAB = COMMAND_PREFIX + COMMAND_SENDTAB_TAIL; + +// OAuth +export let FX_OAUTH_CLIENT_ID = "5882386c6d801776"; +export let SCOPE_PROFILE = "profile"; +export let SCOPE_PROFILE_WRITE = "profile:write"; +export let SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync"; + +// This scope was previously used to calculate a telemetry tracking identifier for +// the account, but that system has since been decommissioned. It's here entirely +// so that we can remove the corresponding key from storage if present. We should +// be safe to remove it after some sensible period of time has elapsed to allow +// most clients to update; ref Bug 1697596. +export let DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY = + "https://identity.mozilla.com/ids/ecosystem_telemetry"; + +// OAuth metadata for other Firefox-related services that we might need to know about +// in order to provide an enhanced user experience. +export let FX_MONITOR_OAUTH_CLIENT_ID = "802d56ef2a9af9fa"; +export let FX_RELAY_OAUTH_CLIENT_ID = "9ebfe2c2f9ea3c58"; +export let VPN_OAUTH_CLIENT_ID = "e6eb0d1e856335fc"; + +// UI Requests. +export let UI_REQUEST_SIGN_IN_FLOW = "signInFlow"; +export let UI_REQUEST_REFRESH_AUTH = "refreshAuthentication"; + +// Firefox Accounts WebChannel ID +export let WEBCHANNEL_ID = "account_updates"; + +// WebChannel commands +export let COMMAND_PAIR_HEARTBEAT = "fxaccounts:pair_heartbeat"; +export let COMMAND_PAIR_SUPP_METADATA = "fxaccounts:pair_supplicant_metadata"; +export let COMMAND_PAIR_AUTHORIZE = "fxaccounts:pair_authorize"; +export let COMMAND_PAIR_DECLINE = "fxaccounts:pair_decline"; +export let COMMAND_PAIR_COMPLETE = "fxaccounts:pair_complete"; + +export let COMMAND_PROFILE_CHANGE = "profile:change"; +export let COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account"; +export let COMMAND_LOGIN = "fxaccounts:login"; +export let COMMAND_OAUTH = "fxaccounts:oauth_login"; +export let COMMAND_LOGOUT = "fxaccounts:logout"; +export let COMMAND_DELETE = "fxaccounts:delete"; +export let COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences"; +export let COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password"; +export let COMMAND_FXA_STATUS = "fxaccounts:fxa_status"; +export let COMMAND_PAIR_PREFERENCES = "fxaccounts:pair_preferences"; +export let COMMAND_FIREFOX_VIEW = "fxaccounts:firefox_view"; + +// The pref branch where any prefs which relate to a specific account should +// be stored. This branch will be reset on account signout and signin. +export let PREF_ACCOUNT_ROOT = "identity.fxaccounts.account."; + +export let PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; +export let PREF_REMOTE_PAIRING_URI = "identity.fxaccounts.remote.pairing.uri"; + +// Server errno. +// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format +export let ERRNO_ACCOUNT_ALREADY_EXISTS = 101; +export let ERRNO_ACCOUNT_DOES_NOT_EXIST = 102; +export let ERRNO_INCORRECT_PASSWORD = 103; +export let ERRNO_UNVERIFIED_ACCOUNT = 104; +export let ERRNO_INVALID_VERIFICATION_CODE = 105; +export let ERRNO_NOT_VALID_JSON_BODY = 106; +export let ERRNO_INVALID_BODY_PARAMETERS = 107; +export let ERRNO_MISSING_BODY_PARAMETERS = 108; +export let ERRNO_INVALID_REQUEST_SIGNATURE = 109; +export let ERRNO_INVALID_AUTH_TOKEN = 110; +export let ERRNO_INVALID_AUTH_TIMESTAMP = 111; +export let ERRNO_MISSING_CONTENT_LENGTH = 112; +export let ERRNO_REQUEST_BODY_TOO_LARGE = 113; +export let ERRNO_TOO_MANY_CLIENT_REQUESTS = 114; +export let ERRNO_INVALID_AUTH_NONCE = 115; +export let ERRNO_ENDPOINT_NO_LONGER_SUPPORTED = 116; +export let ERRNO_INCORRECT_LOGIN_METHOD = 117; +export let ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD = 118; +export let ERRNO_INCORRECT_API_VERSION = 119; +export let ERRNO_INCORRECT_EMAIL_CASE = 120; +export let ERRNO_ACCOUNT_LOCKED = 121; +export let ERRNO_ACCOUNT_UNLOCKED = 122; +export let ERRNO_UNKNOWN_DEVICE = 123; +export let ERRNO_DEVICE_SESSION_CONFLICT = 124; +export let ERRNO_SERVICE_TEMP_UNAVAILABLE = 201; +export let ERRNO_PARSE = 997; +export let ERRNO_NETWORK = 998; +export let ERRNO_UNKNOWN_ERROR = 999; + +// Offset oauth server errnos so they don't conflict with auth server errnos +export let OAUTH_SERVER_ERRNO_OFFSET = 1000; + +// OAuth Server errno. +export let ERRNO_UNKNOWN_CLIENT_ID = 101 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INCORRECT_CLIENT_SECRET = 102 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INCORRECT_REDIRECT_URI = 103 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INVALID_FXA_ASSERTION = 104 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_UNKNOWN_CODE = 105 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INCORRECT_CODE = 106 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_EXPIRED_CODE = 107 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_OAUTH_INVALID_TOKEN = 108 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INVALID_REQUEST_PARAM = 109 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INVALID_RESPONSE_TYPE = 110 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_UNAUTHORIZED = 111 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_FORBIDDEN = 112 + OAUTH_SERVER_ERRNO_OFFSET; +export let ERRNO_INVALID_CONTENT_TYPE = 113 + OAUTH_SERVER_ERRNO_OFFSET; + +// Errors. +export let ERROR_ACCOUNT_ALREADY_EXISTS = "ACCOUNT_ALREADY_EXISTS"; +export let ERROR_ACCOUNT_DOES_NOT_EXIST = "ACCOUNT_DOES_NOT_EXIST "; +export let ERROR_ACCOUNT_LOCKED = "ACCOUNT_LOCKED"; +export let ERROR_ACCOUNT_UNLOCKED = "ACCOUNT_UNLOCKED"; +export let ERROR_ALREADY_SIGNED_IN_USER = "ALREADY_SIGNED_IN_USER"; +export let ERROR_DEVICE_SESSION_CONFLICT = "DEVICE_SESSION_CONFLICT"; +export let ERROR_ENDPOINT_NO_LONGER_SUPPORTED = "ENDPOINT_NO_LONGER_SUPPORTED"; +export let ERROR_INCORRECT_API_VERSION = "INCORRECT_API_VERSION"; +export let ERROR_INCORRECT_EMAIL_CASE = "INCORRECT_EMAIL_CASE"; +export let ERROR_INCORRECT_KEY_RETRIEVAL_METHOD = + "INCORRECT_KEY_RETRIEVAL_METHOD"; +export let ERROR_INCORRECT_LOGIN_METHOD = "INCORRECT_LOGIN_METHOD"; +export let ERROR_INVALID_EMAIL = "INVALID_EMAIL"; +export let ERROR_INVALID_AUDIENCE = "INVALID_AUDIENCE"; +export let ERROR_INVALID_AUTH_TOKEN = "INVALID_AUTH_TOKEN"; +export let ERROR_INVALID_AUTH_TIMESTAMP = "INVALID_AUTH_TIMESTAMP"; +export let ERROR_INVALID_AUTH_NONCE = "INVALID_AUTH_NONCE"; +export let ERROR_INVALID_BODY_PARAMETERS = "INVALID_BODY_PARAMETERS"; +export let ERROR_INVALID_PASSWORD = "INVALID_PASSWORD"; +export let ERROR_INVALID_VERIFICATION_CODE = "INVALID_VERIFICATION_CODE"; +export let ERROR_INVALID_REFRESH_AUTH_VALUE = "INVALID_REFRESH_AUTH_VALUE"; +export let ERROR_INVALID_REQUEST_SIGNATURE = "INVALID_REQUEST_SIGNATURE"; +export let ERROR_INTERNAL_INVALID_USER = "INTERNAL_ERROR_INVALID_USER"; +export let ERROR_MISSING_BODY_PARAMETERS = "MISSING_BODY_PARAMETERS"; +export let ERROR_MISSING_CONTENT_LENGTH = "MISSING_CONTENT_LENGTH"; +export let ERROR_NO_TOKEN_SESSION = "NO_TOKEN_SESSION"; +export let ERROR_NO_SILENT_REFRESH_AUTH = "NO_SILENT_REFRESH_AUTH"; +export let ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY"; +export let ERROR_OFFLINE = "OFFLINE"; +export let ERROR_PERMISSION_DENIED = "PERMISSION_DENIED"; +export let ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE"; +export let ERROR_SERVER_ERROR = "SERVER_ERROR"; +export let ERROR_SYNC_DISABLED = "SYNC_DISABLED"; +export let ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS"; +export let ERROR_SERVICE_TEMP_UNAVAILABLE = "SERVICE_TEMPORARY_UNAVAILABLE"; +export let ERROR_UI_ERROR = "UI_ERROR"; +export let ERROR_UI_REQUEST = "UI_REQUEST"; +export let ERROR_PARSE = "PARSE_ERROR"; +export let ERROR_NETWORK = "NETWORK_ERROR"; +export let ERROR_UNKNOWN = "UNKNOWN_ERROR"; +export let ERROR_UNKNOWN_DEVICE = "UNKNOWN_DEVICE"; +export let ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT"; + +// OAuth errors. +export let ERROR_UNKNOWN_CLIENT_ID = "UNKNOWN_CLIENT_ID"; +export let ERROR_INCORRECT_CLIENT_SECRET = "INCORRECT_CLIENT_SECRET"; +export let ERROR_INCORRECT_REDIRECT_URI = "INCORRECT_REDIRECT_URI"; +export let ERROR_INVALID_FXA_ASSERTION = "INVALID_FXA_ASSERTION"; +export let ERROR_UNKNOWN_CODE = "UNKNOWN_CODE"; +export let ERROR_INCORRECT_CODE = "INCORRECT_CODE"; +export let ERROR_EXPIRED_CODE = "EXPIRED_CODE"; +export let ERROR_OAUTH_INVALID_TOKEN = "OAUTH_INVALID_TOKEN"; +export let ERROR_INVALID_REQUEST_PARAM = "INVALID_REQUEST_PARAM"; +export let ERROR_INVALID_RESPONSE_TYPE = "INVALID_RESPONSE_TYPE"; +export let ERROR_UNAUTHORIZED = "UNAUTHORIZED"; +export let ERROR_FORBIDDEN = "FORBIDDEN"; +export let ERROR_INVALID_CONTENT_TYPE = "INVALID_CONTENT_TYPE"; + +// Additional generic error classes for external consumers +export let ERROR_NO_ACCOUNT = "NO_ACCOUNT"; +export let ERROR_AUTH_ERROR = "AUTH_ERROR"; +export let ERROR_INVALID_PARAMETER = "INVALID_PARAMETER"; + +// Status code errors +export let ERROR_CODE_METHOD_NOT_ALLOWED = 405; +export let ERROR_MSG_METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED"; + +// FxAccounts has the ability to "split" the credentials between a plain-text +// JSON file in the profile dir and in the login manager. +// In order to prevent new fields accidentally ending up in the "wrong" place, +// all fields stored are listed here. + +// The fields we save in the plaintext JSON. +// See bug 1013064 comments 23-25 for why the sessionToken is "safe" +export let FXA_PWDMGR_PLAINTEXT_FIELDS = new Set([ + "email", + "verified", + "authAt", + "sessionToken", + "uid", + "oauthTokens", + "profile", + "device", + "profileCache", + "encryptedSendTabKeys", +]); + +// Fields we store in secure storage if it exists. +export let FXA_PWDMGR_SECURE_FIELDS = new Set([ + "keyFetchToken", + "unwrapBKey", + "scopedKeys", +]); + +// An allowlist of fields that remain in storage when the user needs to +// reauthenticate. All other fields will be removed. +export let FXA_PWDMGR_REAUTH_ALLOWLIST = new Set([ + "email", + "uid", + "profile", + "device", + "verified", +]); + +// The pseudo-host we use in the login manager +export let FXA_PWDMGR_HOST = "chrome://FirefoxAccounts"; +// The realm we use in the login manager. +export let FXA_PWDMGR_REALM = "Firefox Accounts credentials"; + +// Error matching. +export let SERVER_ERRNO_TO_ERROR = { + [ERRNO_ACCOUNT_ALREADY_EXISTS]: ERROR_ACCOUNT_ALREADY_EXISTS, + [ERRNO_ACCOUNT_DOES_NOT_EXIST]: ERROR_ACCOUNT_DOES_NOT_EXIST, + [ERRNO_INCORRECT_PASSWORD]: ERROR_INVALID_PASSWORD, + [ERRNO_UNVERIFIED_ACCOUNT]: ERROR_UNVERIFIED_ACCOUNT, + [ERRNO_INVALID_VERIFICATION_CODE]: ERROR_INVALID_VERIFICATION_CODE, + [ERRNO_NOT_VALID_JSON_BODY]: ERROR_NOT_VALID_JSON_BODY, + [ERRNO_INVALID_BODY_PARAMETERS]: ERROR_INVALID_BODY_PARAMETERS, + [ERRNO_MISSING_BODY_PARAMETERS]: ERROR_MISSING_BODY_PARAMETERS, + [ERRNO_INVALID_REQUEST_SIGNATURE]: ERROR_INVALID_REQUEST_SIGNATURE, + [ERRNO_INVALID_AUTH_TOKEN]: ERROR_INVALID_AUTH_TOKEN, + [ERRNO_INVALID_AUTH_TIMESTAMP]: ERROR_INVALID_AUTH_TIMESTAMP, + [ERRNO_MISSING_CONTENT_LENGTH]: ERROR_MISSING_CONTENT_LENGTH, + [ERRNO_REQUEST_BODY_TOO_LARGE]: ERROR_REQUEST_BODY_TOO_LARGE, + [ERRNO_TOO_MANY_CLIENT_REQUESTS]: ERROR_TOO_MANY_CLIENT_REQUESTS, + [ERRNO_INVALID_AUTH_NONCE]: ERROR_INVALID_AUTH_NONCE, + [ERRNO_ENDPOINT_NO_LONGER_SUPPORTED]: ERROR_ENDPOINT_NO_LONGER_SUPPORTED, + [ERRNO_INCORRECT_LOGIN_METHOD]: ERROR_INCORRECT_LOGIN_METHOD, + [ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD]: ERROR_INCORRECT_KEY_RETRIEVAL_METHOD, + [ERRNO_INCORRECT_API_VERSION]: ERROR_INCORRECT_API_VERSION, + [ERRNO_INCORRECT_EMAIL_CASE]: ERROR_INCORRECT_EMAIL_CASE, + [ERRNO_ACCOUNT_LOCKED]: ERROR_ACCOUNT_LOCKED, + [ERRNO_ACCOUNT_UNLOCKED]: ERROR_ACCOUNT_UNLOCKED, + [ERRNO_UNKNOWN_DEVICE]: ERROR_UNKNOWN_DEVICE, + [ERRNO_DEVICE_SESSION_CONFLICT]: ERROR_DEVICE_SESSION_CONFLICT, + [ERRNO_SERVICE_TEMP_UNAVAILABLE]: ERROR_SERVICE_TEMP_UNAVAILABLE, + [ERRNO_UNKNOWN_ERROR]: ERROR_UNKNOWN, + [ERRNO_NETWORK]: ERROR_NETWORK, + // oauth + [ERRNO_UNKNOWN_CLIENT_ID]: ERROR_UNKNOWN_CLIENT_ID, + [ERRNO_INCORRECT_CLIENT_SECRET]: ERROR_INCORRECT_CLIENT_SECRET, + [ERRNO_INCORRECT_REDIRECT_URI]: ERROR_INCORRECT_REDIRECT_URI, + [ERRNO_INVALID_FXA_ASSERTION]: ERROR_INVALID_FXA_ASSERTION, + [ERRNO_UNKNOWN_CODE]: ERROR_UNKNOWN_CODE, + [ERRNO_INCORRECT_CODE]: ERROR_INCORRECT_CODE, + [ERRNO_EXPIRED_CODE]: ERROR_EXPIRED_CODE, + [ERRNO_OAUTH_INVALID_TOKEN]: ERROR_OAUTH_INVALID_TOKEN, + [ERRNO_INVALID_REQUEST_PARAM]: ERROR_INVALID_REQUEST_PARAM, + [ERRNO_INVALID_RESPONSE_TYPE]: ERROR_INVALID_RESPONSE_TYPE, + [ERRNO_UNAUTHORIZED]: ERROR_UNAUTHORIZED, + [ERRNO_FORBIDDEN]: ERROR_FORBIDDEN, + [ERRNO_INVALID_CONTENT_TYPE]: ERROR_INVALID_CONTENT_TYPE, +}; + +// Map internal errors to more generic error classes for consumers +export let ERROR_TO_GENERAL_ERROR_CLASS = { + [ERROR_ACCOUNT_ALREADY_EXISTS]: ERROR_AUTH_ERROR, + [ERROR_ACCOUNT_DOES_NOT_EXIST]: ERROR_AUTH_ERROR, + [ERROR_ACCOUNT_LOCKED]: ERROR_AUTH_ERROR, + [ERROR_ACCOUNT_UNLOCKED]: ERROR_AUTH_ERROR, + [ERROR_ALREADY_SIGNED_IN_USER]: ERROR_AUTH_ERROR, + [ERROR_DEVICE_SESSION_CONFLICT]: ERROR_AUTH_ERROR, + [ERROR_ENDPOINT_NO_LONGER_SUPPORTED]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_API_VERSION]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_EMAIL_CASE]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_KEY_RETRIEVAL_METHOD]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_LOGIN_METHOD]: ERROR_AUTH_ERROR, + [ERROR_INVALID_EMAIL]: ERROR_AUTH_ERROR, + [ERROR_INVALID_AUDIENCE]: ERROR_AUTH_ERROR, + [ERROR_INVALID_AUTH_TOKEN]: ERROR_AUTH_ERROR, + [ERROR_INVALID_AUTH_TIMESTAMP]: ERROR_AUTH_ERROR, + [ERROR_INVALID_AUTH_NONCE]: ERROR_AUTH_ERROR, + [ERROR_INVALID_BODY_PARAMETERS]: ERROR_AUTH_ERROR, + [ERROR_INVALID_PASSWORD]: ERROR_AUTH_ERROR, + [ERROR_INVALID_VERIFICATION_CODE]: ERROR_AUTH_ERROR, + [ERROR_INVALID_REFRESH_AUTH_VALUE]: ERROR_AUTH_ERROR, + [ERROR_INVALID_REQUEST_SIGNATURE]: ERROR_AUTH_ERROR, + [ERROR_INTERNAL_INVALID_USER]: ERROR_AUTH_ERROR, + [ERROR_MISSING_BODY_PARAMETERS]: ERROR_AUTH_ERROR, + [ERROR_MISSING_CONTENT_LENGTH]: ERROR_AUTH_ERROR, + [ERROR_NO_TOKEN_SESSION]: ERROR_AUTH_ERROR, + [ERROR_NO_SILENT_REFRESH_AUTH]: ERROR_AUTH_ERROR, + [ERROR_NOT_VALID_JSON_BODY]: ERROR_AUTH_ERROR, + [ERROR_PERMISSION_DENIED]: ERROR_AUTH_ERROR, + [ERROR_REQUEST_BODY_TOO_LARGE]: ERROR_AUTH_ERROR, + [ERROR_UNKNOWN_DEVICE]: ERROR_AUTH_ERROR, + [ERROR_UNVERIFIED_ACCOUNT]: ERROR_AUTH_ERROR, + [ERROR_UI_ERROR]: ERROR_AUTH_ERROR, + [ERROR_UI_REQUEST]: ERROR_AUTH_ERROR, + [ERROR_OFFLINE]: ERROR_NETWORK, + [ERROR_SERVER_ERROR]: ERROR_NETWORK, + [ERROR_TOO_MANY_CLIENT_REQUESTS]: ERROR_NETWORK, + [ERROR_SERVICE_TEMP_UNAVAILABLE]: ERROR_NETWORK, + [ERROR_PARSE]: ERROR_NETWORK, + [ERROR_NETWORK]: ERROR_NETWORK, + + // oauth + [ERROR_INCORRECT_CLIENT_SECRET]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_REDIRECT_URI]: ERROR_AUTH_ERROR, + [ERROR_INVALID_FXA_ASSERTION]: ERROR_AUTH_ERROR, + [ERROR_UNKNOWN_CODE]: ERROR_AUTH_ERROR, + [ERROR_INCORRECT_CODE]: ERROR_AUTH_ERROR, + [ERROR_EXPIRED_CODE]: ERROR_AUTH_ERROR, + [ERROR_OAUTH_INVALID_TOKEN]: ERROR_AUTH_ERROR, + [ERROR_INVALID_REQUEST_PARAM]: ERROR_AUTH_ERROR, + [ERROR_INVALID_RESPONSE_TYPE]: ERROR_AUTH_ERROR, + [ERROR_UNAUTHORIZED]: ERROR_AUTH_ERROR, + [ERROR_FORBIDDEN]: ERROR_AUTH_ERROR, + [ERROR_INVALID_CONTENT_TYPE]: ERROR_AUTH_ERROR, +}; diff --git a/services/fxaccounts/FxAccountsConfig.sys.mjs b/services/fxaccounts/FxAccountsConfig.sys.mjs new file mode 100644 index 0000000000..cf26704a50 --- /dev/null +++ b/services/fxaccounts/FxAccountsConfig.sys.mjs @@ -0,0 +1,360 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { RESTRequest } from "resource://services-common/rest.sys.mjs"; + +import { + log, + SCOPE_OLD_SYNC, + SCOPE_PROFILE, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +ChromeUtils.defineESModuleGetters(lazy, { + EnsureFxAccountsWebChannel: + "resource://gre/modules/FxAccountsWebChannel.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "ROOT_URL", + "identity.fxaccounts.remote.root" +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "CONTEXT_PARAM", + "identity.fxaccounts.contextParam" +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "REQUIRES_HTTPS", + "identity.fxaccounts.allowHttp", + false, + null, + val => !val +); + +const CONFIG_PREFS = [ + "identity.fxaccounts.remote.root", + "identity.fxaccounts.auth.uri", + "identity.fxaccounts.remote.oauth.uri", + "identity.fxaccounts.remote.profile.uri", + "identity.fxaccounts.remote.pairing.uri", + "identity.sync.tokenserver.uri", +]; +const SYNC_PARAM = "sync"; + +export var FxAccountsConfig = { + async promiseEmailURI(email, entrypoint, extraParams = {}) { + const authParams = await this._getAuthParams(); + return this._buildURL("", { + extraParams: { + entrypoint, + email, + ...authParams, + ...extraParams, + }, + }); + }, + + async promiseConnectAccountURI(entrypoint, extraParams = {}) { + const authParams = await this._getAuthParams(); + return this._buildURL("", { + extraParams: { + entrypoint, + action: "email", + ...authParams, + ...extraParams, + }, + }); + }, + + async promiseForceSigninURI(entrypoint, extraParams = {}) { + const authParams = await this._getAuthParams(); + return this._buildURL("force_auth", { + extraParams: { entrypoint, ...authParams, ...extraParams }, + addAccountIdentifiers: true, + }); + }, + + async promiseManageURI(entrypoint, extraParams = {}) { + return this._buildURL("settings", { + extraParams: { entrypoint, ...extraParams }, + addAccountIdentifiers: true, + }); + }, + + async promiseChangeAvatarURI(entrypoint, extraParams = {}) { + return this._buildURL("settings/avatar/change", { + extraParams: { entrypoint, ...extraParams }, + addAccountIdentifiers: true, + }); + }, + + async promiseManageDevicesURI(entrypoint, extraParams = {}) { + return this._buildURL("settings/clients", { + extraParams: { entrypoint, ...extraParams }, + addAccountIdentifiers: true, + }); + }, + + async promiseConnectDeviceURI(entrypoint, extraParams = {}) { + return this._buildURL("connect_another_device", { + extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams }, + addAccountIdentifiers: true, + }); + }, + + async promisePairingURI(extraParams = {}) { + return this._buildURL("pair", { + extraParams, + includeDefaultParams: false, + }); + }, + + async promiseOAuthURI(extraParams = {}) { + return this._buildURL("oauth", { + extraParams, + includeDefaultParams: false, + }); + }, + + async promiseMetricsFlowURI(entrypoint, extraParams = {}) { + return this._buildURL("metrics-flow", { + extraParams: { entrypoint, ...extraParams }, + includeDefaultParams: false, + }); + }, + + get defaultParams() { + return { context: lazy.CONTEXT_PARAM }; + }, + + /** + * @param path should be parsable by the URL constructor first parameter. + * @param {bool} [options.includeDefaultParams] If true include the default search params. + * @param {Object.<string, string>} [options.extraParams] Additionnal search params. + * @param {bool} [options.addAccountIdentifiers] if true we add the current logged-in user uid and email to the search params. + */ + async _buildURL( + path, + { + includeDefaultParams = true, + extraParams = {}, + addAccountIdentifiers = false, + } + ) { + await this.ensureConfigured(); + const url = new URL(path, lazy.ROOT_URL); + if (lazy.REQUIRES_HTTPS && url.protocol != "https:") { + throw new Error("Firefox Accounts server must use HTTPS"); + } + const params = { + ...(includeDefaultParams ? this.defaultParams : null), + ...extraParams, + }; + for (let [k, v] of Object.entries(params)) { + url.searchParams.append(k, v); + } + if (addAccountIdentifiers) { + const accountData = await this.getSignedInUser(); + if (!accountData) { + return null; + } + url.searchParams.append("uid", accountData.uid); + url.searchParams.append("email", accountData.email); + } + return url.href; + }, + + async _buildURLFromString(href, extraParams = {}) { + const url = new URL(href); + for (let [k, v] of Object.entries(extraParams)) { + url.searchParams.append(k, v); + } + return url.href; + }, + + resetConfigURLs() { + let autoconfigURL = this.getAutoConfigURL(); + if (!autoconfigURL) { + return; + } + // They have the autoconfig uri pref set, so we clear all the prefs that we + // will have initialized, which will leave them pointing at production. + for (let pref of CONFIG_PREFS) { + Services.prefs.clearUserPref(pref); + } + // Reset the webchannel. + lazy.EnsureFxAccountsWebChannel(); + }, + + getAutoConfigURL() { + let pref = Services.prefs.getStringPref( + "identity.fxaccounts.autoconfig.uri", + "" + ); + if (!pref) { + // no pref / empty pref means we don't bother here. + return ""; + } + let rootURL = Services.urlFormatter.formatURL(pref); + if (rootURL.endsWith("/")) { + rootURL = rootURL.slice(0, -1); + } + return rootURL; + }, + + async ensureConfigured() { + let isSignedIn = !!(await this.getSignedInUser()); + if (!isSignedIn) { + await this.updateConfigURLs(); + } + }, + + // Returns true if this user is using the FxA "production" systems, false + // if using any other configuration, including self-hosting or the FxA + // non-production systems such as "dev" or "staging". + // It's typically used as a proxy for "is this likely to be a self-hosted + // user?", but it's named this way to make the implementation less + // surprising. As a result, it's fairly conservative and would prefer to have + // a false-negative than a false-position as it determines things which users + // might consider sensitive (notably, telemetry). + // Note also that while it's possible to self-host just sync and not FxA, we + // don't make that distinction - that's a self-hoster from the POV of this + // function. + isProductionConfig() { + // Specifically, if the autoconfig URLs, or *any* of the URLs that + // we consider configurable are modified, we assume self-hosted. + if (this.getAutoConfigURL()) { + return false; + } + for (let pref of CONFIG_PREFS) { + if (Services.prefs.prefHasUserValue(pref)) { + return false; + } + } + return true; + }, + + // Read expected client configuration from the fxa auth server + // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration) + // and replace all the relevant our prefs with the information found there. + // This is only done before sign-in and sign-up, and even then only if the + // `identity.fxaccounts.autoconfig.uri` preference is set. + async updateConfigURLs() { + let rootURL = this.getAutoConfigURL(); + if (!rootURL) { + return; + } + const config = await this.fetchConfigDocument(rootURL); + try { + // Update the prefs directly specified by the config. + let authServerBase = config.auth_server_base_url; + if (!authServerBase.endsWith("/v1")) { + authServerBase += "/v1"; + } + Services.prefs.setStringPref( + "identity.fxaccounts.auth.uri", + authServerBase + ); + Services.prefs.setStringPref( + "identity.fxaccounts.remote.oauth.uri", + config.oauth_server_base_url + "/v1" + ); + // At the time of landing this, our servers didn't yet answer with pairing_server_base_uri. + // Remove this condition check once Firefox 68 is stable. + if (config.pairing_server_base_uri) { + Services.prefs.setStringPref( + "identity.fxaccounts.remote.pairing.uri", + config.pairing_server_base_uri + ); + } + Services.prefs.setStringPref( + "identity.fxaccounts.remote.profile.uri", + config.profile_server_base_url + "/v1" + ); + Services.prefs.setStringPref( + "identity.sync.tokenserver.uri", + config.sync_tokenserver_base_url + "/1.0/sync/1.5" + ); + Services.prefs.setStringPref("identity.fxaccounts.remote.root", rootURL); + + // Ensure the webchannel is pointed at the correct uri + lazy.EnsureFxAccountsWebChannel(); + } catch (e) { + log.error( + "Failed to initialize configuration preferences from autoconfig object", + e + ); + throw e; + } + }, + + // Read expected client configuration from the fxa auth server + // (or from the provided rootURL, if present) and return it as an object. + async fetchConfigDocument(rootURL = null) { + if (!rootURL) { + rootURL = lazy.ROOT_URL; + } + let configURL = rootURL + "/.well-known/fxa-client-configuration"; + let request = new RESTRequest(configURL); + request.setHeader("Accept", "application/json"); + + // Catch and rethrow the error inline. + let resp = await request.get().catch(e => { + log.error(`Failed to get configuration object from "${configURL}"`, e); + throw e; + }); + if (!resp.success) { + // Note: 'resp.body' is included with the error log below as we are not concerned + // that the body will contain PII, but if that changes it should be excluded. + log.error( + `Received HTTP response code ${resp.status} from configuration object request: + ${resp.body}` + ); + throw new Error( + `HTTP status ${resp.status} from configuration object request` + ); + } + log.debug("Got successful configuration response", resp.body); + try { + return JSON.parse(resp.body); + } catch (e) { + log.error( + `Failed to parse configuration preferences from ${configURL}`, + e + ); + throw e; + } + }, + + // For test purposes, returns a Promise. + getSignedInUser() { + return lazy.fxAccounts.getSignedInUser(); + }, + + _isOAuthFlow() { + return Services.prefs.getBoolPref( + "identity.fxaccounts.oauth.enabled", + false + ); + }, + + async _getAuthParams() { + if (this._isOAuthFlow()) { + const scopes = [SCOPE_OLD_SYNC, SCOPE_PROFILE]; + return lazy.fxAccounts._internal.beginOAuthFlow(scopes); + } + return { service: SYNC_PARAM }; + }, +}; diff --git a/services/fxaccounts/FxAccountsDevice.sys.mjs b/services/fxaccounts/FxAccountsDevice.sys.mjs new file mode 100644 index 0000000000..6b2089739c --- /dev/null +++ b/services/fxaccounts/FxAccountsDevice.sys.mjs @@ -0,0 +1,640 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { + log, + ERRNO_DEVICE_SESSION_CONFLICT, + ERRNO_UNKNOWN_DEVICE, + ON_NEW_DEVICE_ID, + ON_DEVICELIST_UPDATED, + ON_DEVICE_CONNECTED_NOTIFICATION, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + ONVERIFIED_NOTIFICATION, + PREF_ACCOUNT_ROOT, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { DEVICE_TYPE_DESKTOP } from "resource://services-sync/constants.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CommonUtils: "resource://services-common/utils.sys.mjs", +}); + +const PREF_LOCAL_DEVICE_NAME = PREF_ACCOUNT_ROOT + "device.name"; +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "pref_localDeviceName", + PREF_LOCAL_DEVICE_NAME, + "" +); + +const PREF_DEPRECATED_DEVICE_NAME = "services.sync.client.name"; + +// Sanitizes all characters which the FxA server considers invalid, replacing +// them with the unicode replacement character. +// At time of writing, FxA has a regex DISPLAY_SAFE_UNICODE_WITH_NON_BMP, which +// the regex below is based on. +const INVALID_NAME_CHARS = + // eslint-disable-next-line no-control-regex + /[\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFC\uFFFE-\uFFFF]/g; +const MAX_NAME_LEN = 255; +const REPLACEMENT_CHAR = "\uFFFD"; + +function sanitizeDeviceName(name) { + return name + .substr(0, MAX_NAME_LEN) + .replace(INVALID_NAME_CHARS, REPLACEMENT_CHAR); +} + +// Everything to do with FxA devices. +export class FxAccountsDevice { + constructor(fxai) { + this._fxai = fxai; + this._deviceListCache = null; + this._fetchAndCacheDeviceListPromise = null; + + // The current version of the device registration, we use this to re-register + // devices after we update what we send on device registration. + this.DEVICE_REGISTRATION_VERSION = 2; + + // This is to avoid multiple sequential syncs ending up calling + // this expensive endpoint multiple times in a row. + this.TIME_BETWEEN_FXA_DEVICES_FETCH_MS = 1 * 60 * 1000; // 1 minute + + // Invalidate our cached device list when a device is connected or disconnected. + Services.obs.addObserver(this, ON_DEVICE_CONNECTED_NOTIFICATION, true); + Services.obs.addObserver(this, ON_DEVICE_DISCONNECTED_NOTIFICATION, true); + // A user becoming verified probably means we need to re-register the device + // because we are now able to get the sendtab keys. + Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, true); + } + + async getLocalId() { + return this._withCurrentAccountState(currentState => { + // It turns out _updateDeviceRegistrationIfNecessary() does exactly what we + // need. + return this._updateDeviceRegistrationIfNecessary(currentState); + }); + } + + // Generate a client name if we don't have a useful one yet + getDefaultLocalName() { + let user = Services.env.get("USER") || Services.env.get("USERNAME"); + // Note that we used to fall back to the "services.sync.username" pref here, + // but that's no longer suitable in a world where sync might not be + // configured. However, we almost never *actually* fell back to that, and + // doing so sanely here would mean making this function async, which we don't + // really want to do yet. + + // A little hack for people using the the moz-build environment on Windows + // which sets USER to the literal "%USERNAME%" (yes, really) + if (user == "%USERNAME%" && Services.env.get("USERNAME")) { + user = Services.env.get("USERNAME"); + } + + // The DNS service may fail to provide a hostname in edge-cases we don't + // fully understand - bug 1391488. + let hostname; + try { + // hostname of the system, usually assigned by the user or admin + hostname = Services.dns.myHostName; + } catch (ex) { + console.error(ex); + } + let system = + // 'device' is defined on unix systems + Services.sysinfo.get("device") || + hostname || + // fall back on ua info string + Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ).oscpu; + + const l10n = new Localization( + ["services/accounts.ftl", "branding/brand.ftl"], + true + ); + return sanitizeDeviceName( + l10n.formatValueSync("account-client-name", { user, system }) + ); + } + + getLocalName() { + // We used to store this in services.sync.client.name, but now store it + // under an fxa-specific location. + let deprecated_value = Services.prefs.getStringPref( + PREF_DEPRECATED_DEVICE_NAME, + "" + ); + if (deprecated_value) { + Services.prefs.setStringPref(PREF_LOCAL_DEVICE_NAME, deprecated_value); + Services.prefs.clearUserPref(PREF_DEPRECATED_DEVICE_NAME); + } + let name = lazy.pref_localDeviceName; + if (!name) { + name = this.getDefaultLocalName(); + Services.prefs.setStringPref(PREF_LOCAL_DEVICE_NAME, name); + } + // We need to sanitize here because some names were generated before we + // started sanitizing. + return sanitizeDeviceName(name); + } + + setLocalName(newName) { + Services.prefs.clearUserPref(PREF_DEPRECATED_DEVICE_NAME); + Services.prefs.setStringPref( + PREF_LOCAL_DEVICE_NAME, + sanitizeDeviceName(newName) + ); + // Update the registration in the background. + this.updateDeviceRegistration().catch(error => { + log.warn("failed to update fxa device registration", error); + }); + } + + getLocalType() { + return DEVICE_TYPE_DESKTOP; + } + + /** + * Returns the most recently fetched device list, or `null` if the list + * hasn't been fetched yet. This is synchronous, so that consumers like + * Send Tab can render the device list right away, without waiting for + * it to refresh. + * + * @type {?Array} + */ + get recentDeviceList() { + return this._deviceListCache ? this._deviceListCache.devices : null; + } + + /** + * Refreshes the device list. After this function returns, consumers can + * access the new list using the `recentDeviceList` getter. Note that + * multiple concurrent calls to `refreshDeviceList` will only refresh the + * list once. + * + * @param {Boolean} [options.ignoreCached] + * If `true`, forces a refresh, even if the cached device list is + * still fresh. Defaults to `false`. + * @return {Promise<Boolean>} + * `true` if the list was refreshed, `false` if the cached list is + * fresh. Rejects if an error occurs refreshing the list or device + * push registration. + */ + async refreshDeviceList({ ignoreCached = false } = {}) { + // If we're already refreshing the list in the background, let that finish. + if (this._fetchAndCacheDeviceListPromise) { + log.info("Already fetching device list, return existing promise"); + return this._fetchAndCacheDeviceListPromise; + } + + // If the cache is fresh enough, don't refresh it again. + if (!ignoreCached && this._deviceListCache) { + const ageOfCache = this._fxai.now() - this._deviceListCache.lastFetch; + if (ageOfCache < this.TIME_BETWEEN_FXA_DEVICES_FETCH_MS) { + log.info("Device list cache is fresh, re-using it"); + return false; + } + } + + log.info("fetching updated device list"); + this._fetchAndCacheDeviceListPromise = (async () => { + try { + const devices = await this._withVerifiedAccountState( + async currentState => { + const accountData = await currentState.getUserAccountData([ + "sessionToken", + "device", + ]); + const devices = await this._fxai.fxAccountsClient.getDeviceList( + accountData.sessionToken + ); + log.info( + `Got new device list: ${devices.map(d => d.id).join(", ")}` + ); + + await this._refreshRemoteDevice(currentState, accountData, devices); + return devices; + } + ); + log.info("updating the cache"); + // Be careful to only update the cache once the above has resolved, so + // we know that the current account state didn't change underneath us. + this._deviceListCache = { + lastFetch: this._fxai.now(), + devices, + }; + Services.obs.notifyObservers(null, ON_DEVICELIST_UPDATED); + return true; + } finally { + this._fetchAndCacheDeviceListPromise = null; + } + })(); + return this._fetchAndCacheDeviceListPromise; + } + + async _refreshRemoteDevice(currentState, accountData, remoteDevices) { + // Check if our push registration previously succeeded and is still + // good (although background device registration means it's possible + // we'll be fetching the device list before we've actually + // registered ourself!) + // (For a missing subscription we check for an explicit 'null' - + // both to help tests and as a safety valve - missing might mean + // "no push available" for self-hosters or similar?) + const ourDevice = remoteDevices.find(device => device.isCurrentDevice); + const subscription = await this._fxai.fxaPushService.getSubscription(); + if ( + ourDevice && + (ourDevice.pushCallback === null || // fxa server doesn't know our subscription. + ourDevice.pushEndpointExpired || // fxa server thinks it has expired. + !subscription || // we don't have a local subscription. + subscription.isExpired() || // our local subscription is expired. + ourDevice.pushCallback != subscription.endpoint) // we don't agree with fxa. + ) { + log.warn(`Our push endpoint needs resubscription`); + await this._fxai.fxaPushService.unsubscribe(); + await this._registerOrUpdateDevice(currentState, accountData); + // and there's a reasonable chance there are commands waiting. + await this._fxai.commands.pollDeviceCommands(); + } else if ( + ourDevice && + (await this._checkRemoteCommandsUpdateNeeded(ourDevice.availableCommands)) + ) { + log.warn(`Our commands need to be updated on the server`); + await this._registerOrUpdateDevice(currentState, accountData); + } else { + log.trace(`Our push subscription looks OK`); + } + } + + async updateDeviceRegistration() { + return this._withCurrentAccountState(async currentState => { + const signedInUser = await currentState.getUserAccountData([ + "sessionToken", + "device", + ]); + if (signedInUser) { + await this._registerOrUpdateDevice(currentState, signedInUser); + } + }); + } + + async updateDeviceRegistrationIfNecessary() { + return this._withCurrentAccountState(currentState => { + return this._updateDeviceRegistrationIfNecessary(currentState); + }); + } + + reset() { + this._deviceListCache = null; + this._fetchAndCacheDeviceListPromise = null; + } + + /** + * Here begin our internal helper methods. + * + * Many of these methods take the current account state as first argument, + * in order to avoid racing our state updates with e.g. the uer signing + * out while we're in the middle of an update. If this does happen, the + * resulting promise will be rejected rather than persisting stale state. + * + */ + + _withCurrentAccountState(func) { + return this._fxai.withCurrentAccountState(async currentState => { + try { + return await func(currentState); + } catch (err) { + // `_handleTokenError` always throws, this syntax keeps the linter happy. + // TODO: probably `_handleTokenError` could be done by `_fxai.withCurrentAccountState` + // internally rather than us having to remember to do it here. + throw await this._fxai._handleTokenError(err); + } + }); + } + + _withVerifiedAccountState(func) { + return this._fxai.withVerifiedAccountState(async currentState => { + try { + return await func(currentState); + } catch (err) { + // `_handleTokenError` always throws, this syntax keeps the linter happy. + throw await this._fxai._handleTokenError(err); + } + }); + } + + async _checkDeviceUpdateNeeded(device) { + // There is no device registered or the device registration is outdated. + // Either way, we should register the device with FxA + // before returning the id to the caller. + const availableCommandsKeys = Object.keys( + await this._fxai.commands.availableCommands() + ).sort(); + return ( + !device || + !device.registrationVersion || + device.registrationVersion < this.DEVICE_REGISTRATION_VERSION || + !device.registeredCommandsKeys || + !lazy.CommonUtils.arrayEqual( + device.registeredCommandsKeys, + availableCommandsKeys + ) + ); + } + + async _checkRemoteCommandsUpdateNeeded(remoteAvailableCommands) { + if (!remoteAvailableCommands) { + return true; + } + const remoteAvailableCommandsKeys = Object.keys( + remoteAvailableCommands + ).sort(); + const localAvailableCommands = + await this._fxai.commands.availableCommands(); + const localAvailableCommandsKeys = Object.keys( + localAvailableCommands + ).sort(); + + if ( + !lazy.CommonUtils.arrayEqual( + localAvailableCommandsKeys, + remoteAvailableCommandsKeys + ) + ) { + return true; + } + + for (const key of localAvailableCommandsKeys) { + if (remoteAvailableCommands[key] !== localAvailableCommands[key]) { + return true; + } + } + return false; + } + + async _updateDeviceRegistrationIfNecessary(currentState) { + let data = await currentState.getUserAccountData([ + "sessionToken", + "device", + ]); + if (!data) { + // Can't register a device without a signed-in user. + return null; + } + const { device } = data; + if (await this._checkDeviceUpdateNeeded(device)) { + return this._registerOrUpdateDevice(currentState, data); + } + // Return the device ID we already had. + return device.id; + } + + // If you change what we send to the FxA servers during device registration, + // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older + // devices to re-register when Firefox updates. + async _registerOrUpdateDevice(currentState, signedInUser) { + // This method has the side-effect of setting some account-related prefs + // (e.g. for caching the device name) so it's important we don't execute it + // if the signed-in state has changed. + if (!currentState.isCurrent) { + throw new Error( + "_registerOrUpdateDevice called after a different user has signed in" + ); + } + + const { sessionToken, device: currentDevice } = signedInUser; + if (!sessionToken) { + throw new Error("_registerOrUpdateDevice called without a session token"); + } + + try { + const subscription = + await this._fxai.fxaPushService.registerPushEndpoint(); + const deviceName = this.getLocalName(); + let deviceOptions = {}; + + // if we were able to obtain a subscription + if (subscription && subscription.endpoint) { + deviceOptions.pushCallback = subscription.endpoint; + let publicKey = subscription.getKey("p256dh"); + let authKey = subscription.getKey("auth"); + if (publicKey && authKey) { + deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey); + deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey); + } + } + deviceOptions.availableCommands = + await this._fxai.commands.availableCommands(); + const availableCommandsKeys = Object.keys( + deviceOptions.availableCommands + ).sort(); + log.info("registering with available commands", availableCommandsKeys); + + let device; + let is_existing = currentDevice && currentDevice.id; + if (is_existing) { + log.debug("updating existing device details"); + device = await this._fxai.fxAccountsClient.updateDevice( + sessionToken, + currentDevice.id, + deviceName, + deviceOptions + ); + } else { + log.debug("registering new device details"); + device = await this._fxai.fxAccountsClient.registerDevice( + sessionToken, + deviceName, + this.getLocalType(), + deviceOptions + ); + } + + // Get the freshest device props before updating them. + let { device: deviceProps } = await currentState.getUserAccountData([ + "device", + ]); + await currentState.updateUserAccountData({ + device: { + ...deviceProps, // Copy the other properties (e.g. handledCommands). + id: device.id, + registrationVersion: this.DEVICE_REGISTRATION_VERSION, + registeredCommandsKeys: availableCommandsKeys, + }, + }); + // Must send the notification after we've written the storage. + if (!is_existing) { + Services.obs.notifyObservers(null, ON_NEW_DEVICE_ID); + } + return device.id; + } catch (error) { + return this._handleDeviceError(currentState, error, sessionToken); + } + } + + async _handleDeviceError(currentState, error, sessionToken) { + try { + if (error.code === 400) { + if (error.errno === ERRNO_UNKNOWN_DEVICE) { + return this._recoverFromUnknownDevice(currentState); + } + + if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) { + return this._recoverFromDeviceSessionConflict( + currentState, + error, + sessionToken + ); + } + } + + // `_handleTokenError` always throws, this syntax keeps the linter happy. + // Note that the re-thrown error is immediately caught, logged and ignored + // by the containing scope here, which is why we have to `_handleTokenError` + // ourselves rather than letting it bubble up for handling by the caller. + throw await this._fxai._handleTokenError(error); + } catch (error) { + await this._logErrorAndResetDeviceRegistrationVersion( + currentState, + error + ); + return null; + } + } + + async _recoverFromUnknownDevice(currentState) { + // FxA did not recognise the device id. Handle it by clearing the device + // id on the account data. At next sync or next sign-in, registration is + // retried and should succeed. + log.warn("unknown device id, clearing the local device data"); + try { + await currentState.updateUserAccountData({ + device: null, + encryptedSendTabKeys: null, + }); + } catch (error) { + await this._logErrorAndResetDeviceRegistrationVersion( + currentState, + error + ); + } + return null; + } + + async _recoverFromDeviceSessionConflict(currentState, error, sessionToken) { + // FxA has already associated this session with a different device id. + // Perhaps we were beaten in a race to register. Handle the conflict: + // 1. Fetch the list of devices for the current user from FxA. + // 2. Look for ourselves in the list. + // 3. If we find a match, set the correct device id and device registration + // version on the account data and return the correct device id. At next + // sync or next sign-in, registration is retried and should succeed. + // 4. If we don't find a match, log the original error. + log.warn( + "device session conflict, attempting to ascertain the correct device id" + ); + try { + const devices = await this._fxai.fxAccountsClient.getDeviceList( + sessionToken + ); + const matchingDevices = devices.filter(device => device.isCurrentDevice); + const length = matchingDevices.length; + if (length === 1) { + const deviceId = matchingDevices[0].id; + await currentState.updateUserAccountData({ + device: { + id: deviceId, + registrationVersion: null, + }, + encryptedSendTabKeys: null, + }); + return deviceId; + } + if (length > 1) { + log.error( + "insane server state, " + length + " devices for this session" + ); + } + await this._logErrorAndResetDeviceRegistrationVersion( + currentState, + error + ); + } catch (secondError) { + log.error("failed to recover from device-session conflict", secondError); + await this._logErrorAndResetDeviceRegistrationVersion( + currentState, + error + ); + } + return null; + } + + async _logErrorAndResetDeviceRegistrationVersion(currentState, error) { + // Device registration should never cause other operations to fail. + // If we've reached this point, just log the error and reset the device + // on the account data. At next sync or next sign-in, + // registration will be retried. + log.error("device registration failed", error); + try { + await currentState.updateUserAccountData({ + device: null, + encryptedSendTabKeys: null, + }); + } catch (secondError) { + log.error( + "failed to reset the device registration version, device registration won't be retried", + secondError + ); + } + } + + // Kick off a background refresh when a device is connected or disconnected. + observe(subject, topic, data) { + switch (topic) { + case ON_DEVICE_CONNECTED_NOTIFICATION: + this.refreshDeviceList({ ignoreCached: true }).catch(error => { + log.warn( + "failed to refresh devices after connecting a new device", + error + ); + }); + break; + case ON_DEVICE_DISCONNECTED_NOTIFICATION: + let json = JSON.parse(data); + if (!json.isLocalDevice) { + // If we're the device being disconnected, don't bother fetching a new + // list, since our session token is now invalid. + this.refreshDeviceList({ ignoreCached: true }).catch(error => { + log.warn( + "failed to refresh devices after disconnecting a device", + error + ); + }); + } + break; + case ONVERIFIED_NOTIFICATION: + this.updateDeviceRegistrationIfNecessary().catch(error => { + log.warn( + "updateDeviceRegistrationIfNecessary failed after verification", + error + ); + }); + break; + } + } +} + +FxAccountsDevice.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", +]); + +function urlsafeBase64Encode(buffer) { + return ChromeUtils.base64URLEncode(new Uint8Array(buffer), { pad: false }); +} diff --git a/services/fxaccounts/FxAccountsKeys.sys.mjs b/services/fxaccounts/FxAccountsKeys.sys.mjs new file mode 100644 index 0000000000..ad19df31be --- /dev/null +++ b/services/fxaccounts/FxAccountsKeys.sys.mjs @@ -0,0 +1,649 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; + +import { + SCOPE_OLD_SYNC, + DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY, + FX_OAUTH_CLIENT_ID, + log, + logPII, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +// The following top-level fields have since been deprecated and exist here purely +// to be removed from the account state when seen. After a reasonable period of time +// has passed, where users have been migrated away from those keys they should be safe to be removed +const DEPRECATED_DERIVED_KEYS_NAMES = [ + "kSync", + "kXCS", + "kExtSync", + "kExtKbHash", + "ecosystemUserId", + "ecosystemAnonId", +]; + +// This scope and its associated key material were used by the old Kinto webextension +// storage backend, but has since been decommissioned. It's here entirely so that we +// remove the corresponding key from storage if present. We should be safe to remove it +// after some sensible period of time has elapsed to allow most clients to update. +const DEPRECATED_SCOPE_WEBEXT_SYNC = "sync:addon_storage"; + +// These are the scopes that correspond to new storage for the `LEGACY_DERIVED_KEYS_NAMES`. +// We will, if necessary, migrate storage for those keys so that it's associated with +// these scopes. +const LEGACY_DERIVED_KEY_SCOPES = [SCOPE_OLD_SYNC]; + +// These are scopes that we used to store, but are no longer using, +// and hence should be deleted from storage if present. +const DEPRECATED_KEY_SCOPES = [ + DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY, + DEPRECATED_SCOPE_WEBEXT_SYNC, +]; + +/** + * Utilities for working with key material linked to the user's account. + * + * Each Firefox Account has 32 bytes of root key material called `kB` which is + * linked to the user's password, and which is used to derive purpose-specific + * subkeys for things like encrypting the user's sync data. This class provides + * the interface for working with such key material. + * + * Most recent FxA clients obtain appropriate key material directly as part of + * their sign-in flow, using a special extension of the OAuth2.0 protocol to + * securely deliver the derived keys without revealing `kB`. Keys obtained in + * in this way are called "scoped keys" since each corresponds to a particular + * OAuth scope, and this class provides a `getKeyForScope` method that is the + * preferred method for consumers to work with such keys. + * + * However, since the FxA integration in Firefox Desktop pre-dates the use of + * OAuth2.0, we also have a lot of code for fetching keys via an older flow. + * This flow uses a special `keyFetchToken` to obtain `kB` and then derive various + * sub-keys from it. Consumers should consider this an internal implementation + * detail of the `FxAccountsKeys` class and should prefer `getKeyForScope` where + * possible. We intend to remove support for Firefox ever directly handling `kB` + * at some point in the future. + */ +export class FxAccountsKeys { + constructor(fxAccountsInternal) { + this._fxai = fxAccountsInternal; + } + + /** + * Checks if we currently have the key for a given scope, or if we have enough to + * be able to successfully fetch and unwrap it for the signed-in-user. + * + * Unlike `getKeyForScope`, this will not hit the network to fetch wrapped keys if + * they aren't available locally. + */ + canGetKeyForScope(scope) { + return this._fxai.withCurrentAccountState(async currentState => { + let userData = await currentState.getUserAccountData(); + if (!userData) { + throw new Error("Can't possibly get keys; User is not signed in"); + } + if (!userData.verified) { + log.info("Can't get keys; user is not verified"); + return false; + } + + if (userData.scopedKeys && userData.scopedKeys.hasOwnProperty(scope)) { + return true; + } + + // If we have a `keyFetchToken` we can fetch `kB`. + if (userData.keyFetchToken) { + return true; + } + + log.info("Can't get keys; no key material or tokens available"); + return false; + }); + } + + /** + * Get the key for a specified OAuth scope. + * + * @param {String} scope The OAuth scope whose key should be returned + * + * @return Promise<JWK> + * If no key is available the promise resolves to `null`. + * If a key is available for the given scope, th promise resolves to a JWK with fields: + * { + * scope: The requested scope + * kid: Key identifier + * k: Derived key material + * kty: Always "oct" for scoped keys + * } + * + */ + async getKeyForScope(scope) { + const { scopedKeys } = await this._loadOrFetchKeys(); + if (!scopedKeys.hasOwnProperty(scope)) { + throw new Error(`Key not available for scope "${scope}"`); + } + return { + scope, + ...scopedKeys[scope], + }; + } + + /** + * Format a JWK kid as hex rather than base64. + * + * This is a backwards-compatibility helper for code that needs a raw key fingerprint + * for use as a key identifier, rather than the timestamp+fingerprint format used by + * FxA scoped keys. + * + * @param {Object} jwk The JWK from which to extract the `kid` field as hex. + */ + kidAsHex(jwk) { + // The kid format is "{timestamp}-{b64url(fingerprint)}", but we have to be careful + // because the fingerprint component may contain "-" as well, and we want to ensure + // the timestamp component was non-empty. + const idx = jwk.kid.indexOf("-") + 1; + if (idx <= 1) { + throw new Error(`Invalid kid: ${jwk.kid}`); + } + return CommonUtils.base64urlToHex(jwk.kid.slice(idx)); + } + + /** + * Fetch encryption keys for the signed-in-user from the FxA API server. + * + * Not for user consumption. Exists to cause the keys to be fetched. + * + * Returns user data so that it can be chained with other methods. + * + * @return Promise + * The promise resolves to the credentials object of the signed-in user: + * { + * email: The user's email address + * uid: The user's unique id + * sessionToken: Session for the FxA server + * scopedKeys: Object mapping OAuth scopes to corresponding derived keys + * verified: email verification status + * } + * @throws If there is no user signed in. + */ + async _loadOrFetchKeys() { + return this._fxai.withCurrentAccountState(async currentState => { + try { + let userData = await currentState.getUserAccountData(); + if (!userData) { + throw new Error("Can't get keys; User is not signed in"); + } + // If we have all the keys in latest storage location, we're good. + if (userData.scopedKeys) { + if ( + LEGACY_DERIVED_KEY_SCOPES.every(scope => + userData.scopedKeys.hasOwnProperty(scope) + ) && + !DEPRECATED_KEY_SCOPES.some(scope => + userData.scopedKeys.hasOwnProperty(scope) + ) && + !DEPRECATED_DERIVED_KEYS_NAMES.some(keyName => + userData.hasOwnProperty(keyName) + ) + ) { + return userData; + } + } + // If not, we've got work to do, and we debounce to avoid duplicating it. + if (!currentState.whenKeysReadyDeferred) { + currentState.whenKeysReadyDeferred = Promise.withResolvers(); + // N.B. we deliberately don't `await` here, and instead use the promise + // to resolve `whenKeysReadyDeferred` (which we then `await` below). + this._migrateOrFetchKeys(currentState, userData).then( + dataWithKeys => { + currentState.whenKeysReadyDeferred.resolve(dataWithKeys); + currentState.whenKeysReadyDeferred = null; + }, + err => { + currentState.whenKeysReadyDeferred.reject(err); + currentState.whenKeysReadyDeferred = null; + } + ); + } + return await currentState.whenKeysReadyDeferred.promise; + } catch (err) { + return this._fxai._handleTokenError(err); + } + }); + } + + /** + * Set externally derived scoped keys in internal storage + * @param { Object } scopedKeys: The scoped keys object derived by the oauth flow + * + * @return { Promise }: A promise that resolves if the keys were successfully stored, + * or rejects if we failed to persist the keys, or if the user is not signed in already + */ + async setScopedKeys(scopedKeys) { + return this._fxai.withCurrentAccountState(async currentState => { + const userData = await currentState.getUserAccountData(); + if (!userData) { + throw new Error("Cannot persist keys, no user signed in"); + } + await currentState.updateUserAccountData({ + scopedKeys, + }); + }); + } + + /** + * Key storage migration or fetching logic. + * + * This method contains the doing-expensive-operations part of the logic of + * _loadOrFetchKeys(), factored out into a separate method so we can debounce it. + * + */ + async _migrateOrFetchKeys(currentState, userData) { + // If the required scopes are present in `scopedKeys`, then we know that we've + // previously applied all earlier migrations + // so we are safe to delete deprecated fields that older migrations + // might have depended on. + if ( + userData.scopedKeys && + LEGACY_DERIVED_KEY_SCOPES.every(scope => + userData.scopedKeys.hasOwnProperty(scope) + ) + ) { + return this._removeDeprecatedKeys(currentState, userData); + } + + // Otherwise, we need to fetch from the network and unwrap. + if (!userData.sessionToken) { + throw new Error("No sessionToken"); + } + if (!userData.keyFetchToken) { + throw new Error("No keyFetchToken"); + } + return this._fetchAndUnwrapAndDeriveKeys( + currentState, + userData.sessionToken, + userData.keyFetchToken + ); + } + + /** + * Removes deprecated keys from storage and returns an + * updated user data object + */ + async _removeDeprecatedKeys(currentState, userData) { + // Bug 1838708: Delete any deprecated high level keys from storage + const keysToRemove = DEPRECATED_DERIVED_KEYS_NAMES.filter(keyName => + userData.hasOwnProperty(keyName) + ); + if (keysToRemove.length) { + const removedKeys = {}; + for (const keyName of keysToRemove) { + removedKeys[keyName] = null; + } + await currentState.updateUserAccountData({ + ...removedKeys, + }); + userData = await currentState.getUserAccountData(); + } + // Bug 1697596 - delete any deprecated scoped keys from storage. + const scopesToRemove = DEPRECATED_KEY_SCOPES.filter(scope => + userData.scopedKeys.hasOwnProperty(scope) + ); + if (scopesToRemove.length) { + const updatedScopedKeys = { + ...userData.scopedKeys, + }; + for (const scope of scopesToRemove) { + delete updatedScopedKeys[scope]; + } + await currentState.updateUserAccountData({ + scopedKeys: updatedScopedKeys, + }); + userData = await currentState.getUserAccountData(); + } + return userData; + } + + /** + * Fetch keys from the server, unwrap them, and derive required sub-keys. + * + * Once the user's email is verified, we can resquest the root key `kB` from the + * FxA server, unwrap it using the client-side secret `unwrapBKey`, and then + * derive all the sub-keys required for operation of the browser. + */ + async _fetchAndUnwrapAndDeriveKeys( + currentState, + sessionToken, + keyFetchToken + ) { + if (logPII()) { + log.debug( + `fetchAndUnwrapKeys: sessionToken: ${sessionToken}, keyFetchToken: ${keyFetchToken}` + ); + } + + // Sign out if we don't have the necessary tokens. + if (!sessionToken || !keyFetchToken) { + // this seems really bad and we should remove this - bug 1572313. + log.warn("improper _fetchAndUnwrapKeys() call: token missing"); + await this._fxai.signOut(); + return null; + } + + // Deriving OAuth scoped keys requires additional metadata from the server. + // We fetch this first, before fetching the actual key material, because the + // keyFetchToken is single-use and we don't want to do a potentially-fallible + // operation after consuming it. + const scopedKeysMetadata = await this._fetchScopedKeysMetadata( + sessionToken + ); + + // Fetch the wrapped keys. + // It would be nice to be able to fetch this in a single operation with fetching + // the metadata above, but that requires server-side changes in FxA. + let { wrapKB } = await this._fetchKeys(keyFetchToken); + + let data = await currentState.getUserAccountData(); + + // Sanity check that the user hasn't changed out from under us (which should + // be impossible given this is called within _withCurrentAccountState, but...) + if (data.keyFetchToken !== keyFetchToken) { + throw new Error("Signed in user changed while fetching keys!"); + } + + let kBbytes = CryptoUtils.xor( + CommonUtils.hexToBytes(data.unwrapBKey), + wrapKB + ); + + if (logPII()) { + log.debug("kBbytes: " + kBbytes); + } + + let updateData = { + ...(await this._deriveKeys(data.uid, kBbytes, scopedKeysMetadata)), + keyFetchToken: null, // null values cause the item to be removed. + unwrapBKey: null, + }; + + if (logPII()) { + log.debug(`Keys Obtained: ${updateData.scopedKeys}`); + } else { + log.debug( + "Keys Obtained: " + Object.keys(updateData.scopedKeys).join(", ") + ); + } + + // Just double-check that scoped keys are there now + if (!updateData.scopedKeys) { + throw new Error(`user data missing: scopedKeys`); + } + + await currentState.updateUserAccountData(updateData); + return currentState.getUserAccountData(); + } + + /** + * Fetch the wrapped root key `wrapKB` from the FxA server. + * + * This consumes the single-use `keyFetchToken`. + */ + _fetchKeys(keyFetchToken) { + let client = this._fxai.fxAccountsClient; + log.debug( + `Fetching keys with token ${!!keyFetchToken} from ${client.host}` + ); + if (logPII()) { + log.debug("fetchKeys - the token is " + keyFetchToken); + } + return client.accountKeys(keyFetchToken); + } + + /** + * Fetch additional metadata required for deriving scoped keys. + * + * This includes timestamps and a server-provided secret to mix in to + * the derived value in order to support key rotation. + */ + async _fetchScopedKeysMetadata(sessionToken) { + // Hard-coded list of scopes that we know about. + // This list will probably grow in future. + const scopes = [SCOPE_OLD_SYNC].join(" "); + const scopedKeysMetadata = + await this._fxai.fxAccountsClient.getScopedKeyData( + sessionToken, + FX_OAUTH_CLIENT_ID, + scopes + ); + // The server may decline us permission for some of those scopes, although it really shouldn't. + // We can live without them...except for the OLDSYNC scope, whose absence would be catastrophic. + if (!scopedKeysMetadata.hasOwnProperty(SCOPE_OLD_SYNC)) { + log.warn( + "The FxA server did not grant Firefox the `oldsync` scope; this is most unexpected!" + + ` scopes were: ${Object.keys(scopedKeysMetadata)}` + ); + throw new Error( + "The FxA server did not grant Firefox the `oldsync` scope" + ); + } + return scopedKeysMetadata; + } + + /** + * Derive purpose-specific keys from the root FxA key `kB`. + * + * Everything that uses an encryption key from FxA uses a purpose-specific derived + * key. For new uses this is derived in a structured way based on OAuth scopes, + * while for legacy uses (mainly Firefox Sync) it is derived in a more ad-hoc fashion. + * This method does all the derivations for the uses that we know about. + * + */ + async _deriveKeys(uid, kBbytes, scopedKeysMetadata) { + const scopedKeys = await this._deriveScopedKeys( + uid, + kBbytes, + scopedKeysMetadata + ); + return { + scopedKeys, + }; + } + + /** + * Derive various scoped keys from the root FxA key `kB`. + * + * The `scopedKeysMetadata` object is additional information fetched from the server that + * that gets mixed in to the key derivation, with each member of the object corresponding + * to an OAuth scope that keys its own scoped key. + * + * As a special case for backwards-compatibility, sync-related scopes get special + * treatment to use a legacy derivation algorithm. + * + */ + async _deriveScopedKeys(uid, kBbytes, scopedKeysMetadata) { + const scopedKeys = {}; + for (const scope in scopedKeysMetadata) { + if (LEGACY_DERIVED_KEY_SCOPES.includes(scope)) { + scopedKeys[scope] = await this._deriveLegacyScopedKey( + uid, + kBbytes, + scope, + scopedKeysMetadata[scope] + ); + } else { + scopedKeys[scope] = await this._deriveScopedKey( + uid, + kBbytes, + scope, + scopedKeysMetadata[scope] + ); + } + } + return scopedKeys; + } + + /** + * Derive a scoped key for an individual OAuth scope. + * + * The derivation here uses HKDF to combine: + * - the root key material kB + * - a unique identifier for this scoped key + * - a server-provided secret that allows for key rotation + * - the account uid as an additional salt + * + * It produces 32 bytes of (secret) key material along with a (potentially public) + * key identifier, formatted as a JWK. + * + * The full details are in the technical docs at + * https://docs.google.com/document/d/1IvQJFEBFz0PnL4uVlIvt8fBS_IPwSK-avK0BRIHucxQ/ + */ + async _deriveScopedKey(uid, kBbytes, scope, scopedKeyMetadata) { + kBbytes = CommonUtils.byteStringToArrayBuffer(kBbytes); + + const FINGERPRINT_LENGTH = 16; + const KEY_LENGTH = 32; + const VALID_UID = /^[0-9a-f]{32}$/i; + const VALID_ROTATION_SECRET = /^[0-9a-f]{64}$/i; + + // Engage paranoia mode for input data. + if (!VALID_UID.test(uid)) { + throw new Error("uid must be a 32-character hex string"); + } + if (kBbytes.length != 32) { + throw new Error("kBbytes must be exactly 32 bytes"); + } + if ( + typeof scopedKeyMetadata.identifier !== "string" || + scopedKeyMetadata.identifier.length < 10 + ) { + throw new Error("identifier must be a string of length >= 10"); + } + if (typeof scopedKeyMetadata.keyRotationTimestamp !== "number") { + throw new Error("keyRotationTimestamp must be a number"); + } + if (!VALID_ROTATION_SECRET.test(scopedKeyMetadata.keyRotationSecret)) { + throw new Error("keyRotationSecret must be a 64-character hex string"); + } + + // The server returns milliseconds, we want seconds as a string. + const keyRotationTimestamp = + "" + Math.round(scopedKeyMetadata.keyRotationTimestamp / 1000); + if (keyRotationTimestamp.length < 10) { + throw new Error("keyRotationTimestamp must round to a 10-digit number"); + } + + const keyRotationSecret = CommonUtils.hexToArrayBuffer( + scopedKeyMetadata.keyRotationSecret + ); + const salt = CommonUtils.hexToArrayBuffer(uid); + const context = new TextEncoder().encode( + "identity.mozilla.com/picl/v1/scoped_key\n" + scopedKeyMetadata.identifier + ); + + const inputKey = new Uint8Array(64); + inputKey.set(kBbytes, 0); + inputKey.set(keyRotationSecret, 32); + + const derivedKeyMaterial = await CryptoUtils.hkdf( + inputKey, + salt, + context, + FINGERPRINT_LENGTH + KEY_LENGTH + ); + const fingerprint = derivedKeyMaterial.slice(0, FINGERPRINT_LENGTH); + const key = derivedKeyMaterial.slice( + FINGERPRINT_LENGTH, + FINGERPRINT_LENGTH + KEY_LENGTH + ); + + return { + kid: + keyRotationTimestamp + + "-" + + ChromeUtils.base64URLEncode(fingerprint, { + pad: false, + }), + k: ChromeUtils.base64URLEncode(key, { + pad: false, + }), + kty: "oct", + }; + } + + /** + * Derive the scoped key for the one of our legacy sync-related scopes. + * + * These uses a different key-derivation algoritm that incorporates less server-provided + * data, for backwards-compatibility reasons. + * + */ + async _deriveLegacyScopedKey(uid, kBbytes, scope, scopedKeyMetadata) { + let kid, key; + if (scope == SCOPE_OLD_SYNC) { + kid = await this._deriveXClientState(kBbytes); + key = await this._deriveSyncKey(kBbytes); + } else { + throw new Error(`Unexpected legacy key-bearing scope: ${scope}`); + } + kid = CommonUtils.byteStringToArrayBuffer(kid); + key = CommonUtils.byteStringToArrayBuffer(key); + return this._formatLegacyScopedKey(kid, key, scope, scopedKeyMetadata); + } + + /** + * Format key material for a legacy scyne-related scope as a JWK. + * + * @param {ArrayBuffer} kid bytes of the key hash to use in the key identifier + * @param {ArrayBuffer} key bytes of the derived sync key + * @param {String} scope the scope with which this key is associated + * @param {Number} keyRotationTimestamp server-provided timestamp of last key rotation + * @returns {Object} key material formatted as a JWK object + */ + _formatLegacyScopedKey(kid, key, scope, { keyRotationTimestamp }) { + kid = ChromeUtils.base64URLEncode(kid, { + pad: false, + }); + key = ChromeUtils.base64URLEncode(key, { + pad: false, + }); + return { + kid: `${keyRotationTimestamp}-${kid}`, + k: key, + kty: "oct", + }; + } + + /** + * Derive the Sync Key given the byte string kB. + * + * @returns Promise<HKDF(kB, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)> + */ + async _deriveSyncKey(kBbytes) { + return CryptoUtils.hkdfLegacy( + kBbytes, + undefined, + "identity.mozilla.com/picl/v1/oldsync", + 2 * 32 + ); + } + + /** + * Derive the X-Client-State header given the byte string kB. + * + * @returns Promise<SHA256(kB)[:16]> + */ + async _deriveXClientState(kBbytes) { + return this._sha256(kBbytes).slice(0, 16); + } + + _sha256(bytes) { + let hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(hasher.SHA256); + return CryptoUtils.digestBytes(bytes, hasher); + } +} diff --git a/services/fxaccounts/FxAccountsOAuth.sys.mjs b/services/fxaccounts/FxAccountsOAuth.sys.mjs new file mode 100644 index 0000000000..1935decff2 --- /dev/null +++ b/services/fxaccounts/FxAccountsOAuth.sys.mjs @@ -0,0 +1,224 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", +}); + +import { + FX_OAUTH_CLIENT_ID, + SCOPE_PROFILE, + SCOPE_PROFILE_WRITE, + SCOPE_OLD_SYNC, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const VALID_SCOPES = [SCOPE_PROFILE, SCOPE_PROFILE_WRITE, SCOPE_OLD_SYNC]; + +export const ERROR_INVALID_SCOPES = "INVALID_SCOPES"; +export const ERROR_INVALID_STATE = "INVALID_STATE"; +export const ERROR_SYNC_SCOPE_NOT_GRANTED = "ERROR_SYNC_SCOPE_NOT_GRANTED"; +export const ERROR_NO_KEYS_JWE = "ERROR_NO_KEYS_JWE"; +export const ERROR_OAUTH_FLOW_ABANDONED = "ERROR_OAUTH_FLOW_ABANDONED"; + +/** + * Handles all logic and state related to initializing, and completing OAuth flows + * with FxA + * It's possible to start multiple OAuth flow, but only one can be completed, and once one flow is completed + * all the other in-flight flows will be concluded, and attempting to complete those flows will result in errors. + */ +export class FxAccountsOAuth { + #flow; + #fxaClient; + /** + * Creates a new FxAccountsOAuth + * + * @param { Object } fxaClient: The fxa client used to send http request to the oauth server + */ + constructor(fxaClient) { + this.#flow = {}; + this.#fxaClient = fxaClient; + } + + /** + * Stores a flow in-memory + * @param { string } state: A base-64 URL-safe string represnting a random value created at the start of the flow + * @param { Object } value: The data needed to complete a flow, once the oauth code is available. + * in practice, `value` is: + * - `verifier`: A base=64 URL-safe string representing the PKCE code verifier + * - `key`: The private key need to decrypt the JWE we recieve from the auth server + * - `requestedScopes`: The scopes the caller requested, meant to be compared against the scopes the server authorized + */ + addFlow(state, value) { + this.#flow[state] = value; + } + + /** + * Clears all started flows + */ + clearAllFlows() { + this.#flow = {}; + } + + /* + * Gets a stored flow + * @param { string } state: The base-64 URL-safe state string that was created at the start of the flow + * @returns { Object }: The values initially stored when startign th eoauth flow + * in practice, the return value is: + * - `verifier`: A base=64 URL-safe string representing the PKCE code verifier + * - `key`: The private key need to decrypt the JWE we recieve from the auth server + * - ``requestedScopes`: The scopes the caller requested, meant to be compared against the scopes the server authorized + */ + getFlow(state) { + return this.#flow[state]; + } + + /* Returns the number of flows, used by tests + * + */ + numOfFlows() { + return Object.keys(this.#flow).length; + } + + /** + * Begins an OAuth flow, to be completed with a an OAuth code and state. + * + * This function stores needed information to complete the flow. You must call `completeOAuthFlow` + * on the same instance of `FxAccountsOAuth`, otherwise the completing of the oauth flow will fail. + * + * @param { string[] } scopes: The OAuth scopes the client should request from FxA + * + * @returns { Object }: Returns an object representing the query parameters that should be + * added to the FxA authorization URL to initialize an oAuth flow. + * In practice, the query parameters are: + * - `client_id`: The OAuth client ID for Firefox Desktop + * - `scope`: The scopes given by the caller, space seperated + * - `action`: This will always be `email` + * - `response_type`: This will always be `code` + * - `access_type`: This will always be `offline` + * - `state`: A URL-safe base-64 string randomly generated + * - `code_challenge`: A URL-safe base-64 string representing the PKCE challenge + * - `code_challenge_method`: This will always be `S256` + * For more informatio about PKCE, read https://datatracker.ietf.org/doc/html/rfc7636 + * - `keys_jwk`: A URL-safe base-64 representing a JWK to be used as a public key by the server + * to generate a JWE + */ + async beginOAuthFlow(scopes) { + if ( + !Array.isArray(scopes) || + scopes.some(scope => !VALID_SCOPES.includes(scope)) + ) { + throw new Error(ERROR_INVALID_SCOPES); + } + const queryParams = { + client_id: FX_OAUTH_CLIENT_ID, + action: "email", + response_type: "code", + access_type: "offline", + scope: scopes.join(" "), + }; + + // Generate a random, 16 byte value to represent a state that we verify + // once we complete the oauth flow, to ensure that we only conclude + // an oauth flow that we started + const state = new Uint8Array(16); + crypto.getRandomValues(state); + const stateB64 = ChromeUtils.base64URLEncode(state, { pad: false }); + queryParams.state = stateB64; + + // Generate a 43 byte code verifier for PKCE, in accordance with + // https://datatracker.ietf.org/doc/html/rfc7636#section-7.1 which recommends a + // 43-octet URL safe string + const codeVerifier = new Uint8Array(43); + crypto.getRandomValues(codeVerifier); + const codeVerifierB64 = ChromeUtils.base64URLEncode(codeVerifier, { + pad: false, + }); + const challenge = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(codeVerifierB64) + ); + const challengeB64 = ChromeUtils.base64URLEncode(challenge, { pad: false }); + queryParams.code_challenge = challengeB64; + queryParams.code_challenge_method = "S256"; + + // Generate a public, private key pair to be used during the oauth flow + // to encrypt scoped-keys as they roundtrip through the auth server + const ECDH_KEY = { name: "ECDH", namedCurve: "P-256" }; + const key = await crypto.subtle.generateKey(ECDH_KEY, true, ["deriveKey"]); + const publicKey = await crypto.subtle.exportKey("jwk", key.publicKey); + const privateKey = key.privateKey; + + // We encode the public key as URL-safe base64 to be included in the query parameters + const encodedPublicKey = ChromeUtils.base64URLEncode( + new TextEncoder().encode(JSON.stringify(publicKey)), + { pad: false } + ); + queryParams.keys_jwk = encodedPublicKey; + + // We store the state in-memory, to verify once the oauth flow is completed + this.addFlow(stateB64, { + key: privateKey, + verifier: codeVerifierB64, + requestedScopes: scopes.join(" "), + }); + return queryParams; + } + + /** Completes an OAuth flow and invalidates any other ongoing flows + * @param { string } sessionTokenHex: The session token encoded in hexadecimal + * @param { string } code: OAuth authorization code provided by running an OAuth flow + * @param { string } state: The state first provided by `beginOAuthFlow`, then roundtripped through the server + * + * @returns { Object }: Returns an object representing the result of completing the oauth flow. + * The object includes the following: + * - 'scopedKeys': The encryption keys provided by the server, already decrypted + * - 'refreshToken': The refresh token provided by the server + * - 'accessToken': The access token provided by the server + * */ + async completeOAuthFlow(sessionTokenHex, code, state) { + const flow = this.getFlow(state); + if (!flow) { + throw new Error(ERROR_INVALID_STATE); + } + const { key, verifier, requestedScopes } = flow; + const { keys_jwe, refresh_token, access_token, scope } = + await this.#fxaClient.oauthToken( + sessionTokenHex, + code, + verifier, + FX_OAUTH_CLIENT_ID + ); + if ( + requestedScopes.includes(SCOPE_OLD_SYNC) && + !scope.includes(SCOPE_OLD_SYNC) + ) { + throw new Error(ERROR_SYNC_SCOPE_NOT_GRANTED); + } + if (scope.includes(SCOPE_OLD_SYNC) && !keys_jwe) { + throw new Error(ERROR_NO_KEYS_JWE); + } + let scopedKeys; + if (keys_jwe) { + scopedKeys = JSON.parse( + new TextDecoder().decode(await lazy.jwcrypto.decryptJWE(keys_jwe, key)) + ); + } + + // We make sure no other flow snuck in, and completed before we did + if (!this.getFlow(state)) { + throw new Error(ERROR_OAUTH_FLOW_ABANDONED); + } + + // Clear all flows, so any in-flight or future flows trigger an error as the browser + // would have been signed in + this.clearAllFlows(); + return { + scopedKeys, + refreshToken: refresh_token, + accessToken: access_token, + }; + } +} diff --git a/services/fxaccounts/FxAccountsPairing.sys.mjs b/services/fxaccounts/FxAccountsPairing.sys.mjs new file mode 100644 index 0000000000..e68554f7ab --- /dev/null +++ b/services/fxaccounts/FxAccountsPairing.sys.mjs @@ -0,0 +1,511 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import { + log, + PREF_REMOTE_PAIRING_URI, + COMMAND_PAIR_SUPP_METADATA, + COMMAND_PAIR_AUTHORIZE, + COMMAND_PAIR_DECLINE, + COMMAND_PAIR_HEARTBEAT, + COMMAND_PAIR_COMPLETE, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { + getFxAccountsSingleton, + FxAccounts, +} from "resource://gre/modules/FxAccounts.sys.mjs"; + +const fxAccounts = getFxAccountsSingleton(); +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +ChromeUtils.importESModule("resource://services-common/utils.sys.mjs"); +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + FxAccountsPairingChannel: + "resource://gre/modules/FxAccountsPairingChannel.sys.mjs", + + Weave: "resource://services-sync/main.sys.mjs", + jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", +}); + +const PAIRING_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob:pair-auth-webchannel"; +// A pairing flow is not tied to a specific browser window, can also finish in +// various ways and subsequently might leak a Web Socket, so just in case we +// time out and free-up the resources after a specified amount of time. +const FLOW_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes. + +class PairingStateMachine { + constructor(emitter) { + this._emitter = emitter; + this._transition(SuppConnectionPending); + } + + get currentState() { + return this._currentState; + } + + _transition(StateCtor, ...args) { + const state = new StateCtor(this, ...args); + this._currentState = state; + } + + assertState(RequiredStates, messagePrefix = null) { + if (!(RequiredStates instanceof Array)) { + RequiredStates = [RequiredStates]; + } + if ( + !RequiredStates.some( + RequiredState => this._currentState instanceof RequiredState + ) + ) { + const msg = `${ + messagePrefix ? `${messagePrefix}. ` : "" + }Valid expected states: ${RequiredStates.map(({ name }) => name).join( + ", " + )}. Current state: ${this._currentState.label}.`; + throw new Error(msg); + } + } +} + +/** + * The pairing flow can be modeled by a finite state machine: + * We start by connecting to a WebSocket channel (SuppConnectionPending). + * Then the other party connects and requests some metadata from us (PendingConfirmations). + * A confirmation happens locally first (PendingRemoteConfirmation) + * or the oppposite (PendingLocalConfirmation). + * Any side can decline this confirmation (Aborted). + * Once both sides have confirmed, the pairing flow is finished (Completed). + * During this flow errors can happen and should be handled (Errored). + */ +class State { + constructor(stateMachine, ...args) { + this._transition = (...args) => stateMachine._transition(...args); + this._notify = (...args) => stateMachine._emitter.emit(...args); + this.init(...args); + } + + init() { + /* Does nothing by default but can be re-implemented. */ + } + + get label() { + return this.constructor.name; + } + + hasErrored(error) { + this._notify("view:Error", error); + this._transition(Errored, error); + } + + hasAborted() { + this._transition(Aborted); + } +} +class SuppConnectionPending extends State { + suppConnected(sender, oauthOptions) { + this._transition(PendingConfirmations, sender, oauthOptions); + } +} +class PendingConfirmationsState extends State { + localConfirmed() { + throw new Error("Subclasses must implement this method."); + } + remoteConfirmed() { + throw new Error("Subclasses must implement this method."); + } +} +class PendingConfirmations extends PendingConfirmationsState { + init(sender, oauthOptions) { + this.sender = sender; + this.oauthOptions = oauthOptions; + } + + localConfirmed() { + this._transition(PendingRemoteConfirmation); + } + + remoteConfirmed() { + this._transition(PendingLocalConfirmation, this.sender, this.oauthOptions); + } +} +class PendingLocalConfirmation extends PendingConfirmationsState { + init(sender, oauthOptions) { + this.sender = sender; + this.oauthOptions = oauthOptions; + } + + localConfirmed() { + this._transition(Completed); + } + + remoteConfirmed() { + throw new Error( + "Insane state! Remote has already been confirmed at this point." + ); + } +} +class PendingRemoteConfirmation extends PendingConfirmationsState { + localConfirmed() { + throw new Error( + "Insane state! Local has already been confirmed at this point." + ); + } + + remoteConfirmed() { + this._transition(Completed); + } +} +class Completed extends State {} +class Aborted extends State {} +class Errored extends State { + init(error) { + this.error = error; + } +} + +const flows = new Map(); + +export class FxAccountsPairingFlow { + static get(channelId) { + return flows.get(channelId); + } + + static finalizeAll() { + for (const flow of flows) { + flow.finalize(); + } + } + + static async start(options) { + const { emitter } = options; + const fxaConfig = options.fxaConfig || FxAccounts.config; + const fxa = options.fxAccounts || fxAccounts; + const weave = options.weave || lazy.Weave; + const flowTimeout = options.flowTimeout || FLOW_TIMEOUT_MS; + + const contentPairingURI = await fxaConfig.promisePairingURI(); + const wsUri = Services.urlFormatter.formatURLPref(PREF_REMOTE_PAIRING_URI); + const pairingChannel = + options.pairingChannel || + (await lazy.FxAccountsPairingChannel.create(wsUri)); + const { channelId, channelKey } = pairingChannel; + const channelKeyB64 = ChromeUtils.base64URLEncode(channelKey, { + pad: false, + }); + const pairingFlow = new FxAccountsPairingFlow({ + channelId, + pairingChannel, + emitter, + fxa, + fxaConfig, + flowTimeout, + weave, + }); + flows.set(channelId, pairingFlow); + + return `${contentPairingURI}#channel_id=${channelId}&channel_key=${channelKeyB64}`; + } + + constructor(options) { + this._channelId = options.channelId; + this._pairingChannel = options.pairingChannel; + this._emitter = options.emitter; + this._fxa = options.fxa; + this._fxai = options.fxai || this._fxa._internal; + this._fxaConfig = options.fxaConfig; + this._weave = options.weave; + this._stateMachine = new PairingStateMachine(this._emitter); + this._setupListeners(); + this._flowTimeoutId = setTimeout( + () => this._onFlowTimeout(), + options.flowTimeout + ); + } + + _onFlowTimeout() { + log.warn(`The pairing flow ${this._channelId} timed out.`); + this._onError(new Error("Timeout")); + this.finalize(); + } + + _closeChannel() { + if (!this._closed && !this._pairingChannel.closed) { + this._pairingChannel.close(); + this._closed = true; + } + } + + finalize() { + this._closeChannel(); + clearTimeout(this._flowTimeoutId); + // Free up resources and let the GC do its thing. + flows.delete(this._channelId); + } + + _setupListeners() { + this._pairingChannel.addEventListener( + "message", + ({ detail: { sender, data } }) => + this.onPairingChannelMessage(sender, data) + ); + this._pairingChannel.addEventListener("error", event => + this._onPairingChannelError(event.detail.error) + ); + this._emitter.on("view:Closed", () => this.onPrefViewClosed()); + } + + _onAbort() { + this._stateMachine.currentState.hasAborted(); + this.finalize(); + } + + _onError(error) { + this._stateMachine.currentState.hasErrored(error); + this._closeChannel(); + } + + _onPairingChannelError(error) { + log.error("Pairing channel error", error); + this._onError(error); + } + + // Any non-falsy returned value is sent back through WebChannel. + async onWebChannelMessage(command) { + const stateMachine = this._stateMachine; + const curState = stateMachine.currentState; + try { + switch (command) { + case COMMAND_PAIR_SUPP_METADATA: + stateMachine.assertState( + [PendingConfirmations, PendingLocalConfirmation], + `Wrong state for ${command}` + ); + const { + ua, + city, + region, + country, + remote: ipAddress, + } = curState.sender; + return { ua, city, region, country, ipAddress }; + case COMMAND_PAIR_AUTHORIZE: + stateMachine.assertState( + [PendingConfirmations, PendingLocalConfirmation], + `Wrong state for ${command}` + ); + const { + client_id, + state, + scope, + code_challenge, + code_challenge_method, + keys_jwk, + } = curState.oauthOptions; + const authorizeParams = { + client_id, + access_type: "offline", + state, + scope, + code_challenge, + code_challenge_method, + keys_jwk, + }; + const codeAndState = await this._authorizeOAuthCode(authorizeParams); + if (codeAndState.state != state) { + throw new Error(`OAuth state mismatch`); + } + await this._pairingChannel.send({ + message: "pair:auth:authorize", + data: { + ...codeAndState, + }, + }); + curState.localConfirmed(); + break; + case COMMAND_PAIR_DECLINE: + this._onAbort(); + break; + case COMMAND_PAIR_HEARTBEAT: + if (curState instanceof Errored || this._pairingChannel.closed) { + return { err: curState.error.message || "Pairing channel closed" }; + } + const suppAuthorized = !( + curState instanceof PendingConfirmations || + curState instanceof PendingRemoteConfirmation + ); + return { suppAuthorized }; + case COMMAND_PAIR_COMPLETE: + this.finalize(); + break; + default: + throw new Error(`Received unknown WebChannel command: ${command}`); + } + } catch (e) { + log.error(e); + curState.hasErrored(e); + } + return {}; + } + + async onPairingChannelMessage(sender, payload) { + const { message } = payload; + const stateMachine = this._stateMachine; + const curState = stateMachine.currentState; + try { + switch (message) { + case "pair:supp:request": + stateMachine.assertState( + SuppConnectionPending, + `Wrong state for ${message}` + ); + const oauthUri = await this._fxaConfig.promiseOAuthURI(); + const { uid, email, avatar, displayName } = + await this._fxa.getSignedInUser(); + const deviceName = this._weave.Service.clientsEngine.localName; + await this._pairingChannel.send({ + message: "pair:auth:metadata", + data: { + email, + avatar, + displayName, + deviceName, + }, + }); + const { + client_id, + state, + scope, + code_challenge, + code_challenge_method, + keys_jwk, + } = payload.data; + const url = new URL(oauthUri); + url.searchParams.append("client_id", client_id); + url.searchParams.append("scope", scope); + url.searchParams.append("email", email); + url.searchParams.append("uid", uid); + url.searchParams.append("channel_id", this._channelId); + url.searchParams.append("redirect_uri", PAIRING_REDIRECT_URI); + this._emitter.emit("view:SwitchToWebContent", url.href); + curState.suppConnected(sender, { + client_id, + state, + scope, + code_challenge, + code_challenge_method, + keys_jwk, + }); + break; + case "pair:supp:authorize": + stateMachine.assertState( + [PendingConfirmations, PendingRemoteConfirmation], + `Wrong state for ${message}` + ); + curState.remoteConfirmed(); + break; + default: + throw new Error( + `Received unknown Pairing Channel message: ${message}` + ); + } + } catch (e) { + log.error(e); + curState.hasErrored(e); + } + } + + onPrefViewClosed() { + const curState = this._stateMachine.currentState; + // We don't want to stop the pairing process in the later stages. + if ( + curState instanceof SuppConnectionPending || + curState instanceof Aborted || + curState instanceof Errored + ) { + this.finalize(); + } + } + + /** + * Grant an OAuth authorization code for the connecting client. + * + * @param {Object} options + * @param options.client_id + * @param options.state + * @param options.scope + * @param options.access_type + * @param options.code_challenge_method + * @param options.code_challenge + * @param [options.keys_jwe] + * @returns {Promise<Object>} Object containing "code" and "state" properties. + */ + _authorizeOAuthCode(options) { + return this._fxa._withVerifiedAccountState(async state => { + const { sessionToken } = await state.getUserAccountData(["sessionToken"]); + const params = { ...options }; + if (params.keys_jwk) { + const jwk = JSON.parse( + new TextDecoder().decode( + ChromeUtils.base64URLDecode(params.keys_jwk, { padding: "reject" }) + ) + ); + params.keys_jwe = await this._createKeysJWE( + sessionToken, + params.client_id, + params.scope, + jwk + ); + delete params.keys_jwk; + } + try { + return await this._fxai.fxAccountsClient.oauthAuthorize( + sessionToken, + params + ); + } catch (err) { + throw this._fxai._errorToErrorClass(err); + } + }); + } + + /** + * Create a JWE to deliver keys to another client via the OAuth scoped-keys flow. + * + * This method is used to transfer key material to another client, by providing + * an appropriately-encrypted value for the `keys_jwe` OAuth response parameter. + * Since we're transferring keys from one client to another, two things must be + * true: + * + * * This client must actually have the key. + * * The other client must be allowed to request that key. + * + * @param {String} sessionToken the sessionToken to use when fetching key metadata + * @param {String} clientId the client requesting access to our keys + * @param {String} scopes Space separated requested scopes being requested + * @param {Object} jwk Ephemeral JWK provided by the client for secure key transfer + */ + async _createKeysJWE(sessionToken, clientId, scopes, jwk) { + // This checks with the FxA server about what scopes the client is allowed. + // Note that we pass the requesting client_id here, not our own client_id. + const clientKeyData = await this._fxai.fxAccountsClient.getScopedKeyData( + sessionToken, + clientId, + scopes + ); + const scopedKeys = {}; + for (const scope of Object.keys(clientKeyData)) { + const key = await this._fxai.keys.getKeyForScope(scope); + if (!key) { + throw new Error(`Key not available for scope "${scope}"`); + } + scopedKeys[scope] = key; + } + return lazy.jwcrypto.generateJWE( + jwk, + new TextEncoder().encode(JSON.stringify(scopedKeys)) + ); + } +} diff --git a/services/fxaccounts/FxAccountsPairingChannel.sys.mjs b/services/fxaccounts/FxAccountsPairingChannel.sys.mjs new file mode 100644 index 0000000000..cb6d3fdb91 --- /dev/null +++ b/services/fxaccounts/FxAccountsPairingChannel.sys.mjs @@ -0,0 +1,3693 @@ +/*! + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The following bundle is from an external repository at github.com/mozilla/fxa-pairing-channel, + * it implements a shared library for two javascript environments to create an encrypted and authenticated + * communication channel by sharing a secret key and by relaying messages through a websocket server. + * + * It is used by the Firefox Accounts pairing flow, with one side of the channel being web + * content from https://accounts.firefox.com and the other side of the channel being chrome native code. + * + * This uses the event-target-shim node library published under the MIT license: + * https://github.com/mysticatea/event-target-shim/blob/master/LICENSE + * + * Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:c8ec3119920b4ffa833b, Chunkhash:378a5f51445e7aa7630e. + * + */ + +// This header provides a little bit of plumbing to use `FxAccountsPairingChannel` +// from Firefox browser code, hence the presence of these privileged browser APIs. +// If you're trying to use this from ordinary web content you're in for a bad time. + +import { setTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +// We cannot use WebSocket from chrome code without a window, +// see https://bugzilla.mozilla.org/show_bug.cgi?id=784686 +const browser = Services.appShell.createWindowlessBrowser(true); +const {WebSocket} = browser.document.ownerGlobal; + +export var FxAccountsPairingChannel = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +// ESM COMPAT FLAG +__webpack_require__.r(__webpack_exports__); + +// EXPORTS +__webpack_require__.d(__webpack_exports__, "PairingChannel", function() { return /* binding */ src_PairingChannel; }); +__webpack_require__.d(__webpack_exports__, "base64urlToBytes", function() { return /* reexport */ base64urlToBytes; }); +__webpack_require__.d(__webpack_exports__, "bytesToBase64url", function() { return /* reexport */ bytesToBase64url; }); +__webpack_require__.d(__webpack_exports__, "bytesToHex", function() { return /* reexport */ bytesToHex; }); +__webpack_require__.d(__webpack_exports__, "bytesToUtf8", function() { return /* reexport */ bytesToUtf8; }); +__webpack_require__.d(__webpack_exports__, "hexToBytes", function() { return /* reexport */ hexToBytes; }); +__webpack_require__.d(__webpack_exports__, "TLSCloseNotify", function() { return /* reexport */ TLSCloseNotify; }); +__webpack_require__.d(__webpack_exports__, "TLSError", function() { return /* reexport */ TLSError; }); +__webpack_require__.d(__webpack_exports__, "utf8ToBytes", function() { return /* reexport */ utf8ToBytes; }); +__webpack_require__.d(__webpack_exports__, "_internals", function() { return /* binding */ _internals; }); + +// CONCATENATED MODULE: ./src/alerts.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-disable sorting/sort-object-props */ +const ALERT_LEVEL = { + WARNING: 1, + FATAL: 2 +}; + +const ALERT_DESCRIPTION = { + CLOSE_NOTIFY: 0, + UNEXPECTED_MESSAGE: 10, + BAD_RECORD_MAC: 20, + RECORD_OVERFLOW: 22, + HANDSHAKE_FAILURE: 40, + ILLEGAL_PARAMETER: 47, + DECODE_ERROR: 50, + DECRYPT_ERROR: 51, + PROTOCOL_VERSION: 70, + INTERNAL_ERROR: 80, + MISSING_EXTENSION: 109, + UNSUPPORTED_EXTENSION: 110, + UNKNOWN_PSK_IDENTITY: 115, + NO_APPLICATION_PROTOCOL: 120, +}; +/* eslint-enable sorting/sort-object-props */ + +function alertTypeToName(type) { + for (const name in ALERT_DESCRIPTION) { + if (ALERT_DESCRIPTION[name] === type) { + return `${name} (${type})`; + } + } + return `UNKNOWN (${type})`; +} + +class TLSAlert extends Error { + constructor(description, level) { + super(`TLS Alert: ${alertTypeToName(description)}`); + this.description = description; + this.level = level; + } + + static fromBytes(bytes) { + if (bytes.byteLength !== 2) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + switch (bytes[1]) { + case ALERT_DESCRIPTION.CLOSE_NOTIFY: + if (bytes[0] !== ALERT_LEVEL.WARNING) { + // Close notifications should be fatal. + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + return new TLSCloseNotify(); + default: + return new TLSError(bytes[1]); + } + } + + toBytes() { + return new Uint8Array([this.level, this.description]); + } +} + +class TLSCloseNotify extends TLSAlert { + constructor() { + super(ALERT_DESCRIPTION.CLOSE_NOTIFY, ALERT_LEVEL.WARNING); + } +} + +class TLSError extends TLSAlert { + constructor(description = ALERT_DESCRIPTION.INTERNAL_ERROR) { + super(description, ALERT_LEVEL.FATAL); + } +} + +// CONCATENATED MODULE: ./src/utils.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +// +// Various low-level utility functions. +// +// These are mostly conveniences for working with Uint8Arrays as +// the primitive "bytes" type. +// + +const UTF8_ENCODER = new TextEncoder(); +const UTF8_DECODER = new TextDecoder(); + +function noop() {} + +function assert(cond, msg) { + if (! cond) { + throw new Error('assert failed: ' + msg); + } +} + +function assertIsBytes(value, msg = 'value must be a Uint8Array') { + // Using `value instanceof Uint8Array` seems to fail in Firefox chrome code + // for inscrutable reasons, so we do a less direct check. + assert(ArrayBuffer.isView(value), msg); + assert(value.BYTES_PER_ELEMENT === 1, msg); + return value; +} + +const EMPTY = new Uint8Array(0); + +function zeros(n) { + return new Uint8Array(n); +} + +function arrayToBytes(value) { + return new Uint8Array(value); +} + +function bytesToHex(bytes) { + return Array.prototype.map.call(bytes, byte => { + let s = byte.toString(16); + if (s.length === 1) { + s = '0' + s; + } + return s; + }).join(''); +} + +function hexToBytes(hexstr) { + assert(hexstr.length % 2 === 0, 'hexstr.length must be even'); + return new Uint8Array(Array.prototype.map.call(hexstr, (c, n) => { + if (n % 2 === 1) { + return hexstr[n - 1] + c; + } else { + return ''; + } + }).filter(s => { + return !! s; + }).map(s => { + return parseInt(s, 16); + })); +} + +function bytesToUtf8(bytes) { + return UTF8_DECODER.decode(bytes); +} + +function utf8ToBytes(str) { + return UTF8_ENCODER.encode(str); +} + +function bytesToBase64url(bytes) { + // XXX TODO: try to use something constant-time, in case calling code + // uses it to encode secrets? + const charCodes = String.fromCharCode.apply(String, bytes); + return btoa(charCodes).replace(/\+/g, '-').replace(/\//g, '_'); +} + +function base64urlToBytes(str) { + // XXX TODO: try to use something constant-time, in case calling code + // uses it to decode secrets? + str = atob(str.replace(/-/g, '+').replace(/_/g, '/')); + const bytes = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes; +} + +function bytesAreEqual(v1, v2) { + assertIsBytes(v1); + assertIsBytes(v2); + if (v1.length !== v2.length) { + return false; + } + for (let i = 0; i < v1.length; i++) { + if (v1[i] !== v2[i]) { + return false; + } + } + return true; +} + +// The `BufferReader` and `BufferWriter` classes are helpers for dealing with the +// binary struct format that's used for various TLS message. Think of them as a +// buffer with a pointer to the "current position" and a bunch of helper methods +// to read/write structured data and advance said pointer. + +class utils_BufferWithPointer { + constructor(buf) { + this._buffer = buf; + this._dataview = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); + this._pos = 0; + } + + length() { + return this._buffer.byteLength; + } + + tell() { + return this._pos; + } + + seek(pos) { + if (pos < 0) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + if (pos > this.length()) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + this._pos = pos; + } + + incr(offset) { + this.seek(this._pos + offset); + } +} + +// The `BufferReader` class helps you read structured data from a byte array. +// It offers methods for reading both primitive values, and the variable-length +// vector structures defined in https://tools.ietf.org/html/rfc8446#section-3.4. +// +// Such vectors are represented as a length followed by the concatenated +// bytes of each item, and the size of the length field is determined by +// the maximum allowed number of bytes in the vector. For example +// to read a vector that may contain up to 65535 bytes, use `readVector16`. +// +// To read a variable-length vector of between 1 and 100 uint16 values, +// defined in the RFC like this: +// +// uint16 items<2..200>; +// +// You would do something like this: +// +// const items = [] +// buf.readVector8(buf => { +// items.push(buf.readUint16()) +// }) +// +// The various `read` will throw `DECODE_ERROR` if you attempt to read path +// the end of the buffer, or past the end of a variable-length list. +// +class utils_BufferReader extends utils_BufferWithPointer { + + hasMoreBytes() { + return this.tell() < this.length(); + } + + readBytes(length) { + // This avoids copies by returning a view onto the existing buffer. + const start = this._buffer.byteOffset + this.tell(); + this.incr(length); + return new Uint8Array(this._buffer.buffer, start, length); + } + + _rangeErrorToAlert(cb) { + try { + return cb(this); + } catch (err) { + if (err instanceof RangeError) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + throw err; + } + } + + readUint8() { + return this._rangeErrorToAlert(() => { + const n = this._dataview.getUint8(this._pos); + this.incr(1); + return n; + }); + } + + readUint16() { + return this._rangeErrorToAlert(() => { + const n = this._dataview.getUint16(this._pos); + this.incr(2); + return n; + }); + } + + readUint24() { + return this._rangeErrorToAlert(() => { + let n = this._dataview.getUint16(this._pos); + n = (n << 8) | this._dataview.getUint8(this._pos + 2); + this.incr(3); + return n; + }); + } + + readUint32() { + return this._rangeErrorToAlert(() => { + const n = this._dataview.getUint32(this._pos); + this.incr(4); + return n; + }); + } + + _readVector(length, cb) { + const contentsBuf = new utils_BufferReader(this.readBytes(length)); + const expectedEnd = this.tell(); + // Keep calling the callback until we've consumed the expected number of bytes. + let n = 0; + while (contentsBuf.hasMoreBytes()) { + const prevPos = contentsBuf.tell(); + cb(contentsBuf, n); + // Check that the callback made forward progress, otherwise we'll infinite loop. + if (contentsBuf.tell() <= prevPos) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + n += 1; + } + // Check that the callback correctly consumed the vector's entire contents. + if (this.tell() !== expectedEnd) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + } + + readVector8(cb) { + const length = this.readUint8(); + return this._readVector(length, cb); + } + + readVector16(cb) { + const length = this.readUint16(); + return this._readVector(length, cb); + } + + readVector24(cb) { + const length = this.readUint24(); + return this._readVector(length, cb); + } + + readVectorBytes8() { + return this.readBytes(this.readUint8()); + } + + readVectorBytes16() { + return this.readBytes(this.readUint16()); + } + + readVectorBytes24() { + return this.readBytes(this.readUint24()); + } +} + + +class utils_BufferWriter extends utils_BufferWithPointer { + constructor(size = 1024) { + super(new Uint8Array(size)); + } + + _maybeGrow(n) { + const curSize = this._buffer.byteLength; + const newPos = this._pos + n; + const shortfall = newPos - curSize; + if (shortfall > 0) { + // Classic grow-by-doubling, up to 4kB max increment. + // This formula was not arrived at by any particular science. + const incr = Math.min(curSize, 4 * 1024); + const newbuf = new Uint8Array(curSize + Math.ceil(shortfall / incr) * incr); + newbuf.set(this._buffer, 0); + this._buffer = newbuf; + this._dataview = new DataView(newbuf.buffer, newbuf.byteOffset, newbuf.byteLength); + } + } + + slice(start = 0, end = this.tell()) { + if (end < 0) { + end = this.tell() + end; + } + if (start < 0) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + if (end < 0) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + if (end > this.length()) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + return this._buffer.slice(start, end); + } + + flush() { + const slice = this.slice(); + this.seek(0); + return slice; + } + + writeBytes(data) { + this._maybeGrow(data.byteLength); + this._buffer.set(data, this.tell()); + this.incr(data.byteLength); + } + + writeUint8(n) { + this._maybeGrow(1); + this._dataview.setUint8(this._pos, n); + this.incr(1); + } + + writeUint16(n) { + this._maybeGrow(2); + this._dataview.setUint16(this._pos, n); + this.incr(2); + } + + writeUint24(n) { + this._maybeGrow(3); + this._dataview.setUint16(this._pos, n >> 8); + this._dataview.setUint8(this._pos + 2, n & 0xFF); + this.incr(3); + } + + writeUint32(n) { + this._maybeGrow(4); + this._dataview.setUint32(this._pos, n); + this.incr(4); + } + + // These are helpers for writing the variable-length vector structure + // defined in https://tools.ietf.org/html/rfc8446#section-3.4. + // + // Such vectors are represented as a length followed by the concatenated + // bytes of each item, and the size of the length field is determined by + // the maximum allowed size of the vector. For example to write a vector + // that may contain up to 65535 bytes, use `writeVector16`. + // + // To write a variable-length vector of between 1 and 100 uint16 values, + // defined in the RFC like this: + // + // uint16 items<2..200>; + // + // You would do something like this: + // + // buf.writeVector8(buf => { + // for (let item of items) { + // buf.writeUint16(item) + // } + // }) + // + // The helper will automatically take care of writing the appropriate + // length field once the callback completes. + + _writeVector(maxLength, writeLength, cb) { + // Initially, write the length field as zero. + const lengthPos = this.tell(); + writeLength(0); + // Call the callback to write the vector items. + const bodyPos = this.tell(); + cb(this); + const length = this.tell() - bodyPos; + if (length >= maxLength) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + // Backfill the actual length field. + this.seek(lengthPos); + writeLength(length); + this.incr(length); + return length; + } + + writeVector8(cb) { + return this._writeVector(Math.pow(2, 8), len => this.writeUint8(len), cb); + } + + writeVector16(cb) { + return this._writeVector(Math.pow(2, 16), len => this.writeUint16(len), cb); + } + + writeVector24(cb) { + return this._writeVector(Math.pow(2, 24), len => this.writeUint24(len), cb); + } + + writeVectorBytes8(bytes) { + return this.writeVector8(buf => { + buf.writeBytes(bytes); + }); + } + + writeVectorBytes16(bytes) { + return this.writeVector16(buf => { + buf.writeBytes(bytes); + }); + } + + writeVectorBytes24(bytes) { + return this.writeVector24(buf => { + buf.writeBytes(bytes); + }); + } +} + +// CONCATENATED MODULE: ./src/crypto.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Low-level crypto primitives. +// +// This file implements the AEAD encrypt/decrypt and hashing routines +// for the TLS_AES_128_GCM_SHA256 ciphersuite. They are (thankfully) +// fairly light-weight wrappers around what's available via the WebCrypto +// API. +// + + + + +const AEAD_SIZE_INFLATION = 16; +const KEY_LENGTH = 16; +const IV_LENGTH = 12; +const HASH_LENGTH = 32; + +async function prepareKey(key, mode) { + return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]); +} + +async function encrypt(key, iv, plaintext, additionalData) { + const ciphertext = await crypto.subtle.encrypt({ + additionalData, + iv, + name: 'AES-GCM', + tagLength: AEAD_SIZE_INFLATION * 8 + }, key, plaintext); + return new Uint8Array(ciphertext); +} + +async function decrypt(key, iv, ciphertext, additionalData) { + try { + const plaintext = await crypto.subtle.decrypt({ + additionalData, + iv, + name: 'AES-GCM', + tagLength: AEAD_SIZE_INFLATION * 8 + }, key, ciphertext); + return new Uint8Array(plaintext); + } catch (err) { + // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC, + // and a 'bad_record_mac' error when failing to decrypt. + throw new TLSError(ALERT_DESCRIPTION.BAD_RECORD_MAC); + } +} + +async function hash(message) { + return new Uint8Array(await crypto.subtle.digest({ name: 'SHA-256' }, message)); +} + +async function hmac(keyBytes, message) { + const key = await crypto.subtle.importKey('raw', keyBytes, { + hash: { name: 'SHA-256' }, + name: 'HMAC', + }, false, ['sign']); + const sig = await crypto.subtle.sign({ name: 'HMAC' }, key, message); + return new Uint8Array(sig); +} + +async function verifyHmac(keyBytes, signature, message) { + const key = await crypto.subtle.importKey('raw', keyBytes, { + hash: { name: 'SHA-256' }, + name: 'HMAC', + }, false, ['verify']); + if (! (await crypto.subtle.verify({ name: 'HMAC' }, key, signature, message))) { + // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC, + // and a 'bad_record_mac' error when failing to decrypt. + throw new TLSError(ALERT_DESCRIPTION.DECRYPT_ERROR); + } +} + +async function hkdfExtract(salt, ikm) { + // Ref https://tools.ietf.org/html/rfc5869#section-2.2 + return await hmac(salt, ikm); +} + +async function hkdfExpand(prk, info, length) { + // Ref https://tools.ietf.org/html/rfc5869#section-2.3 + const N = Math.ceil(length / HASH_LENGTH); + if (N <= 0) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + if (N >= 255) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + const input = new utils_BufferWriter(); + const output = new utils_BufferWriter(); + let T = new Uint8Array(0); + for (let i = 1; i <= N; i++) { + input.writeBytes(T); + input.writeBytes(info); + input.writeUint8(i); + T = await hmac(prk, input.flush()); + output.writeBytes(T); + } + return output.slice(0, length); +} + +async function hkdfExpandLabel(secret, label, context, length) { + // struct { + // uint16 length = Length; + // opaque label < 7..255 > = "tls13 " + Label; + // opaque context < 0..255 > = Context; + // } HkdfLabel; + const hkdfLabel = new utils_BufferWriter(); + hkdfLabel.writeUint16(length); + hkdfLabel.writeVectorBytes8(utf8ToBytes('tls13 ' + label)); + hkdfLabel.writeVectorBytes8(context); + return hkdfExpand(secret, hkdfLabel.flush(), length); +} + +async function getRandomBytes(size) { + const bytes = new Uint8Array(size); + crypto.getRandomValues(bytes); + return bytes; +} + +// CONCATENATED MODULE: ./src/extensions.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Extension parsing. +// +// This file contains some helpers for reading/writing the various kinds +// of Extension that might appear in a HandshakeMessage. +// +// "Extensions" are how TLS signals the presence of particular bits of optional +// functionality in the protocol. Lots of parts of TLS1.3 that don't seem like +// they're optional are implemented in terms of an extension, IIUC because that's +// what was needed for a clean deployment in amongst earlier versions of the protocol. +// + + + + + +/* eslint-disable sorting/sort-object-props */ +const EXTENSION_TYPE = { + PRE_SHARED_KEY: 41, + SUPPORTED_VERSIONS: 43, + PSK_KEY_EXCHANGE_MODES: 45, +}; +/* eslint-enable sorting/sort-object-props */ + +// Base class for generic reading/writing of extensions, +// which are all uniformly formatted as: +// +// struct { +// ExtensionType extension_type; +// opaque extension_data<0..2^16-1>; +// } Extension; +// +// Extensions always appear inside of a handshake message, +// and their internal structure may differ based on the +// type of that message. + +class extensions_Extension { + + get TYPE_TAG() { + throw new Error('not implemented'); + } + + static read(messageType, buf) { + const type = buf.readUint16(); + let ext = { + TYPE_TAG: type, + }; + buf.readVector16(buf => { + switch (type) { + case EXTENSION_TYPE.PRE_SHARED_KEY: + ext = extensions_PreSharedKeyExtension._read(messageType, buf); + break; + case EXTENSION_TYPE.SUPPORTED_VERSIONS: + ext = extensions_SupportedVersionsExtension._read(messageType, buf); + break; + case EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES: + ext = extensions_PskKeyExchangeModesExtension._read(messageType, buf); + break; + default: + // Skip over unrecognised extensions. + buf.incr(buf.length()); + } + if (buf.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + }); + return ext; + } + + write(messageType, buf) { + buf.writeUint16(this.TYPE_TAG); + buf.writeVector16(buf => { + this._write(messageType, buf); + }); + } + + static _read(messageType, buf) { + throw new Error('not implemented'); + } + + static _write(messageType, buf) { + throw new Error('not implemented'); + } +} + +// The PreSharedKey extension: +// +// struct { +// opaque identity<1..2^16-1>; +// uint32 obfuscated_ticket_age; +// } PskIdentity; +// opaque PskBinderEntry<32..255>; +// struct { +// PskIdentity identities<7..2^16-1>; +// PskBinderEntry binders<33..2^16-1>; +// } OfferedPsks; +// struct { +// select(Handshake.msg_type) { +// case client_hello: OfferedPsks; +// case server_hello: uint16 selected_identity; +// }; +// } PreSharedKeyExtension; + +class extensions_PreSharedKeyExtension extends extensions_Extension { + constructor(identities, binders, selectedIdentity) { + super(); + this.identities = identities; + this.binders = binders; + this.selectedIdentity = selectedIdentity; + } + + get TYPE_TAG() { + return EXTENSION_TYPE.PRE_SHARED_KEY; + } + + static _read(messageType, buf) { + let identities = null, binders = null, selectedIdentity = null; + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + identities = []; binders = []; + buf.readVector16(buf => { + const identity = buf.readVectorBytes16(); + buf.readBytes(4); // Skip over the ticket age. + identities.push(identity); + }); + buf.readVector16(buf => { + const binder = buf.readVectorBytes8(); + if (binder.byteLength < HASH_LENGTH) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + binders.push(binder); + }); + if (identities.length !== binders.length) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + break; + case HANDSHAKE_TYPE.SERVER_HELLO: + selectedIdentity = buf.readUint16(); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + return new this(identities, binders, selectedIdentity); + } + + _write(messageType, buf) { + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + buf.writeVector16(buf => { + this.identities.forEach(pskId => { + buf.writeVectorBytes16(pskId); + buf.writeUint32(0); // Zero for "tag age" field. + }); + }); + buf.writeVector16(buf => { + this.binders.forEach(pskBinder => { + buf.writeVectorBytes8(pskBinder); + }); + }); + break; + case HANDSHAKE_TYPE.SERVER_HELLO: + buf.writeUint16(this.selectedIdentity); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + } +} + + +// The SupportedVersions extension: +// +// struct { +// select(Handshake.msg_type) { +// case client_hello: +// ProtocolVersion versions < 2..254 >; +// case server_hello: +// ProtocolVersion selected_version; +// }; +// } SupportedVersions; + +class extensions_SupportedVersionsExtension extends extensions_Extension { + constructor(versions, selectedVersion) { + super(); + this.versions = versions; + this.selectedVersion = selectedVersion; + } + + get TYPE_TAG() { + return EXTENSION_TYPE.SUPPORTED_VERSIONS; + } + + static _read(messageType, buf) { + let versions = null, selectedVersion = null; + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + versions = []; + buf.readVector8(buf => { + versions.push(buf.readUint16()); + }); + break; + case HANDSHAKE_TYPE.SERVER_HELLO: + selectedVersion = buf.readUint16(); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + return new this(versions, selectedVersion); + } + + _write(messageType, buf) { + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + buf.writeVector8(buf => { + this.versions.forEach(version => { + buf.writeUint16(version); + }); + }); + break; + case HANDSHAKE_TYPE.SERVER_HELLO: + buf.writeUint16(this.selectedVersion); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + } +} + + +class extensions_PskKeyExchangeModesExtension extends extensions_Extension { + constructor(modes) { + super(); + this.modes = modes; + } + + get TYPE_TAG() { + return EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES; + } + + static _read(messageType, buf) { + const modes = []; + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + buf.readVector8(buf => { + modes.push(buf.readUint8()); + }); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + return new this(modes); + } + + _write(messageType, buf) { + switch (messageType) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + buf.writeVector8(buf => { + this.modes.forEach(mode => { + buf.writeUint8(mode); + }); + }); + break; + default: + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + } +} + +// CONCATENATED MODULE: ./src/constants.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const VERSION_TLS_1_0 = 0x0301; +const VERSION_TLS_1_2 = 0x0303; +const VERSION_TLS_1_3 = 0x0304; +const TLS_AES_128_GCM_SHA256 = 0x1301; +const PSK_MODE_KE = 0; + +// CONCATENATED MODULE: ./src/messages.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Message parsing. +// +// Herein we have code for reading and writing the various Handshake +// messages involved in the TLS protocol. +// + + + + + + + +/* eslint-disable sorting/sort-object-props */ +const HANDSHAKE_TYPE = { + CLIENT_HELLO: 1, + SERVER_HELLO: 2, + NEW_SESSION_TICKET: 4, + ENCRYPTED_EXTENSIONS: 8, + FINISHED: 20, +}; +/* eslint-enable sorting/sort-object-props */ + +// Base class for generic reading/writing of handshake messages, +// which are all uniformly formatted as: +// +// struct { +// HandshakeType msg_type; /* handshake type */ +// uint24 length; /* bytes in message */ +// select(Handshake.msg_type) { +// ... type specific cases here ... +// }; +// } Handshake; + +class messages_HandshakeMessage { + + get TYPE_TAG() { + throw new Error('not implemented'); + } + + static fromBytes(bytes) { + // Each handshake message has a type and length prefix, per + // https://tools.ietf.org/html/rfc8446#appendix-B.3 + const buf = new utils_BufferReader(bytes); + const msg = this.read(buf); + if (buf.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + return msg; + } + + toBytes() { + const buf = new utils_BufferWriter(); + this.write(buf); + return buf.flush(); + } + + static read(buf) { + const type = buf.readUint8(); + let msg = null; + buf.readVector24(buf => { + switch (type) { + case HANDSHAKE_TYPE.CLIENT_HELLO: + msg = messages_ClientHello._read(buf); + break; + case HANDSHAKE_TYPE.SERVER_HELLO: + msg = messages_ServerHello._read(buf); + break; + case HANDSHAKE_TYPE.NEW_SESSION_TICKET: + msg = messages_NewSessionTicket._read(buf); + break; + case HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS: + msg = EncryptedExtensions._read(buf); + break; + case HANDSHAKE_TYPE.FINISHED: + msg = messages_Finished._read(buf); + break; + } + if (buf.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + }); + if (msg === null) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + return msg; + } + + write(buf) { + buf.writeUint8(this.TYPE_TAG); + buf.writeVector24(buf => { + this._write(buf); + }); + } + + static _read(buf) { + throw new Error('not implemented'); + } + + _write(buf) { + throw new Error('not implemented'); + } + + // Some little helpers for reading a list of extensions, + // which is uniformly represented as: + // + // Extension extensions<8..2^16-1>; + // + // Recognized extensions are returned as a Map from extension type + // to extension data object, with a special `lastSeenExtension` + // property to make it easy to check which one came last. + + static _readExtensions(messageType, buf) { + const extensions = new Map(); + buf.readVector16(buf => { + const ext = extensions_Extension.read(messageType, buf); + if (extensions.has(ext.TYPE_TAG)) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + extensions.set(ext.TYPE_TAG, ext); + extensions.lastSeenExtension = ext.TYPE_TAG; + }); + return extensions; + } + + _writeExtensions(buf, extensions) { + buf.writeVector16(buf => { + extensions.forEach(ext => { + ext.write(this.TYPE_TAG, buf); + }); + }); + } +} + + +// The ClientHello message: +// +// struct { +// ProtocolVersion legacy_version = 0x0303; +// Random random; +// opaque legacy_session_id<0..32>; +// CipherSuite cipher_suites<2..2^16-2>; +// opaque legacy_compression_methods<1..2^8-1>; +// Extension extensions<8..2^16-1>; +// } ClientHello; + +class messages_ClientHello extends messages_HandshakeMessage { + + constructor(random, sessionId, extensions) { + super(); + this.random = random; + this.sessionId = sessionId; + this.extensions = extensions; + } + + get TYPE_TAG() { + return HANDSHAKE_TYPE.CLIENT_HELLO; + } + + static _read(buf) { + // The legacy_version field may indicate an earlier version of TLS + // for backwards compatibility, but must not predate TLS 1.0! + if (buf.readUint16() < VERSION_TLS_1_0) { + throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION); + } + // The random bytes provided by the peer. + const random = buf.readBytes(32); + // Read legacy_session_id, so the server can echo it. + const sessionId = buf.readVectorBytes8(); + // We only support a single ciphersuite, but the peer may offer several. + // Scan the list to confirm that the one we want is present. + let found = false; + buf.readVector16(buf => { + const cipherSuite = buf.readUint16(); + if (cipherSuite === TLS_AES_128_GCM_SHA256) { + found = true; + } + }); + if (! found) { + throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE); + } + // legacy_compression_methods must be a single zero byte for TLS1.3 ClientHellos. + // It can be non-zero in previous versions of TLS, but we're not going to + // make a successful handshake with such versions, so better to just bail out now. + const legacyCompressionMethods = buf.readVectorBytes8(); + if (legacyCompressionMethods.byteLength !== 1) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + if (legacyCompressionMethods[0] !== 0x00) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + // Read and check the extensions. + const extensions = this._readExtensions(HANDSHAKE_TYPE.CLIENT_HELLO, buf); + if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) { + throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION); + } + if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).versions.indexOf(VERSION_TLS_1_3) === -1) { + throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION); + } + // Was the PreSharedKey extension the last one? + if (extensions.has(EXTENSION_TYPE.PRE_SHARED_KEY)) { + if (extensions.lastSeenExtension !== EXTENSION_TYPE.PRE_SHARED_KEY) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + } + return new this(random, sessionId, extensions); + } + + _write(buf) { + buf.writeUint16(VERSION_TLS_1_2); + buf.writeBytes(this.random); + buf.writeVectorBytes8(this.sessionId); + // Our single supported ciphersuite + buf.writeVector16(buf => { + buf.writeUint16(TLS_AES_128_GCM_SHA256); + }); + // A single zero byte for legacy_compression_methods + buf.writeVectorBytes8(new Uint8Array(1)); + this._writeExtensions(buf, this.extensions); + } +} + + +// The ServerHello message: +// +// struct { +// ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */ +// Random random; +// opaque legacy_session_id_echo<0..32>; +// CipherSuite cipher_suite; +// uint8 legacy_compression_method = 0; +// Extension extensions < 6..2 ^ 16 - 1 >; +// } ServerHello; + +class messages_ServerHello extends messages_HandshakeMessage { + + constructor(random, sessionId, extensions) { + super(); + this.random = random; + this.sessionId = sessionId; + this.extensions = extensions; + } + + get TYPE_TAG() { + return HANDSHAKE_TYPE.SERVER_HELLO; + } + + static _read(buf) { + // Fixed value for legacy_version. + if (buf.readUint16() !== VERSION_TLS_1_2) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + // Random bytes from the server. + const random = buf.readBytes(32); + // It should have echoed our vector for legacy_session_id. + const sessionId = buf.readVectorBytes8(); + // It should have selected our single offered ciphersuite. + if (buf.readUint16() !== TLS_AES_128_GCM_SHA256) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + // legacy_compression_method must be zero. + if (buf.readUint8() !== 0) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + const extensions = this._readExtensions(HANDSHAKE_TYPE.SERVER_HELLO, buf); + if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) { + throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION); + } + if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).selectedVersion !== VERSION_TLS_1_3) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + return new this(random, sessionId, extensions); + } + + _write(buf) { + buf.writeUint16(VERSION_TLS_1_2); + buf.writeBytes(this.random); + buf.writeVectorBytes8(this.sessionId); + // Our single supported ciphersuite + buf.writeUint16(TLS_AES_128_GCM_SHA256); + // A single zero byte for legacy_compression_method + buf.writeUint8(0); + this._writeExtensions(buf, this.extensions); + } +} + + +// The EncryptedExtensions message: +// +// struct { +// Extension extensions < 0..2 ^ 16 - 1 >; +// } EncryptedExtensions; +// +// We don't actually send any EncryptedExtensions, +// but still have to send an empty message. + +class EncryptedExtensions extends messages_HandshakeMessage { + constructor(extensions) { + super(); + this.extensions = extensions; + } + + get TYPE_TAG() { + return HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS; + } + + static _read(buf) { + const extensions = this._readExtensions(HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS, buf); + return new this(extensions); + } + + _write(buf) { + this._writeExtensions(buf, this.extensions); + } +} + + +// The Finished message: +// +// struct { +// opaque verify_data[Hash.length]; +// } Finished; + +class messages_Finished extends messages_HandshakeMessage { + + constructor(verifyData) { + super(); + this.verifyData = verifyData; + } + + get TYPE_TAG() { + return HANDSHAKE_TYPE.FINISHED; + } + + static _read(buf) { + const verifyData = buf.readBytes(HASH_LENGTH); + return new this(verifyData); + } + + _write(buf) { + buf.writeBytes(this.verifyData); + } +} + + +// The NewSessionTicket message: +// +// struct { +// uint32 ticket_lifetime; +// uint32 ticket_age_add; +// opaque ticket_nonce < 0..255 >; +// opaque ticket < 1..2 ^ 16 - 1 >; +// Extension extensions < 0..2 ^ 16 - 2 >; +// } NewSessionTicket; +// +// We don't actually make use of these, but we need to be able +// to accept them and do basic validation. + +class messages_NewSessionTicket extends messages_HandshakeMessage { + constructor(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions) { + super(); + this.ticketLifetime = ticketLifetime; + this.ticketAgeAdd = ticketAgeAdd; + this.ticketNonce = ticketNonce; + this.ticket = ticket; + this.extensions = extensions; + } + + get TYPE_TAG() { + return HANDSHAKE_TYPE.NEW_SESSION_TICKET; + } + + static _read(buf) { + const ticketLifetime = buf.readUint32(); + const ticketAgeAdd = buf.readUint32(); + const ticketNonce = buf.readVectorBytes8(); + const ticket = buf.readVectorBytes16(); + if (ticket.byteLength < 1) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + const extensions = this._readExtensions(HANDSHAKE_TYPE.NEW_SESSION_TICKET, buf); + return new this(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions); + } + + _write(buf) { + buf.writeUint32(this.ticketLifetime); + buf.writeUint32(this.ticketAgeAdd); + buf.writeVectorBytes8(this.ticketNonce); + buf.writeVectorBytes16(this.ticket); + this._writeExtensions(buf, this.extensions); + } +} + +// CONCATENATED MODULE: ./src/states.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + + + + + +// +// State-machine for TLS Handshake Management. +// +// Internally, we manage the TLS connection by explicitly modelling the +// client and server state-machines from RFC8446. You can think of +// these `State` objects as little plugins for the `Connection` class +// that provide different behaviours of `send` and `receive` depending +// on the state of the connection. +// + +class states_State { + + constructor(conn) { + this.conn = conn; + } + + async initialize() { + // By default, nothing to do when entering the state. + } + + async sendApplicationData(bytes) { + // By default, assume we're not ready to send yet and the caller + // should be blocking on the connection promise before reaching here. + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + + async recvApplicationData(bytes) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + + async recvHandshakeMessage(msg) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + + async recvAlertMessage(alert) { + switch (alert.description) { + case ALERT_DESCRIPTION.CLOSE_NOTIFY: + this.conn._closeForRecv(alert); + throw alert; + default: + return await this.handleErrorAndRethrow(alert); + } + } + + async recvChangeCipherSpec(bytes) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + + async handleErrorAndRethrow(err) { + let alert = err; + if (! (alert instanceof TLSAlert)) { + alert = new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + // Try to send error alert to the peer, but we may not + // be able to if the outgoing connection was already closed. + try { + await this.conn._sendAlertMessage(alert); + } catch (_) { } + await this.conn._transition(ERROR, err); + throw err; + } + + async close() { + const alert = new TLSCloseNotify(); + await this.conn._sendAlertMessage(alert); + this.conn._closeForSend(alert); + } + +} + +// A special "guard" state to prevent us from using +// an improperly-initialized Connection. + +class UNINITIALIZED extends states_State { + async initialize() { + throw new Error('uninitialized state'); + } + async sendApplicationData(bytes) { + throw new Error('uninitialized state'); + } + async recvApplicationData(bytes) { + throw new Error('uninitialized state'); + } + async recvHandshakeMessage(msg) { + throw new Error('uninitialized state'); + } + async recvChangeCipherSpec(bytes) { + throw new Error('uninitialized state'); + } + async handleErrorAndRethrow(err) { + throw err; + } + async close() { + throw new Error('uninitialized state'); + } +} + +// A special "error" state for when something goes wrong. +// This state never transitions to another state, effectively +// terminating the connection. + +class ERROR extends states_State { + async initialize(err) { + this.error = err; + this.conn._setConnectionFailure(err); + // Unceremoniously shut down the record layer on error. + this.conn._recordlayer.setSendError(err); + this.conn._recordlayer.setRecvError(err); + } + async sendApplicationData(bytes) { + throw this.error; + } + async recvApplicationData(bytes) { + throw this.error; + } + async recvHandshakeMessage(msg) { + throw this.error; + } + async recvAlertMessage(err) { + throw this.error; + } + async recvChangeCipherSpec(bytes) { + throw this.error; + } + async handleErrorAndRethrow(err) { + throw err; + } + async close() { + throw this.error; + } +} + +// The "connected" state, for when the handshake is complete +// and we're ready to send application-level data. +// The logic for this is largely symmetric between client and server. + +class states_CONNECTED extends states_State { + async initialize() { + this.conn._setConnectionSuccess(); + } + async sendApplicationData(bytes) { + await this.conn._sendApplicationData(bytes); + } + async recvApplicationData(bytes) { + return bytes; + } + async recvChangeCipherSpec(bytes) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } +} + +// A base class for states that occur in the middle of the handshake +// (that is, between ClientHello and Finished). These states may receive +// CHANGE_CIPHER_SPEC records for b/w compat reasons, which must contain +// exactly a single 0x01 byte and must otherwise be ignored. + +class states_MidHandshakeState extends states_State { + async recvChangeCipherSpec(bytes) { + if (this.conn._hasSeenChangeCipherSpec) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + if (bytes.byteLength !== 1 || bytes[0] !== 1) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + this.conn._hasSeenChangeCipherSpec = true; + } +} + +// These states implement (part of) the client state-machine from +// https://tools.ietf.org/html/rfc8446#appendix-A.1 +// +// Since we're only implementing a small subset of TLS1.3, +// we only need a small subset of the handshake. It basically goes: +// +// * send ClientHello +// * receive ServerHello +// * receive EncryptedExtensions +// * receive server Finished +// * send client Finished +// +// We include some unused states for completeness, so that it's easier +// to check the implementation against the diagrams in the RFC. + +class states_CLIENT_START extends states_State { + async initialize() { + const keyschedule = this.conn._keyschedule; + await keyschedule.addPSK(this.conn.psk); + // Construct a ClientHello message with our single PSK. + // We can't know the PSK binder value yet, so we initially write zeros. + const clientHello = new messages_ClientHello( + // Client random salt. + await getRandomBytes(32), + // Random legacy_session_id; we *could* send an empty string here, + // but sending a random one makes it easier to be compatible with + // the data emitted by tlslite-ng for test-case generation. + await getRandomBytes(32), + [ + new extensions_SupportedVersionsExtension([VERSION_TLS_1_3]), + new extensions_PskKeyExchangeModesExtension([PSK_MODE_KE]), + new extensions_PreSharedKeyExtension([this.conn.pskId], [zeros(HASH_LENGTH)]), + ], + ); + const buf = new utils_BufferWriter(); + clientHello.write(buf); + // Now that we know what the ClientHello looks like, + // go back and calculate the appropriate PSK binder value. + // We only support a single PSK, so the length of the binders field is the + // length of the hash plus one for rendering it as a variable-length byte array, + // plus two for rendering the variable-length list of PSK binders. + const PSK_BINDERS_SIZE = HASH_LENGTH + 1 + 2; + const truncatedTranscript = buf.slice(0, buf.tell() - PSK_BINDERS_SIZE); + const pskBinder = await keyschedule.calculateFinishedMAC(keyschedule.extBinderKey, truncatedTranscript); + buf.incr(-HASH_LENGTH); + buf.writeBytes(pskBinder); + await this.conn._sendHandshakeMessageBytes(buf.flush()); + await this.conn._transition(states_CLIENT_WAIT_SH, clientHello.sessionId); + } +} + +class states_CLIENT_WAIT_SH extends states_State { + async initialize(sessionId) { + this._sessionId = sessionId; + } + async recvHandshakeMessage(msg) { + if (! (msg instanceof messages_ServerHello)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + if (! bytesAreEqual(msg.sessionId, this._sessionId)) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY); + if (! pskExt) { + throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION); + } + // We expect only the SUPPORTED_VERSIONS and PRE_SHARED_KEY extensions. + if (msg.extensions.size !== 2) { + throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION); + } + if (pskExt.selectedIdentity !== 0) { + throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER); + } + await this.conn._keyschedule.addECDHE(null); + await this.conn._setSendKey(this.conn._keyschedule.clientHandshakeTrafficSecret); + await this.conn._setRecvKey(this.conn._keyschedule.serverHandshakeTrafficSecret); + await this.conn._transition(states_CLIENT_WAIT_EE); + } +} + +class states_CLIENT_WAIT_EE extends states_MidHandshakeState { + async recvHandshakeMessage(msg) { + // We don't make use of any encrypted extensions, but we still + // have to wait for the server to send the (empty) list of them. + if (! (msg instanceof EncryptedExtensions)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + // We do not support any EncryptedExtensions. + if (msg.extensions.size !== 0) { + throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION); + } + const keyschedule = this.conn._keyschedule; + const serverFinishedTranscript = keyschedule.getTranscript(); + await this.conn._transition(states_CLIENT_WAIT_FINISHED, serverFinishedTranscript); + } +} + +class states_CLIENT_WAIT_FINISHED extends states_State { + async initialize(serverFinishedTranscript) { + this._serverFinishedTranscript = serverFinishedTranscript; + } + async recvHandshakeMessage(msg) { + if (! (msg instanceof messages_Finished)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + // Verify server Finished MAC. + const keyschedule = this.conn._keyschedule; + await keyschedule.verifyFinishedMAC(keyschedule.serverHandshakeTrafficSecret, msg.verifyData, this._serverFinishedTranscript); + // Send our own Finished message in return. + // This must be encrypted with the handshake traffic key, + // but must not appear in the transcript used to calculate the application keys. + const clientFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.clientHandshakeTrafficSecret); + await keyschedule.finalize(); + await this.conn._sendHandshakeMessage(new messages_Finished(clientFinishedMAC)); + await this.conn._setSendKey(keyschedule.clientApplicationTrafficSecret); + await this.conn._setRecvKey(keyschedule.serverApplicationTrafficSecret); + await this.conn._transition(states_CLIENT_CONNECTED); + } +} + +class states_CLIENT_CONNECTED extends states_CONNECTED { + async recvHandshakeMessage(msg) { + // A connected client must be prepared to accept NewSessionTicket + // messages. We never use them, but other server implementations + // might send them. + if (! (msg instanceof messages_NewSessionTicket)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + } +} + +// These states implement (part of) the server state-machine from +// https://tools.ietf.org/html/rfc8446#appendix-A.2 +// +// Since we're only implementing a small subset of TLS1.3, +// we only need a small subset of the handshake. It basically goes: +// +// * receive ClientHello +// * send ServerHello +// * send empty EncryptedExtensions +// * send server Finished +// * receive client Finished +// +// We include some unused states for completeness, so that it's easier +// to check the implementation against the diagrams in the RFC. + +class states_SERVER_START extends states_State { + async recvHandshakeMessage(msg) { + if (! (msg instanceof messages_ClientHello)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + // In the spec, this is where we select connection parameters, and maybe + // tell the client to try again if we can't find a compatible set. + // Since we only support a fixed cipherset, the only thing to "negotiate" + // is whether they provided an acceptable PSK. + const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY); + const pskModesExt = msg.extensions.get(EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES); + if (! pskExt || ! pskModesExt) { + throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION); + } + if (pskModesExt.modes.indexOf(PSK_MODE_KE) === -1) { + throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE); + } + const pskIndex = pskExt.identities.findIndex(pskId => bytesAreEqual(pskId, this.conn.pskId)); + if (pskIndex === -1) { + throw new TLSError(ALERT_DESCRIPTION.UNKNOWN_PSK_IDENTITY); + } + await this.conn._keyschedule.addPSK(this.conn.psk); + // Validate the PSK binder. + const keyschedule = this.conn._keyschedule; + const transcript = keyschedule.getTranscript(); + // Calculate size occupied by the PSK binders. + let pskBindersSize = 2; // Vector16 representation overhead. + for (const binder of pskExt.binders) { + pskBindersSize += binder.byteLength + 1; // Vector8 representation overhead. + } + await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, pskExt.binders[pskIndex], transcript.slice(0, -pskBindersSize)); + await this.conn._transition(states_SERVER_NEGOTIATED, msg.sessionId, pskIndex); + } +} + +class states_SERVER_NEGOTIATED extends states_MidHandshakeState { + async initialize(sessionId, pskIndex) { + await this.conn._sendHandshakeMessage(new messages_ServerHello( + // Server random + await getRandomBytes(32), + sessionId, + [ + new extensions_SupportedVersionsExtension(null, VERSION_TLS_1_3), + new extensions_PreSharedKeyExtension(null, null, pskIndex), + ] + )); + // If the client sent a non-empty sessionId, the server *must* send a change-cipher-spec for b/w compat. + if (sessionId.byteLength > 0) { + await this.conn._sendChangeCipherSpec(); + } + // We can now transition to the encrypted part of the handshake. + const keyschedule = this.conn._keyschedule; + await keyschedule.addECDHE(null); + await this.conn._setSendKey(keyschedule.serverHandshakeTrafficSecret); + await this.conn._setRecvKey(keyschedule.clientHandshakeTrafficSecret); + // Send an empty EncryptedExtensions message. + await this.conn._sendHandshakeMessage(new EncryptedExtensions([])); + // Send the Finished message. + const serverFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.serverHandshakeTrafficSecret); + await this.conn._sendHandshakeMessage(new messages_Finished(serverFinishedMAC)); + // We can now *send* using the application traffic key, + // but have to wait to receive the client Finished before receiving under that key. + // We need to remember the handshake state from before the client Finished + // in order to successfully verify the client Finished. + const clientFinishedTranscript = await keyschedule.getTranscript(); + const clientHandshakeTrafficSecret = keyschedule.clientHandshakeTrafficSecret; + await keyschedule.finalize(); + await this.conn._setSendKey(keyschedule.serverApplicationTrafficSecret); + await this.conn._transition(states_SERVER_WAIT_FINISHED, clientHandshakeTrafficSecret, clientFinishedTranscript); + } +} + +class states_SERVER_WAIT_FINISHED extends states_MidHandshakeState { + async initialize(clientHandshakeTrafficSecret, clientFinishedTranscript) { + this._clientHandshakeTrafficSecret = clientHandshakeTrafficSecret; + this._clientFinishedTranscript = clientFinishedTranscript; + } + async recvHandshakeMessage(msg) { + if (! (msg instanceof messages_Finished)) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + const keyschedule = this.conn._keyschedule; + await keyschedule.verifyFinishedMAC(this._clientHandshakeTrafficSecret, msg.verifyData, this._clientFinishedTranscript); + this._clientHandshakeTrafficSecret = this._clientFinishedTranscript = null; + await this.conn._setRecvKey(keyschedule.clientApplicationTrafficSecret); + await this.conn._transition(states_CONNECTED); + } +} + +// CONCATENATED MODULE: ./src/keyschedule.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// TLS1.3 Key Schedule. +// +// In this file we implement the "key schedule" from +// https://tools.ietf.org/html/rfc8446#section-7.1, which +// defines how to calculate various keys as the handshake +// state progresses. + + + + + + + +// The `KeySchedule` class progresses through three stages corresponding +// to the three phases of the TLS1.3 key schedule: +// +// UNINITIALIZED +// | +// | addPSK() +// v +// EARLY_SECRET +// | +// | addECDHE() +// v +// HANDSHAKE_SECRET +// | +// | finalize() +// v +// MASTER_SECRET +// +// It will error out if the calling code attempts to add key material +// in the wrong order. + +const STAGE_UNINITIALIZED = 0; +const STAGE_EARLY_SECRET = 1; +const STAGE_HANDSHAKE_SECRET = 2; +const STAGE_MASTER_SECRET = 3; + +class keyschedule_KeySchedule { + constructor() { + this.stage = STAGE_UNINITIALIZED; + // WebCrypto doesn't support a rolling hash construct, so we have to + // keep the entire message transcript in memory. + this.transcript = new utils_BufferWriter(); + // This tracks the main secret from with other keys are derived at each stage. + this.secret = null; + // And these are all the various keys we'll derive as the handshake progresses. + this.extBinderKey = null; + this.clientHandshakeTrafficSecret = null; + this.serverHandshakeTrafficSecret = null; + this.clientApplicationTrafficSecret = null; + this.serverApplicationTrafficSecret = null; + } + + async addPSK(psk) { + // Use the selected PSK (if any) to calculate the "early secret". + if (psk === null) { + psk = zeros(HASH_LENGTH); + } + if (this.stage !== STAGE_UNINITIALIZED) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + this.stage = STAGE_EARLY_SECRET; + this.secret = await hkdfExtract(zeros(HASH_LENGTH), psk); + this.extBinderKey = await this.deriveSecret('ext binder', EMPTY); + this.secret = await this.deriveSecret('derived', EMPTY); + } + + async addECDHE(ecdhe) { + // Mix in the ECDHE output (if any) to calculate the "handshake secret". + if (ecdhe === null) { + ecdhe = zeros(HASH_LENGTH); + } + if (this.stage !== STAGE_EARLY_SECRET) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + this.stage = STAGE_HANDSHAKE_SECRET; + this.extBinderKey = null; + this.secret = await hkdfExtract(this.secret, ecdhe); + this.clientHandshakeTrafficSecret = await this.deriveSecret('c hs traffic'); + this.serverHandshakeTrafficSecret = await this.deriveSecret('s hs traffic'); + this.secret = await this.deriveSecret('derived', EMPTY); + } + + async finalize() { + if (this.stage !== STAGE_HANDSHAKE_SECRET) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + this.stage = STAGE_MASTER_SECRET; + this.clientHandshakeTrafficSecret = null; + this.serverHandshakeTrafficSecret = null; + this.secret = await hkdfExtract(this.secret, zeros(HASH_LENGTH)); + this.clientApplicationTrafficSecret = await this.deriveSecret('c ap traffic'); + this.serverApplicationTrafficSecret = await this.deriveSecret('s ap traffic'); + this.secret = null; + } + + addToTranscript(bytes) { + this.transcript.writeBytes(bytes); + } + + getTranscript() { + return this.transcript.slice(); + } + + async deriveSecret(label, transcript = undefined) { + transcript = transcript || this.getTranscript(); + return await hkdfExpandLabel(this.secret, label, await hash(transcript), HASH_LENGTH); + } + + async calculateFinishedMAC(baseKey, transcript = undefined) { + transcript = transcript || this.getTranscript(); + const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH); + return await hmac(finishedKey, await hash(transcript)); + } + + async verifyFinishedMAC(baseKey, mac, transcript = undefined) { + transcript = transcript || this.getTranscript(); + const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH); + await verifyHmac(finishedKey, mac, await hash(transcript)); + } +} + +// CONCATENATED MODULE: ./src/recordlayer.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// This file implements the "record layer" for TLS1.3, as defined in +// https://tools.ietf.org/html/rfc8446#section-5. +// +// The record layer is responsible for encrypting/decrypting bytes to be +// sent over the wire, including stateful management of sequence numbers +// for the incoming and outgoing stream. +// +// The main interface is the RecordLayer class, which takes a callback function +// sending data and can be used like so: +// +// rl = new RecordLayer(async function send_encrypted_data(data) { +// // application-specific sending logic here. +// }); +// +// // Records are sent and received in plaintext by default, +// // until you specify the key to use. +// await rl.setSendKey(key) +// +// // Send some data by specifying the record type and the bytes. +// // Where allowed by the record type, it will be buffered until +// // explicitly flushed, and then sent by calling the callback. +// await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for a handshake message>) +// await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for another handshake message>) +// await rl.flush() +// +// // Separate keys are used for sending and receiving. +// rl.setRecvKey(key); +// +// // When data is received, push it into the RecordLayer +// // and pass a callback that will be called with a [type, bytes] +// // pair for each message parsed from the data. +// rl.recv(dataReceivedFromPeer, async (type, bytes) => { +// switch (type) { +// case RECORD_TYPE.APPLICATION_DATA: +// // do something with application data +// case RECORD_TYPE.HANDSHAKE: +// // do something with a handshake message +// default: +// // etc... +// } +// }); +// + + + + + + + +/* eslint-disable sorting/sort-object-props */ +const RECORD_TYPE = { + CHANGE_CIPHER_SPEC: 20, + ALERT: 21, + HANDSHAKE: 22, + APPLICATION_DATA: 23, +}; +/* eslint-enable sorting/sort-object-props */ + +// Encrypting at most 2^24 records will force us to stay +// below data limits on AES-GCM encryption key use, and also +// means we can accurately represent the sequence number as +// a javascript double. +const MAX_SEQUENCE_NUMBER = Math.pow(2, 24); +const MAX_RECORD_SIZE = Math.pow(2, 14); +const MAX_ENCRYPTED_RECORD_SIZE = MAX_RECORD_SIZE + 256; +const RECORD_HEADER_SIZE = 5; + +// These are some helper classes to manage the encryption/decryption state +// for a particular key. + +class recordlayer_CipherState { + constructor(key, iv) { + this.key = key; + this.iv = iv; + this.seqnum = 0; + } + + static async create(baseKey, mode) { + // Derive key and iv per https://tools.ietf.org/html/rfc8446#section-7.3 + const key = await prepareKey(await hkdfExpandLabel(baseKey, 'key', EMPTY, KEY_LENGTH), mode); + const iv = await hkdfExpandLabel(baseKey, 'iv', EMPTY, IV_LENGTH); + return new this(key, iv); + } + + nonce() { + // Ref https://tools.ietf.org/html/rfc8446#section-5.3: + // * left-pad the sequence number with zeros to IV_LENGTH + // * xor with the provided iv + // Our sequence numbers are always less than 2^24, so fit in a Uint32 + // in the last 4 bytes of the nonce. + const nonce = this.iv.slice(); + const dv = new DataView(nonce.buffer, nonce.byteLength - 4, 4); + dv.setUint32(0, dv.getUint32(0) ^ this.seqnum); + this.seqnum += 1; + if (this.seqnum > MAX_SEQUENCE_NUMBER) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + return nonce; + } +} + +class recordlayer_EncryptionState extends recordlayer_CipherState { + static async create(key) { + return super.create(key, 'encrypt'); + } + + async encrypt(plaintext, additionalData) { + return await encrypt(this.key, this.nonce(), plaintext, additionalData); + } +} + +class recordlayer_DecryptionState extends recordlayer_CipherState { + static async create(key) { + return super.create(key, 'decrypt'); + } + + async decrypt(ciphertext, additionalData) { + return await decrypt(this.key, this.nonce(), ciphertext, additionalData); + } +} + +// The main RecordLayer class. + +class recordlayer_RecordLayer { + constructor(sendCallback) { + this.sendCallback = sendCallback; + this._sendEncryptState = null; + this._sendError = null; + this._recvDecryptState = null; + this._recvError = null; + this._pendingRecordType = 0; + this._pendingRecordBuf = null; + } + + async setSendKey(key) { + await this.flush(); + this._sendEncryptState = await recordlayer_EncryptionState.create(key); + } + + async setRecvKey(key) { + this._recvDecryptState = await recordlayer_DecryptionState.create(key); + } + + async setSendError(err) { + this._sendError = err; + } + + async setRecvError(err) { + this._recvError = err; + } + + async send(type, data) { + if (this._sendError !== null) { + throw this._sendError; + } + // Forbid sending data that doesn't fit into a single record. + // We do not support fragmentation over multiple records. + if (data.byteLength > MAX_RECORD_SIZE) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + // Flush if we're switching to a different record type. + if (this._pendingRecordType && this._pendingRecordType !== type) { + await this.flush(); + } + // Flush if we would overflow the max size of a record. + if (this._pendingRecordBuf !== null) { + if (this._pendingRecordBuf.tell() + data.byteLength > MAX_RECORD_SIZE) { + await this.flush(); + } + } + // Start a new pending record if necessary. + // We reserve space at the start of the buffer for the record header, + // which is conveniently always a fixed size. + if (this._pendingRecordBuf === null) { + this._pendingRecordType = type; + this._pendingRecordBuf = new utils_BufferWriter(); + this._pendingRecordBuf.incr(RECORD_HEADER_SIZE); + } + this._pendingRecordBuf.writeBytes(data); + } + + async flush() { + // If there's nothing to flush, bail out early. + // Don't throw `_sendError` if we're not sending anything, because `flush()` + // can be called when we're trying to transition into an error state. + const buf = this._pendingRecordBuf; + let type = this._pendingRecordType; + if (! type) { + if (buf !== null) { + throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR); + } + return; + } + if (this._sendError !== null) { + throw this._sendError; + } + // If we're encrypting, turn the existing buffer contents into a `TLSInnerPlaintext` by + // appending the type. We don't do any zero-padding, although the spec allows it. + let inflation = 0, innerPlaintext = null; + if (this._sendEncryptState !== null) { + buf.writeUint8(type); + innerPlaintext = buf.slice(RECORD_HEADER_SIZE); + inflation = AEAD_SIZE_INFLATION; + type = RECORD_TYPE.APPLICATION_DATA; + } + // Write the common header for either `TLSPlaintext` or `TLSCiphertext` record. + const length = buf.tell() - RECORD_HEADER_SIZE + inflation; + buf.seek(0); + buf.writeUint8(type); + buf.writeUint16(VERSION_TLS_1_2); + buf.writeUint16(length); + // Followed by different payload depending on encryption status. + if (this._sendEncryptState !== null) { + const additionalData = buf.slice(0, RECORD_HEADER_SIZE); + const ciphertext = await this._sendEncryptState.encrypt(innerPlaintext, additionalData); + buf.writeBytes(ciphertext); + } else { + buf.incr(length); + } + this._pendingRecordBuf = null; + this._pendingRecordType = 0; + await this.sendCallback(buf.flush()); + } + + async recv(data) { + if (this._recvError !== null) { + throw this._recvError; + } + // For simplicity, we assume that the given data contains exactly one record. + // Peers using this library will send one record at a time over the websocket + // connection, and we can assume that the server-side websocket bridge will split + // up any traffic into individual records if we ever start interoperating with + // peers using a different TLS implementation. + // Similarly, we assume that handshake messages will not be fragmented across + // multiple records. This should be trivially true for the PSK-only mode used + // by this library, but we may want to relax it in future for interoperability + // with e.g. large ClientHello messages that contain lots of different options. + const buf = new utils_BufferReader(data); + // The data to read is either a TLSPlaintext or TLSCiphertext struct, + // depending on whether record protection has been enabled yet: + // + // struct { + // ContentType type; + // ProtocolVersion legacy_record_version; + // uint16 length; + // opaque fragment[TLSPlaintext.length]; + // } TLSPlaintext; + // + // struct { + // ContentType opaque_type = application_data; /* 23 */ + // ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */ + // uint16 length; + // opaque encrypted_record[TLSCiphertext.length]; + // } TLSCiphertext; + // + let type = buf.readUint8(); + // The spec says legacy_record_version "MUST be ignored for all purposes", + // but we know TLS1.3 implementations will only ever emit two possible values, + // so it seems useful to bail out early if we receive anything else. + const version = buf.readUint16(); + if (version !== VERSION_TLS_1_2) { + // TLS1.0 is only acceptable on initial plaintext records. + if (this._recvDecryptState !== null || version !== VERSION_TLS_1_0) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + } + const length = buf.readUint16(); + let plaintext; + if (this._recvDecryptState === null || type === RECORD_TYPE.CHANGE_CIPHER_SPEC) { + [type, plaintext] = await this._readPlaintextRecord(type, length, buf); + } else { + [type, plaintext] = await this._readEncryptedRecord(type, length, buf); + } + // Sanity-check that we received exactly one record. + if (buf.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + return [type, plaintext]; + } + + // Helper to read an unencrypted `TLSPlaintext` struct + + async _readPlaintextRecord(type, length, buf) { + if (length > MAX_RECORD_SIZE) { + throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW); + } + return [type, buf.readBytes(length)]; + } + + // Helper to read an encrypted `TLSCiphertext` struct, + // decrypting it into plaintext. + + async _readEncryptedRecord(type, length, buf) { + if (length > MAX_ENCRYPTED_RECORD_SIZE) { + throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW); + } + // The outer type for encrypted records is always APPLICATION_DATA. + if (type !== RECORD_TYPE.APPLICATION_DATA) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + // Decrypt and decode the contained `TLSInnerPlaintext` struct: + // + // struct { + // opaque content[TLSPlaintext.length]; + // ContentType type; + // uint8 zeros[length_of_padding]; + // } TLSInnerPlaintext; + // + // The additional data for the decryption is the `TLSCiphertext` record + // header, which is a fixed size and immediately prior to current buffer position. + buf.incr(-RECORD_HEADER_SIZE); + const additionalData = buf.readBytes(RECORD_HEADER_SIZE); + const ciphertext = buf.readBytes(length); + const paddedPlaintext = await this._recvDecryptState.decrypt(ciphertext, additionalData); + // We have to scan backwards over the zero padding at the end of the struct + // in order to find the non-zero `type` byte. + let i; + for (i = paddedPlaintext.byteLength - 1; i >= 0; i--) { + if (paddedPlaintext[i] !== 0) { + break; + } + } + if (i < 0) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + type = paddedPlaintext[i]; + // `change_cipher_spec` records must always be plaintext. + if (type === RECORD_TYPE.CHANGE_CIPHER_SPEC) { + throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR); + } + return [type, paddedPlaintext.slice(0, i)]; + } +} + +// CONCATENATED MODULE: ./src/tlsconnection.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// The top-level APIs offered by this module are `ClientConnection` and +// `ServerConnection` classes, which provide authenticated and encrypted +// communication via the "externally-provisioned PSK" mode of TLS1.3. +// They each take a callback to be used for sending data to the remote peer, +// and operate like this: +// +// conn = await ClientConnection.create(psk, pskId, async function send_data_to_server(data) { +// // application-specific sending logic here. +// }) +// +// // Send data to the server by calling `send`, +// // which will use the callback provided in the constructor. +// // A single `send()` by the application may result in multiple +// // invokations of the callback. +// +// await conn.send('application-level data') +// +// // When data is received from the server, push it into +// // the connection and let it return any decrypted app-level data. +// // There might not be any app-level data if it was a protocol control +// // message, and the receipt of the data might trigger additional calls +// // to the send callback for protocol control purposes. +// +// serverSocket.on('data', async encrypted_data => { +// const plaintext = await conn.recv(data) +// if (plaintext !== null) { +// do_something_with_app_level_data(plaintext) +// } +// }) +// +// // It's good practice to explicitly close the connection +// // when finished. This will send a "closed" notification +// // to the server. +// +// await conn.close() +// +// // When the peer sends a "closed" notification it will show up +// // as a `TLSCloseNotify` exception from recv: +// +// try { +// data = await conn.recv(data); +// } catch (err) { +// if (! (err instanceof TLSCloseNotify) { throw err } +// do_something_to_cleanly_close_data_connection(); +// } +// +// The `ServerConnection` API operates similarly; the distinction is mainly +// in which side is expected to send vs receieve during the protocol handshake. + + + + + + + + + + +class tlsconnection_Connection { + constructor(psk, pskId, sendCallback) { + this.psk = assertIsBytes(psk); + this.pskId = assertIsBytes(pskId); + this.connected = new Promise((resolve, reject) => { + this._onConnectionSuccess = resolve; + this._onConnectionFailure = reject; + }); + this._state = new UNINITIALIZED(this); + this._handshakeRecvBuffer = null; + this._hasSeenChangeCipherSpec = false; + this._recordlayer = new recordlayer_RecordLayer(sendCallback); + this._keyschedule = new keyschedule_KeySchedule(); + this._lastPromise = Promise.resolve(); + } + + // Subclasses will override this with some async initialization logic. + static async create(psk, pskId, sendCallback) { + return new this(psk, pskId, sendCallback); + } + + // These are the three public API methods that consumers can use + // to send and receive data encrypted with TLS1.3. + + async send(data) { + assertIsBytes(data); + await this.connected; + await this._synchronized(async () => { + await this._state.sendApplicationData(data); + }); + } + + async recv(data) { + assertIsBytes(data); + return await this._synchronized(async () => { + // Decrypt the data using the record layer. + // We expect to receive precisely one record at a time. + const [type, bytes] = await this._recordlayer.recv(data); + // Dispatch based on the type of the record. + switch (type) { + case RECORD_TYPE.CHANGE_CIPHER_SPEC: + await this._state.recvChangeCipherSpec(bytes); + return null; + case RECORD_TYPE.ALERT: + await this._state.recvAlertMessage(TLSAlert.fromBytes(bytes)); + return null; + case RECORD_TYPE.APPLICATION_DATA: + return await this._state.recvApplicationData(bytes); + case RECORD_TYPE.HANDSHAKE: + // Multiple handshake messages may be coalesced into a single record. + // Store the in-progress record buffer on `this` so that we can guard + // against handshake messages that span a change in keys. + this._handshakeRecvBuffer = new utils_BufferReader(bytes); + if (! this._handshakeRecvBuffer.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + do { + // Each handshake messages has a type and length prefix, per + // https://tools.ietf.org/html/rfc8446#appendix-B.3 + this._handshakeRecvBuffer.incr(1); + const mlength = this._handshakeRecvBuffer.readUint24(); + this._handshakeRecvBuffer.incr(-4); + const messageBytes = this._handshakeRecvBuffer.readBytes(mlength + 4); + this._keyschedule.addToTranscript(messageBytes); + await this._state.recvHandshakeMessage(messages_HandshakeMessage.fromBytes(messageBytes)); + } while (this._handshakeRecvBuffer.hasMoreBytes()); + this._handshakeRecvBuffer = null; + return null; + default: + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + }); + } + + async close() { + await this._synchronized(async () => { + await this._state.close(); + }); + } + + // Ensure that async functions execute one at a time, + // by waiting for the previous call to `_synchronized()` to complete + // before starting a new one. This helps ensure that we complete + // one state-machine transition before starting to do the next. + // It's also a convenient place to catch and alert on errors. + + _synchronized(cb) { + const nextPromise = this._lastPromise.then(() => { + return cb(); + }).catch(async err => { + if (err instanceof TLSCloseNotify) { + throw err; + } + await this._state.handleErrorAndRethrow(err); + }); + // We don't want to hold on to the return value or error, + // just synchronize on the fact that it completed. + this._lastPromise = nextPromise.then(noop, noop); + return nextPromise; + } + + // This drives internal transition of the state-machine, + // ensuring that the new state is properly initialized. + + async _transition(State, ...args) { + this._state = new State(this); + await this._state.initialize(...args); + await this._recordlayer.flush(); + } + + // These are helpers to allow the State to manipulate the recordlayer + // and send out various types of data. + + async _sendApplicationData(bytes) { + await this._recordlayer.send(RECORD_TYPE.APPLICATION_DATA, bytes); + await this._recordlayer.flush(); + } + + async _sendHandshakeMessage(msg) { + await this._sendHandshakeMessageBytes(msg.toBytes()); + } + + async _sendHandshakeMessageBytes(bytes) { + this._keyschedule.addToTranscript(bytes); + await this._recordlayer.send(RECORD_TYPE.HANDSHAKE, bytes); + // Don't flush after each handshake message, since we can probably + // coalesce multiple messages into a single record. + } + + async _sendAlertMessage(err) { + await this._recordlayer.send(RECORD_TYPE.ALERT, err.toBytes()); + await this._recordlayer.flush(); + } + + async _sendChangeCipherSpec() { + await this._recordlayer.send(RECORD_TYPE.CHANGE_CIPHER_SPEC, new Uint8Array([0x01])); + await this._recordlayer.flush(); + } + + async _setSendKey(key) { + return await this._recordlayer.setSendKey(key); + } + + async _setRecvKey(key) { + // Handshake messages that change keys must be on a record boundary. + if (this._handshakeRecvBuffer && this._handshakeRecvBuffer.hasMoreBytes()) { + throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE); + } + return await this._recordlayer.setRecvKey(key); + } + + _setConnectionSuccess() { + if (this._onConnectionSuccess !== null) { + this._onConnectionSuccess(); + this._onConnectionSuccess = null; + this._onConnectionFailure = null; + } + } + + _setConnectionFailure(err) { + if (this._onConnectionFailure !== null) { + this._onConnectionFailure(err); + this._onConnectionSuccess = null; + this._onConnectionFailure = null; + } + } + + _closeForSend(alert) { + this._recordlayer.setSendError(alert); + } + + _closeForRecv(alert) { + this._recordlayer.setRecvError(alert); + } +} + +class tlsconnection_ClientConnection extends tlsconnection_Connection { + static async create(psk, pskId, sendCallback) { + const instance = await super.create(psk, pskId, sendCallback); + await instance._transition(states_CLIENT_START); + return instance; + } +} + +class tlsconnection_ServerConnection extends tlsconnection_Connection { + static async create(psk, pskId, sendCallback) { + const instance = await super.create(psk, pskId, sendCallback); + await instance._transition(states_SERVER_START); + return instance; + } +} + +// CONCATENATED MODULE: ./node_modules/event-target-shim/dist/event-target-shim.mjs +/** + * @author Toru Nagashima <https://github.com/mysticatea> + * @copyright 2015 Toru Nagashima. All rights reserved. + * See LICENSE file in root directory for full license. + */ +/** + * @typedef {object} PrivateData + * @property {EventTarget} eventTarget The event target. + * @property {{type:string}} event The original event object. + * @property {number} eventPhase The current event phase. + * @property {EventTarget|null} currentTarget The current event target. + * @property {boolean} canceled The flag to prevent default. + * @property {boolean} stopped The flag to stop propagation. + * @property {boolean} immediateStopped The flag to stop propagation immediately. + * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null. + * @property {number} timeStamp The unix time. + * @private + */ + +/** + * Private data for event wrappers. + * @type {WeakMap<Event, PrivateData>} + * @private + */ +const privateData = new WeakMap(); + +/** + * Cache for wrapper classes. + * @type {WeakMap<Object, Function>} + * @private + */ +const wrappers = new WeakMap(); + +/** + * Get private data. + * @param {Event} event The event object to get private data. + * @returns {PrivateData} The private data of the event. + * @private + */ +function pd(event) { + const retv = privateData.get(event); + console.assert( + retv != null, + "'this' is expected an Event object, but got", + event + ); + return retv +} + +/** + * https://dom.spec.whatwg.org/#set-the-canceled-flag + * @param data {PrivateData} private data. + */ +function setCancelFlag(data) { + if (data.passiveListener != null) { + if ( + typeof console !== "undefined" && + typeof console.error === "function" + ) { + console.error( + "Unable to preventDefault inside passive event listener invocation.", + data.passiveListener + ); + } + return + } + if (!data.event.cancelable) { + return + } + + data.canceled = true; + if (typeof data.event.preventDefault === "function") { + data.event.preventDefault(); + } +} + +/** + * @see https://dom.spec.whatwg.org/#interface-event + * @private + */ +/** + * The event wrapper. + * @constructor + * @param {EventTarget} eventTarget The event target of this dispatching. + * @param {Event|{type:string}} event The original event to wrap. + */ +function Event(eventTarget, event) { + privateData.set(this, { + eventTarget, + event, + eventPhase: 2, + currentTarget: eventTarget, + canceled: false, + stopped: false, + immediateStopped: false, + passiveListener: null, + timeStamp: event.timeStamp || Date.now(), + }); + + // https://heycam.github.io/webidl/#Unforgeable + Object.defineProperty(this, "isTrusted", { value: false, enumerable: true }); + + // Define accessors + const keys = Object.keys(event); + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (!(key in this)) { + Object.defineProperty(this, key, defineRedirectDescriptor(key)); + } + } +} + +// Should be enumerable, but class methods are not enumerable. +Event.prototype = { + /** + * The type of this event. + * @type {string} + */ + get type() { + return pd(this).event.type + }, + + /** + * The target of this event. + * @type {EventTarget} + */ + get target() { + return pd(this).eventTarget + }, + + /** + * The target of this event. + * @type {EventTarget} + */ + get currentTarget() { + return pd(this).currentTarget + }, + + /** + * @returns {EventTarget[]} The composed path of this event. + */ + composedPath() { + const currentTarget = pd(this).currentTarget; + if (currentTarget == null) { + return [] + } + return [currentTarget] + }, + + /** + * Constant of NONE. + * @type {number} + */ + get NONE() { + return 0 + }, + + /** + * Constant of CAPTURING_PHASE. + * @type {number} + */ + get CAPTURING_PHASE() { + return 1 + }, + + /** + * Constant of AT_TARGET. + * @type {number} + */ + get AT_TARGET() { + return 2 + }, + + /** + * Constant of BUBBLING_PHASE. + * @type {number} + */ + get BUBBLING_PHASE() { + return 3 + }, + + /** + * The target of this event. + * @type {number} + */ + get eventPhase() { + return pd(this).eventPhase + }, + + /** + * Stop event bubbling. + * @returns {void} + */ + stopPropagation() { + const data = pd(this); + + data.stopped = true; + if (typeof data.event.stopPropagation === "function") { + data.event.stopPropagation(); + } + }, + + /** + * Stop event bubbling. + * @returns {void} + */ + stopImmediatePropagation() { + const data = pd(this); + + data.stopped = true; + data.immediateStopped = true; + if (typeof data.event.stopImmediatePropagation === "function") { + data.event.stopImmediatePropagation(); + } + }, + + /** + * The flag to be bubbling. + * @type {boolean} + */ + get bubbles() { + return Boolean(pd(this).event.bubbles) + }, + + /** + * The flag to be cancelable. + * @type {boolean} + */ + get cancelable() { + return Boolean(pd(this).event.cancelable) + }, + + /** + * Cancel this event. + * @returns {void} + */ + preventDefault() { + setCancelFlag(pd(this)); + }, + + /** + * The flag to indicate cancellation state. + * @type {boolean} + */ + get defaultPrevented() { + return pd(this).canceled + }, + + /** + * The flag to be composed. + * @type {boolean} + */ + get composed() { + return Boolean(pd(this).event.composed) + }, + + /** + * The unix time of this event. + * @type {number} + */ + get timeStamp() { + return pd(this).timeStamp + }, + + /** + * The target of this event. + * @type {EventTarget} + * @deprecated + */ + get srcElement() { + return pd(this).eventTarget + }, + + /** + * The flag to stop event bubbling. + * @type {boolean} + * @deprecated + */ + get cancelBubble() { + return pd(this).stopped + }, + set cancelBubble(value) { + if (!value) { + return + } + const data = pd(this); + + data.stopped = true; + if (typeof data.event.cancelBubble === "boolean") { + data.event.cancelBubble = true; + } + }, + + /** + * The flag to indicate cancellation state. + * @type {boolean} + * @deprecated + */ + get returnValue() { + return !pd(this).canceled + }, + set returnValue(value) { + if (!value) { + setCancelFlag(pd(this)); + } + }, + + /** + * Initialize this event object. But do nothing under event dispatching. + * @param {string} type The event type. + * @param {boolean} [bubbles=false] The flag to be possible to bubble up. + * @param {boolean} [cancelable=false] The flag to be possible to cancel. + * @deprecated + */ + initEvent() { + // Do nothing. + }, +}; + +// `constructor` is not enumerable. +Object.defineProperty(Event.prototype, "constructor", { + value: Event, + configurable: true, + writable: true, +}); + +// Ensure `event instanceof window.Event` is `true`. +if (typeof window !== "undefined" && typeof window.Event !== "undefined") { + Object.setPrototypeOf(Event.prototype, window.Event.prototype); + + // Make association for wrappers. + wrappers.set(window.Event.prototype, Event); +} + +/** + * Get the property descriptor to redirect a given property. + * @param {string} key Property name to define property descriptor. + * @returns {PropertyDescriptor} The property descriptor to redirect the property. + * @private + */ +function defineRedirectDescriptor(key) { + return { + get() { + return pd(this).event[key] + }, + set(value) { + pd(this).event[key] = value; + }, + configurable: true, + enumerable: true, + } +} + +/** + * Get the property descriptor to call a given method property. + * @param {string} key Property name to define property descriptor. + * @returns {PropertyDescriptor} The property descriptor to call the method property. + * @private + */ +function defineCallDescriptor(key) { + return { + value() { + const event = pd(this).event; + return event[key].apply(event, arguments) + }, + configurable: true, + enumerable: true, + } +} + +/** + * Define new wrapper class. + * @param {Function} BaseEvent The base wrapper class. + * @param {Object} proto The prototype of the original event. + * @returns {Function} The defined wrapper class. + * @private + */ +function defineWrapper(BaseEvent, proto) { + const keys = Object.keys(proto); + if (keys.length === 0) { + return BaseEvent + } + + /** CustomEvent */ + function CustomEvent(eventTarget, event) { + BaseEvent.call(this, eventTarget, event); + } + + CustomEvent.prototype = Object.create(BaseEvent.prototype, { + constructor: { value: CustomEvent, configurable: true, writable: true }, + }); + + // Define accessors. + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + if (!(key in BaseEvent.prototype)) { + const descriptor = Object.getOwnPropertyDescriptor(proto, key); + const isFunc = typeof descriptor.value === "function"; + Object.defineProperty( + CustomEvent.prototype, + key, + isFunc + ? defineCallDescriptor(key) + : defineRedirectDescriptor(key) + ); + } + } + + return CustomEvent +} + +/** + * Get the wrapper class of a given prototype. + * @param {Object} proto The prototype of the original event to get its wrapper. + * @returns {Function} The wrapper class. + * @private + */ +function getWrapper(proto) { + if (proto == null || proto === Object.prototype) { + return Event + } + + let wrapper = wrappers.get(proto); + if (wrapper == null) { + wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto); + wrappers.set(proto, wrapper); + } + return wrapper +} + +/** + * Wrap a given event to management a dispatching. + * @param {EventTarget} eventTarget The event target of this dispatching. + * @param {Object} event The event to wrap. + * @returns {Event} The wrapper instance. + * @private + */ +function wrapEvent(eventTarget, event) { + const Wrapper = getWrapper(Object.getPrototypeOf(event)); + return new Wrapper(eventTarget, event) +} + +/** + * Get the immediateStopped flag of a given event. + * @param {Event} event The event to get. + * @returns {boolean} The flag to stop propagation immediately. + * @private + */ +function isStopped(event) { + return pd(event).immediateStopped +} + +/** + * Set the current event phase of a given event. + * @param {Event} event The event to set current target. + * @param {number} eventPhase New event phase. + * @returns {void} + * @private + */ +function setEventPhase(event, eventPhase) { + pd(event).eventPhase = eventPhase; +} + +/** + * Set the current target of a given event. + * @param {Event} event The event to set current target. + * @param {EventTarget|null} currentTarget New current target. + * @returns {void} + * @private + */ +function setCurrentTarget(event, currentTarget) { + pd(event).currentTarget = currentTarget; +} + +/** + * Set a passive listener of a given event. + * @param {Event} event The event to set current target. + * @param {Function|null} passiveListener New passive listener. + * @returns {void} + * @private + */ +function setPassiveListener(event, passiveListener) { + pd(event).passiveListener = passiveListener; +} + +/** + * @typedef {object} ListenerNode + * @property {Function} listener + * @property {1|2|3} listenerType + * @property {boolean} passive + * @property {boolean} once + * @property {ListenerNode|null} next + * @private + */ + +/** + * @type {WeakMap<object, Map<string, ListenerNode>>} + * @private + */ +const listenersMap = new WeakMap(); + +// Listener types +const CAPTURE = 1; +const BUBBLE = 2; +const ATTRIBUTE = 3; + +/** + * Check whether a given value is an object or not. + * @param {any} x The value to check. + * @returns {boolean} `true` if the value is an object. + */ +function isObject(x) { + return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax +} + +/** + * Get listeners. + * @param {EventTarget} eventTarget The event target to get. + * @returns {Map<string, ListenerNode>} The listeners. + * @private + */ +function getListeners(eventTarget) { + const listeners = listenersMap.get(eventTarget); + if (listeners == null) { + throw new TypeError( + "'this' is expected an EventTarget object, but got another value." + ) + } + return listeners +} + +/** + * Get the property descriptor for the event attribute of a given event. + * @param {string} eventName The event name to get property descriptor. + * @returns {PropertyDescriptor} The property descriptor. + * @private + */ +function defineEventAttributeDescriptor(eventName) { + return { + get() { + const listeners = getListeners(this); + let node = listeners.get(eventName); + while (node != null) { + if (node.listenerType === ATTRIBUTE) { + return node.listener + } + node = node.next; + } + return null + }, + + set(listener) { + if (typeof listener !== "function" && !isObject(listener)) { + listener = null; // eslint-disable-line no-param-reassign + } + const listeners = getListeners(this); + + // Traverse to the tail while removing old value. + let prev = null; + let node = listeners.get(eventName); + while (node != null) { + if (node.listenerType === ATTRIBUTE) { + // Remove old value. + if (prev !== null) { + prev.next = node.next; + } else if (node.next !== null) { + listeners.set(eventName, node.next); + } else { + listeners.delete(eventName); + } + } else { + prev = node; + } + + node = node.next; + } + + // Add new value. + if (listener !== null) { + const newNode = { + listener, + listenerType: ATTRIBUTE, + passive: false, + once: false, + next: null, + }; + if (prev === null) { + listeners.set(eventName, newNode); + } else { + prev.next = newNode; + } + } + }, + configurable: true, + enumerable: true, + } +} + +/** + * Define an event attribute (e.g. `eventTarget.onclick`). + * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite. + * @param {string} eventName The event name to define. + * @returns {void} + */ +function defineEventAttribute(eventTargetPrototype, eventName) { + Object.defineProperty( + eventTargetPrototype, + `on${eventName}`, + defineEventAttributeDescriptor(eventName) + ); +} + +/** + * Define a custom EventTarget with event attributes. + * @param {string[]} eventNames Event names for event attributes. + * @returns {EventTarget} The custom EventTarget. + * @private + */ +function defineCustomEventTarget(eventNames) { + /** CustomEventTarget */ + function CustomEventTarget() { + EventTarget.call(this); + } + + CustomEventTarget.prototype = Object.create(EventTarget.prototype, { + constructor: { + value: CustomEventTarget, + configurable: true, + writable: true, + }, + }); + + for (let i = 0; i < eventNames.length; ++i) { + defineEventAttribute(CustomEventTarget.prototype, eventNames[i]); + } + + return CustomEventTarget +} + +/** + * EventTarget. + * + * - This is constructor if no arguments. + * - This is a function which returns a CustomEventTarget constructor if there are arguments. + * + * For example: + * + * class A extends EventTarget {} + * class B extends EventTarget("message") {} + * class C extends EventTarget("message", "error") {} + * class D extends EventTarget(["message", "error"]) {} + */ +function EventTarget() { + /*eslint-disable consistent-return */ + if (this instanceof EventTarget) { + listenersMap.set(this, new Map()); + return + } + if (arguments.length === 1 && Array.isArray(arguments[0])) { + return defineCustomEventTarget(arguments[0]) + } + if (arguments.length > 0) { + const types = new Array(arguments.length); + for (let i = 0; i < arguments.length; ++i) { + types[i] = arguments[i]; + } + return defineCustomEventTarget(types) + } + throw new TypeError("Cannot call a class as a function") + /*eslint-enable consistent-return */ +} + +// Should be enumerable, but class methods are not enumerable. +EventTarget.prototype = { + /** + * Add a given listener to this event target. + * @param {string} eventName The event name to add. + * @param {Function} listener The listener to add. + * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. + * @returns {void} + */ + addEventListener(eventName, listener, options) { + if (listener == null) { + return + } + if (typeof listener !== "function" && !isObject(listener)) { + throw new TypeError("'listener' should be a function or an object.") + } + + const listeners = getListeners(this); + const optionsIsObj = isObject(options); + const capture = optionsIsObj + ? Boolean(options.capture) + : Boolean(options); + const listenerType = capture ? CAPTURE : BUBBLE; + const newNode = { + listener, + listenerType, + passive: optionsIsObj && Boolean(options.passive), + once: optionsIsObj && Boolean(options.once), + next: null, + }; + + // Set it as the first node if the first node is null. + let node = listeners.get(eventName); + if (node === undefined) { + listeners.set(eventName, newNode); + return + } + + // Traverse to the tail while checking duplication.. + let prev = null; + while (node != null) { + if ( + node.listener === listener && + node.listenerType === listenerType + ) { + // Should ignore duplication. + return + } + prev = node; + node = node.next; + } + + // Add it. + prev.next = newNode; + }, + + /** + * Remove a given listener from this event target. + * @param {string} eventName The event name to remove. + * @param {Function} listener The listener to remove. + * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. + * @returns {void} + */ + removeEventListener(eventName, listener, options) { + if (listener == null) { + return + } + + const listeners = getListeners(this); + const capture = isObject(options) + ? Boolean(options.capture) + : Boolean(options); + const listenerType = capture ? CAPTURE : BUBBLE; + + let prev = null; + let node = listeners.get(eventName); + while (node != null) { + if ( + node.listener === listener && + node.listenerType === listenerType + ) { + if (prev !== null) { + prev.next = node.next; + } else if (node.next !== null) { + listeners.set(eventName, node.next); + } else { + listeners.delete(eventName); + } + return + } + + prev = node; + node = node.next; + } + }, + + /** + * Dispatch a given event. + * @param {Event|{type:string}} event The event to dispatch. + * @returns {boolean} `false` if canceled. + */ + dispatchEvent(event) { + if (event == null || typeof event.type !== "string") { + throw new TypeError('"event.type" should be a string.') + } + + // If listeners aren't registered, terminate. + const listeners = getListeners(this); + const eventName = event.type; + let node = listeners.get(eventName); + if (node == null) { + return true + } + + // Since we cannot rewrite several properties, so wrap object. + const wrappedEvent = wrapEvent(this, event); + + // This doesn't process capturing phase and bubbling phase. + // This isn't participating in a tree. + let prev = null; + while (node != null) { + // Remove this listener if it's once + if (node.once) { + if (prev !== null) { + prev.next = node.next; + } else if (node.next !== null) { + listeners.set(eventName, node.next); + } else { + listeners.delete(eventName); + } + } else { + prev = node; + } + + // Call this listener + setPassiveListener( + wrappedEvent, + node.passive ? node.listener : null + ); + if (typeof node.listener === "function") { + try { + node.listener.call(this, wrappedEvent); + } catch (err) { + if ( + typeof console !== "undefined" && + typeof console.error === "function" + ) { + console.error(err); + } + } + } else if ( + node.listenerType !== ATTRIBUTE && + typeof node.listener.handleEvent === "function" + ) { + node.listener.handleEvent(wrappedEvent); + } + + // Break if `event.stopImmediatePropagation` was called. + if (isStopped(wrappedEvent)) { + break + } + + node = node.next; + } + setPassiveListener(wrappedEvent, null); + setEventPhase(wrappedEvent, 0); + setCurrentTarget(wrappedEvent, null); + + return !wrappedEvent.defaultPrevented + }, +}; + +// `constructor` is not enumerable. +Object.defineProperty(EventTarget.prototype, "constructor", { + value: EventTarget, + configurable: true, + writable: true, +}); + +// Ensure `eventTarget instanceof window.EventTarget` is `true`. +if ( + typeof window !== "undefined" && + typeof window.EventTarget !== "undefined" +) { + Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype); +} + +/* harmony default export */ var event_target_shim = (EventTarget); + + +// CONCATENATED MODULE: ./src/index.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// A wrapper that combines a WebSocket to the channelserver +// with some client-side encryption for securing the channel. +// +// This code is responsible for the event handling and the consumer API. +// All the details of encrypting the messages are delegated to`./tlsconnection.js`. + + + + + + + +const CLOSE_FLUSH_BUFFER_INTERVAL_MS = 200; +const CLOSE_FLUSH_BUFFER_MAX_TRIES = 5; + +class src_PairingChannel extends EventTarget { + constructor(channelId, channelKey, socket, connection) { + super(); + this._channelId = channelId; + this._channelKey = channelKey; + this._socket = socket; + this._connection = connection; + this._selfClosed = false; + this._peerClosed = false; + this._setupListeners(); + } + + /** + * Create a new pairing channel. + * + * This will open a channel on the channelserver, and generate a random client-side + * encryption key. When the promise resolves, `this.channelId` and `this.channelKey` + * can be transferred to another client to allow it to securely connect to the channel. + * + * @returns Promise<PairingChannel> + */ + static create(channelServerURI) { + const wsURI = new URL('/v1/ws/', channelServerURI).href; + const channelKey = crypto.getRandomValues(new Uint8Array(32)); + // The one who creates the channel plays the role of 'server' in the underlying TLS exchange. + return this._makePairingChannel(wsURI, tlsconnection_ServerConnection, channelKey); + } + + /** + * Connect to an existing pairing channel. + * + * This will connect to a channel on the channelserver previously established by + * another client calling `create`. The `channelId` and `channelKey` must have been + * obtained via some out-of-band mechanism (such as by scanning from a QR code). + * + * @returns Promise<PairingChannel> + */ + static connect(channelServerURI, channelId, channelKey) { + const wsURI = new URL(`/v1/ws/${channelId}`, channelServerURI).href; + // The one who connects to an existing channel plays the role of 'client' + // in the underlying TLS exchange. + return this._makePairingChannel(wsURI, tlsconnection_ClientConnection, channelKey); + } + + static _makePairingChannel(wsUri, ConnectionClass, psk) { + const socket = new WebSocket(wsUri); + return new Promise((resolve, reject) => { + // eslint-disable-next-line prefer-const + let stopListening; + const onConnectionError = async () => { + stopListening(); + reject(new Error('Error while creating the pairing channel')); + }; + const onFirstMessage = async event => { + stopListening(); + try { + // The channelserver echos back the channel id, and we use it as an + // additional input to the TLS handshake via the "psk id" field. + const {channelid: channelId} = JSON.parse(event.data); + const pskId = utf8ToBytes(channelId); + const connection = await ConnectionClass.create(psk, pskId, data => { + // Send data by forwarding it via the channelserver websocket. + // The TLS connection gives us `data` as raw bytes, but channelserver + // expects b64urlsafe strings, because it wraps them in a JSON object envelope. + socket.send(bytesToBase64url(data)); + }); + const instance = new this(channelId, psk, socket, connection); + resolve(instance); + } catch (err) { + reject(err); + } + }; + stopListening = () => { + socket.removeEventListener('close', onConnectionError); + socket.removeEventListener('error', onConnectionError); + socket.removeEventListener('message', onFirstMessage); + }; + socket.addEventListener('close', onConnectionError); + socket.addEventListener('error', onConnectionError); + socket.addEventListener('message', onFirstMessage); + }); + } + + _setupListeners() { + this._socket.addEventListener('message', async event => { + try { + // When we receive data from the channelserver, pump it through the TLS connection + // to decrypt it, then echo it back out to consumers as an event. + const channelServerEnvelope = JSON.parse(event.data); + const payload = await this._connection.recv(base64urlToBytes(channelServerEnvelope.message)); + if (payload !== null) { + const data = JSON.parse(bytesToUtf8(payload)); + this.dispatchEvent(new CustomEvent('message', { + detail: { + data, + sender: channelServerEnvelope.sender, + }, + })); + } + } catch (error) { + let event; + // The underlying TLS connection will signal a clean shutdown of the channel + // by throwing a special error, because it doesn't really have a better + // signally mechanism available. + if (error instanceof TLSCloseNotify) { + this._peerClosed = true; + if (this._selfClosed) { + this._shutdown(); + } + event = new CustomEvent('close'); + } else { + event = new CustomEvent('error', { + detail: { + error, + } + }); + } + this.dispatchEvent(event); + } + }); + // Relay the WebSocket events. + this._socket.addEventListener('error', () => { + this._shutdown(); + // The dispatched event that we receive has no useful information. + this.dispatchEvent(new CustomEvent('error', { + detail: { + error: new Error('WebSocket error.'), + }, + })); + }); + // In TLS, the peer has to explicitly send a close notification, + // which we dispatch above. Unexpected socket close is an error. + this._socket.addEventListener('close', () => { + this._shutdown(); + if (! this._peerClosed) { + this.dispatchEvent(new CustomEvent('error', { + detail: { + error: new Error('WebSocket unexpectedly closed'), + } + })); + } + }); + } + + /** + * @param {Object} data + */ + async send(data) { + const payload = utf8ToBytes(JSON.stringify(data)); + await this._connection.send(payload); + } + + async close() { + this._selfClosed = true; + await this._connection.close(); + try { + // Ensure all queued bytes have been sent before closing the connection. + let tries = 0; + while (this._socket.bufferedAmount > 0) { + if (++tries > CLOSE_FLUSH_BUFFER_MAX_TRIES) { + throw new Error('Could not flush the outgoing buffer in time.'); + } + await new Promise(res => setTimeout(res, CLOSE_FLUSH_BUFFER_INTERVAL_MS)); + } + } finally { + // If the peer hasn't closed, we might still receive some data. + if (this._peerClosed) { + this._shutdown(); + } + } + } + + _shutdown() { + if (this._socket) { + this._socket.close(); + this._socket = null; + this._connection = null; + } + } + + get closed() { + return (! this._socket) || (this._socket.readyState === 3); + } + + get channelId() { + return this._channelId; + } + + get channelKey() { + return this._channelKey; + } +} + +// Re-export helpful utilities for calling code to use. + + +// For running tests using the built bundle, +// expose a bunch of implementation details. + + + + + + + +const _internals = { + arrayToBytes: arrayToBytes, + BufferReader: utils_BufferReader, + BufferWriter: utils_BufferWriter, + bytesAreEqual: bytesAreEqual, + bytesToHex: bytesToHex, + bytesToUtf8: bytesToUtf8, + ClientConnection: tlsconnection_ClientConnection, + Connection: tlsconnection_Connection, + DecryptionState: recordlayer_DecryptionState, + EncryptedExtensions: EncryptedExtensions, + EncryptionState: recordlayer_EncryptionState, + Finished: messages_Finished, + HASH_LENGTH: HASH_LENGTH, + hexToBytes: hexToBytes, + hkdfExpand: hkdfExpand, + KeySchedule: keyschedule_KeySchedule, + NewSessionTicket: messages_NewSessionTicket, + RecordLayer: recordlayer_RecordLayer, + ServerConnection: tlsconnection_ServerConnection, + utf8ToBytes: utf8ToBytes, + zeros: zeros, +}; + + +/***/ }) +/******/ ])["PairingChannel"]; diff --git a/services/fxaccounts/FxAccountsProfile.sys.mjs b/services/fxaccounts/FxAccountsProfile.sys.mjs new file mode 100644 index 0000000000..de8bdb2f0e --- /dev/null +++ b/services/fxaccounts/FxAccountsProfile.sys.mjs @@ -0,0 +1,193 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Firefox Accounts Profile helper. + * + * This class abstracts interaction with the profile server for an account. + * It will handle things like fetching profile data, listening for updates to + * the user's profile in open browser tabs, and cacheing/invalidating profile data. + */ + +import { + ON_PROFILE_CHANGE_NOTIFICATION, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs"; + +const fxAccounts = getFxAccountsSingleton(); + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FxAccountsProfileClient: + "resource://gre/modules/FxAccountsProfileClient.sys.mjs", +}); + +export var FxAccountsProfile = function (options = {}) { + this._currentFetchPromise = null; + this._cachedAt = 0; // when we saved the cached version. + this._isNotifying = false; // are we sending a notification? + this.fxai = options.fxai || fxAccounts._internal; + this.client = + options.profileClient || + new lazy.FxAccountsProfileClient({ + fxai: this.fxai, + serverURL: options.profileServerUrl, + }); + + // An observer to invalidate our _cachedAt optimization. We use a weak-ref + // just incase this.tearDown isn't called in some cases. + Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true); + // for testing + if (options.channel) { + this.channel = options.channel; + } +}; + +FxAccountsProfile.prototype = { + // If we get subsequent requests for a profile within this period, don't bother + // making another request to determine if it is fresh or not. + PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes + + observe(subject, topic, data) { + // If we get a profile change notification from our webchannel it means + // the user has just changed their profile via the web, so we want to + // ignore our "freshness threshold" + if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) { + log.debug("FxAccountsProfile observed profile change"); + this._cachedAt = 0; + } + }, + + tearDown() { + this.fxai = null; + this.client = null; + Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION); + }, + + _notifyProfileChange(uid) { + this._isNotifying = true; + Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid); + this._isNotifying = false; + }, + + // Cache fetched data and send out a notification so that UI can update. + _cacheProfile(response) { + return this.fxai.withCurrentAccountState(async state => { + const profile = response.body; + const userData = await state.getUserAccountData(); + if (profile.uid != userData.uid) { + throw new Error( + "The fetched profile does not correspond with the current account." + ); + } + let profileCache = { + profile, + etag: response.etag, + }; + await state.updateUserAccountData({ profileCache }); + if (profile.email != userData.email) { + await this.fxai._handleEmailUpdated(profile.email); + } + log.debug("notifying profile changed for user ${uid}", userData); + this._notifyProfileChange(userData.uid); + return profile; + }); + }, + + async _getProfileCache() { + let data = await this.fxai.currentAccountState.getUserAccountData([ + "profileCache", + ]); + return data ? data.profileCache : null; + }, + + async _fetchAndCacheProfileInternal() { + try { + const profileCache = await this._getProfileCache(); + const etag = profileCache ? profileCache.etag : null; + let response; + try { + response = await this.client.fetchProfile(etag); + } catch (err) { + await this.fxai._handleTokenError(err); + // _handleTokenError always re-throws. + throw new Error("not reached!"); + } + + // response may be null if the profile was not modified (same ETag). + if (!response) { + return null; + } + return await this._cacheProfile(response); + } finally { + this._cachedAt = Date.now(); + this._currentFetchPromise = null; + } + }, + + _fetchAndCacheProfile() { + if (!this._currentFetchPromise) { + this._currentFetchPromise = this._fetchAndCacheProfileInternal(); + } + return this._currentFetchPromise; + }, + + // Returns cached data right away if available, otherwise returns null - if + // it returns null, or if the profile is possibly stale, it attempts to + // fetch the latest profile data in the background. After data is fetched a + // notification will be sent out if the profile has changed. + async getProfile() { + const profileCache = await this._getProfileCache(); + if (!profileCache) { + // fetch and cache it in the background. + this._fetchAndCacheProfile().catch(err => { + log.error("Background refresh of initial profile failed", err); + }); + return null; + } + if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) { + // Note that _fetchAndCacheProfile isn't returned, so continues + // in the background. + this._fetchAndCacheProfile().catch(err => { + log.error("Background refresh of profile failed", err); + }); + } else { + log.trace("not checking freshness of profile as it remains recent"); + } + return profileCache.profile; + }, + + // Get the user's profile data, fetching from the network if necessary. + // Most callers should instead use `getProfile()`; this methods exists to support + // callers who need to await the underlying network request. + async ensureProfile({ staleOk = false, forceFresh = false } = {}) { + if (staleOk && forceFresh) { + throw new Error("contradictory options specified"); + } + const profileCache = await this._getProfileCache(); + if ( + forceFresh || + !profileCache || + (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD && + !staleOk) + ) { + const profile = await this._fetchAndCacheProfile().catch(err => { + log.error("Background refresh of profile failed", err); + }); + if (profile) { + return profile; + } + } + log.trace("not checking freshness of profile as it remains recent"); + return profileCache ? profileCache.profile : null; + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), +}; diff --git a/services/fxaccounts/FxAccountsProfileClient.sys.mjs b/services/fxaccounts/FxAccountsProfileClient.sys.mjs new file mode 100644 index 0000000000..7ae1bd95db --- /dev/null +++ b/services/fxaccounts/FxAccountsProfileClient.sys.mjs @@ -0,0 +1,273 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A client to fetch profile information for a Firefox Account. + */ +"use strict;"; + +import { + ERRNO_NETWORK, + ERRNO_PARSE, + ERRNO_UNKNOWN_ERROR, + ERROR_CODE_METHOD_NOT_ALLOWED, + ERROR_MSG_METHOD_NOT_ALLOWED, + ERROR_NETWORK, + ERROR_PARSE, + ERROR_UNKNOWN, + log, + SCOPE_PROFILE, + SCOPE_PROFILE_WRITE, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs"; + +const fxAccounts = getFxAccountsSingleton(); +import { RESTRequest } from "resource://services-common/rest.sys.mjs"; + +/** + * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information. + * + * @param {Object} options Options + * @param {String} options.serverURL + * The URL of the profile server to query. + * Example: https://profile.accounts.firefox.com/v1 + * @param {String} options.token + * The bearer token to access the profile server + * @constructor + */ +export var FxAccountsProfileClient = function (options) { + if (!options || !options.serverURL) { + throw new Error("Missing 'serverURL' configuration option"); + } + + this.fxai = options.fxai || fxAccounts._internal; + + try { + this.serverURL = new URL(options.serverURL); + } catch (e) { + throw new Error("Invalid 'serverURL'"); + } + log.debug("FxAccountsProfileClient: Initialized"); +}; + +FxAccountsProfileClient.prototype = { + /** + * {nsIURI} + * The server to fetch profile information from. + */ + serverURL: null, + + /** + * Interface for making remote requests. + */ + _Request: RESTRequest, + + /** + * Remote request helper which abstracts authentication away. + * + * @param {String} path + * Profile server path, i.e "/profile". + * @param {String} [method] + * Type of request, e.g. "GET". + * @param {String} [etag] + * Optional ETag used for caching purposes. + * @param {Object} [body] + * Optional request body, to be sent as application/json. + * @return Promise + * Resolves: {body: Object, etag: Object} Successful response from the Profile server. + * Rejects: {FxAccountsProfileClientError} Profile client error. + * @private + */ + async _createRequest(path, method = "GET", etag = null, body = null) { + method = method.toUpperCase(); + let token = await this._getTokenForRequest(method); + try { + return await this._rawRequest(path, method, token, etag, body); + } catch (ex) { + if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) { + throw ex; + } + // it's an auth error - assume our token expired and retry. + log.info( + "Fetching the profile returned a 401 - revoking our token and retrying" + ); + await this.fxai.removeCachedOAuthToken({ token }); + token = await this._getTokenForRequest(method); + // and try with the new token - if that also fails then we fail after + // revoking the token. + try { + return await this._rawRequest(path, method, token, etag, body); + } catch (ex) { + if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) { + throw ex; + } + log.info( + "Retry fetching the profile still returned a 401 - revoking our token and failing" + ); + await this.fxai.removeCachedOAuthToken({ token }); + throw ex; + } + } + }, + + /** + * Helper to get an OAuth token for a request. + * + * OAuth tokens are cached, so it's fine to call this for each request. + * + * @param {String} [method] + * Type of request, i.e "GET". + * @return Promise + * Resolves: Object containing "scope", "token" and "key" properties + * Rejects: {FxAccountsProfileClientError} Profile client error. + * @private + */ + async _getTokenForRequest(method) { + let scope = SCOPE_PROFILE; + if (method === "POST") { + scope = SCOPE_PROFILE_WRITE; + } + return this.fxai.getOAuthToken({ scope }); + }, + + /** + * Remote "raw" request helper - doesn't handle auth errors and tokens. + * + * @param {String} path + * Profile server path, i.e "/profile". + * @param {String} method + * Type of request, i.e "GET". + * @param {String} token + * @param {String} etag + * @param {Object} payload + * The payload of the request, if any. + * @return Promise + * Resolves: {body: Object, etag: Object} Successful response from the Profile server + or null if 304 is hit (same ETag). + * Rejects: {FxAccountsProfileClientError} Profile client error. + * @private + */ + async _rawRequest(path, method, token, etag = null, payload = null) { + let profileDataUrl = this.serverURL + path; + let request = new this._Request(profileDataUrl); + + request.setHeader("Authorization", "Bearer " + token); + request.setHeader("Accept", "application/json"); + if (etag) { + request.setHeader("If-None-Match", etag); + } + + if (method != "GET" && method != "POST") { + // method not supported + throw new FxAccountsProfileClientError({ + error: ERROR_NETWORK, + errno: ERRNO_NETWORK, + code: ERROR_CODE_METHOD_NOT_ALLOWED, + message: ERROR_MSG_METHOD_NOT_ALLOWED, + }); + } + try { + await request.dispatch(method, payload); + } catch (error) { + throw new FxAccountsProfileClientError({ + error: ERROR_NETWORK, + errno: ERRNO_NETWORK, + message: error.toString(), + }); + } + + let body = null; + try { + if (request.response.status == 304) { + return null; + } + body = JSON.parse(request.response.body); + } catch (e) { + throw new FxAccountsProfileClientError({ + error: ERROR_PARSE, + errno: ERRNO_PARSE, + code: request.response.status, + message: request.response.body, + }); + } + + // "response.success" means status code is 200 + if (!request.response.success) { + throw new FxAccountsProfileClientError({ + error: body.error || ERROR_UNKNOWN, + errno: body.errno || ERRNO_UNKNOWN_ERROR, + code: request.response.status, + message: body.message || body, + }); + } + return { + body, + etag: request.response.headers.etag, + }; + }, + + /** + * Retrieve user's profile from the server + * + * @param {String} [etag] + * Optional ETag used for caching purposes. (may generate a 304 exception) + * @return Promise + * Resolves: {body: Object, etag: Object} Successful response from the '/profile' endpoint. + * Rejects: {FxAccountsProfileClientError} profile client error. + */ + fetchProfile(etag) { + log.debug("FxAccountsProfileClient: Requested profile"); + return this._createRequest("/profile", "GET", etag); + }, +}; + +/** + * Normalized profile client errors + * @param {Object} [details] + * Error details object + * @param {number} [details.code] + * Error code + * @param {number} [details.errno] + * Error number + * @param {String} [details.error] + * Error description + * @param {String|null} [details.message] + * Error message + * @constructor + */ +export var FxAccountsProfileClientError = function (details) { + details = details || {}; + + this.name = "FxAccountsProfileClientError"; + this.code = details.code || null; + this.errno = details.errno || ERRNO_UNKNOWN_ERROR; + this.error = details.error || ERROR_UNKNOWN; + this.message = details.message || null; +}; + +/** + * Returns error object properties + * + * @returns {{name: *, code: *, errno: *, error: *, message: *}} + * @private + */ +FxAccountsProfileClientError.prototype._toStringFields = function () { + return { + name: this.name, + code: this.code, + errno: this.errno, + error: this.error, + message: this.message, + }; +}; + +/** + * String representation of a profile client error + * + * @returns {String} + */ +FxAccountsProfileClientError.prototype.toString = function () { + return this.name + "(" + JSON.stringify(this._toStringFields()) + ")"; +}; diff --git a/services/fxaccounts/FxAccountsPush.sys.mjs b/services/fxaccounts/FxAccountsPush.sys.mjs new file mode 100644 index 0000000000..e3e5f32de5 --- /dev/null +++ b/services/fxaccounts/FxAccountsPush.sys.mjs @@ -0,0 +1,315 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Async } from "resource://services-common/async.sys.mjs"; + +import { + FXA_PUSH_SCOPE_ACCOUNT_UPDATE, + ONLOGOUT_NOTIFICATION, + ON_ACCOUNT_DESTROYED_NOTIFICATION, + ON_COLLECTION_CHANGED_NOTIFICATION, + ON_COMMAND_RECEIVED_NOTIFICATION, + ON_DEVICE_CONNECTED_NOTIFICATION, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + ON_PASSWORD_CHANGED_NOTIFICATION, + ON_PASSWORD_RESET_NOTIFICATION, + ON_PROFILE_CHANGE_NOTIFICATION, + ON_PROFILE_UPDATED_NOTIFICATION, + ON_VERIFY_LOGIN_NOTIFICATION, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +/** + * FxAccountsPushService manages Push notifications for Firefox Accounts in the browser + * + * @param [options] + * Object, custom options that used for testing + * @constructor + */ +export function FxAccountsPushService(options = {}) { + this.log = log; + + if (options.log) { + // allow custom log for testing purposes + this.log = options.log; + } + + this.log.debug("FxAccountsPush loading service"); + this.wrappedJSObject = this; + this.initialize(options); +} + +FxAccountsPushService.prototype = { + /** + * Helps only initialize observers once. + */ + _initialized: false, + /** + * Instance of the nsIPushService or a mocked object. + */ + pushService: null, + /** + * Instance of FxAccountsInternal or a mocked object. + */ + fxai: null, + /** + * Component ID of this service, helps register this component. + */ + classID: Components.ID("{1b7db999-2ecd-4abf-bb95-a726896798ca}"), + /** + * Register used interfaces in this service + */ + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + /** + * Initialize the service and register all the required observers. + * + * @param [options] + */ + initialize(options) { + if (this._initialized) { + return false; + } + + this._initialized = true; + + if (options.pushService) { + this.pushService = options.pushService; + } else { + this.pushService = Cc["@mozilla.org/push/Service;1"].getService( + Ci.nsIPushService + ); + } + + if (options.fxai) { + this.fxai = options.fxai; + } else { + const { getFxAccountsSingleton } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ); + const fxAccounts = getFxAccountsSingleton(); + this.fxai = fxAccounts._internal; + } + + this.asyncObserver = Async.asyncObserver(this, this.log); + // We use an async observer because a device waking up can + // observe multiple "Send Tab received" push notifications at the same time. + // The way these notifications are handled is as follows: + // Read index from storage, make network request, update the index. + // You can imagine what happens when multiple calls race: we load + // the same index multiple times and receive the same exact tabs, multiple times. + // The async observer will ensure we make these network requests serially. + Services.obs.addObserver(this.asyncObserver, this.pushService.pushTopic); + Services.obs.addObserver( + this.asyncObserver, + this.pushService.subscriptionChangeTopic + ); + Services.obs.addObserver(this.asyncObserver, ONLOGOUT_NOTIFICATION); + + this.log.debug("FxAccountsPush initialized"); + return true; + }, + /** + * Registers a new endpoint with the Push Server + * + * @returns {Promise} + * Promise always resolves with a subscription or a null if failed to subscribe. + */ + registerPushEndpoint() { + this.log.trace("FxAccountsPush registerPushEndpoint"); + + return new Promise(resolve => { + this.pushService.subscribe( + FXA_PUSH_SCOPE_ACCOUNT_UPDATE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, subscription) => { + if (Components.isSuccessCode(result)) { + this.log.debug("FxAccountsPush got subscription"); + resolve(subscription); + } else { + this.log.warn("FxAccountsPush failed to subscribe", result); + resolve(null); + } + } + ); + }); + }, + /** + * Async observer interface to listen to push messages, changes and logout. + * + * @param subject + * @param topic + * @param data + * @returns {Promise} + */ + async observe(subject, topic, data) { + try { + this.log.trace( + `observed topic=${topic}, data=${data}, subject=${subject}` + ); + switch (topic) { + case this.pushService.pushTopic: + if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) { + let message = subject.QueryInterface(Ci.nsIPushMessage); + await this._onPushMessage(message); + } + break; + case this.pushService.subscriptionChangeTopic: + if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) { + await this._onPushSubscriptionChange(); + } + break; + case ONLOGOUT_NOTIFICATION: + // user signed out, we need to stop polling the Push Server + await this.unsubscribe(); + break; + } + } catch (err) { + this.log.error(err); + } + }, + + /** + * Fired when the Push server sends a notification. + * + * @private + * @returns {Promise} + */ + async _onPushMessage(message) { + this.log.trace("FxAccountsPushService _onPushMessage"); + if (!message.data) { + // Use the empty signal to check the verification state of the account right away + this.log.debug("empty push message - checking account status"); + this.fxai.checkVerificationStatus(); + return; + } + let payload = message.data.json(); + this.log.debug(`push command: ${payload.command}`); + switch (payload.command) { + case ON_COMMAND_RECEIVED_NOTIFICATION: + await this.fxai.commands.pollDeviceCommands(payload.data.index); + break; + case ON_DEVICE_CONNECTED_NOTIFICATION: + Services.obs.notifyObservers( + null, + ON_DEVICE_CONNECTED_NOTIFICATION, + payload.data.deviceName + ); + break; + case ON_DEVICE_DISCONNECTED_NOTIFICATION: + this.fxai._handleDeviceDisconnection(payload.data.id); + return; + case ON_PROFILE_UPDATED_NOTIFICATION: + // We already have a "profile updated" notification sent via WebChannel, + // let's just re-use that. + Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION); + return; + case ON_PASSWORD_CHANGED_NOTIFICATION: + case ON_PASSWORD_RESET_NOTIFICATION: + this._onPasswordChanged(); + return; + case ON_ACCOUNT_DESTROYED_NOTIFICATION: + this.fxai._handleAccountDestroyed(payload.data.uid); + return; + case ON_COLLECTION_CHANGED_NOTIFICATION: + Services.obs.notifyObservers( + null, + ON_COLLECTION_CHANGED_NOTIFICATION, + payload.data.collections + ); + return; + case ON_VERIFY_LOGIN_NOTIFICATION: + Services.obs.notifyObservers( + null, + ON_VERIFY_LOGIN_NOTIFICATION, + JSON.stringify(payload.data) + ); + break; + default: + this.log.warn("FxA Push command unrecognized: " + payload.command); + } + }, + /** + * Check the FxA session status after a password change/reset event. + * If the session is invalid, reset credentials and notify listeners of + * ON_ACCOUNT_STATE_CHANGE_NOTIFICATION that the account may have changed + * + * @returns {Promise} + * @private + */ + _onPasswordChanged() { + return this.fxai.withCurrentAccountState(async state => { + return this.fxai.checkAccountStatus(state); + }); + }, + /** + * Fired when the Push server drops a subscription, or the subscription identifier changes. + * + * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages + * + * @returns {Promise} + * @private + */ + _onPushSubscriptionChange() { + this.log.trace("FxAccountsPushService _onPushSubscriptionChange"); + return this.fxai.updateDeviceRegistration(); + }, + /** + * Unsubscribe from the Push server + * + * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#unsubscribe() + * + * @returns {Promise} - The promise resolves with a bool to indicate if we successfully unsubscribed. + * The promise never rejects. + * @private + */ + unsubscribe() { + this.log.trace("FxAccountsPushService unsubscribe"); + return new Promise(resolve => { + this.pushService.unsubscribe( + FXA_PUSH_SCOPE_ACCOUNT_UPDATE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, ok) => { + if (Components.isSuccessCode(result)) { + if (ok === true) { + this.log.debug("FxAccountsPushService unsubscribed"); + } else { + this.log.debug( + "FxAccountsPushService had no subscription to unsubscribe" + ); + } + } else { + this.log.warn( + "FxAccountsPushService failed to unsubscribe", + result + ); + } + return resolve(ok); + } + ); + }); + }, + + /** + * Get our Push server subscription. + * + * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#getSubscription() + * + * @returns {Promise} - resolves with the subscription or null. Never rejects. + */ + getSubscription() { + return new Promise(resolve => { + this.pushService.getSubscription( + FXA_PUSH_SCOPE_ACCOUNT_UPDATE, + Services.scriptSecurityManager.getSystemPrincipal(), + (result, subscription) => { + if (!subscription) { + this.log.info("FxAccountsPushService no subscription found"); + return resolve(null); + } + return resolve(subscription); + } + ); + }); + }, +}; diff --git a/services/fxaccounts/FxAccountsStorage.sys.mjs b/services/fxaccounts/FxAccountsStorage.sys.mjs new file mode 100644 index 0000000000..24c85dbc2d --- /dev/null +++ b/services/fxaccounts/FxAccountsStorage.sys.mjs @@ -0,0 +1,618 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + DATA_FORMAT_VERSION, + DEFAULT_STORAGE_FILENAME, + FXA_PWDMGR_HOST, + FXA_PWDMGR_PLAINTEXT_FIELDS, + FXA_PWDMGR_REALM, + FXA_PWDMGR_SECURE_FIELDS, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +// A helper function so code can check what fields are able to be stored by +// the storage manager without having a reference to a manager instance. +export function FxAccountsStorageManagerCanStoreField(fieldName) { + return ( + FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName) || + FXA_PWDMGR_SECURE_FIELDS.has(fieldName) + ); +} + +// The storage manager object. +export var FxAccountsStorageManager = function (options = {}) { + this.options = { + filename: options.filename || DEFAULT_STORAGE_FILENAME, + baseDir: options.baseDir || Services.dirsvc.get("ProfD", Ci.nsIFile).path, + }; + this.plainStorage = new JSONStorage(this.options); + // Tests may want to pretend secure storage isn't available. + let useSecure = "useSecure" in options ? options.useSecure : true; + if (useSecure) { + this.secureStorage = new LoginManagerStorage(); + } else { + this.secureStorage = null; + } + this._clearCachedData(); + // See .initialize() below - this protects against it not being called. + this._promiseInitialized = Promise.reject("initialize not called"); + // A promise to avoid storage races - see _queueStorageOperation + this._promiseStorageComplete = Promise.resolve(); +}; + +FxAccountsStorageManager.prototype = { + _initialized: false, + _needToReadSecure: true, + + // An initialization routine that *looks* synchronous to the callers, but + // is actually async as everything else waits for it to complete. + initialize(accountData) { + if (this._initialized) { + throw new Error("already initialized"); + } + this._initialized = true; + // If we just throw away our pre-rejected promise it is reported as an + // unhandled exception when it is GCd - so add an empty .catch handler here + // to prevent this. + this._promiseInitialized.catch(() => {}); + this._promiseInitialized = this._initialize(accountData); + }, + + async _initialize(accountData) { + log.trace("initializing new storage manager"); + try { + if (accountData) { + // If accountData is passed we don't need to read any storage. + this._needToReadSecure = false; + // split it into the 2 parts, write it and we are done. + for (let [name, val] of Object.entries(accountData)) { + if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) { + this.cachedPlain[name] = val; + } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) { + this.cachedSecure[name] = val; + } else { + // Unknown fields are silently discarded, because there is no way + // for them to be read back later. + log.error( + "Unknown FxA field name in user data, it will be ignored", + name + ); + } + } + // write it out and we are done. + await this._write(); + return; + } + // So we were initialized without account data - that means we need to + // read the state from storage. We try and read plain storage first and + // only attempt to read secure storage if the plain storage had a user. + this._needToReadSecure = await this._readPlainStorage(); + if (this._needToReadSecure && this.secureStorage) { + await this._doReadAndUpdateSecure(); + } + } finally { + log.trace("initializing of new storage manager done"); + } + }, + + finalize() { + // We can't throw this instance away while it is still writing or we may + // end up racing with the newly created one. + log.trace("StorageManager finalizing"); + return this._promiseInitialized + .then(() => { + return this._promiseStorageComplete; + }) + .then(() => { + this._promiseStorageComplete = null; + this._promiseInitialized = null; + this._clearCachedData(); + log.trace("StorageManager finalized"); + }); + }, + + // We want to make sure we don't end up doing multiple storage requests + // concurrently - which has a small window for reads if the master-password + // is locked at initialization time and becomes unlocked later, and always + // has an opportunity for updates. + // We also want to make sure we finished writing when finalizing, so we + // can't accidentally end up with the previous user's write finishing after + // a signOut attempts to clear it. + // So all such operations "queue" themselves via this. + _queueStorageOperation(func) { + // |result| is the promise we return - it has no .catch handler, so callers + // of the storage operation still see failure as a normal rejection. + let result = this._promiseStorageComplete.then(func); + // But the promise we assign to _promiseStorageComplete *does* have a catch + // handler so that rejections in one storage operation does not prevent + // future operations from starting (ie, _promiseStorageComplete must never + // be in a rejected state) + this._promiseStorageComplete = result.catch(err => { + log.error("${func} failed: ${err}", { func, err }); + }); + return result; + }, + + // Get the account data by combining the plain and secure storage. + // If fieldNames is specified, it may be a string or an array of strings, + // and only those fields are returned. If not specified the entire account + // data is returned except for "in memory" fields. Note that not specifying + // field names will soon be deprecated/removed - we want all callers to + // specify the fields they care about. + async getAccountData(fieldNames = null) { + await this._promiseInitialized; + // We know we are initialized - this means our .cachedPlain is accurate + // and doesn't need to be read (it was read if necessary by initialize). + // So if there's no uid, there's no user signed in. + if (!("uid" in this.cachedPlain)) { + return null; + } + let result = {}; + if (fieldNames === null) { + // The "old" deprecated way of fetching a logged in user. + for (let [name, value] of Object.entries(this.cachedPlain)) { + result[name] = value; + } + // But the secure data may not have been read, so try that now. + await this._maybeReadAndUpdateSecure(); + // .cachedSecure now has as much as it possibly can (which is possibly + // nothing if (a) secure storage remains locked and (b) we've never updated + // a field to be stored in secure storage.) + for (let [name, value] of Object.entries(this.cachedSecure)) { + result[name] = value; + } + return result; + } + // The new explicit way of getting attributes. + if (!Array.isArray(fieldNames)) { + fieldNames = [fieldNames]; + } + let checkedSecure = false; + for (let fieldName of fieldNames) { + if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName)) { + if (this.cachedPlain[fieldName] !== undefined) { + result[fieldName] = this.cachedPlain[fieldName]; + } + } else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) { + // We may not have read secure storage yet. + if (!checkedSecure) { + await this._maybeReadAndUpdateSecure(); + checkedSecure = true; + } + if (this.cachedSecure[fieldName] !== undefined) { + result[fieldName] = this.cachedSecure[fieldName]; + } + } else { + throw new Error("unexpected field '" + fieldName + "'"); + } + } + return result; + }, + + // Update just the specified fields. This DOES NOT allow you to change to + // a different user, nor to set the user as signed-out. + async updateAccountData(newFields) { + await this._promiseInitialized; + if (!("uid" in this.cachedPlain)) { + // If this storage instance shows no logged in user, then you can't + // update fields. + throw new Error("No user is logged in"); + } + if (!newFields || "uid" in newFields) { + throw new Error("Can't change uid"); + } + log.debug("_updateAccountData with items", Object.keys(newFields)); + // work out what bucket. + for (let [name, value] of Object.entries(newFields)) { + if (value == null) { + delete this.cachedPlain[name]; + // no need to do the "delete on null" thing for this.cachedSecure - + // we need to keep it until we have managed to read so we can nuke + // it on write. + this.cachedSecure[name] = null; + } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) { + this.cachedPlain[name] = value; + } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) { + this.cachedSecure[name] = value; + } else { + // Throwing seems reasonable here as some client code has explicitly + // specified the field name, so it's either confused or needs to update + // how this field is to be treated. + throw new Error("unexpected field '" + name + "'"); + } + } + // If we haven't yet read the secure data, do so now, else we may write + // out partial data. + await this._maybeReadAndUpdateSecure(); + // Now save it - but don't wait on the _write promise - it's queued up as + // a storage operation, so .finalize() will wait for completion, but no need + // for us to. + this._write(); + }, + + _clearCachedData() { + this.cachedPlain = {}; + // If we don't have secure storage available we have cachedPlain and + // cachedSecure be the same object. + this.cachedSecure = this.secureStorage == null ? this.cachedPlain : {}; + }, + + /* Reads the plain storage and caches the read values in this.cachedPlain. + Only ever called once and unlike the "secure" storage, is expected to never + fail (ie, plain storage is considered always available, whereas secure + storage may be unavailable if it is locked). + + Returns a promise that resolves with true if valid account data was found, + false otherwise. + + Note: _readPlainStorage is only called during initialize, so isn't + protected via _queueStorageOperation() nor _promiseInitialized. + */ + async _readPlainStorage() { + let got; + try { + got = await this.plainStorage.get(); + } catch (err) { + // File hasn't been created yet. That will be done + // when write is called. + if (!err.name == "NotFoundError") { + log.error("Failed to read plain storage", err); + } + // either way, we return null. + got = null; + } + if ( + !got || + !got.accountData || + !got.accountData.uid || + got.version != DATA_FORMAT_VERSION + ) { + return false; + } + // We need to update our .cachedPlain, but can't just assign to it as + // it may need to be the exact same object as .cachedSecure + // As a sanity check, .cachedPlain must be empty (as we are called by init) + // XXX - this would be a good use-case for a RuntimeAssert or similar, as + // being added in bug 1080457. + if (Object.keys(this.cachedPlain).length) { + throw new Error("should be impossible to have cached data already."); + } + for (let [name, value] of Object.entries(got.accountData)) { + this.cachedPlain[name] = value; + } + return true; + }, + + /* If we haven't managed to read the secure storage, try now, so + we can merge our cached data with the data that's already been set. + */ + _maybeReadAndUpdateSecure() { + if (this.secureStorage == null || !this._needToReadSecure) { + return null; + } + return this._queueStorageOperation(() => { + if (this._needToReadSecure) { + // we might have read it by now! + return this._doReadAndUpdateSecure(); + } + return null; + }); + }, + + /* Unconditionally read the secure storage and merge our cached data (ie, data + which has already been set while the secure storage was locked) with + the read data + */ + async _doReadAndUpdateSecure() { + let { uid, email } = this.cachedPlain; + try { + log.debug( + "reading secure storage with existing", + Object.keys(this.cachedSecure) + ); + // If we already have anything in .cachedSecure it means something has + // updated cachedSecure before we've read it. That means that after we do + // manage to read we must write back the merged data. + let needWrite = !!Object.keys(this.cachedSecure).length; + let readSecure = await this.secureStorage.get(uid, email); + // and update our cached data with it - anything already in .cachedSecure + // wins (including the fact it may be null or undefined, the latter + // which means it will be removed from storage. + if (readSecure && readSecure.version != DATA_FORMAT_VERSION) { + log.warn("got secure data but the data format version doesn't match"); + readSecure = null; + } + if (readSecure && readSecure.accountData) { + log.debug( + "secure read fetched items", + Object.keys(readSecure.accountData) + ); + for (let [name, value] of Object.entries(readSecure.accountData)) { + if (!(name in this.cachedSecure)) { + this.cachedSecure[name] = value; + } + } + if (needWrite) { + log.debug("successfully read secure data; writing updated data back"); + await this._doWriteSecure(); + } + } + this._needToReadSecure = false; + } catch (ex) { + if (ex instanceof this.secureStorage.STORAGE_LOCKED) { + log.debug("setAccountData: secure storage is locked trying to read"); + } else { + log.error("failed to read secure storage", ex); + throw ex; + } + } + }, + + _write() { + // We don't want multiple writes happening concurrently, and we also need to + // know when an "old" storage manager is done (this.finalize() waits for this) + return this._queueStorageOperation(() => this.__write()); + }, + + async __write() { + // Write everything back - later we could track what's actually dirty, + // but for now we write it all. + log.debug("writing plain storage", Object.keys(this.cachedPlain)); + let toWritePlain = { + version: DATA_FORMAT_VERSION, + accountData: this.cachedPlain, + }; + await this.plainStorage.set(toWritePlain); + + // If we have no secure storage manager we are done. + if (this.secureStorage == null) { + return; + } + // and only attempt to write to secure storage if we've managed to read it, + // otherwise we might clobber data that's already there. + if (!this._needToReadSecure) { + await this._doWriteSecure(); + } + }, + + /* Do the actual write of secure data. Caller is expected to check if we actually + need to write and to ensure we are in a queued storage operation. + */ + async _doWriteSecure() { + // We need to remove null items here. + for (let [name, value] of Object.entries(this.cachedSecure)) { + if (value == null) { + delete this.cachedSecure[name]; + } + } + log.debug("writing secure storage", Object.keys(this.cachedSecure)); + let toWriteSecure = { + version: DATA_FORMAT_VERSION, + accountData: this.cachedSecure, + }; + try { + await this.secureStorage.set(this.cachedPlain.uid, toWriteSecure); + } catch (ex) { + if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) { + throw ex; + } + // This shouldn't be possible as once it is unlocked it can't be + // re-locked, and we can only be here if we've previously managed to + // read. + log.error("setAccountData: secure storage is locked trying to write"); + } + }, + + // Delete the data for an account - ie, called on "sign out". + deleteAccountData() { + return this._queueStorageOperation(() => this._deleteAccountData()); + }, + + async _deleteAccountData() { + log.debug("removing account data"); + await this._promiseInitialized; + await this.plainStorage.set(null); + if (this.secureStorage) { + await this.secureStorage.set(null); + } + this._clearCachedData(); + log.debug("account data reset"); + }, +}; + +/** + * JSONStorage constructor that creates instances that may set/get + * to a specified file, in a directory that will be created if it + * doesn't exist. + * + * @param options { + * filename: of the file to write to + * baseDir: directory where the file resides + * } + * @return instance + */ +function JSONStorage(options) { + this.baseDir = options.baseDir; + this.path = PathUtils.join(options.baseDir, options.filename); +} + +JSONStorage.prototype = { + set(contents) { + log.trace( + "starting write of json user data", + contents ? Object.keys(contents.accountData) : "null" + ); + let start = Date.now(); + return IOUtils.makeDirectory(this.baseDir, { ignoreExisting: true }) + .then(IOUtils.writeJSON.bind(null, this.path, contents)) + .then(result => { + log.trace( + "finished write of json user data - took", + Date.now() - start + ); + return result; + }); + }, + + get() { + log.trace("starting fetch of json user data"); + let start = Date.now(); + return IOUtils.readJSON(this.path).then(result => { + log.trace("finished fetch of json user data - took", Date.now() - start); + return result; + }); + }, +}; + +function StorageLockedError() {} + +/** + * LoginManagerStorage constructor that creates instances that set/get + * data stored securely in the nsILoginManager. + * + * @return instance + */ + +export function LoginManagerStorage() {} + +LoginManagerStorage.prototype = { + STORAGE_LOCKED: StorageLockedError, + // The fields in the credentials JSON object that are stored in plain-text + // in the profile directory. All other fields are stored in the login manager, + // and thus are only available when the master-password is unlocked. + + // a hook point for testing. + get _isLoggedIn() { + return Services.logins.isLoggedIn; + }, + + // Clear any data from the login manager. Returns true if the login manager + // was unlocked (even if no existing logins existed) or false if it was + // locked (meaning we don't even know if it existed or not.) + async _clearLoginMgrData() { + try { + // Services.logins might be third-party and broken... + await Services.logins.initializationPromise; + if (!this._isLoggedIn) { + return false; + } + let logins = await Services.logins.searchLoginsAsync({ + origin: FXA_PWDMGR_HOST, + httpRealm: FXA_PWDMGR_REALM, + }); + for (let login of logins) { + Services.logins.removeLogin(login); + } + return true; + } catch (ex) { + log.error("Failed to clear login data: ${}", ex); + return false; + } + }, + + async set(uid, contents) { + if (!contents) { + // Nuke it from the login manager. + let cleared = await this._clearLoginMgrData(); + if (!cleared) { + // just log a message - we verify that the uid matches when + // we reload it, so having a stale entry doesn't really hurt. + log.info("not removing credentials from login manager - not logged in"); + } + log.trace("storage set finished clearing account data"); + return; + } + + // We are saving actual data. + log.trace("starting write of user data to the login manager"); + try { + // Services.logins might be third-party and broken... + // and the stuff into the login manager. + await Services.logins.initializationPromise; + // If MP is locked we silently fail - the user may need to re-auth + // next startup. + if (!this._isLoggedIn) { + log.info("not saving credentials to login manager - not logged in"); + throw new this.STORAGE_LOCKED(); + } + // write the data to the login manager. + let loginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" + ); + let login = new loginInfo( + FXA_PWDMGR_HOST, + null, // aFormActionOrigin, + FXA_PWDMGR_REALM, // aHttpRealm, + uid, // aUsername + JSON.stringify(contents), // aPassword + "", // aUsernameField + "" + ); // aPasswordField + + let existingLogins = await Services.logins.searchLoginsAsync({ + origin: FXA_PWDMGR_HOST, + httpRealm: FXA_PWDMGR_REALM, + }); + if (existingLogins.length) { + Services.logins.modifyLogin(existingLogins[0], login); + } else { + await Services.logins.addLoginAsync(login); + } + log.trace("finished write of user data to the login manager"); + } catch (ex) { + if (ex instanceof this.STORAGE_LOCKED) { + throw ex; + } + // just log and consume the error here - it may be a 3rd party login + // manager replacement that's simply broken. + log.error("Failed to save data to the login manager", ex); + } + }, + + async get(uid, email) { + log.trace("starting fetch of user data from the login manager"); + + try { + // Services.logins might be third-party and broken... + // read the data from the login manager and merge it for return. + await Services.logins.initializationPromise; + + if (!this._isLoggedIn) { + log.info( + "returning partial account data as the login manager is locked." + ); + throw new this.STORAGE_LOCKED(); + } + + let logins = await Services.logins.searchLoginsAsync({ + origin: FXA_PWDMGR_HOST, + httpRealm: FXA_PWDMGR_REALM, + }); + if (!logins.length) { + // This could happen if the MP was locked when we wrote the data. + log.info("Can't find any credentials in the login manager"); + return null; + } + let login = logins[0]; + // Support either the uid or the email as the username - as of bug 1183951 + // we store the uid, but we support having either for b/w compat. + if (login.username == uid || login.username == email) { + return JSON.parse(login.password); + } + log.info("username in the login manager doesn't match - ignoring it"); + await this._clearLoginMgrData(); + } catch (ex) { + if (ex instanceof this.STORAGE_LOCKED) { + throw ex; + } + // just log and consume the error here - it may be a 3rd party login + // manager replacement that's simply broken. + log.error("Failed to get data from the login manager", ex); + } + return null; + }, +}; diff --git a/services/fxaccounts/FxAccountsTelemetry.sys.mjs b/services/fxaccounts/FxAccountsTelemetry.sys.mjs new file mode 100644 index 0000000000..1d7b3d4954 --- /dev/null +++ b/services/fxaccounts/FxAccountsTelemetry.sys.mjs @@ -0,0 +1,173 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// FxA Telemetry support. For hysterical raisins, the actual implementation +// is inside "sync". We should move the core implementation somewhere that's +// sanely shared (eg, services-common?), but let's wait and see where we end up +// first... + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CryptoUtils: "resource://services-crypto/utils.sys.mjs", + + // We use this observers module because we leverage its support for richer + // "subject" data. + Observers: "resource://services-common/observers.sys.mjs", +}); + +import { + PREF_ACCOUNT_ROOT, + log, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const PREF_SANITIZED_UID = PREF_ACCOUNT_ROOT + "telemetry.sanitized_uid"; +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "pref_sanitizedUid", + PREF_SANITIZED_UID, + "" +); + +export class FxAccountsTelemetry { + constructor(fxai) { + this._fxai = fxai; + Services.telemetry.setEventRecordingEnabled("fxa", true); + } + + // Records an event *in the Fxa/Sync ping*. + recordEvent(object, method, value, extra = undefined) { + // We need to ensure the telemetry module is loaded. + ChromeUtils.importESModule("resource://services-sync/telemetry.sys.mjs"); + // Now it will be listening for the notifications... + lazy.Observers.notify("fxa:telemetry:event", { + object, + method, + value, + extra, + }); + } + + generateUUID() { + return Services.uuid.generateUUID().toString().slice(1, -1); + } + + // A flow ID can be anything that's "probably" unique, so for now use a UUID. + generateFlowID() { + return this.generateUUID(); + } + + // FxA- and Sync-related metrics are submitted in a special-purpose "sync ping". This ping + // identifies the user by a version of their FxA uid that is HMAC-ed with a server-side secret + // key, in an attempt to provide a bit of anonymity. + + // Secret back-channel by which tokenserver client code can set the hashed UID. + // This value conceptually belongs to FxA, but we currently get it from tokenserver, + // so there's some light hackery to put it in the right place. + _setHashedUID(hashedUID) { + if (!hashedUID) { + Services.prefs.clearUserPref(PREF_SANITIZED_UID); + } else { + Services.prefs.setStringPref(PREF_SANITIZED_UID, hashedUID); + } + } + + getSanitizedUID() { + // Sadly, we can only currently obtain this value if the user has enabled sync. + return lazy.pref_sanitizedUid || null; + } + + // Sanitize the ID of a device into something suitable for including in the + // ping. Returns null if no transformation is possible. + sanitizeDeviceId(deviceId) { + const uid = this.getSanitizedUID(); + if (!uid) { + // Sadly, we can only currently get this if the user has enabled sync. + return null; + } + // Combine the raw device id with the sanitized uid to create a stable + // unique identifier that can't be mapped back to the user's FxA + // identity without knowing the metrics HMAC key. + // The result is 64 bytes long, which in retrospect is probably excessive, + // but it's already shipping... + return lazy.CryptoUtils.sha256(deviceId + uid); + } + + // Record the connection of FxA or one of its services. + // Note that you must call this before performing the actual connection + // or we may record incorrect data - for example, we will not be able to + // determine whether FxA itself was connected before this call. + // + // Currently sends an event in the main telemetry event ping rather than the + // FxA/Sync ping (although this might change in the future) + // + // @param services - An array of service names which should be recorded. FxA + // itself is not counted as a "service" - ie, an empty array should be passed + // if the account is connected without anything else . + // + // @param how - How the connection was done. + async recordConnection(services, how = null) { + try { + let extra = {}; + // Record that fxa was connected if it isn't currently - it will be soon. + if (!(await this._fxai.getUserAccountData())) { + extra.fxa = "true"; + } + // Events.yaml only declares "sync" as a valid service. + if (services.includes("sync")) { + extra.sync = "true"; + } + Services.telemetry.recordEvent("fxa", "connect", "account", how, extra); + } catch (ex) { + log.error("Failed to record connection telemetry", ex); + console.error("Failed to record connection telemetry", ex); + } + } + + // Record the disconnection of FxA or one of its services. + // Note that you must call this before performing the actual disconnection + // or we may record incomplete data - for example, if this is called after + // disconnection, we've almost certainly lost the ability to record what + // services were enabled prior to disconnection. + // + // Currently sends an event in the main telemetry event ping rather than the + // FxA/Sync ping (although this might change in the future) + // + // @param service - the service being disconnected. If null, the account + // itself is being disconnected, so all connected services are too. + // + // @param how - how the disconnection was done. + async recordDisconnection(service = null, how = null) { + try { + let extra = {}; + if (!service) { + extra.fxa = "true"; + // We need a way to enumerate all services - but for now we just hard-code + // all possibilities here. + if (Services.prefs.prefHasUserValue("services.sync.username")) { + extra.sync = "true"; + } + } else if (service == "sync") { + extra[service] = "true"; + } else { + // Events.yaml only declares "sync" as a valid service. + log.warn( + `recordDisconnection has invalid value for service: ${service}` + ); + } + Services.telemetry.recordEvent( + "fxa", + "disconnect", + "account", + how, + extra + ); + } catch (ex) { + log.error("Failed to record disconnection telemetry", ex); + console.error("Failed to record disconnection telemetry", ex); + } + } +} diff --git a/services/fxaccounts/FxAccountsWebChannel.sys.mjs b/services/fxaccounts/FxAccountsWebChannel.sys.mjs new file mode 100644 index 0000000000..fdd0b75e93 --- /dev/null +++ b/services/fxaccounts/FxAccountsWebChannel.sys.mjs @@ -0,0 +1,824 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Firefox Accounts Web Channel. + * + * Uses the WebChannel component to receive messages + * about account state changes. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { + COMMAND_PROFILE_CHANGE, + COMMAND_LOGIN, + COMMAND_LOGOUT, + COMMAND_OAUTH, + COMMAND_DELETE, + COMMAND_CAN_LINK_ACCOUNT, + COMMAND_SYNC_PREFERENCES, + COMMAND_CHANGE_PASSWORD, + COMMAND_FXA_STATUS, + COMMAND_PAIR_HEARTBEAT, + COMMAND_PAIR_SUPP_METADATA, + COMMAND_PAIR_AUTHORIZE, + COMMAND_PAIR_DECLINE, + COMMAND_PAIR_COMPLETE, + COMMAND_PAIR_PREFERENCES, + COMMAND_FIREFOX_VIEW, + FX_OAUTH_CLIENT_ID, + ON_PROFILE_CHANGE_NOTIFICATION, + PREF_LAST_FXA_USER, + WEBCHANNEL_ID, + log, + logPII, +} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CryptoUtils: "resource://services-crypto/utils.sys.mjs", + FxAccountsPairingFlow: "resource://gre/modules/FxAccountsPairing.sys.mjs", + FxAccountsStorageManagerCanStoreField: + "resource://gre/modules/FxAccountsStorage.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", + WebChannel: "resource://gre/modules/WebChannel.sys.mjs", +}); +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "pairingEnabled", + "identity.fxaccounts.pairing.enabled" +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "separatePrivilegedMozillaWebContentProcess", + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", + false +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "separatedMozillaDomains", + "browser.tabs.remote.separatedMozillaDomains", + "", + false, + val => val.split(",") +); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "accountServer", + "identity.fxaccounts.remote.root", + null, + false, + val => Services.io.newURI(val) +); + +// These engines were added years after Sync had been introduced, they need +// special handling since they are system add-ons and are un-available on +// older versions of Firefox. +const EXTRA_ENGINES = ["addresses", "creditcards"]; + +// These engines will be displayed to the user to pick which they would like to +// use +const CHOOSE_WHAT_TO_SYNC = [ + "addons", + "addresses", + "bookmarks", + "creditcards", + "history", + "passwords", + "preferences", + "tabs", +]; + +/** + * A helper function that extracts the message and stack from an error object. + * Returns a `{ message, stack }` tuple. `stack` will be null if the error + * doesn't have a stack trace. + */ +function getErrorDetails(error) { + // Replace anything that looks like it might be a filepath on Windows or Unix + let cleanMessage = String(error) + .replace(/\\.*\\/gm, "[REDACTED]") + .replace(/\/.*\//gm, "[REDACTED]"); + let details = { message: cleanMessage, stack: null }; + + // Adapted from Console.sys.mjs. + if (error.stack) { + let frames = []; + for (let frame = error.stack; frame; frame = frame.caller) { + frames.push(String(frame).padStart(4)); + } + details.stack = frames.join("\n"); + } + + return details; +} + +/** + * Create a new FxAccountsWebChannel to listen for account updates + * + * @param {Object} options Options + * @param {Object} options + * @param {String} options.content_uri + * The FxA Content server uri + * @param {String} options.channel_id + * The ID of the WebChannel + * @param {String} options.helpers + * Helpers functions. Should only be passed in for testing. + * @constructor + */ +export function FxAccountsWebChannel(options) { + if (!options) { + throw new Error("Missing configuration options"); + } + if (!options.content_uri) { + throw new Error("Missing 'content_uri' option"); + } + this._contentUri = options.content_uri; + + if (!options.channel_id) { + throw new Error("Missing 'channel_id' option"); + } + this._webChannelId = options.channel_id; + + // options.helpers is only specified by tests. + ChromeUtils.defineLazyGetter(this, "_helpers", () => { + return options.helpers || new FxAccountsWebChannelHelpers(options); + }); + + this._setupChannel(); +} + +FxAccountsWebChannel.prototype = { + /** + * WebChannel that is used to communicate with content page + */ + _channel: null, + + /** + * Helpers interface that does the heavy lifting. + */ + _helpers: null, + + /** + * WebChannel ID. + */ + _webChannelId: null, + /** + * WebChannel origin, used to validate origin of messages + */ + _webChannelOrigin: null, + + /** + * Release all resources that are in use. + */ + tearDown() { + this._channel.stopListening(); + this._channel = null; + this._channelCallback = null; + }, + + /** + * Configures and registers a new WebChannel + * + * @private + */ + _setupChannel() { + // if this.contentUri is present but not a valid URI, then this will throw an error. + try { + this._webChannelOrigin = Services.io.newURI(this._contentUri); + this._registerChannel(); + } catch (e) { + log.error(e); + throw e; + } + }, + + _receiveMessage(message, sendingContext) { + const { command, data } = message; + let shouldCheckRemoteType = + lazy.separatePrivilegedMozillaWebContentProcess && + lazy.separatedMozillaDomains.some(function (val) { + return ( + lazy.accountServer.asciiHost == val || + lazy.accountServer.asciiHost.endsWith("." + val) + ); + }); + let { currentRemoteType } = sendingContext.browsingContext; + if (shouldCheckRemoteType && currentRemoteType != "privilegedmozilla") { + log.error( + `Rejected FxA webchannel message from remoteType = ${currentRemoteType}` + ); + return; + } + + let browser = sendingContext.browsingContext.top.embedderElement; + switch (command) { + case COMMAND_PROFILE_CHANGE: + Services.obs.notifyObservers( + null, + ON_PROFILE_CHANGE_NOTIFICATION, + data.uid + ); + break; + case COMMAND_LOGIN: + this._helpers + .login(data) + .catch(error => this._sendError(error, message, sendingContext)); + break; + case COMMAND_OAUTH: + this._helpers + .oauthLogin(data) + .catch(error => this._sendError(error, message, sendingContext)); + break; + case COMMAND_LOGOUT: + case COMMAND_DELETE: + this._helpers + .logout(data.uid) + .catch(error => this._sendError(error, message, sendingContext)); + break; + case COMMAND_CAN_LINK_ACCOUNT: + let canLinkAccount = this._helpers.shouldAllowRelink(data.email); + + let response = { + command, + messageId: message.messageId, + data: { ok: canLinkAccount }, + }; + + log.debug("FxAccountsWebChannel response", response); + this._channel.send(response, sendingContext); + break; + case COMMAND_SYNC_PREFERENCES: + this._helpers.openSyncPreferences(browser, data.entryPoint); + break; + case COMMAND_PAIR_PREFERENCES: + if (lazy.pairingEnabled) { + let window = browser.ownerGlobal; + // We should close the FxA tab after we open our pref page + let selectedTab = window.gBrowser.selectedTab; + window.switchToTabHavingURI( + "about:preferences?action=pair#sync", + true, + { + ignoreQueryString: true, + replaceQueryString: true, + adoptIntoActiveWindow: true, + ignoreFragment: "whenComparing", + triggeringPrincipal: + Services.scriptSecurityManager.getSystemPrincipal(), + } + ); + // close the tab + window.gBrowser.removeTab(selectedTab); + } + break; + case COMMAND_FIREFOX_VIEW: + this._helpers.openFirefoxView(browser, data.entryPoint); + break; + case COMMAND_CHANGE_PASSWORD: + this._helpers + .changePassword(data) + .catch(error => this._sendError(error, message, sendingContext)); + break; + case COMMAND_FXA_STATUS: + log.debug("fxa_status received"); + + const service = data && data.service; + const isPairing = data && data.isPairing; + const context = data && data.context; + this._helpers + .getFxaStatus(service, sendingContext, isPairing, context) + .then(fxaStatus => { + let response = { + command, + messageId: message.messageId, + data: fxaStatus, + }; + this._channel.send(response, sendingContext); + }) + .catch(error => this._sendError(error, message, sendingContext)); + break; + case COMMAND_PAIR_HEARTBEAT: + case COMMAND_PAIR_SUPP_METADATA: + case COMMAND_PAIR_AUTHORIZE: + case COMMAND_PAIR_DECLINE: + case COMMAND_PAIR_COMPLETE: + log.debug(`Pairing command ${command} received`); + const { channel_id: channelId } = data; + delete data.channel_id; + const flow = lazy.FxAccountsPairingFlow.get(channelId); + if (!flow) { + log.warn(`Could not find a pairing flow for ${channelId}`); + return; + } + flow.onWebChannelMessage(command, data).then(replyData => { + this._channel.send( + { + command, + messageId: message.messageId, + data: replyData, + }, + sendingContext + ); + }); + break; + default: + log.warn("Unrecognized FxAccountsWebChannel command", command); + // As a safety measure we also terminate any pending FxA pairing flow. + lazy.FxAccountsPairingFlow.finalizeAll(); + break; + } + }, + + _sendError(error, incomingMessage, sendingContext) { + log.error("Failed to handle FxAccountsWebChannel message", error); + this._channel.send( + { + command: incomingMessage.command, + messageId: incomingMessage.messageId, + data: { + error: getErrorDetails(error), + }, + }, + sendingContext + ); + }, + + /** + * Create a new channel with the WebChannelBroker, setup a callback listener + * @private + */ + _registerChannel() { + /** + * Processes messages that are called back from the FxAccountsChannel + * + * @param webChannelId {String} + * Command webChannelId + * @param message {Object} + * Command message + * @param sendingContext {Object} + * Message sending context. + * @param sendingContext.browsingContext {BrowsingContext} + * The browsingcontext from which the + * WebChannelMessageToChrome was sent. + * @param sendingContext.eventTarget {EventTarget} + * The <EventTarget> where the message was sent. + * @param sendingContext.principal {Principal} + * The <Principal> of the EventTarget where the message was sent. + * @private + * + */ + let listener = (webChannelId, message, sendingContext) => { + if (message) { + log.debug("FxAccountsWebChannel message received", message.command); + if (logPII()) { + log.debug("FxAccountsWebChannel message details", message); + } + try { + this._receiveMessage(message, sendingContext); + } catch (error) { + this._sendError(error, message, sendingContext); + } + } + }; + + this._channelCallback = listener; + this._channel = new lazy.WebChannel( + this._webChannelId, + this._webChannelOrigin + ); + this._channel.listen(listener); + log.debug( + "FxAccountsWebChannel registered: " + + this._webChannelId + + " with origin " + + this._webChannelOrigin.prePath + ); + }, +}; + +export function FxAccountsWebChannelHelpers(options) { + options = options || {}; + + this._fxAccounts = options.fxAccounts || lazy.fxAccounts; + this._weaveXPCOM = options.weaveXPCOM || null; + this._privateBrowsingUtils = + options.privateBrowsingUtils || lazy.PrivateBrowsingUtils; +} + +FxAccountsWebChannelHelpers.prototype = { + // If the last fxa account used for sync isn't this account, we display + // a modal dialog checking they really really want to do this... + // (This is sync-specific, so ideally would be in sync's identity module, + // but it's a little more seamless to do here, and sync is currently the + // only fxa consumer, so... + shouldAllowRelink(acctName) { + return ( + !this._needRelinkWarning(acctName) || this._promptForRelink(acctName) + ); + }, + + async _initializeSync() { + // A sync-specific hack - we want to ensure sync has been initialized + // before we set the signed-in user. + // XXX - probably not true any more, especially now we have observerPreloads + // in FxAccounts.jsm? + let xps = + this._weaveXPCOM || + Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports) + .wrappedJSObject; + await xps.whenLoaded(); + return xps; + }, + + _setEnabledEngines(offeredEngines, declinedEngines) { + if (offeredEngines && declinedEngines) { + EXTRA_ENGINES.forEach(engine => { + if ( + offeredEngines.includes(engine) && + !declinedEngines.includes(engine) + ) { + // These extra engines are disabled by default. + Services.prefs.setBoolPref(`services.sync.engine.${engine}`, true); + } + }); + log.debug("Received declined engines", declinedEngines); + lazy.Weave.Service.engineManager.setDeclined(declinedEngines); + declinedEngines.forEach(engine => { + Services.prefs.setBoolPref(`services.sync.engine.${engine}`, false); + }); + } + }, + /** + * stores sync login info it in the fxaccounts service + * + * @param accountData the user's account data and credentials + */ + async login(accountData) { + // We don't act on customizeSync anymore, it used to open a dialog inside + // the browser to selecte the engines to sync but we do it on the web now. + log.debug("Webchannel is logging a user in."); + delete accountData.customizeSync; + + // Save requested services for later. + const requestedServices = accountData.services; + delete accountData.services; + + // the user has already been shown the "can link account" + // screen. No need to keep this data around. + delete accountData.verifiedCanLinkAccount; + + // Remember who it was so we can log out next time. + if (accountData.verified) { + this.setPreviousAccountNameHashPref(accountData.email); + } + + await this._fxAccounts.telemetry.recordConnection( + Object.keys(requestedServices || {}), + "webchannel" + ); + + const xps = await this._initializeSync(); + await this._fxAccounts._internal.setSignedInUser(accountData); + + if (requestedServices) { + // User has enabled Sync. + if (requestedServices.sync) { + const { offeredEngines, declinedEngines } = requestedServices.sync; + this._setEnabledEngines(offeredEngines, declinedEngines); + log.debug("Webchannel is enabling sync"); + await xps.Weave.Service.configure(); + } + } + }, + + /** + * Logins in to sync by completing an OAuth flow + * @param { Object } oauthData: The oauth code and state as returned by the server */ + async oauthLogin(oauthData) { + log.debug("Webchannel is completing the oauth flow"); + const xps = await this._initializeSync(); + const { code, state, declinedSyncEngines, offeredSyncEngines } = oauthData; + const { sessionToken } = + await this._fxAccounts._internal.getUserAccountData(["sessionToken"]); + // First we finish the ongoing oauth flow + const { scopedKeys, refreshToken } = + await this._fxAccounts._internal.completeOAuthFlow( + sessionToken, + code, + state + ); + + // We don't currently use the refresh token in Firefox Desktop, lets be good citizens and revoke it. + await this._fxAccounts._internal.destroyOAuthToken({ token: refreshToken }); + + // Then, we persist the sync keys + await this._fxAccounts._internal.setScopedKeys(scopedKeys); + + // Now that we have the scoped keys, we set our status to verified + await this._fxAccounts._internal.setUserVerified(); + this._setEnabledEngines(offeredSyncEngines, declinedSyncEngines); + log.debug("Webchannel is enabling sync"); + xps.Weave.Service.configure(); + }, + + /** + * logout the fxaccounts service + * + * @param the uid of the account which have been logged out + */ + async logout(uid) { + let fxa = this._fxAccounts; + let userData = await fxa._internal.getUserAccountData(["uid"]); + if (userData && userData.uid === uid) { + await fxa.telemetry.recordDisconnection(null, "webchannel"); + // true argument is `localOnly`, because server-side stuff + // has already been taken care of by the content server + await fxa.signOut(true); + } + }, + + /** + * Check if `sendingContext` is in private browsing mode. + */ + isPrivateBrowsingMode(sendingContext) { + if (!sendingContext) { + log.error("Unable to check for private browsing mode, assuming true"); + return true; + } + + let browser = sendingContext.browsingContext.top.embedderElement; + const isPrivateBrowsing = + this._privateBrowsingUtils.isBrowserPrivate(browser); + log.debug("is private browsing", isPrivateBrowsing); + return isPrivateBrowsing; + }, + + /** + * Check whether sending fxa_status data should be allowed. + */ + shouldAllowFxaStatus(service, sendingContext, isPairing, context) { + // Return user data for any service in non-PB mode. In PB mode, + // only return user data if service==="sync" or is in pairing mode + // (as service will be equal to the OAuth client ID and not "sync"). + // + // This behaviour allows users to click the "Manage Account" + // link from about:preferences#sync while in PB mode and things + // "just work". While in non-PB mode, users can sign into + // Pocket w/o entering their password a 2nd time, while in PB + // mode they *will* have to enter their email/password again. + // + // The difference in behaviour is to try to match user + // expectations as to what is and what isn't part of the browser. + // Sync is viewed as an integral part of the browser, interacting + // with FxA as part of a Sync flow should work all the time. If + // Sync is broken in PB mode, users will think Firefox is broken. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1323853 + log.debug("service", service); + return ( + !this.isPrivateBrowsingMode(sendingContext) || + service === "sync" || + context === "fx_desktop_v3" || + isPairing + ); + }, + + /** + * Get fxa_status information. Resolves to { signedInUser: <user_data> }. + * If returning status information is not allowed or no user is signed into + * Sync, `user_data` will be null. + */ + async getFxaStatus(service, sendingContext, isPairing, context) { + let signedInUser = null; + + if ( + this.shouldAllowFxaStatus(service, sendingContext, isPairing, context) + ) { + const userData = await this._fxAccounts._internal.getUserAccountData([ + "email", + "sessionToken", + "uid", + "verified", + ]); + if (userData) { + signedInUser = { + email: userData.email, + sessionToken: userData.sessionToken, + uid: userData.uid, + verified: userData.verified, + }; + } + } + + const capabilities = this._getCapabilities(); + + return { + signedInUser, + clientId: FX_OAUTH_CLIENT_ID, + capabilities, + }; + }, + _getCapabilities() { + if ( + Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled", false) + ) { + return { + multiService: true, + pairing: lazy.pairingEnabled, + choose_what_to_sync: true, + engines: CHOOSE_WHAT_TO_SYNC, + }; + } + return { + multiService: true, + pairing: lazy.pairingEnabled, + engines: this._getAvailableExtraEngines(), + }; + }, + + _getAvailableExtraEngines() { + return EXTRA_ENGINES.filter(engineName => { + try { + return Services.prefs.getBoolPref( + `services.sync.engine.${engineName}.available` + ); + } catch (e) { + return false; + } + }); + }, + + async changePassword(credentials) { + // If |credentials| has fields that aren't handled by accounts storage, + // updateUserAccountData will throw - mainly to prevent errors in code + // that hard-codes field names. + // However, in this case the field names aren't really in our control. + // We *could* still insist the server know what fields names are valid, + // but that makes life difficult for the server when Firefox adds new + // features (ie, new fields) - forcing the server to track a map of + // versions to supported field names doesn't buy us much. + // So we just remove field names we know aren't handled. + let newCredentials = { + device: null, // Force a brand new device registration. + // We force the re-encryption of the send tab keys using the new sync key after the password change + encryptedSendTabKeys: null, + }; + for (let name of Object.keys(credentials)) { + if ( + name == "email" || + name == "uid" || + lazy.FxAccountsStorageManagerCanStoreField(name) + ) { + newCredentials[name] = credentials[name]; + } else { + log.info("changePassword ignoring unsupported field", name); + } + } + await this._fxAccounts._internal.updateUserAccountData(newCredentials); + await this._fxAccounts._internal.updateDeviceRegistration(); + }, + + /** + * Get the hash of account name of the previously signed in account + */ + getPreviousAccountNameHashPref() { + try { + return Services.prefs.getStringPref(PREF_LAST_FXA_USER); + } catch (_) { + return ""; + } + }, + + /** + * Given an account name, set the hash of the previously signed in account + * + * @param acctName the account name of the user's account. + */ + setPreviousAccountNameHashPref(acctName) { + Services.prefs.setStringPref( + PREF_LAST_FXA_USER, + lazy.CryptoUtils.sha256Base64(acctName) + ); + }, + + /** + * Open Sync Preferences in the current tab of the browser + * + * @param {Object} browser the browser in which to open preferences + * @param {String} [entryPoint] entryPoint to use for logging + */ + openSyncPreferences(browser, entryPoint) { + let uri = "about:preferences"; + if (entryPoint) { + uri += "?entrypoint=" + encodeURIComponent(entryPoint); + } + uri += "#sync"; + + browser.loadURI(Services.io.newURI(uri), { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + }, + + /** + * Open Firefox View in the browser's window + * + * @param {Object} browser the browser in whose window we'll open Firefox View + */ + openFirefoxView(browser) { + browser.ownerGlobal.FirefoxViewHandler.openTab("syncedtabs"); + }, + + /** + * If a user signs in using a different account, the data from the + * previous account and the new account will be merged. Ask the user + * if they want to continue. + * + * @private + */ + _needRelinkWarning(acctName) { + let prevAcctHash = this.getPreviousAccountNameHashPref(); + return ( + prevAcctHash && prevAcctHash != lazy.CryptoUtils.sha256Base64(acctName) + ); + }, + + /** + * Show the user a warning dialog that the data from the previous account + * and the new account will be merged. + * + * @private + */ + _promptForRelink(acctName) { + let sb = Services.strings.createBundle( + "chrome://browser/locale/syncSetup.properties" + ); + let continueLabel = sb.GetStringFromName("continue.label"); + let title = sb.GetStringFromName("relinkVerify.title"); + let description = sb.formatStringFromName("relinkVerify.description", [ + acctName, + ]); + let body = + sb.GetStringFromName("relinkVerify.heading") + "\n\n" + description; + let ps = Services.prompt; + let buttonFlags = + ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING + + ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL + + ps.BUTTON_POS_1_DEFAULT; + + // If running in context of the browser chrome, window does not exist. + let pressed = Services.prompt.confirmEx( + null, + title, + body, + buttonFlags, + continueLabel, + null, + null, + null, + {} + ); + return pressed === 0; // 0 is the "continue" button + }, +}; + +var singleton; + +// The entry-point for this module, which ensures only one of our channels is +// ever created - we require this because the WebChannel is global in scope +// (eg, it uses the observer service to tell interested parties of interesting +// things) and allowing multiple channels would cause such notifications to be +// sent multiple times. +export var EnsureFxAccountsWebChannel = () => { + let contentUri = Services.urlFormatter.formatURLPref( + "identity.fxaccounts.remote.root" + ); + if (singleton && singleton._contentUri !== contentUri) { + singleton.tearDown(); + singleton = null; + } + if (!singleton) { + try { + if (contentUri) { + // The FxAccountsWebChannel listens for events and updates + // the state machine accordingly. + singleton = new FxAccountsWebChannel({ + content_uri: contentUri, + channel_id: WEBCHANNEL_ID, + }); + } else { + log.warn("FxA WebChannel functionaly is disabled due to no URI pref."); + } + } catch (ex) { + log.error("Failed to create FxA WebChannel", ex); + } + } +}; diff --git a/services/fxaccounts/components.conf b/services/fxaccounts/components.conf new file mode 100644 index 0000000000..992c88d0cb --- /dev/null +++ b/services/fxaccounts/components.conf @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{1b7db999-2ecd-4abf-bb95-a726896798ca}', + 'contract_ids': ['@mozilla.org/fxaccounts/push;1'], + 'esModule': 'resource://gre/modules/FxAccountsPush.sys.mjs', + 'constructor': 'FxAccountsPushService', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + 'categories': {'push': 'chrome://fxa-device-update'}, + }, +] diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build new file mode 100644 index 0000000000..0047122c2d --- /dev/null +++ b/services/fxaccounts/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Firefox Accounts") + +MOCHITEST_CHROME_MANIFESTS += ["tests/mochitest/chrome.toml"] + +BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"] + +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"] + +EXTRA_JS_MODULES += [ + "Credentials.sys.mjs", + "FxAccounts.sys.mjs", + "FxAccountsClient.sys.mjs", + "FxAccountsCommands.sys.mjs", + "FxAccountsCommon.sys.mjs", + "FxAccountsConfig.sys.mjs", + "FxAccountsDevice.sys.mjs", + "FxAccountsKeys.sys.mjs", + "FxAccountsOAuth.sys.mjs", + "FxAccountsPairing.sys.mjs", + "FxAccountsPairingChannel.sys.mjs", + "FxAccountsProfile.sys.mjs", + "FxAccountsProfileClient.sys.mjs", + "FxAccountsPush.sys.mjs", + "FxAccountsStorage.sys.mjs", + "FxAccountsTelemetry.sys.mjs", + "FxAccountsWebChannel.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/services/fxaccounts/tests/browser/browser.toml b/services/fxaccounts/tests/browser/browser.toml new file mode 100644 index 0000000000..7cb752b59a --- /dev/null +++ b/services/fxaccounts/tests/browser/browser.toml @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = ["head.js"] + +["browser_device_connected.js"] + +["browser_verify_login.js"] diff --git a/services/fxaccounts/tests/browser/browser_device_connected.js b/services/fxaccounts/tests/browser/browser_device_connected.js new file mode 100644 index 0000000000..8e567ddf35 --- /dev/null +++ b/services/fxaccounts/tests/browser/browser_device_connected.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService( + Ci.nsIObserver +); +const DEVICES_URL = "https://example.com/devices"; + +add_setup(async function () { + const origManageDevicesURI = FxAccounts.config.promiseManageDevicesURI; + FxAccounts.config.promiseManageDevicesURI = () => + Promise.resolve(DEVICES_URL); + setupMockAlertsService(); + + registerCleanupFunction(function () { + FxAccounts.config.promiseManageDevicesURI = origManageDevicesURI; + delete window.FxAccounts; + }); +}); + +async function testDeviceConnected(deviceName) { + info("testDeviceConnected with deviceName=" + deviceName); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "about:mozilla" + ); + await waitForDocLoadComplete(); + + let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + + Services.obs.notifyObservers(null, "fxaccounts:device_connected", deviceName); + + let tab = await waitForTabPromise; + Assert.ok("Tab successfully opened"); + + Assert.equal(tab.linkedBrowser.currentURI.spec, DEVICES_URL); + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function () { + await testDeviceConnected("My phone"); +}); + +add_task(async function () { + await testDeviceConnected(null); +}); diff --git a/services/fxaccounts/tests/browser/browser_verify_login.js b/services/fxaccounts/tests/browser/browser_verify_login.js new file mode 100644 index 0000000000..fa9d952a52 --- /dev/null +++ b/services/fxaccounts/tests/browser/browser_verify_login.js @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function () { + let payload = { + data: { + deviceName: "Laptop", + url: "https://example.com/newLogin", + title: "Sign-in Request", + body: "New sign-in request from vershwal's Nighty on Intel Mac OS X 10.12", + }, + }; + info("testVerifyNewSignin"); + setupMockAlertsService(); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "about:mozilla" + ); + await waitForDocLoadComplete(); + + let waitForTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); + + Services.obs.notifyObservers( + null, + "fxaccounts:verify_login", + JSON.stringify(payload.data) + ); + + let tab = await waitForTabPromise; + Assert.ok("Tab successfully opened"); + Assert.equal(tab.linkedBrowser.currentURI.spec, payload.data.url); + BrowserTestUtils.removeTab(tab); +}); diff --git a/services/fxaccounts/tests/browser/head.js b/services/fxaccounts/tests/browser/head.js new file mode 100644 index 0000000000..e9fb8ad0dc --- /dev/null +++ b/services/fxaccounts/tests/browser/head.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Waits for the next load to complete in any browser or the given browser. + * If a <tabbrowser> is given it waits for a load in any of its browsers. + * + * @return promise + */ +function waitForDocLoadComplete(aBrowser = gBrowser) { + return new Promise(resolve => { + let listener = { + onStateChange(webProgress, req, flags, status) { + let docStop = + Ci.nsIWebProgressListener.STATE_IS_NETWORK | + Ci.nsIWebProgressListener.STATE_STOP; + info( + "Saw state " + + flags.toString(16) + + " and status " + + status.toString(16) + ); + + // When a load needs to be retargetted to a new process it is cancelled + // with NS_BINDING_ABORTED so ignore that case + if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) { + aBrowser.removeProgressListener(this); + waitForDocLoadComplete.listeners.delete(this); + + let chan = req.QueryInterface(Ci.nsIChannel); + info("Browser loaded " + chan.originalURI.spec); + resolve(); + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIWebProgressListener", + "nsISupportsWeakReference", + ]), + }; + aBrowser.addProgressListener(listener); + waitForDocLoadComplete.listeners.add(listener); + info("Waiting for browser load"); + }); +} + +function setupMockAlertsService() { + const alertsService = { + showAlertNotification: ( + image, + title, + text, + clickable, + cookie, + clickCallback + ) => { + // We are invoking the event handler ourselves directly. + clickCallback(null, "alertclickcallback", null); + }, + }; + const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService( + Ci.nsIObserver + ); + gBrowserGlue.observe( + { wrappedJSObject: alertsService }, + "browser-glue-test", + "mock-alerts-service" + ); +} + +// Keep a set of progress listeners for waitForDocLoadComplete() to make sure +// they're not GC'ed before we saw the page load. +waitForDocLoadComplete.listeners = new Set(); +registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear()); diff --git a/services/fxaccounts/tests/mochitest/chrome.toml b/services/fxaccounts/tests/mochitest/chrome.toml new file mode 100644 index 0000000000..5e88133317 --- /dev/null +++ b/services/fxaccounts/tests/mochitest/chrome.toml @@ -0,0 +1,5 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = ["file_invalidEmailCase.sjs"] + +["test_invalidEmailCase.html"] diff --git a/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs b/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs new file mode 100644 index 0000000000..971cf52bba --- /dev/null +++ b/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This server simulates the behavior of /account/login on the Firefox Accounts + * auth server in the case where the user is trying to sign in with an email + * with the wrong capitalization. + * + * https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountlogin + * + * The expected behavior is that on the first attempt, with the wrong email, + * the server will respond with a 400 and the canonical email capitalization + * that the client should use. The client then has one chance to sign in with + * this different capitalization. + * + * In this test, the user with the account id "Greta.Garbo@gmail.COM" initially + * tries to sign in as "greta.garbo@gmail.com". + * + * On success, the client is responsible for updating its sign-in user state + * and recording the proper email capitalization. + */ + +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +const goodEmail = "Greta.Garbo@gmail.COM"; +const badEmail = "greta.garbo@gmail.com"; + +function handleRequest(request, response) { + let body = new BinaryInputStream(request.bodyInputStream); + let bytes = []; + let available; + while ((available = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(available)); + } + + let data = JSON.parse(String.fromCharCode.apply(null, bytes)); + let message; + + switch (data.email) { + case badEmail: + // Almost - try again with fixed email case + message = { + code: 400, + errno: 120, + error: "Incorrect email case", + email: goodEmail, + }; + response.setStatusLine(request.httpVersion, 400, "Almost"); + break; + + case goodEmail: + // Successful login. + message = { + uid: "your-uid", + sessionToken: "your-sessionToken", + keyFetchToken: "your-keyFetchToken", + verified: true, + authAt: 1392144866, + }; + response.setStatusLine(request.httpVersion, 200, "Yay"); + break; + + default: + // Anything else happening in this test is a failure. + message = { + code: 400, + errno: 999, + error: "What happened!?", + }; + response.setStatusLine(request.httpVersion, 400, "Ouch"); + break; + } + + let messageStr = JSON.stringify(message); + response.bodyOutputStream.write(messageStr, messageStr.length); +} diff --git a/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html b/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html new file mode 100644 index 0000000000..4b5e943591 --- /dev/null +++ b/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html @@ -0,0 +1,129 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<!-- +Tests for Firefox Accounts signin with invalid email case +https://bugzilla.mozilla.org/show_bug.cgi?id=963835 +--> +<head> + <title>Test for Firefox Accounts (Bug 963835)</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> + +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963835">Mozilla Bug 963835</a> +<p id="display"></p> +<div id="content" style="display: none"> + Test for correction of invalid email case in Fx Accounts signIn +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +const {FxAccounts} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const {FxAccountsClient} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); + +const TEST_SERVER = + "http://mochi.test:8888/chrome/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs?path="; + +let MockStorage = function() { + this.data = null; +}; +MockStorage.prototype = Object.freeze({ + set(contents) { + this.data = contents; + return Promise.resolve(null); + }, + get() { + return Promise.resolve(this.data); + }, + getOAuthTokens() { + return Promise.resolve(null); + }, + setOAuthTokens(contents) { + return Promise.resolve(); + }, +}); + +function MockFxAccounts() { + return new FxAccounts({ + _now_is: new Date(), + + now() { + return this._now_is; + }, + + signedInUserStorage: new MockStorage(), + + fxAccountsClient: new FxAccountsClient(TEST_SERVER), + }); +} + +let wrongEmail = "greta.garbo@gmail.com"; +let rightEmail = "Greta.Garbo@gmail.COM"; +let password = "123456"; + +function runTest() { + is(Services.prefs.getStringPref("identity.fxaccounts.auth.uri"), TEST_SERVER, + "Pref for auth.uri should be set to test server"); + + let fxa = new MockFxAccounts(); + let client = fxa._internal.fxAccountsClient; + + is(true, !!fxa, "Couldn't mock fxa"); + is(true, !!client, "Couldn't mock fxa client"); + is(client.host, TEST_SERVER, "Should be using the test auth server uri"); + + // First try to sign in using the email with the wrong capitalization. The + // FxAccountsClient will receive a 400 from the server with the corrected email. + // It will automatically try to sign in again. We expect this to succeed. + client.signIn(wrongEmail, password).then( + user => { + // Now store the signed-in user state. This will include the correct + // email capitalization. + fxa._internal.setSignedInUser(user).then( + () => { + // Confirm that the correct email got stored. + fxa.getSignedInUser().then( + data => { + is(data.email, rightEmail); + SimpleTest.finish(); + }, + getUserError => { + ok(false, JSON.stringify(getUserError)); + } + ); + }, + setSignedInUserError => { + ok(false, JSON.stringify(setSignedInUserError)); + } + ); + }, + signInError => { + ok(false, JSON.stringify(signInError)); + } + ); +} + +SpecialPowers.pushPrefEnv({"set": [ + ["identity.fxaccounts.enabled", true], // fx accounts + ["identity.fxaccounts.auth.uri", TEST_SERVER], // our sjs server + ["browser.dom.window.dump.enabled", true], + ["devtools.console.stdout.chrome", true], + ]}, + function() { runTest(); } +); + +</script> +</pre> +</body> +</html> diff --git a/services/fxaccounts/tests/xpcshell/head.js b/services/fxaccounts/tests/xpcshell/head.js new file mode 100644 index 0000000000..921888e2e3 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/head.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ +/* import-globals-from ../../../common/tests/unit/head_http.js */ + +"use strict"; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +const { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); +const { SCOPE_OLD_SYNC } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +// Some mock key data, in both scoped-key and legacy field formats. +const MOCK_ACCOUNT_KEYS = { + scopedKeys: { + [SCOPE_OLD_SYNC]: { + kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw", + k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg", + kty: "oct", + }, + }, +}; + +(function initFxAccountsTestingInfrastructure() { + do_get_profile(); + + let { initTestLogging } = ChromeUtils.importESModule( + "resource://testing-common/services/common/logging.sys.mjs" + ); + + initTestLogging("Trace"); +}).call(this); diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js new file mode 100644 index 0000000000..c4aec73a03 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -0,0 +1,1642 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); +const { + ERRNO_INVALID_AUTH_TOKEN, + ERROR_NO_ACCOUNT, + FX_OAUTH_CLIENT_ID, + ONLOGIN_NOTIFICATION, + ONLOGOUT_NOTIFICATION, + ONVERIFIED_NOTIFICATION, + DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY, + PREF_LAST_FXA_USER, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +// We grab some additional stuff via backstage passes. +var { AccountState } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +const MOCK_TOKEN_RESPONSE = { + access_token: + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69", + token_type: "bearer", + scope: "https://identity.mozilla.com/apps/oldsync", + expires_in: 21600, + auth_at: 1589579900, +}; + +initTestLogging("Trace"); + +var log = Log.repository.getLogger("Services.FxAccounts.test"); +log.level = Log.Level.Debug; + +// See verbose logging from FxAccounts.jsm and jwcrypto.jsm. +Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); +Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace; +Services.prefs.setStringPref("services.crypto.jwcrypto.log.level", "Debug"); + +/* + * The FxAccountsClient communicates with the remote Firefox + * Accounts auth server. Mock the server calls, with a little + * lag time to simulate some latency. + * + * We add the _verified attribute to mock the change in verification + * state on the FXA server. + */ + +function MockStorageManager() {} + +MockStorageManager.prototype = { + promiseInitialized: Promise.resolve(), + + initialize(accountData) { + this.accountData = accountData; + }, + + finalize() { + return Promise.resolve(); + }, + + getAccountData(fields = null) { + let result; + if (!this.accountData) { + result = null; + } else if (fields == null) { + // can't use cloneInto as the keys get upset... + result = {}; + for (let field of Object.keys(this.accountData)) { + result[field] = this.accountData[field]; + } + } else { + if (!Array.isArray(fields)) { + fields = [fields]; + } + result = {}; + for (let field of fields) { + result[field] = this.accountData[field]; + } + } + return Promise.resolve(result); + }, + + updateAccountData(updatedFields) { + if (!this.accountData) { + return Promise.resolve(); + } + for (let [name, value] of Object.entries(updatedFields)) { + if (value == null) { + delete this.accountData[name]; + } else { + this.accountData[name] = value; + } + } + return Promise.resolve(); + }, + + deleteAccountData() { + this.accountData = null; + return Promise.resolve(); + }, +}; + +function MockFxAccountsClient() { + this._email = "nobody@example.com"; + this._verified = false; + this._deletedOnServer = false; // for our accountStatus mock + + // mock calls up to the auth server to determine whether the + // user account has been verified + this.recoveryEmailStatus = async function (sessionToken) { + // simulate a call to /recovery_email/status + return { + email: this._email, + verified: this._verified, + }; + }; + + this.accountStatus = async function (uid) { + return !!uid && !this._deletedOnServer; + }; + + this.sessionStatus = async function () { + // If the sessionStatus check says an account is OK, we typically will not + // end up calling accountStatus - so this must return false if accountStatus + // would. + return !this._deletedOnServer; + }; + + this.accountKeys = function (keyFetchToken) { + return new Promise(resolve => { + do_timeout(50, () => { + resolve({ + kA: expandBytes("11"), + wrapKB: expandBytes("22"), + }); + }); + }); + }; + + this.getScopedKeyData = function (sessionToken, client_id, scopes) { + Assert.ok(sessionToken); + Assert.equal(client_id, FX_OAUTH_CLIENT_ID); + Assert.equal(scopes, SCOPE_OLD_SYNC); + return new Promise(resolve => { + do_timeout(50, () => { + resolve({ + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + keyRotationTimestamp: 1234567890123, + }, + }); + }); + }); + }; + + this.resendVerificationEmail = function (sessionToken) { + // Return the session token to show that we received it in the first place + return Promise.resolve(sessionToken); + }; + + this.signOut = () => Promise.resolve(); + + FxAccountsClient.apply(this); +} +MockFxAccountsClient.prototype = {}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); +/* + * We need to mock the FxAccounts module's interfaces to external + * services, such as storage and the FxAccounts client. We also + * mock the now() method, so that we can simulate the passing of + * time and verify that signatures expire correctly. + */ +function MockFxAccounts(credentials = null) { + let result = new FxAccounts({ + VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms + + _getCertificateSigned_calls: [], + _d_signCertificate: Promise.withResolvers(), + _now_is: new Date(), + now() { + return this._now_is; + }, + newAccountState(newCredentials) { + // we use a real accountState but mocked storage. + let storage = new MockStorageManager(); + storage.initialize(newCredentials); + return new AccountState(storage); + }, + fxAccountsClient: new MockFxAccountsClient(), + observerPreloads: [], + device: { + _registerOrUpdateDevice() {}, + _checkRemoteCommandsUpdateNeeded: async () => false, + }, + profile: { + getProfile() { + return null; + }, + }, + }); + // and for convenience so we don't have to touch as many lines in this test + // when we refactored FxAccounts.jsm :) + result.setSignedInUser = function (creds) { + return result._internal.setSignedInUser(creds); + }; + return result; +} + +/* + * Some tests want a "real" fxa instance - however, we still mock the storage + * to keep the tests fast on b2g. + */ +async function MakeFxAccounts({ internal = {}, credentials } = {}) { + if (!internal.newAccountState) { + // we use a real accountState but mocked storage. + internal.newAccountState = function (newCredentials) { + let storage = new MockStorageManager(); + storage.initialize(newCredentials); + return new AccountState(storage); + }; + } + if (!internal._signOutServer) { + internal._signOutServer = () => Promise.resolve(); + } + if (internal.device) { + if (!internal.device._registerOrUpdateDevice) { + internal.device._registerOrUpdateDevice = () => Promise.resolve(); + internal.device._checkRemoteCommandsUpdateNeeded = async () => false; + } + } else { + internal.device = { + _registerOrUpdateDevice() {}, + _checkRemoteCommandsUpdateNeeded: async () => false, + }; + } + if (!internal.observerPreloads) { + internal.observerPreloads = []; + } + let result = new FxAccounts(internal); + + if (credentials) { + await result._internal.setSignedInUser(credentials); + } + return result; +} + +add_task(async function test_get_signed_in_user_initially_unset() { + _("Check getSignedInUser initially and after signout reports no user"); + let account = await MakeFxAccounts(); + let credentials = { + email: "foo@example.com", + uid: "1234567890abcdef1234567890abcdef", + sessionToken: "dead", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + let result = await account.getSignedInUser(); + Assert.equal(result, null); + + await account._internal.setSignedInUser(credentials); + + // getSignedInUser only returns a subset. + result = await account.getSignedInUser(); + Assert.deepEqual(result.email, credentials.email); + Assert.deepEqual(result.scopedKeys, undefined); + + // for the sake of testing, use the low-level function to check it's all there + result = await account._internal.currentAccountState.getUserAccountData(); + Assert.deepEqual(result.email, credentials.email); + Assert.deepEqual(result.scopedKeys, credentials.scopedKeys); + + // sign out + let localOnly = true; + await account.signOut(localOnly); + + // user should be undefined after sign out + result = await account.getSignedInUser(); + Assert.equal(result, null); +}); + +add_task(async function test_set_signed_in_user_signs_out_previous_account() { + _("Check setSignedInUser signs out the previous account."); + let signOutServerCalled = false; + let credentials = { + email: "foo@example.com", + uid: "1234567890abcdef1234567890abcdef", + sessionToken: "dead", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + let account = await MakeFxAccounts({ credentials }); + + account._internal._signOutServer = () => { + signOutServerCalled = true; + return Promise.resolve(true); + }; + + await account._internal.setSignedInUser(credentials); + Assert.ok(signOutServerCalled); +}); + +add_task(async function test_update_account_data() { + _("Check updateUserAccountData does the right thing."); + let credentials = { + email: "foo@example.com", + uid: "1234567890abcdef1234567890abcdef", + sessionToken: "dead", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + let account = await MakeFxAccounts({ credentials }); + + let newCreds = { + email: credentials.email, + uid: credentials.uid, + sessionToken: "alive", + }; + await account._internal.updateUserAccountData(newCreds); + Assert.equal( + (await account._internal.getUserAccountData()).sessionToken, + "alive", + "new field value was saved" + ); + + // but we should fail attempting to change the uid. + newCreds = { + email: credentials.email, + uid: "11111111111111111111222222222222", + sessionToken: "alive", + }; + await Assert.rejects( + account._internal.updateUserAccountData(newCreds), + /The specified credentials aren't for the current user/ + ); + + // should fail without the uid. + newCreds = { + sessionToken: "alive", + }; + await Assert.rejects( + account._internal.updateUserAccountData(newCreds), + /The specified credentials have no uid/ + ); + + // and should fail with a field name that's not known by storage. + newCreds = { + email: credentials.email, + uid: "11111111111111111111222222222222", + foo: "bar", + }; + await Assert.rejects( + account._internal.updateUserAccountData(newCreds), + /The specified credentials aren't for the current user/ + ); +}); + +// Sanity-check that our mocked client is working correctly +add_test(function test_client_mock() { + let fxa = new MockFxAccounts(); + let client = fxa._internal.fxAccountsClient; + Assert.equal(client._verified, false); + Assert.equal(typeof client.signIn, "function"); + + // The recoveryEmailStatus function eventually fulfills its promise + client.recoveryEmailStatus().then(response => { + Assert.equal(response.verified, false); + run_next_test(); + }); +}); + +// Sign in a user, and after a little while, verify the user's email. +// Right after signing in the user, we should get the 'onlogin' notification. +// Polling should detect that the email is verified, and eventually +// 'onverified' should be observed +add_test(function test_verification_poll() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("francine"); + let login_notification_received = false; + + makeObserver(ONVERIFIED_NOTIFICATION, function () { + log.debug("test_verification_poll observed onverified"); + // Once email verification is complete, we will observe onverified + fxa._internal + .getUserAccountData() + .then(user => { + // And confirm that the user's state has changed + Assert.equal(user.verified, true); + Assert.equal(user.email, test_user.email); + Assert.equal( + Services.prefs.getStringPref(PREF_LAST_FXA_USER), + CryptoUtils.sha256Base64(test_user.email) + ); + Assert.ok(login_notification_received); + }) + .finally(run_next_test); + }); + + makeObserver(ONLOGIN_NOTIFICATION, function () { + log.debug("test_verification_poll observer onlogin"); + login_notification_received = true; + }); + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + // The user is signing in, but email has not been verified yet + Assert.equal(user.verified, false); + do_timeout(200, function () { + log.debug("Mocking verification of francine's email"); + fxa._internal.fxAccountsClient._email = test_user.email; + fxa._internal.fxAccountsClient._verified = true; + }); + }); + }); +}); + +// Sign in the user, but never verify the email. The check-email +// poll should time out. No verifiedlogin event should be observed, and the +// internal whenVerified promise should be rejected +add_test(function test_polling_timeout() { + // This test could be better - the onverified observer might fire on + // somebody else's stack, and we're not making sure that we're not receiving + // such a message. In other words, this tests either failure, or success, but + // not both. + + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function () { + do_throw("We should not be getting a login event!"); + }); + + fxa._internal.POLL_SESSION = 1; + + let p = fxa._internal.whenVerified({}); + + fxa.setSignedInUser(test_user).then(() => { + p.then( + success => { + do_throw("this should not succeed"); + }, + fail => { + removeObserver(); + fxa.signOut().then(run_next_test); + } + ); + }); +}); + +// For bug 1585299 - ensure we only get a single ONVERIFIED notification. +add_task(async function test_onverified_once() { + let fxa = new MockFxAccounts(); + let user = getTestUser("francine"); + + let numNotifications = 0; + + function observe(aSubject, aTopic, aData) { + numNotifications += 1; + } + Services.obs.addObserver(observe, ONVERIFIED_NOTIFICATION); + + fxa._internal.POLL_SESSION = 1; + + await fxa.setSignedInUser(user); + + Assert.ok(!(await fxa.getSignedInUser()).verified, "starts unverified"); + + await fxa._internal.startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "start" + ); + + Assert.ok(!(await fxa.getSignedInUser()).verified, "still unverified"); + + log.debug("Mocking verification of francine's email"); + fxa._internal.fxAccountsClient._email = user.email; + fxa._internal.fxAccountsClient._verified = true; + + await fxa._internal.startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "again" + ); + + Assert.ok((await fxa.getSignedInUser()).verified, "now verified"); + + Assert.equal(numNotifications, 1, "expect exactly 1 ONVERIFIED"); + + Services.obs.removeObserver(observe, ONVERIFIED_NOTIFICATION); + await fxa.signOut(); +}); + +add_test(function test_pollEmailStatus_start_verified() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + fxa._internal.POLL_SESSION = 20 * 60000; + fxa._internal.VERIFICATION_POLL_TIMEOUT_INITIAL = 50000; + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + fxa._internal.fxAccountsClient._email = test_user.email; + fxa._internal.fxAccountsClient._verified = true; + const mock = sinon.mock(fxa._internal); + mock.expects("_scheduleNextPollEmailStatus").never(); + fxa._internal + .startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "start" + ) + .then(() => { + mock.verify(); + mock.restore(); + run_next_test(); + }); + }); + }); +}); + +add_test(function test_pollEmailStatus_start() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + fxa._internal.POLL_SESSION = 20 * 60000; + fxa._internal.VERIFICATION_POLL_TIMEOUT_INITIAL = 123456; + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + const mock = sinon.mock(fxa._internal); + mock + .expects("_scheduleNextPollEmailStatus") + .once() + .withArgs( + fxa._internal.currentAccountState, + user.sessionToken, + 123456, + "start" + ); + fxa._internal + .startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "start" + ) + .then(() => { + mock.verify(); + mock.restore(); + run_next_test(); + }); + }); + }); +}); + +add_test(function test_pollEmailStatus_start_subsequent() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + fxa._internal.POLL_SESSION = 20 * 60000; + fxa._internal.VERIFICATION_POLL_TIMEOUT_INITIAL = 123456; + fxa._internal.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT = 654321; + fxa._internal.VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD = -1; + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + const mock = sinon.mock(fxa._internal); + mock + .expects("_scheduleNextPollEmailStatus") + .once() + .withArgs( + fxa._internal.currentAccountState, + user.sessionToken, + 654321, + "start" + ); + fxa._internal + .startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "start" + ) + .then(() => { + mock.verify(); + mock.restore(); + run_next_test(); + }); + }); + }); +}); + +add_test(function test_pollEmailStatus_browser_startup() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + fxa._internal.POLL_SESSION = 20 * 60000; + fxa._internal.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT = 654321; + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + const mock = sinon.mock(fxa._internal); + mock + .expects("_scheduleNextPollEmailStatus") + .once() + .withArgs( + fxa._internal.currentAccountState, + user.sessionToken, + 654321, + "browser-startup" + ); + fxa._internal + .startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "browser-startup" + ) + .then(() => { + mock.verify(); + mock.restore(); + run_next_test(); + }); + }); + }); +}); + +add_test(function test_pollEmailStatus_push() { + let fxa = new MockFxAccounts(); + let test_user = getTestUser("carol"); + + fxa.setSignedInUser(test_user).then(() => { + fxa._internal.getUserAccountData().then(user => { + const mock = sinon.mock(fxa._internal); + mock.expects("_scheduleNextPollEmailStatus").never(); + fxa._internal + .startPollEmailStatus( + fxa._internal.currentAccountState, + user.sessionToken, + "push" + ) + .then(() => { + mock.verify(); + mock.restore(); + run_next_test(); + }); + }); + }); +}); + +add_test(function test_getKeyForScope() { + let fxa = new MockFxAccounts(); + let user = getTestUser("eusebius"); + + // Once email has been verified, we will be able to get keys + user.verified = true; + + fxa.setSignedInUser(user).then(() => { + fxa._internal.getUserAccountData().then(user2 => { + // Before getKeyForScope, we have no keys + Assert.equal(!!user2.scopedKeys, false); + // And we still have a key-fetch token and unwrapBKey to use + Assert.equal(!!user2.keyFetchToken, true); + Assert.equal(!!user2.unwrapBKey, true); + + fxa.keys.getKeyForScope(SCOPE_OLD_SYNC).then(() => { + fxa._internal.getUserAccountData().then(user3 => { + // Now we should have keys + Assert.equal(fxa._internal.isUserEmailVerified(user3), true); + Assert.equal(!!user3.verified, true); + Assert.notEqual(null, user3.scopedKeys); + Assert.equal(user3.keyFetchToken, undefined); + Assert.equal(user3.unwrapBKey, undefined); + run_next_test(); + }); + }); + }); + }); +}); + +add_task( + async function test_getKeyForScope_scopedKeys_migration_removes_deprecated_high_level_keys() { + let fxa = new MockFxAccounts(); + let user = getTestUser("eusebius"); + + user.verified = true; + + // An account state with the deprecated kinto extension sync keys... + user.kExtSync = + "f5ccd9cfdefd9b1ac4d02c56964f59239d8dfa1ca326e63696982765c1352cdc" + + "5d78a5a9c633a6d25edfea0a6c221a3480332a49fd866f311c2e3508ddd07395"; + user.kExtKbHash = + "6192f1cc7dce95334455ba135fa1d8fca8f70e8f594ae318528de06f24ed0273"; + user.scopedKeys = { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + }; + + await fxa.setSignedInUser(user); + // getKeyForScope will run the migration + await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + let newUser = await fxa._internal.getUserAccountData(); + // Then, the deprecated keys will be removed + Assert.strictEqual(newUser.kExtSync, undefined); + Assert.strictEqual(newUser.kExtKbHash, undefined); + } +); + +add_task( + async function test_getKeyForScope_scopedKeys_migration_removes_deprecated_scoped_keys() { + let fxa = new MockFxAccounts(); + let user = getTestUser("eusebius"); + const DEPRECATED_SCOPE_WEBEXT_SYNC = "sync:addon_storage"; + const EXTRA_SCOPE = "an unknown, but non-deprecated scope"; + user.verified = true; + user.ecosystemUserId = "ecoUserId"; + user.ecosystemAnonId = "ecoAnonId"; + user.scopedKeys = { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + [DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY]: + MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + [DEPRECATED_SCOPE_WEBEXT_SYNC]: + MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + [EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + }; + + await fxa.setSignedInUser(user); + await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + let newUser = await fxa._internal.getUserAccountData(); + // It should have removed the deprecated ecosystem_telemetry key, + // and the old kinto extension sync key + // but left the other keys intact. + const expectedScopedKeys = { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + [EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + }; + Assert.deepEqual(newUser.scopedKeys, expectedScopedKeys); + Assert.equal(newUser.ecosystemUserId, null); + Assert.equal(newUser.ecosystemAnonId, null); + } +); + +add_task(async function test_getKeyForScope_nonexistent_account() { + let fxa = new MockFxAccounts(); + let bismarck = getTestUser("bismarck"); + + let client = fxa._internal.fxAccountsClient; + client.accountStatus = () => Promise.resolve(false); + client.sessionStatus = () => Promise.resolve(false); + client.accountKeys = () => { + return Promise.reject({ + code: 401, + errno: ERRNO_INVALID_AUTH_TOKEN, + }); + }; + + await fxa.setSignedInUser(bismarck); + + let promiseLogout = new Promise(resolve => { + makeObserver(ONLOGOUT_NOTIFICATION, function () { + log.debug("test_getKeyForScope_nonexistent_account observed logout"); + resolve(); + }); + }); + + // XXX - the exception message here isn't ideal, but doesn't really matter... + await Assert.rejects( + fxa.keys.getKeyForScope(SCOPE_OLD_SYNC), + /A different user signed in/ + ); + + await promiseLogout; + + let user = await fxa._internal.getUserAccountData(); + Assert.equal(user, null); +}); + +// getKeyForScope with invalid keyFetchToken should delete keyFetchToken from storage +add_task(async function test_getKeyForScope_invalid_token() { + let fxa = new MockFxAccounts(); + let yusuf = getTestUser("yusuf"); + + let client = fxa._internal.fxAccountsClient; + client.accountStatus = () => Promise.resolve(true); // account exists. + client.sessionStatus = () => Promise.resolve(false); // session is invalid. + client.accountKeys = () => { + return Promise.reject({ + code: 401, + errno: ERRNO_INVALID_AUTH_TOKEN, + }); + }; + + await fxa.setSignedInUser(yusuf); + let user = await fxa._internal.getUserAccountData(); + Assert.notEqual(user.encryptedSendTabKeys, null); + + try { + await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + Assert.ok(false); + } catch (err) { + Assert.equal(err.code, 401); + Assert.equal(err.errno, ERRNO_INVALID_AUTH_TOKEN); + } + + user = await fxa._internal.getUserAccountData(); + Assert.equal(user.email, yusuf.email); + Assert.equal(user.keyFetchToken, null); + // We verify that encryptedSendTabKeys are also wiped + // when a user's credentials are wiped + Assert.equal(user.encryptedSendTabKeys, null); + await fxa._internal.abortExistingFlow(); +}); + +// Test vectors from +// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#Test_Vectors +add_task(async function test_getKeyForScope_oldsync() { + let fxa = new MockFxAccounts(); + let client = fxa._internal.fxAccountsClient; + client.getScopedKeyData = () => + Promise.resolve({ + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + keyRotationTimestamp: 1510726317123, + }, + }); + + // We mock the server returning the wrapKB from our test vectors + client.accountKeys = async () => { + return { + wrapKB: CommonUtils.hexToBytes( + "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f" + ), + }; + }; + + // We set the user to have the keyFetchToken and unwrapBKey from our test vectors + let user = { + ...getTestUser("eusebius"), + uid: "aeaa1725c7a24ff983c6295725d5fc9b", + keyFetchToken: + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + unwrapBKey: + "6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a", + sessionToken: "mock session token, used in metadata request", + verified: true, + }; + await fxa.setSignedInUser(user); + + // We derive, persist and return the sync key + const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + + // We verify the key returned matches what we would expect from the test vectors + // kb = 2ee722fdd8ccaa721bdeb2d1b76560efef705b04349d9357c3e592cf4906e075 (from test vectors) + // + // kid can be verified by "${keyRotationTimestamp}-${sha256(kb)[0:16]}" + // + // k can be verified by HKDF(kb, undefined, "identity.mozilla.com/picl/v1/oldsync", 64) + Assert.deepEqual(key, { + scope: SCOPE_OLD_SYNC, + kid: "1510726317123-BAik7hEOdpGnPZnPBSdaTg", + k: "fwM5VZu0Spf5XcFRZYX2zk6MrqZP7zvovCBcvuKwgYMif3hz98FHmIVa3qjKjrW0J244Zj-P5oWaOcQbvypmpw", + kty: "oct", + }); +}); + +add_task(async function test_getScopedKeys_cached_key() { + let fxa = new MockFxAccounts(); + let user = { + ...getTestUser("eusebius"), + uid: "aeaa1725c7a24ff983c6295725d5fc9b", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + + await fxa.setSignedInUser(user); + let key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + Assert.deepEqual(key, { + scope: SCOPE_OLD_SYNC, + ...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + }); +}); + +add_task(async function test_getScopedKeys_unavailable_scope() { + let fxa = new MockFxAccounts(); + let user = { + ...getTestUser("eusebius"), + uid: "aeaa1725c7a24ff983c6295725d5fc9b", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + await fxa.setSignedInUser(user); + await Assert.rejects( + fxa.keys.getKeyForScope("otherkeybearingscope"), + /Key not available for scope/ + ); +}); + +add_task(async function test_getScopedKeys_misconfigured_fxa_server() { + let fxa = new MockFxAccounts(); + let client = fxa._internal.fxAccountsClient; + client.getScopedKeyData = () => + Promise.resolve({ + wrongscope: { + identifier: "wrongscope", + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + keyRotationTimestamp: 1510726331712, + }, + }); + let user = { + ...getTestUser("eusebius"), + uid: "aeaa1725c7a24ff983c6295725d5fc9b", + verified: true, + keyFetchToken: + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", + unwrapBKey: + "6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a", + sessionToken: "mock session token, used in metadata request", + }; + await fxa.setSignedInUser(user); + await Assert.rejects( + fxa.keys.getKeyForScope(SCOPE_OLD_SYNC), + /The FxA server did not grant Firefox the `oldsync` scope/ + ); +}); + +add_task(async function test_setScopedKeys() { + const fxa = new MockFxAccounts(); + const user = { + ...getTestUser("foo"), + verified: true, + }; + await fxa.setSignedInUser(user); + await fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys); + const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + Assert.deepEqual(key, { + scope: SCOPE_OLD_SYNC, + ...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], + }); +}); + +add_task(async function test_setScopedKeys_user_not_signed_in() { + const fxa = new MockFxAccounts(); + await Assert.rejects( + fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys), + /Cannot persist keys, no user signed in/ + ); +}); + +// _fetchAndUnwrapAndDeriveKeys with no keyFetchToken should trigger signOut +// XXX - actually, it probably shouldn't - bug 1572313. +add_test(function test_fetchAndUnwrapAndDeriveKeys_no_token() { + let fxa = new MockFxAccounts(); + let user = getTestUser("lettuce.protheroe"); + delete user.keyFetchToken; + + makeObserver(ONLOGOUT_NOTIFICATION, function () { + log.debug("test_fetchAndUnwrapKeys_no_token observed logout"); + fxa._internal.getUserAccountData().then(user2 => { + fxa._internal.abortExistingFlow().then(run_next_test); + }); + }); + + fxa + .setSignedInUser(user) + .then(user2 => { + return fxa.keys._fetchAndUnwrapAndDeriveKeys(); + }) + .catch(error => { + log.info("setSignedInUser correctly rejected"); + }); +}); + +// Alice (User A) signs up but never verifies her email. Then Bob (User B) +// signs in with a verified email. Ensure that no sign-in events are triggered +// on Alice's behalf. In the end, Bob should be the signed-in user. +add_test(function test_overlapping_signins() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + let bob = getTestUser("bob"); + + makeObserver(ONVERIFIED_NOTIFICATION, function () { + log.debug("test_overlapping_signins observed onverified"); + // Once email verification is complete, we will observe onverified + fxa._internal.getUserAccountData().then(user => { + Assert.equal(user.email, bob.email); + Assert.equal(user.verified, true); + run_next_test(); + }); + }); + + // Alice is the user signing in; her email is unverified. + fxa.setSignedInUser(alice).then(() => { + log.debug("Alice signing in ..."); + fxa._internal.getUserAccountData().then(user => { + Assert.equal(user.email, alice.email); + Assert.equal(user.verified, false); + log.debug("Alice has not verified her email ..."); + + // Now Bob signs in instead and actually verifies his email + log.debug("Bob signing in ..."); + fxa.setSignedInUser(bob).then(() => { + do_timeout(200, function () { + // Mock email verification ... + log.debug("Bob verifying his email ..."); + fxa._internal.fxAccountsClient._verified = true; + }); + }); + }); + }); +}); + +add_task(async function test_resend_email_not_signed_in() { + let fxa = new MockFxAccounts(); + + try { + await fxa.resendVerificationEmail(); + } catch (err) { + Assert.equal(err.message, ERROR_NO_ACCOUNT); + return; + } + do_throw("Should not be able to resend email when nobody is signed in"); +}); + +add_task(async function test_accountStatus() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + + // If we have no user, we have no account server-side + let result = await fxa.checkAccountStatus(); + Assert.ok(!result); + // Set a user - the fxAccountsClient mock will say "ok". + await fxa.setSignedInUser(alice); + result = await fxa.checkAccountStatus(); + Assert.ok(result); + // flag the item as deleted on the server. + fxa._internal.fxAccountsClient._deletedOnServer = true; + result = await fxa.checkAccountStatus(); + Assert.ok(!result); + fxa._internal.fxAccountsClient._deletedOnServer = false; + await fxa.signOut(); +}); + +add_task(async function test_resend_email_invalid_token() { + let fxa = new MockFxAccounts(); + let sophia = getTestUser("sophia"); + Assert.notEqual(sophia.sessionToken, null); + + let client = fxa._internal.fxAccountsClient; + client.resendVerificationEmail = () => { + return Promise.reject({ + code: 401, + errno: ERRNO_INVALID_AUTH_TOKEN, + }); + }; + // This test wants the account to exist but the local session invalid. + client.accountStatus = uid => { + Assert.ok(uid, "got a uid to check"); + return Promise.resolve(true); + }; + client.sessionStatus = token => { + Assert.ok(token, "got a token to check"); + return Promise.resolve(false); + }; + + await fxa.setSignedInUser(sophia); + let user = await fxa._internal.getUserAccountData(); + Assert.equal(user.email, sophia.email); + Assert.equal(user.verified, false); + log.debug("Sophia wants verification email resent"); + + try { + await fxa.resendVerificationEmail(); + Assert.ok( + false, + "resendVerificationEmail should reject invalid session token" + ); + } catch (err) { + Assert.equal(err.code, 401); + Assert.equal(err.errno, ERRNO_INVALID_AUTH_TOKEN); + } + + user = await fxa._internal.getUserAccountData(); + Assert.equal(user.email, sophia.email); + Assert.equal(user.sessionToken, null); + await fxa._internal.abortExistingFlow(); +}); + +add_test(function test_resend_email() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + + let initialState = fxa._internal.currentAccountState; + + // Alice is the user signing in; her email is unverified. + fxa.setSignedInUser(alice).then(() => { + log.debug("Alice signing in"); + + // We're polling for the first email + Assert.ok(fxa._internal.currentAccountState !== initialState); + let aliceState = fxa._internal.currentAccountState; + + // The polling timer is ticking + Assert.ok(fxa._internal.currentTimer > 0); + + fxa._internal.getUserAccountData().then(user => { + Assert.equal(user.email, alice.email); + Assert.equal(user.verified, false); + log.debug("Alice wants verification email resent"); + + fxa.resendVerificationEmail().then(result => { + // Mock server response; ensures that the session token actually was + // passed to the client to make the hawk call + Assert.equal(result, "alice's session token"); + + // Timer was not restarted + Assert.ok(fxa._internal.currentAccountState === aliceState); + + // Timer is still ticking + Assert.ok(fxa._internal.currentTimer > 0); + + // Ok abort polling before we go on to the next test + fxa._internal.abortExistingFlow(); + run_next_test(); + }); + }); + }); +}); + +Services.prefs.setStringPref( + "identity.fxaccounts.remote.oauth.uri", + "https://example.com/v1" +); + +add_test(async function test_getOAuthTokenWithSessionToken() { + Services.prefs.setBoolPref( + "identity.fxaccounts.useSessionTokensForOAuth", + true + ); + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + let oauthTokenCalled = false; + + let client = fxa._internal.fxAccountsClient; + client.accessTokenWithSessionToken = async ( + sessionTokenHex, + clientId, + scope, + ttl + ) => { + oauthTokenCalled = true; + Assert.equal(sessionTokenHex, "alice's session token"); + Assert.equal(clientId, "5882386c6d801776"); + Assert.equal(scope, "profile"); + Assert.equal(ttl, undefined); + return MOCK_TOKEN_RESPONSE; + }; + + await fxa.setSignedInUser(alice); + const result = await fxa.getOAuthToken({ scope: "profile" }); + Assert.ok(oauthTokenCalled); + Assert.equal(result, MOCK_TOKEN_RESPONSE.access_token); + Services.prefs.setBoolPref( + "identity.fxaccounts.useSessionTokensForOAuth", + false + ); + run_next_test(); +}); + +add_task(async function test_getOAuthTokenCachedWithSessionToken() { + Services.prefs.setBoolPref( + "identity.fxaccounts.useSessionTokensForOAuth", + true + ); + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + let numOauthTokenCalls = 0; + + let client = fxa._internal.fxAccountsClient; + client.accessTokenWithSessionToken = async () => { + numOauthTokenCalls++; + return MOCK_TOKEN_RESPONSE; + }; + + await fxa.setSignedInUser(alice); + let result = await fxa.getOAuthToken({ + scope: "profile", + service: "test-service", + }); + Assert.equal(numOauthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + + // requesting it again should not re-fetch the token. + result = await fxa.getOAuthToken({ + scope: "profile", + service: "test-service", + }); + Assert.equal(numOauthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + // But requesting the same service and a different scope *will* get a new one. + result = await fxa.getOAuthToken({ + scope: "something-else", + service: "test-service", + }); + Assert.equal(numOauthTokenCalls, 2); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + Services.prefs.setBoolPref( + "identity.fxaccounts.useSessionTokensForOAuth", + false + ); +}); + +add_test(function test_getOAuthTokenScopedWithSessionToken() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + let numOauthTokenCalls = 0; + + let client = fxa._internal.fxAccountsClient; + client.accessTokenWithSessionToken = async ( + _sessionTokenHex, + _clientId, + scopeString + ) => { + equal(scopeString, "bar foo"); // scopes are sorted locally before request. + numOauthTokenCalls++; + return MOCK_TOKEN_RESPONSE; + }; + + fxa.setSignedInUser(alice).then(() => { + fxa.getOAuthToken({ scope: ["foo", "bar"] }).then(result => { + Assert.equal(numOauthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + run_next_test(); + }); + }); +}); + +add_task(async function test_getOAuthTokenCachedScopeNormalization() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + let numOAuthTokenCalls = 0; + + let client = fxa._internal.fxAccountsClient; + client.accessTokenWithSessionToken = async ( + _sessionTokenHex, + _clientId, + scopeString + ) => { + numOAuthTokenCalls++; + return MOCK_TOKEN_RESPONSE; + }; + + await fxa.setSignedInUser(alice); + let result = await fxa.getOAuthToken({ + scope: ["foo", "bar"], + service: "test-service", + }); + Assert.equal(numOAuthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + + // requesting it again with the scope array in a different order should not re-fetch the token. + result = await fxa.getOAuthToken({ + scope: ["bar", "foo"], + service: "test-service", + }); + Assert.equal(numOAuthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + // requesting it again with the scope array in different case should not re-fetch the token. + result = await fxa.getOAuthToken({ + scope: ["Bar", "Foo"], + service: "test-service", + }); + Assert.equal(numOAuthTokenCalls, 1); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); + // But requesting with a new entry in the array does fetch one. + result = await fxa.getOAuthToken({ + scope: ["foo", "bar", "etc"], + service: "test-service", + }); + Assert.equal(numOAuthTokenCalls, 2); + Assert.equal( + result, + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69" + ); +}); + +add_test(function test_getOAuthToken_invalid_param() { + let fxa = new MockFxAccounts(); + + fxa.getOAuthToken().catch(err => { + Assert.equal(err.message, "INVALID_PARAMETER"); + fxa.signOut().then(run_next_test); + }); +}); + +add_test(function test_getOAuthToken_invalid_scope_array() { + let fxa = new MockFxAccounts(); + + fxa.getOAuthToken({ scope: [] }).catch(err => { + Assert.equal(err.message, "INVALID_PARAMETER"); + fxa.signOut().then(run_next_test); + }); +}); + +add_test(function test_getOAuthToken_misconfigure_oauth_uri() { + let fxa = new MockFxAccounts(); + + const prevServerURL = Services.prefs.getStringPref( + "identity.fxaccounts.remote.oauth.uri" + ); + Services.prefs.deleteBranch("identity.fxaccounts.remote.oauth.uri"); + + fxa.getOAuthToken().catch(err => { + Assert.equal(err.message, "INVALID_PARAMETER"); + // revert the pref + Services.prefs.setStringPref( + "identity.fxaccounts.remote.oauth.uri", + prevServerURL + ); + fxa.signOut().then(run_next_test); + }); +}); + +add_test(function test_getOAuthToken_no_account() { + let fxa = new MockFxAccounts(); + + fxa._internal.currentAccountState.getUserAccountData = function () { + return Promise.resolve(null); + }; + + fxa.getOAuthToken({ scope: "profile" }).catch(err => { + Assert.equal(err.message, "NO_ACCOUNT"); + fxa.signOut().then(run_next_test); + }); +}); + +add_test(function test_getOAuthToken_unverified() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + + fxa.setSignedInUser(alice).then(() => { + fxa.getOAuthToken({ scope: "profile" }).catch(err => { + Assert.equal(err.message, "UNVERIFIED_ACCOUNT"); + fxa.signOut().then(run_next_test); + }); + }); +}); + +add_test(function test_getOAuthToken_error() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + + let client = fxa._internal.fxAccountsClient; + client.accessTokenWithSessionToken = () => { + return Promise.reject("boom"); + }; + + fxa.setSignedInUser(alice).then(() => { + fxa.getOAuthToken({ scope: "profile" }).catch(err => { + equal(err.details, "boom"); + run_next_test(); + }); + }); +}); + +add_task(async function test_listAttachedOAuthClients() { + const ONE_HOUR = 60 * 60 * 1000; + const ONE_DAY = 24 * ONE_HOUR; + + const timestamp = Date.now(); + + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + + let client = fxa._internal.fxAccountsClient; + client.attachedClients = async () => { + return { + body: [ + // This entry was previously filtered but no longer is! + { + clientId: "a2270f727f45f648", + deviceId: "deadbeef", + sessionTokenId: null, + name: "Firefox Preview (no session token)", + scope: ["profile", "https://identity.mozilla.com/apps/oldsync"], + lastAccessTime: Date.now(), + }, + { + clientId: "802d56ef2a9af9fa", + deviceId: null, + sessionTokenId: null, + name: "Firefox Monitor", + scope: ["profile"], + lastAccessTime: Date.now() - ONE_DAY - ONE_HOUR, + }, + { + clientId: "1f30e32975ae5112", + deviceId: null, + sessionTokenId: null, + name: "Firefox Send", + scope: ["profile", "https://identity.mozilla.com/apps/send"], + lastAccessTime: Date.now() - ONE_DAY * 2 - ONE_HOUR, + }, + // One with a future date should be impossible, but having a negative + // result here would almost certainly confuse something! + { + clientId: "future-date", + deviceId: null, + sessionTokenId: null, + name: "Whatever", + lastAccessTime: Date.now() + ONE_DAY, + }, + // A missing/null lastAccessTime should end up with a missing lastAccessedDaysAgo + { + clientId: "missing-date", + deviceId: null, + sessionTokenId: null, + name: "Whatever", + }, + ], + headers: { "x-timestamp": timestamp.toString() }, + }; + }; + + await fxa.setSignedInUser(alice); + const clients = await fxa.listAttachedOAuthClients(); + Assert.deepEqual(clients, [ + { + id: "a2270f727f45f648", + lastAccessedDaysAgo: 0, + }, + { + id: "802d56ef2a9af9fa", + lastAccessedDaysAgo: 1, + }, + { + id: "1f30e32975ae5112", + lastAccessedDaysAgo: 2, + }, + { + id: "future-date", + lastAccessedDaysAgo: 0, + }, + { + id: "missing-date", + lastAccessedDaysAgo: null, + }, + ]); +}); + +add_task(async function test_getSignedInUserProfile() { + let alice = getTestUser("alice"); + alice.verified = true; + + let mockProfile = { + getProfile() { + return Promise.resolve({ avatar: "image" }); + }, + tearDown() {}, + }; + let fxa = new FxAccounts({ + _signOutServer() { + return Promise.resolve(); + }, + device: { + _registerOrUpdateDevice() { + return Promise.resolve(); + }, + }, + }); + + await fxa._internal.setSignedInUser(alice); + fxa._internal._profile = mockProfile; + let result = await fxa.getSignedInUser(); + Assert.ok(!!result); + Assert.equal(result.avatar, "image"); +}); + +add_task(async function test_getSignedInUserProfile_error_uses_account_data() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + + fxa._internal.getSignedInUser = function () { + return Promise.resolve({ email: "foo@bar.com" }); + }; + fxa._internal._profile = { + getProfile() { + return Promise.reject("boom"); + }, + tearDown() { + teardownCalled = true; + }, + }; + + let teardownCalled = false; + await fxa.setSignedInUser(alice); + let result = await fxa.getSignedInUser(); + Assert.deepEqual(result.avatar, null); + await fxa.signOut(); + Assert.ok(teardownCalled); +}); + +add_task(async function test_checkVerificationStatusFailed() { + let fxa = new MockFxAccounts(); + let alice = getTestUser("alice"); + alice.verified = true; + + let client = fxa._internal.fxAccountsClient; + client.recoveryEmailStatus = () => { + return Promise.reject({ + code: 401, + errno: ERRNO_INVALID_AUTH_TOKEN, + }); + }; + client.accountStatus = () => Promise.resolve(true); + client.sessionStatus = () => Promise.resolve(false); + + await fxa.setSignedInUser(alice); + let user = await fxa._internal.getUserAccountData(); + Assert.notEqual(alice.sessionToken, null); + Assert.equal(user.email, alice.email); + Assert.equal(user.verified, true); + + await fxa._internal.checkVerificationStatus(); + + user = await fxa._internal.getUserAccountData(); + Assert.equal(user.email, alice.email); + Assert.equal(user.sessionToken, null); +}); + +add_task(async function test_flushLogFile() { + _("Tests flushLogFile"); + let account = await MakeFxAccounts(); + let promiseObserved = new Promise(res => { + log.info("Adding flush-log-file observer."); + Services.obs.addObserver(function onFlushLogFile() { + Services.obs.removeObserver( + onFlushLogFile, + "service:log-manager:flush-log-file" + ); + res(); + }, "service:log-manager:flush-log-file"); + }); + account.flushLogFile(); + await promiseObserved; +}); + +/* + * End of tests. + * Utility functions follow. + */ + +function expandHex(two_hex) { + // Return a 64-character hex string, encoding 32 identical bytes. + let eight_hex = two_hex + two_hex + two_hex + two_hex; + let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex; + return thirtytwo_hex + thirtytwo_hex; +} + +function expandBytes(two_hex) { + return CommonUtils.hexToBytes(expandHex(two_hex)); +} + +function getTestUser(name) { + return { + email: name + "@example.com", + uid: "1ad7f5024cc74ec1a209071fd2fae348", + sessionToken: name + "'s session token", + keyFetchToken: name + "'s keyfetch token", + unwrapBKey: expandHex("44"), + verified: false, + encryptedSendTabKeys: name + "'s encrypted Send tab keys", + }; +} + +function makeObserver(aObserveTopic, aObserveFunc) { + let observer = { + // nsISupports provides type management in C++ + // nsIObserver is to be an observer + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe(aSubject, aTopic, aData) { + log.debug("observed " + aTopic + " " + aData); + if (aTopic == aObserveTopic) { + removeMe(); + aObserveFunc(aSubject, aTopic, aData); + } + }, + }; + + function removeMe() { + log.debug("removing observer for " + aObserveTopic); + Services.obs.removeObserver(observer, aObserveTopic); + } + + Services.obs.addObserver(observer, aObserveTopic); + return removeMe; +} diff --git a/services/fxaccounts/tests/xpcshell/test_accounts_config.js b/services/fxaccounts/tests/xpcshell/test_accounts_config.js new file mode 100644 index 0000000000..33ace13c47 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_accounts_config.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +add_task( + async function test_non_https_remote_server_uri_with_requireHttps_false() { + Services.prefs.setBoolPref("identity.fxaccounts.allowHttp", true); + Services.prefs.setStringPref( + "identity.fxaccounts.remote.root", + "http://example.com/" + ); + Assert.equal( + await FxAccounts.config.promiseConnectAccountURI("test"), + "http://example.com/?context=fx_desktop_v3&entrypoint=test&action=email&service=sync" + ); + + Services.prefs.clearUserPref("identity.fxaccounts.remote.root"); + Services.prefs.clearUserPref("identity.fxaccounts.allowHttp"); + } +); + +add_task(async function test_non_https_remote_server_uri() { + Services.prefs.setStringPref( + "identity.fxaccounts.remote.root", + "http://example.com/" + ); + await Assert.rejects( + FxAccounts.config.promiseConnectAccountURI(), + /Firefox Accounts server must use HTTPS/ + ); + Services.prefs.clearUserPref("identity.fxaccounts.remote.root"); +}); + +add_task(async function test_is_production_config() { + // should start with no auto-config URL. + Assert.ok(!FxAccounts.config.getAutoConfigURL()); + // which means we are using prod. + Assert.ok(FxAccounts.config.isProductionConfig()); + + // Set an auto-config URL. + Services.prefs.setStringPref( + "identity.fxaccounts.autoconfig.uri", + "http://x" + ); + Assert.equal(FxAccounts.config.getAutoConfigURL(), "http://x"); + Assert.ok(!FxAccounts.config.isProductionConfig()); + + // Clear the auto-config URL, but set one of the other config params. + Services.prefs.clearUserPref("identity.fxaccounts.autoconfig.uri"); + Services.prefs.setStringPref("identity.sync.tokenserver.uri", "http://t"); + Assert.ok(!FxAccounts.config.isProductionConfig()); + Services.prefs.clearUserPref("identity.sync.tokenserver.uri"); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js new file mode 100644 index 0000000000..68337eb69e --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js @@ -0,0 +1,1204 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); +const { FxAccountsDevice } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsDevice.sys.mjs" +); +const { + ERRNO_DEVICE_SESSION_CONFLICT, + ERRNO_TOO_MANY_CLIENT_REQUESTS, + ERRNO_UNKNOWN_DEVICE, + ON_DEVICE_CONNECTED_NOTIFICATION, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + ON_DEVICELIST_UPDATED, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +var { AccountState } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +initTestLogging("Trace"); + +var log = Log.repository.getLogger("Services.FxAccounts.test"); +log.level = Log.Level.Debug; + +const BOGUS_PUBLICKEY = + "BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc"; +const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw"; + +Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); + +const DEVICE_REGISTRATION_VERSION = 42; + +function MockStorageManager() {} + +MockStorageManager.prototype = { + initialize(accountData) { + this.accountData = accountData; + }, + + finalize() { + return Promise.resolve(); + }, + + getAccountData() { + return Promise.resolve(this.accountData); + }, + + updateAccountData(updatedFields) { + for (let [name, value] of Object.entries(updatedFields)) { + if (value == null) { + delete this.accountData[name]; + } else { + this.accountData[name] = value; + } + } + return Promise.resolve(); + }, + + deleteAccountData() { + this.accountData = null; + return Promise.resolve(); + }, +}; + +function MockFxAccountsClient(device) { + this._email = "nobody@example.com"; + // Be careful relying on `this._verified` as it doesn't change if the user's + // state does via setting the `verified` flag in the user data. + this._verified = false; + this._deletedOnServer = false; // for testing accountStatus + + // mock calls up to the auth server to determine whether the + // user account has been verified + this.recoveryEmailStatus = function (sessionToken) { + // simulate a call to /recovery_email/status + return Promise.resolve({ + email: this._email, + verified: this._verified, + }); + }; + + this.accountKeys = function (keyFetchToken) { + Assert.ok(keyFetchToken, "must be called with a key-fetch-token"); + // ideally we'd check the verification status here to more closely simulate + // the server, but `this._verified` is a test-only construct and doesn't + // update when the user changes verification status. + Assert.ok(!this._deletedOnServer, "this test thinks the acct is deleted!"); + return { + kA: "test-ka", + wrapKB: "X".repeat(32), + }; + }; + + this.accountStatus = function (uid) { + return Promise.resolve(!!uid && !this._deletedOnServer); + }; + + this.registerDevice = (st, name, type) => + Promise.resolve({ id: device.id, name }); + this.updateDevice = (st, id, name) => Promise.resolve({ id, name }); + this.signOut = () => Promise.resolve({}); + this.getDeviceList = st => + Promise.resolve([ + { + id: device.id, + name: device.name, + type: device.type, + pushCallback: device.pushCallback, + pushEndpointExpired: device.pushEndpointExpired, + isCurrentDevice: st === device.sessionToken, + }, + ]); + + FxAccountsClient.apply(this); +} +MockFxAccountsClient.prototype = {}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); + +async function MockFxAccounts(credentials, device = {}) { + let fxa = new FxAccounts({ + newAccountState(creds) { + // we use a real accountState but mocked storage. + let storage = new MockStorageManager(); + storage.initialize(creds); + return new AccountState(storage); + }, + fxAccountsClient: new MockFxAccountsClient(device, credentials), + fxaPushService: { + registerPushEndpoint() { + return new Promise(resolve => { + resolve({ + endpoint: "http://mochi.test:8888", + getKey(type) { + return ChromeUtils.base64URLDecode( + type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY, + { padding: "ignore" } + ); + }, + }); + }); + }, + unsubscribe() { + return Promise.resolve(); + }, + }, + commands: { + async availableCommands() { + return {}; + }, + }, + device: { + DEVICE_REGISTRATION_VERSION, + _checkRemoteCommandsUpdateNeeded: async () => false, + }, + VERIFICATION_POLL_TIMEOUT_INITIAL: 1, + }); + fxa._internal.device._fxai = fxa._internal; + await fxa._internal.setSignedInUser(credentials); + Services.prefs.setStringPref( + "identity.fxaccounts.account.device.name", + device.name || "mock device name" + ); + return fxa; +} + +function updateUserAccountData(fxa, data) { + return fxa._internal.updateUserAccountData(data); +} + +add_task(async function test_updateDeviceRegistration_with_new_device() { + const deviceName = "foo"; + const deviceType = "bar"; + + const credentials = getTestUser("baz"); + const fxa = await MockFxAccounts(credentials, { name: deviceName }); + // Remove the current device registration (setSignedInUser does one!). + await updateUserAccountData(fxa, { uid: credentials.uid, device: null }); + + const spy = { + registerDevice: { count: 0, args: [] }, + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.registerDevice = function () { + spy.registerDevice.count += 1; + spy.registerDevice.args.push(arguments); + return Promise.resolve({ + id: "newly-generated device id", + createdAt: Date.now(), + name: deviceName, + type: deviceType, + }); + }; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([]); + }; + + await fxa.updateDeviceRegistration(); + + Assert.equal(spy.updateDevice.count, 0); + Assert.equal(spy.getDeviceList.count, 0); + Assert.equal(spy.registerDevice.count, 1); + Assert.equal(spy.registerDevice.args[0].length, 4); + Assert.equal(spy.registerDevice.args[0][0], credentials.sessionToken); + Assert.equal(spy.registerDevice.args[0][1], deviceName); + Assert.equal(spy.registerDevice.args[0][2], "desktop"); + Assert.equal( + spy.registerDevice.args[0][3].pushCallback, + "http://mochi.test:8888" + ); + Assert.equal(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY); + Assert.equal(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY); + + const state = fxa._internal.currentAccountState; + const data = await state.getUserAccountData(); + + Assert.equal(data.device.id, "newly-generated device id"); + Assert.equal(data.device.registrationVersion, DEVICE_REGISTRATION_VERSION); + await fxa.signOut(true); +}); + +add_task(async function test_updateDeviceRegistration_with_existing_device() { + const deviceId = "my device id"; + const deviceName = "phil's device"; + + const credentials = getTestUser("pb"); + const fxa = await MockFxAccounts(credentials, { name: deviceName }); + await updateUserAccountData(fxa, { + uid: credentials.uid, + device: { + id: deviceId, + registeredCommandsKeys: [], + registrationVersion: 1, // < 42 + }, + }); + + const spy = { + registerDevice: { count: 0, args: [] }, + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.registerDevice = function () { + spy.registerDevice.count += 1; + spy.registerDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.resolve({ + id: deviceId, + name: deviceName, + }); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([]); + }; + await fxa.updateDeviceRegistration(); + + Assert.equal(spy.registerDevice.count, 0); + Assert.equal(spy.getDeviceList.count, 0); + Assert.equal(spy.updateDevice.count, 1); + Assert.equal(spy.updateDevice.args[0].length, 4); + Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken); + Assert.equal(spy.updateDevice.args[0][1], deviceId); + Assert.equal(spy.updateDevice.args[0][2], deviceName); + Assert.equal( + spy.updateDevice.args[0][3].pushCallback, + "http://mochi.test:8888" + ); + Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY); + Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY); + + const state = fxa._internal.currentAccountState; + const data = await state.getUserAccountData(); + + Assert.equal(data.device.id, deviceId); + Assert.equal(data.device.registrationVersion, DEVICE_REGISTRATION_VERSION); + await fxa.signOut(true); +}); + +add_task( + async function test_updateDeviceRegistration_with_unknown_device_error() { + const deviceName = "foo"; + const deviceType = "bar"; + const currentDeviceId = "my device id"; + + const credentials = getTestUser("baz"); + const fxa = await MockFxAccounts(credentials, { name: deviceName }); + await updateUserAccountData(fxa, { + uid: credentials.uid, + device: { + id: currentDeviceId, + registeredCommandsKeys: [], + registrationVersion: 1, // < 42 + }, + }); + + const spy = { + registerDevice: { count: 0, args: [] }, + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.registerDevice = function () { + spy.registerDevice.count += 1; + spy.registerDevice.args.push(arguments); + return Promise.resolve({ + id: "a different newly-generated device id", + createdAt: Date.now(), + name: deviceName, + type: deviceType, + }); + }; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.reject({ + code: 400, + errno: ERRNO_UNKNOWN_DEVICE, + }); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([]); + }; + + await fxa.updateDeviceRegistration(); + + Assert.equal(spy.getDeviceList.count, 0); + Assert.equal(spy.registerDevice.count, 0); + Assert.equal(spy.updateDevice.count, 1); + Assert.equal(spy.updateDevice.args[0].length, 4); + Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken); + Assert.equal(spy.updateDevice.args[0][1], currentDeviceId); + Assert.equal(spy.updateDevice.args[0][2], deviceName); + Assert.equal( + spy.updateDevice.args[0][3].pushCallback, + "http://mochi.test:8888" + ); + Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY); + Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY); + + const state = fxa._internal.currentAccountState; + const data = await state.getUserAccountData(); + + Assert.equal(null, data.device); + await fxa.signOut(true); + } +); + +add_task( + async function test_updateDeviceRegistration_with_device_session_conflict_error() { + const deviceName = "foo"; + const deviceType = "bar"; + const currentDeviceId = "my device id"; + const conflictingDeviceId = "conflicting device id"; + + const credentials = getTestUser("baz"); + const fxa = await MockFxAccounts(credentials, { name: deviceName }); + await updateUserAccountData(fxa, { + uid: credentials.uid, + device: { + id: currentDeviceId, + registeredCommandsKeys: [], + registrationVersion: 1, // < 42 + }, + }); + + const spy = { + registerDevice: { count: 0, args: [] }, + updateDevice: { count: 0, args: [], times: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.registerDevice = function () { + spy.registerDevice.count += 1; + spy.registerDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + spy.updateDevice.time = Date.now(); + if (spy.updateDevice.count === 1) { + return Promise.reject({ + code: 400, + errno: ERRNO_DEVICE_SESSION_CONFLICT, + }); + } + return Promise.resolve({ + id: conflictingDeviceId, + name: deviceName, + }); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + spy.getDeviceList.time = Date.now(); + return Promise.resolve([ + { + id: "ignore", + name: "ignore", + type: "ignore", + isCurrentDevice: false, + }, + { + id: conflictingDeviceId, + name: deviceName, + type: deviceType, + isCurrentDevice: true, + }, + ]); + }; + + await fxa.updateDeviceRegistration(); + + Assert.equal(spy.registerDevice.count, 0); + Assert.equal(spy.updateDevice.count, 1); + Assert.equal(spy.updateDevice.args[0].length, 4); + Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken); + Assert.equal(spy.updateDevice.args[0][1], currentDeviceId); + Assert.equal(spy.updateDevice.args[0][2], deviceName); + Assert.equal( + spy.updateDevice.args[0][3].pushCallback, + "http://mochi.test:8888" + ); + Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY); + Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY); + Assert.equal(spy.getDeviceList.count, 1); + Assert.equal(spy.getDeviceList.args[0].length, 1); + Assert.equal(spy.getDeviceList.args[0][0], credentials.sessionToken); + Assert.ok(spy.getDeviceList.time >= spy.updateDevice.time); + + const state = fxa._internal.currentAccountState; + const data = await state.getUserAccountData(); + + Assert.equal(data.device.id, conflictingDeviceId); + Assert.equal(data.device.registrationVersion, null); + await fxa.signOut(true); + } +); + +add_task( + async function test_updateDeviceRegistration_with_unrecoverable_error() { + const deviceName = "foo"; + + const credentials = getTestUser("baz"); + const fxa = await MockFxAccounts(credentials, { name: deviceName }); + await updateUserAccountData(fxa, { uid: credentials.uid, device: null }); + + const spy = { + registerDevice: { count: 0, args: [] }, + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.registerDevice = function () { + spy.registerDevice.count += 1; + spy.registerDevice.args.push(arguments); + return Promise.reject({ + code: 400, + errno: ERRNO_TOO_MANY_CLIENT_REQUESTS, + }); + }; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([]); + }; + + await fxa.updateDeviceRegistration(); + + Assert.equal(spy.getDeviceList.count, 0); + Assert.equal(spy.updateDevice.count, 0); + Assert.equal(spy.registerDevice.count, 1); + Assert.equal(spy.registerDevice.args[0].length, 4); + + const state = fxa._internal.currentAccountState; + const data = await state.getUserAccountData(); + + Assert.equal(null, data.device); + await fxa.signOut(true); + } +); + +add_task( + async function test_getDeviceId_with_no_device_id_invokes_device_registration() { + const credentials = getTestUser("foo"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + await updateUserAccountData(fxa, { uid: credentials.uid, device: null }); + + const spy = { count: 0, args: [] }; + fxa._internal.currentAccountState.getUserAccountData = () => + Promise.resolve({ + email: credentials.email, + registrationVersion: DEVICE_REGISTRATION_VERSION, + }); + fxa._internal.device._registerOrUpdateDevice = function () { + spy.count += 1; + spy.args.push(arguments); + return Promise.resolve("bar"); + }; + + const result = await fxa.device.getLocalId(); + + Assert.equal(spy.count, 1); + Assert.equal(spy.args[0].length, 2); + Assert.equal(spy.args[0][1].email, credentials.email); + Assert.equal(null, spy.args[0][1].device); + Assert.equal(result, "bar"); + await fxa.signOut(true); + } +); + +add_task( + async function test_getDeviceId_with_registration_version_outdated_invokes_device_registration() { + const credentials = getTestUser("foo"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + + const spy = { count: 0, args: [] }; + fxa._internal.currentAccountState.getUserAccountData = () => + Promise.resolve({ + device: { + id: "my id", + registrationVersion: 0, + registeredCommandsKeys: [], + }, + }); + fxa._internal.device._registerOrUpdateDevice = function () { + spy.count += 1; + spy.args.push(arguments); + return Promise.resolve("wibble"); + }; + + const result = await fxa.device.getLocalId(); + + Assert.equal(spy.count, 1); + Assert.equal(spy.args[0].length, 2); + Assert.equal(spy.args[0][1].device.id, "my id"); + Assert.equal(result, "wibble"); + await fxa.signOut(true); + } +); + +add_task( + async function test_getDeviceId_with_device_id_and_uptodate_registration_version_doesnt_invoke_device_registration() { + const credentials = getTestUser("foo"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + + const spy = { count: 0 }; + fxa._internal.currentAccountState.getUserAccountData = async () => ({ + device: { + id: "foo's device id", + registrationVersion: DEVICE_REGISTRATION_VERSION, + registeredCommandsKeys: [], + }, + }); + fxa._internal.device._registerOrUpdateDevice = function () { + spy.count += 1; + return Promise.resolve("bar"); + }; + + const result = await fxa.device.getLocalId(); + + Assert.equal(spy.count, 0); + Assert.equal(result, "foo's device id"); + await fxa.signOut(true); + } +); + +add_task( + async function test_getDeviceId_with_device_id_and_with_no_registration_version_invokes_device_registration() { + const credentials = getTestUser("foo"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + + const spy = { count: 0, args: [] }; + fxa._internal.currentAccountState.getUserAccountData = () => + Promise.resolve({ device: { id: "wibble" } }); + fxa._internal.device._registerOrUpdateDevice = function () { + spy.count += 1; + spy.args.push(arguments); + return Promise.resolve("wibble"); + }; + + const result = await fxa.device.getLocalId(); + + Assert.equal(spy.count, 1); + Assert.equal(spy.args[0].length, 2); + Assert.equal(spy.args[0][1].device.id, "wibble"); + Assert.equal(result, "wibble"); + await fxa.signOut(true); + } +); + +add_task(async function test_verification_updates_registration() { + const deviceName = "foo"; + + const credentials = getTestUser("baz"); + const fxa = await MockFxAccounts(credentials, { + id: "device-id", + name: deviceName, + }); + + // We should already have a device registration, but without send-tab due to + // our inability to fetch keys for an unverified users. + const state = fxa._internal.currentAccountState; + const { device } = await state.getUserAccountData(); + Assert.equal(device.registeredCommandsKeys.length, 0); + + let updatePromise = new Promise(resolve => { + const old_registerOrUpdateDevice = fxa.device._registerOrUpdateDevice.bind( + fxa.device + ); + fxa.device._registerOrUpdateDevice = async function ( + currentState, + signedInUser + ) { + await old_registerOrUpdateDevice(currentState, signedInUser); + fxa.device._registerOrUpdateDevice = old_registerOrUpdateDevice; + resolve(); + }; + }); + + fxa._internal.checkEmailStatus = async function (sessionToken) { + credentials.verified = true; + return credentials; + }; + + await updatePromise; + + const { device: newDevice, encryptedSendTabKeys } = + await state.getUserAccountData(); + Assert.equal(newDevice.registeredCommandsKeys.length, 1); + Assert.notEqual(encryptedSendTabKeys, null); + await fxa.signOut(true); +}); + +add_task(async function test_devicelist_pushendpointexpired() { + const deviceId = "mydeviceid"; + const credentials = getTestUser("baz"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + await updateUserAccountData(fxa, { + uid: credentials.uid, + device: { + id: deviceId, + registeredCommandsKeys: [], + registrationVersion: 1, // < 42 + }, + }); + + const spy = { + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([ + { + id: "mydeviceid", + name: "foo", + type: "desktop", + isCurrentDevice: true, + pushEndpointExpired: true, + pushCallback: "https://example.com", + }, + ]); + }; + let polledForMissedCommands = false; + fxa._internal.commands.pollDeviceCommands = () => { + polledForMissedCommands = true; + }; + + await fxa.device.refreshDeviceList(); + + Assert.equal(spy.getDeviceList.count, 1); + Assert.equal(spy.updateDevice.count, 1); + Assert.ok(polledForMissedCommands); + await fxa.signOut(true); +}); + +add_task(async function test_devicelist_nopushcallback() { + const deviceId = "mydeviceid"; + const credentials = getTestUser("baz"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + await updateUserAccountData(fxa, { + uid: credentials.uid, + device: { + id: deviceId, + registeredCommandsKeys: [], + registrationVersion: 1, + }, + }); + + const spy = { + updateDevice: { count: 0, args: [] }, + getDeviceList: { count: 0, args: [] }, + }; + const client = fxa._internal.fxAccountsClient; + client.updateDevice = function () { + spy.updateDevice.count += 1; + spy.updateDevice.args.push(arguments); + return Promise.resolve({}); + }; + client.getDeviceList = function () { + spy.getDeviceList.count += 1; + spy.getDeviceList.args.push(arguments); + return Promise.resolve([ + { + id: "mydeviceid", + name: "foo", + type: "desktop", + isCurrentDevice: true, + pushEndpointExpired: false, + pushCallback: null, + }, + ]); + }; + + let polledForMissedCommands = false; + fxa._internal.commands.pollDeviceCommands = () => { + polledForMissedCommands = true; + }; + + await fxa.device.refreshDeviceList(); + + Assert.equal(spy.getDeviceList.count, 1); + Assert.equal(spy.updateDevice.count, 1); + Assert.ok(polledForMissedCommands); + await fxa.signOut(true); +}); + +add_task(async function test_refreshDeviceList() { + let credentials = getTestUser("baz"); + + let storage = new MockStorageManager(); + storage.initialize(credentials); + let state = new AccountState(storage); + + let fxAccountsClient = new MockFxAccountsClient({ + id: "deviceAAAAAA", + name: "iPhone", + type: "phone", + pushCallback: "http://mochi.test:8888", + pushEndpointExpired: false, + sessionToken: credentials.sessionToken, + }); + let spy = { + getDeviceList: { count: 0 }, + }; + const deviceListUpdateObserver = { + count: 0, + observe(subject, topic, data) { + this.count++; + }, + }; + Services.obs.addObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED); + + fxAccountsClient.getDeviceList = (function (old) { + return function getDeviceList() { + spy.getDeviceList.count += 1; + return old.apply(this, arguments); + }; + })(fxAccountsClient.getDeviceList); + let fxai = { + _now: Date.now(), + _generation: 0, + fxAccountsClient, + now() { + return this._now; + }, + withVerifiedAccountState(func) { + // Ensure `func` is called asynchronously, and simulate the possibility + // of a different user signng in while the promise is in-flight. + const currentGeneration = this._generation; + return Promise.resolve() + .then(_ => func(state)) + .then(result => { + if (currentGeneration < this._generation) { + throw new Error("Another user has signed in"); + } + return result; + }); + }, + fxaPushService: { + registerPushEndpoint() { + return new Promise(resolve => { + resolve({ + endpoint: "http://mochi.test:8888", + getKey(type) { + return ChromeUtils.base64URLDecode( + type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY, + { padding: "ignore" } + ); + }, + }); + }); + }, + unsubscribe() { + return Promise.resolve(); + }, + getSubscription() { + return Promise.resolve({ + isExpired: () => { + return false; + }, + endpoint: "http://mochi.test:8888", + }); + }, + }, + async _handleTokenError(e) { + _(`Test failure: ${e} - ${e.stack}`); + throw e; + }, + }; + let device = new FxAccountsDevice(fxai); + device._checkRemoteCommandsUpdateNeeded = async () => false; + + Assert.equal( + device.recentDeviceList, + null, + "Should not have device list initially" + ); + Assert.ok(await device.refreshDeviceList(), "Should refresh list"); + Assert.equal( + deviceListUpdateObserver.count, + 1, + `${ON_DEVICELIST_UPDATED} was notified` + ); + Assert.deepEqual( + device.recentDeviceList, + [ + { + id: "deviceAAAAAA", + name: "iPhone", + type: "phone", + pushCallback: "http://mochi.test:8888", + pushEndpointExpired: false, + isCurrentDevice: true, + }, + ], + "Should fetch device list" + ); + Assert.equal( + spy.getDeviceList.count, + 1, + "Should make request to refresh list" + ); + Assert.ok( + !(await device.refreshDeviceList()), + "Should not refresh device list if fresh" + ); + Assert.equal( + deviceListUpdateObserver.count, + 1, + `${ON_DEVICELIST_UPDATED} was not notified` + ); + + fxai._now += device.TIME_BETWEEN_FXA_DEVICES_FETCH_MS; + + let refreshPromise = device.refreshDeviceList(); + let secondRefreshPromise = device.refreshDeviceList(); + Assert.ok( + await Promise.all([refreshPromise, secondRefreshPromise]), + "Should refresh list if stale" + ); + Assert.equal( + spy.getDeviceList.count, + 2, + "Should only make one request if called with pending request" + ); + Assert.equal( + deviceListUpdateObserver.count, + 2, + `${ON_DEVICELIST_UPDATED} only notified once` + ); + + device.observe(null, ON_DEVICE_CONNECTED_NOTIFICATION); + await device.refreshDeviceList(); + Assert.equal( + spy.getDeviceList.count, + 3, + "Should refresh device list after connecting new device" + ); + Assert.equal( + deviceListUpdateObserver.count, + 3, + `${ON_DEVICELIST_UPDATED} notified when new device connects` + ); + device.observe( + null, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + JSON.stringify({ isLocalDevice: false }) + ); + await device.refreshDeviceList(); + Assert.equal( + spy.getDeviceList.count, + 4, + "Should refresh device list after disconnecting device" + ); + Assert.equal( + deviceListUpdateObserver.count, + 4, + `${ON_DEVICELIST_UPDATED} notified when device disconnects` + ); + device.observe( + null, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + JSON.stringify({ isLocalDevice: true }) + ); + await device.refreshDeviceList(); + Assert.equal( + spy.getDeviceList.count, + 4, + "Should not refresh device list after disconnecting this device" + ); + Assert.equal( + deviceListUpdateObserver.count, + 4, + `${ON_DEVICELIST_UPDATED} not notified again` + ); + + let refreshBeforeResetPromise = device.refreshDeviceList({ + ignoreCached: true, + }); + fxai._generation++; + Assert.equal( + deviceListUpdateObserver.count, + 4, + `${ON_DEVICELIST_UPDATED} not notified` + ); + await Assert.rejects(refreshBeforeResetPromise, /Another user has signed in/); + + device.reset(); + Assert.equal( + device.recentDeviceList, + null, + "Should clear device list after resetting" + ); + Assert.ok( + await device.refreshDeviceList(), + "Should fetch new list after resetting" + ); + Assert.equal( + deviceListUpdateObserver.count, + 5, + `${ON_DEVICELIST_UPDATED} notified after reset` + ); + Services.obs.removeObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED); +}); + +add_task(async function test_push_resubscribe() { + let credentials = getTestUser("baz"); + + let storage = new MockStorageManager(); + storage.initialize(credentials); + let state = new AccountState(storage); + + let mockDevice = { + id: "deviceAAAAAA", + name: "iPhone", + type: "phone", + pushCallback: "http://mochi.test:8888", + pushEndpointExpired: false, + sessionToken: credentials.sessionToken, + }; + + var mockSubscription = { + isExpired: () => { + return false; + }, + endpoint: "http://mochi.test:8888", + }; + + let fxAccountsClient = new MockFxAccountsClient(mockDevice); + + const spy = { + _registerOrUpdateDevice: { count: 0 }, + }; + + let fxai = { + _now: Date.now(), + _generation: 0, + fxAccountsClient, + now() { + return this._now; + }, + withVerifiedAccountState(func) { + // Ensure `func` is called asynchronously, and simulate the possibility + // of a different user signng in while the promise is in-flight. + const currentGeneration = this._generation; + return Promise.resolve() + .then(_ => func(state)) + .then(result => { + if (currentGeneration < this._generation) { + throw new Error("Another user has signed in"); + } + return result; + }); + }, + fxaPushService: { + registerPushEndpoint() { + return new Promise(resolve => { + resolve({ + endpoint: "http://mochi.test:8888", + getKey(type) { + return ChromeUtils.base64URLDecode( + type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY, + { padding: "ignore" } + ); + }, + }); + }); + }, + unsubscribe() { + return Promise.resolve(); + }, + getSubscription() { + return Promise.resolve(mockSubscription); + }, + }, + commands: { + async pollDeviceCommands() {}, + }, + async _handleTokenError(e) { + _(`Test failure: ${e} - ${e.stack}`); + throw e; + }, + }; + let device = new FxAccountsDevice(fxai); + device._checkRemoteCommandsUpdateNeeded = async () => false; + device._registerOrUpdateDevice = async () => { + spy._registerOrUpdateDevice.count += 1; + }; + + Assert.ok(await device.refreshDeviceList(), "Should refresh list"); + Assert.equal(spy._registerOrUpdateDevice.count, 0, "not expecting a refresh"); + + mockDevice.pushEndpointExpired = true; + Assert.ok( + await device.refreshDeviceList({ ignoreCached: true }), + "Should refresh list" + ); + Assert.equal( + spy._registerOrUpdateDevice.count, + 1, + "end-point expired means should resubscribe" + ); + + mockDevice.pushEndpointExpired = false; + mockSubscription.isExpired = () => true; + Assert.ok( + await device.refreshDeviceList({ ignoreCached: true }), + "Should refresh list" + ); + Assert.equal( + spy._registerOrUpdateDevice.count, + 2, + "push service saying expired should resubscribe" + ); + + mockSubscription.isExpired = () => false; + mockSubscription.endpoint = "something-else"; + Assert.ok( + await device.refreshDeviceList({ ignoreCached: true }), + "Should refresh list" + ); + Assert.equal( + spy._registerOrUpdateDevice.count, + 3, + "push service endpoint diff should resubscribe" + ); + + mockSubscription = null; + Assert.ok( + await device.refreshDeviceList({ ignoreCached: true }), + "Should refresh list" + ); + Assert.equal( + spy._registerOrUpdateDevice.count, + 4, + "push service saying no sub should resubscribe" + ); + + // reset everything to make sure we didn't leave something behind causing the above to + // not check what we thought it was. + mockSubscription = { + isExpired: () => { + return false; + }, + endpoint: "http://mochi.test:8888", + }; + Assert.ok( + await device.refreshDeviceList({ ignoreCached: true }), + "Should refresh list" + ); + Assert.equal( + spy._registerOrUpdateDevice.count, + 4, + "resetting to good data should not resubscribe" + ); +}); + +add_task(async function test_checking_remote_availableCommands_mismatch() { + const credentials = getTestUser("baz"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + fxa.device._checkRemoteCommandsUpdateNeeded = + FxAccountsDevice.prototype._checkRemoteCommandsUpdateNeeded; + fxa.commands.availableCommands = async () => { + return { + "https://identity.mozilla.com/cmd/open-uri": "local-keys", + }; + }; + + const ourDevice = { + isCurrentDevice: true, + availableCommands: { + "https://identity.mozilla.com/cmd/open-uri": "remote-keys", + }, + }; + Assert.ok( + await fxa.device._checkRemoteCommandsUpdateNeeded( + ourDevice.availableCommands + ) + ); +}); + +add_task(async function test_checking_remote_availableCommands_match() { + const credentials = getTestUser("baz"); + credentials.verified = true; + const fxa = await MockFxAccounts(credentials); + fxa.device._checkRemoteCommandsUpdateNeeded = + FxAccountsDevice.prototype._checkRemoteCommandsUpdateNeeded; + fxa.commands.availableCommands = async () => { + return { + "https://identity.mozilla.com/cmd/open-uri": "local-keys", + }; + }; + + const ourDevice = { + isCurrentDevice: true, + availableCommands: { + "https://identity.mozilla.com/cmd/open-uri": "local-keys", + }, + }; + Assert.ok( + !(await fxa.device._checkRemoteCommandsUpdateNeeded( + ourDevice.availableCommands + )) + ); +}); + +function getTestUser(name) { + return { + email: name + "@example.com", + uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348", + sessionToken: name + "'s session token", + verified: false, + ...MOCK_ACCOUNT_KEYS, + }; +} diff --git a/services/fxaccounts/tests/xpcshell/test_client.js b/services/fxaccounts/tests/xpcshell/test_client.js new file mode 100644 index 0000000000..f3cc48a70e --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_client.js @@ -0,0 +1,966 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); + +const FAKE_SESSION_TOKEN = + "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"; + +// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys +var ACCOUNT_KEYS = { + keyFetch: h( + // eslint-disable-next-line no-useless-concat + "8081828384858687 88898a8b8c8d8e8f" + "9091929394959697 98999a9b9c9d9e9f" + ), + + response: h( + "ee5c58845c7c9412 b11bbd20920c2fdd" + + "d83c33c9cd2c2de2 d66b222613364636" + + "c2c0f8cfbb7c6304 72c0bd88451342c6" + + "c05b14ce342c5ad4 6ad89e84464c993c" + + "3927d30230157d08 17a077eef4b20d97" + + "6f7a97363faf3f06 4c003ada7d01aa70" + ), + + kA: h( + // eslint-disable-next-line no-useless-concat + "2021222324252627 28292a2b2c2d2e2f" + "3031323334353637 38393a3b3c3d3e3f" + ), + + wrapKB: h( + // eslint-disable-next-line no-useless-concat + "4041424344454647 48494a4b4c4d4e4f" + "5051525354555657 58595a5b5c5d5e5f" + ), +}; + +add_task(async function test_authenticated_get_request() { + let message = '{"msg": "Great Success!"}'; + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "GET"; + + let server = httpd_setup({ + "/foo": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + let result = await client._request("/foo", method, credentials); + Assert.equal("Great Success!", result.msg); + + await promiseStopServer(server); +}); + +add_task(async function test_authenticated_post_request() { + let credentials = { + id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x", + key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=", + algorithm: "sha256", + }; + let method = "POST"; + + let server = httpd_setup({ + "/foo": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json"); + response.bodyOutputStream.writeFrom( + request.bodyInputStream, + request.bodyInputStream.available() + ); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + let result = await client._request("/foo", method, credentials, { + foo: "bar", + }); + Assert.equal("bar", result.foo); + + await promiseStopServer(server); +}); + +add_task(async function test_500_error() { + let message = "<h1>Ooops!</h1>"; + let method = "GET"; + + let server = httpd_setup({ + "/foo": function (request, response) { + response.setStatusLine(request.httpVersion, 500, "Internal Server Error"); + response.bodyOutputStream.write(message, message.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + try { + await client._request("/foo", method); + do_throw("Expected to catch an exception"); + } catch (e) { + Assert.equal(500, e.code); + Assert.equal("Internal Server Error", e.message); + } + + await promiseStopServer(server); +}); + +add_task(async function test_backoffError() { + let method = "GET"; + let server = httpd_setup({ + "/retryDelay": function (request, response) { + response.setHeader("Retry-After", "30"); + response.setStatusLine( + request.httpVersion, + 429, + "Client has sent too many requests" + ); + let message = "<h1>Ooops!</h1>"; + response.bodyOutputStream.write(message, message.length); + }, + "/duringDelayIShouldNotBeCalled": function (request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + let jsonMessage = '{"working": "yes"}'; + response.bodyOutputStream.write(jsonMessage, jsonMessage.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + // Retry-After header sets client.backoffError + Assert.equal(client.backoffError, null); + try { + await client._request("/retryDelay", method); + } catch (e) { + Assert.equal(429, e.code); + Assert.equal(30, e.retryAfter); + Assert.notEqual(typeof client.fxaBackoffTimer, "undefined"); + Assert.notEqual(client.backoffError, null); + } + // While delay is in effect, client short-circuits any requests + // and re-rejects with previous error. + try { + await client._request("/duringDelayIShouldNotBeCalled", method); + throw new Error("I should not be reached"); + } catch (e) { + Assert.equal(e.retryAfter, 30); + Assert.equal(e.message, "Client has sent too many requests"); + Assert.notEqual(client.backoffError, null); + } + // Once timer fires, client nulls error out and HTTP calls work again. + client._clearBackoff(); + let result = await client._request("/duringDelayIShouldNotBeCalled", method); + Assert.equal(client.backoffError, null); + Assert.equal(result.working, "yes"); + + await promiseStopServer(server); +}); + +add_task(async function test_signUp() { + let creationMessage_noKey = JSON.stringify({ + uid: "uid", + sessionToken: "sessionToken", + }); + let creationMessage_withKey = JSON.stringify({ + uid: "uid", + sessionToken: "sessionToken", + keyFetchToken: "keyFetchToken", + }); + let errorMessage = JSON.stringify({ + code: 400, + errno: 101, + error: "account exists", + }); + let created = false; + + // Note these strings must be unicode and not already utf-8 encoded. + let unicodeUsername = "andr\xe9@example.org"; // 'andré@example.org' + let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd' + let server = httpd_setup({ + "/account/create": function (request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + body = CommonUtils.decodeUTF8(body); + let jsonBody = JSON.parse(body); + + // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors + + if (created) { + // Error trying to create same account a second time + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + return; + } + + if (jsonBody.email == unicodeUsername) { + Assert.equal("", request._queryString); + Assert.equal( + jsonBody.authPW, + "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375" + ); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + creationMessage_noKey, + creationMessage_noKey.length + ); + return; + } + + if (jsonBody.email == "you@example.org") { + Assert.equal("keys=true", request._queryString); + Assert.equal( + jsonBody.authPW, + "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930" + ); + created = true; + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + creationMessage_withKey, + creationMessage_withKey.length + ); + return; + } + // just throwing here doesn't make any log noise, so have an assertion + // fail instead. + Assert.ok(false, "unexpected email: " + jsonBody.email); + }, + }); + + // Try to create an account without retrieving optional keys. + let client = new FxAccountsClient(server.baseURI); + let result = await client.signUp(unicodeUsername, unicodePassword); + Assert.equal("uid", result.uid); + Assert.equal("sessionToken", result.sessionToken); + Assert.equal(undefined, result.keyFetchToken); + Assert.equal( + result.unwrapBKey, + "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28" + ); + + // Try to create an account retrieving optional keys. + result = await client.signUp("you@example.org", "pässwörd", true); + Assert.equal("uid", result.uid); + Assert.equal("sessionToken", result.sessionToken); + Assert.equal("keyFetchToken", result.keyFetchToken); + Assert.equal( + result.unwrapBKey, + "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c" + ); + + // Try to create an existing account. Triggers error path. + try { + result = await client.signUp(unicodeUsername, unicodePassword); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(101, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_signIn() { + let sessionMessage_noKey = JSON.stringify({ + sessionToken: FAKE_SESSION_TOKEN, + }); + let sessionMessage_withKey = JSON.stringify({ + sessionToken: FAKE_SESSION_TOKEN, + keyFetchToken: "keyFetchToken", + }); + let errorMessage_notExistent = JSON.stringify({ + code: 400, + errno: 102, + error: "doesn't exist", + }); + let errorMessage_wrongCap = JSON.stringify({ + code: 400, + errno: 120, + error: "Incorrect email case", + email: "you@example.com", + }); + + // Note this strings must be unicode and not already utf-8 encoded. + let unicodeUsername = "m\xe9@example.com"; // 'mé@example.com' + let server = httpd_setup({ + "/account/login": function (request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + body = CommonUtils.decodeUTF8(body); + let jsonBody = JSON.parse(body); + + if (jsonBody.email == unicodeUsername) { + Assert.equal("", request._queryString); + Assert.equal( + jsonBody.authPW, + "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6" + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + sessionMessage_noKey, + sessionMessage_noKey.length + ); + } else if (jsonBody.email == "you@example.com") { + Assert.equal("keys=true", request._queryString); + Assert.equal( + jsonBody.authPW, + "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7" + ); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + sessionMessage_withKey, + sessionMessage_withKey.length + ); + } else if (jsonBody.email == "You@example.com") { + // Error trying to sign in with a wrong capitalization + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write( + errorMessage_wrongCap, + errorMessage_wrongCap.length + ); + } else { + // Error trying to sign in to nonexistent account + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write( + errorMessage_notExistent, + errorMessage_notExistent.length + ); + } + }, + }); + + // Login without retrieving optional keys + let client = new FxAccountsClient(server.baseURI); + let result = await client.signIn(unicodeUsername, "bigsecret"); + Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); + Assert.equal( + result.unwrapBKey, + "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8" + ); + Assert.equal(undefined, result.keyFetchToken); + + // Login with retrieving optional keys + result = await client.signIn("you@example.com", "bigsecret", true); + Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); + Assert.equal( + result.unwrapBKey, + "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624" + ); + Assert.equal("keyFetchToken", result.keyFetchToken); + + // Retry due to wrong email capitalization + result = await client.signIn("You@example.com", "bigsecret", true); + Assert.equal(FAKE_SESSION_TOKEN, result.sessionToken); + Assert.equal( + result.unwrapBKey, + "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624" + ); + Assert.equal("keyFetchToken", result.keyFetchToken); + + // Trigger error path + try { + result = await client.signIn("yøü@bad.example.org", "nofear"); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(102, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_signOut() { + let signoutMessage = JSON.stringify({}); + let errorMessage = JSON.stringify({ + code: 400, + errno: 102, + error: "doesn't exist", + }); + let signedOut = false; + + let server = httpd_setup({ + "/session/destroy": function (request, response) { + if (!signedOut) { + signedOut = true; + Assert.ok(request.hasHeader("Authorization")); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(signoutMessage, signoutMessage.length); + return; + } + + // Error trying to sign out of nonexistent account + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let result = await client.signOut("FakeSession"); + Assert.equal(typeof result, "object"); + + // Trigger error path + try { + result = await client.signOut("FakeSession"); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(102, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_recoveryEmailStatus() { + let emailStatus = JSON.stringify({ verified: true }); + let errorMessage = JSON.stringify({ + code: 400, + errno: 102, + error: "doesn't exist", + }); + let tries = 0; + + let server = httpd_setup({ + "/recovery_email/status": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + Assert.equal("", request._queryString); + + if (tries === 0) { + tries += 1; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(emailStatus, emailStatus.length); + return; + } + + // Second call gets an error trying to query a nonexistent account + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let result = await client.recoveryEmailStatus(FAKE_SESSION_TOKEN); + Assert.equal(result.verified, true); + + // Trigger error path + try { + result = await client.recoveryEmailStatus("some bogus session"); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(102, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_recoveryEmailStatusWithReason() { + let emailStatus = JSON.stringify({ verified: true }); + let server = httpd_setup({ + "/recovery_email/status": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + // if there is a query string then it will have a reason + Assert.equal("reason=push", request._queryString); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(emailStatus, emailStatus.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let result = await client.recoveryEmailStatus(FAKE_SESSION_TOKEN, { + reason: "push", + }); + Assert.equal(result.verified, true); + await promiseStopServer(server); +}); + +add_task(async function test_resendVerificationEmail() { + let emptyMessage = "{}"; + let errorMessage = JSON.stringify({ + code: 400, + errno: 102, + error: "doesn't exist", + }); + let tries = 0; + + let server = httpd_setup({ + "/recovery_email/resend_code": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + if (tries === 0) { + tries += 1; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); + return; + } + + // Second call gets an error trying to query a nonexistent account + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let result = await client.resendVerificationEmail(FAKE_SESSION_TOKEN); + Assert.equal(JSON.stringify(result), emptyMessage); + + // Trigger error path + try { + result = await client.resendVerificationEmail("some bogus session"); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(102, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_accountKeys() { + // Four calls to accountKeys(). The first one should work correctly, and we + // should get a valid bundle back, in exchange for our keyFetch token, from + // which we correctly derive kA and wrapKB. The subsequent three calls + // should all trigger separate error paths. + let responseMessage = JSON.stringify({ bundle: ACCOUNT_KEYS.response }); + let errorMessage = JSON.stringify({ + code: 400, + errno: 102, + error: "doesn't exist", + }); + let emptyMessage = "{}"; + let attempt = 0; + + let server = httpd_setup({ + "/account/keys": function (request, response) { + Assert.ok(request.hasHeader("Authorization")); + attempt += 1; + + switch (attempt) { + case 1: + // First time succeeds + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + responseMessage, + responseMessage.length + ); + break; + + case 2: + // Second time, return no bundle to trigger client error + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); + break; + + case 3: + // Return gibberish to trigger client MAC error + // Tweak a byte + let garbageResponse = JSON.stringify({ + bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1", + }); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write( + garbageResponse, + garbageResponse.length + ); + break; + + case 4: + // Trigger error for nonexistent account + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(errorMessage, errorMessage.length); + break; + } + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + // First try, all should be good + let result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); + Assert.equal(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA); + Assert.equal(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB); + + // Second try, empty bundle should trigger error + try { + result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(expectedError.message, "failed to retrieve keys"); + } + + // Third try, bad bundle results in MAC error + try { + result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(expectedError.message, "error unbundling encryption keys"); + } + + // Fourth try, pretend account doesn't exist + try { + result = await client.accountKeys(ACCOUNT_KEYS.keyFetch); + do_throw("Expected to catch an exception"); + } catch (expectedError) { + Assert.equal(102, expectedError.errno); + } + + await promiseStopServer(server); +}); + +add_task(async function test_accessTokenWithSessionToken() { + let server = httpd_setup({ + "/oauth/token": function (request, response) { + const responseMessage = JSON.stringify({ + access_token: + "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69", + token_type: "bearer", + scope: "https://identity.mozilla.com/apps/oldsync", + expires_in: 21600, + auth_at: 1589579900, + }); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(responseMessage, responseMessage.length); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let sessionTokenHex = + "0599c36ebb5cad6feb9285b9547b65342b5434d55c07b33bffd4307ab8f82dc4"; + let clientId = "5882386c6d801776"; + let scope = "https://identity.mozilla.com/apps/oldsync"; + let ttl = 100; + let result = await client.accessTokenWithSessionToken( + sessionTokenHex, + clientId, + scope, + ttl + ); + Assert.ok(result); + + await promiseStopServer(server); +}); + +add_task(async function test_accountExists() { + let existsMessage = JSON.stringify({ + error: "wrong password", + code: 400, + errno: 103, + }); + let doesntExistMessage = JSON.stringify({ + error: "no such account", + code: 400, + errno: 102, + }); + let emptyMessage = "{}"; + + let server = httpd_setup({ + "/account/login": function (request, response) { + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let jsonBody = JSON.parse(body); + + switch (jsonBody.email) { + // We'll test that these users' accounts exist + case "i.exist@example.com": + case "i.also.exist@example.com": + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write(existsMessage, existsMessage.length); + break; + + // This user's account doesn't exist + case "i.dont.exist@example.com": + response.setStatusLine(request.httpVersion, 400, "Bad request"); + response.bodyOutputStream.write( + doesntExistMessage, + doesntExistMessage.length + ); + break; + + // This user throws an unexpected response + // This will reject the client signIn promise + case "i.break.things@example.com": + response.setStatusLine(request.httpVersion, 500, "Alas"); + response.bodyOutputStream.write(emptyMessage, emptyMessage.length); + break; + + default: + throw new Error("Unexpected login from " + jsonBody.email); + } + }, + }); + + let client = new FxAccountsClient(server.baseURI); + let result; + + result = await client.accountExists("i.exist@example.com"); + Assert.ok(result); + + result = await client.accountExists("i.also.exist@example.com"); + Assert.ok(result); + + result = await client.accountExists("i.dont.exist@example.com"); + Assert.ok(!result); + + try { + result = await client.accountExists("i.break.things@example.com"); + do_throw("Expected to catch an exception"); + } catch (unexpectedError) { + Assert.equal(unexpectedError.code, 500); + } + + await promiseStopServer(server); +}); + +add_task(async function test_registerDevice() { + const DEVICE_ID = "device id"; + const DEVICE_NAME = "device name"; + const DEVICE_TYPE = "device type"; + const ERROR_NAME = "test that the client promise rejects"; + + const server = httpd_setup({ + "/account/device": function (request, response) { + const body = JSON.parse( + CommonUtils.readBytesFromInputStream(request.bodyInputStream) + ); + + if ( + body.id || + !body.name || + !body.type || + Object.keys(body).length !== 2 + ) { + response.setStatusLine(request.httpVersion, 400, "Invalid request"); + response.bodyOutputStream.write("{}", 2); + return; + } + + if (body.name === ERROR_NAME) { + response.setStatusLine(request.httpVersion, 500, "Alas"); + response.bodyOutputStream.write("{}", 2); + return; + } + + body.id = DEVICE_ID; + body.createdAt = Date.now(); + + const responseMessage = JSON.stringify(body); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(responseMessage, responseMessage.length); + }, + }); + + const client = new FxAccountsClient(server.baseURI); + const result = await client.registerDevice( + FAKE_SESSION_TOKEN, + DEVICE_NAME, + DEVICE_TYPE + ); + + Assert.ok(result); + Assert.equal(Object.keys(result).length, 4); + Assert.equal(result.id, DEVICE_ID); + Assert.equal(typeof result.createdAt, "number"); + Assert.ok(result.createdAt > 0); + Assert.equal(result.name, DEVICE_NAME); + Assert.equal(result.type, DEVICE_TYPE); + + try { + await client.registerDevice(FAKE_SESSION_TOKEN, ERROR_NAME, DEVICE_TYPE); + do_throw("Expected to catch an exception"); + } catch (unexpectedError) { + Assert.equal(unexpectedError.code, 500); + } + + await promiseStopServer(server); +}); + +add_task(async function test_updateDevice() { + const DEVICE_ID = "some other id"; + const DEVICE_NAME = "some other name"; + const ERROR_ID = "test that the client promise rejects"; + + const server = httpd_setup({ + "/account/device": function (request, response) { + const body = JSON.parse( + CommonUtils.readBytesFromInputStream(request.bodyInputStream) + ); + + if ( + !body.id || + !body.name || + body.type || + Object.keys(body).length !== 2 + ) { + response.setStatusLine(request.httpVersion, 400, "Invalid request"); + response.bodyOutputStream.write("{}", 2); + return; + } + + if (body.id === ERROR_ID) { + response.setStatusLine(request.httpVersion, 500, "Alas"); + response.bodyOutputStream.write("{}", 2); + return; + } + + const responseMessage = JSON.stringify(body); + + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(responseMessage, responseMessage.length); + }, + }); + + const client = new FxAccountsClient(server.baseURI); + const result = await client.updateDevice( + FAKE_SESSION_TOKEN, + DEVICE_ID, + DEVICE_NAME + ); + + Assert.ok(result); + Assert.equal(Object.keys(result).length, 2); + Assert.equal(result.id, DEVICE_ID); + Assert.equal(result.name, DEVICE_NAME); + + try { + await client.updateDevice(FAKE_SESSION_TOKEN, ERROR_ID, DEVICE_NAME); + do_throw("Expected to catch an exception"); + } catch (unexpectedError) { + Assert.equal(unexpectedError.code, 500); + } + + await promiseStopServer(server); +}); + +add_task(async function test_getDeviceList() { + let canReturnDevices; + + const server = httpd_setup({ + "/account/devices": function (request, response) { + if (canReturnDevices) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write("[]", 2); + } else { + response.setStatusLine(request.httpVersion, 500, "Alas"); + response.bodyOutputStream.write("{}", 2); + } + }, + }); + + const client = new FxAccountsClient(server.baseURI); + + canReturnDevices = true; + const result = await client.getDeviceList(FAKE_SESSION_TOKEN); + Assert.ok(Array.isArray(result)); + Assert.equal(result.length, 0); + + try { + canReturnDevices = false; + await client.getDeviceList(FAKE_SESSION_TOKEN); + do_throw("Expected to catch an exception"); + } catch (unexpectedError) { + Assert.equal(unexpectedError.code, 500); + } + + await promiseStopServer(server); +}); + +add_task(async function test_client_metrics() { + function writeResp(response, msg) { + if (typeof msg === "object") { + msg = JSON.stringify(msg); + } + response.bodyOutputStream.write(msg, msg.length); + } + + let server = httpd_setup({ + "/session/destroy": function (request, response) { + response.setHeader("Content-Type", "application/json; charset=utf-8"); + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + writeResp(response, { + error: "invalid authentication timestamp", + code: 401, + errno: 111, + }); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + await Assert.rejects( + client.signOut(FAKE_SESSION_TOKEN, { + service: "sync", + }), + function (err) { + return err.errno == 111; + } + ); + + await promiseStopServer(server); +}); + +add_task(async function test_email_case() { + let canonicalEmail = "greta.garbo@gmail.com"; + let clientEmail = "Greta.Garbo@gmail.COM"; + let attempts = 0; + + function writeResp(response, msg) { + if (typeof msg === "object") { + msg = JSON.stringify(msg); + } + response.bodyOutputStream.write(msg, msg.length); + } + + let server = httpd_setup({ + "/account/login": function (request, response) { + response.setHeader("Content-Type", "application/json; charset=utf-8"); + attempts += 1; + if (attempts > 2) { + response.setStatusLine( + request.httpVersion, + 429, + "Sorry, you had your chance" + ); + return writeResp(response, ""); + } + + let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream); + let jsonBody = JSON.parse(body); + let email = jsonBody.email; + + // If the client has the wrong case on the email, we return a 400, with + // the capitalization of the email as saved in the accounts database. + if (email == canonicalEmail) { + response.setStatusLine(request.httpVersion, 200, "Yay"); + return writeResp(response, { areWeHappy: "yes" }); + } + + response.setStatusLine(request.httpVersion, 400, "Incorrect email case"); + return writeResp(response, { + code: 400, + errno: 120, + error: "Incorrect email case", + email: canonicalEmail, + }); + }, + }); + + let client = new FxAccountsClient(server.baseURI); + + let result = await client.signIn(clientEmail, "123456"); + Assert.equal(result.areWeHappy, "yes"); + Assert.equal(attempts, 2); + + await promiseStopServer(server); +}); + +// turn formatted test vectors into normal hex strings +function h(hexStr) { + return hexStr.replace(/\s+/g, ""); +} diff --git a/services/fxaccounts/tests/xpcshell/test_commands.js b/services/fxaccounts/tests/xpcshell/test_commands.js new file mode 100644 index 0000000000..3fa42da439 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_commands.js @@ -0,0 +1,708 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccountsCommands, SendTab } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommands.sys.mjs" +); + +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); + +const { COMMAND_SENDTAB, COMMAND_SENDTAB_TAIL } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +class TelemetryMock { + constructor() { + this._events = []; + this._uuid_counter = 0; + } + + recordEvent(object, method, value, extra = undefined) { + this._events.push({ object, method, value, extra }); + } + + generateFlowID() { + this._uuid_counter += 1; + return this._uuid_counter.toString(); + } + + sanitizeDeviceId(id) { + return id + "-san"; + } +} + +function FxaInternalMock() { + return { + telemetry: new TelemetryMock(), + }; +} + +function MockFxAccountsClient() { + FxAccountsClient.apply(this); +} + +MockFxAccountsClient.prototype = {}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); + +add_task(async function test_sendtab_isDeviceCompatible() { + const sendTab = new SendTab(null, null); + let device = { name: "My device" }; + Assert.ok(!sendTab.isDeviceCompatible(device)); + device = { name: "My device", availableCommands: {} }; + Assert.ok(!sendTab.isDeviceCompatible(device)); + device = { + name: "My device", + availableCommands: { + "https://identity.mozilla.com/cmd/open-uri": "payload", + }, + }; + Assert.ok(sendTab.isDeviceCompatible(device)); +}); + +add_task(async function test_sendtab_send() { + const commands = { + invoke: sinon.spy((cmd, device, payload) => { + if (device.name == "Device 1") { + throw new Error("Invoke error!"); + } + Assert.equal(payload.encrypted, "encryptedpayload"); + }), + }; + const fxai = FxaInternalMock(); + const sendTab = new SendTab(commands, fxai); + sendTab._encrypt = (bytes, device) => { + if (device.name == "Device 2") { + throw new Error("Encrypt error!"); + } + return "encryptedpayload"; + }; + const to = [ + { name: "Device 1" }, + { name: "Device 2" }, + { id: "dev3", name: "Device 3" }, + ]; + // although we are sending to 3 devices, only 1 is successful - so there's + // only 1 streamID we care about. However, we've created IDs even for the + // failing items - so it's "4" + const expectedTelemetryStreamID = "4"; + const tab = { title: "Foo", url: "https://foo.bar/" }; + const report = await sendTab.send(to, tab); + Assert.equal(report.succeeded.length, 1); + Assert.equal(report.failed.length, 2); + Assert.equal(report.succeeded[0].name, "Device 3"); + Assert.equal(report.failed[0].device.name, "Device 1"); + Assert.equal(report.failed[0].error.message, "Invoke error!"); + Assert.equal(report.failed[1].device.name, "Device 2"); + Assert.equal(report.failed[1].error.message, "Encrypt error!"); + Assert.ok(commands.invoke.calledTwice); + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_SENDTAB_TAIL, + value: "dev3-san", + extra: { flowID: "1", streamID: expectedTelemetryStreamID }, + }, + ]); +}); + +add_task(async function test_sendtab_send_rate_limit() { + const rateLimitReject = { + code: 429, + retryAfter: 5, + retryAfterLocalized: "retry after 5 seconds", + }; + const fxAccounts = { + fxAccountsClient: new MockFxAccountsClient(), + getUserAccountData() { + return {}; + }, + telemetry: new TelemetryMock(), + }; + let rejected = false; + let invoked = 0; + fxAccounts.fxAccountsClient.invokeCommand = async function invokeCommand() { + invoked++; + Assert.ok(invoked <= 2, "only called twice and not more"); + if (rejected) { + return {}; + } + rejected = true; + return Promise.reject(rateLimitReject); + }; + const commands = new FxAccountsCommands(fxAccounts); + const sendTab = new SendTab(commands, fxAccounts); + sendTab._encrypt = () => "encryptedpayload"; + + const tab = { title: "Foo", url: "https://foo.bar/" }; + let report = await sendTab.send([{ name: "Device 1" }], tab); + Assert.equal(report.succeeded.length, 0); + Assert.equal(report.failed.length, 1); + Assert.equal(report.failed[0].error, rateLimitReject); + + report = await sendTab.send([{ name: "Device 1" }], tab); + Assert.equal(report.succeeded.length, 0); + Assert.equal(report.failed.length, 1); + Assert.ok( + report.failed[0].error.message.includes( + "Invoke for " + + "https://identity.mozilla.com/cmd/open-uri is rate-limited" + ) + ); + + commands._invokeRateLimitExpiry = Date.now() - 1000; + report = await sendTab.send([{ name: "Device 1" }], tab); + Assert.equal(report.succeeded.length, 1); + Assert.equal(report.failed.length, 0); +}); + +add_task(async function test_sendtab_receive() { + // We are testing 'receive' here, but might as well go through 'send' + // to package the data and for additional testing... + const commands = { + _invokes: [], + invoke(cmd, device, payload) { + this._invokes.push({ cmd, device, payload }); + }, + }; + + const fxai = FxaInternalMock(); + const sendTab = new SendTab(commands, fxai); + sendTab._encrypt = (bytes, device) => { + return bytes; + }; + sendTab._decrypt = bytes => { + return bytes; + }; + const tab = { title: "tab title", url: "http://example.com" }; + const to = [{ id: "devid", name: "The Device" }]; + const reason = "push"; + + await sendTab.send(to, tab); + Assert.equal(commands._invokes.length, 1); + + for (let { cmd, device, payload } of commands._invokes) { + Assert.equal(cmd, COMMAND_SENDTAB); + // Older Firefoxes would send a plaintext flowID in the top-level payload. + // Test that we sensibly ignore it. + Assert.ok(!payload.hasOwnProperty("flowID")); + // change it - ensure we still get what we expect in telemetry later. + payload.flowID = "ignore-me"; + Assert.deepEqual(await sendTab.handle(device.id, payload, reason), { + title: "tab title", + uri: "http://example.com", + }); + } + + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-sent", + method: COMMAND_SENDTAB_TAIL, + value: "devid-san", + extra: { flowID: "1", streamID: "2" }, + }, + { + object: "command-received", + method: COMMAND_SENDTAB_TAIL, + value: "devid-san", + extra: { flowID: "1", streamID: "2", reason }, + }, + ]); +}); + +// Test that a client which only sends the flowID in the envelope and not in the +// encrypted body gets recorded without the flowID. +add_task(async function test_sendtab_receive_old_client() { + const fxai = FxaInternalMock(); + const sendTab = new SendTab(null, fxai); + sendTab._decrypt = bytes => { + return bytes; + }; + const data = { entries: [{ title: "title", url: "url" }] }; + // No 'flowID' in the encrypted payload, no 'streamID' anywhere. + const payload = { + flowID: "flow-id", + encrypted: new TextEncoder().encode(JSON.stringify(data)), + }; + const reason = "push"; + await sendTab.handle("sender-id", payload, reason); + Assert.deepEqual(fxai.telemetry._events, [ + { + object: "command-received", + method: COMMAND_SENDTAB_TAIL, + value: "sender-id-san", + // deepEqual doesn't ignore undefined, but our telemetry code and + // JSON.stringify() do... + extra: { flowID: undefined, streamID: undefined, reason }, + }, + ]); +}); + +add_task(function test_commands_getReason() { + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb({}); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const testCases = [ + { + receivedIndex: 0, + currentIndex: 0, + expectedReason: "poll", + message: "should return reason 'poll'", + }, + { + receivedIndex: 7, + currentIndex: 3, + expectedReason: "push-missed", + message: "should return reason 'push-missed'", + }, + { + receivedIndex: 2, + currentIndex: 8, + expectedReason: "push", + message: "should return reason 'push'", + }, + ]; + for (const tc of testCases) { + const reason = commands._getReason(tc.receivedIndex, tc.currentIndex); + Assert.equal(reason, tc.expectedReason, tc.message); + } +}); + +add_task(async function test_commands_pollDeviceCommands_push() { + // Server state. + const remoteMessages = [ + { + index: 11, + data: {}, + }, + { + index: 12, + data: {}, + }, + ]; + const remoteIndex = 12; + + // Local state. + const pushIndexReceived = 11; + const accountState = { + data: { + device: { + lastCommandIndex: 10, + }, + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const mockCommands = sinon.mock(commands); + mockCommands.expects("_fetchDeviceCommands").once().withArgs(11).returns({ + index: remoteIndex, + messages: remoteMessages, + }); + mockCommands + .expects("_handleCommands") + .once() + .withArgs(remoteMessages, pushIndexReceived); + await commands.pollDeviceCommands(pushIndexReceived); + + mockCommands.verify(); + Assert.equal(accountState.data.device.lastCommandIndex, 12); +}); + +add_task( + async function test_commands_pollDeviceCommands_push_already_fetched() { + // Local state. + const pushIndexReceived = 12; + const accountState = { + data: { + device: { + lastCommandIndex: 12, + }, + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const mockCommands = sinon.mock(commands); + mockCommands.expects("_fetchDeviceCommands").never(); + mockCommands.expects("_handleCommands").never(); + await commands.pollDeviceCommands(pushIndexReceived); + + mockCommands.verify(); + Assert.equal(accountState.data.device.lastCommandIndex, 12); + } +); + +add_task(async function test_commands_handleCommands() { + // This test ensures that `_getReason` is being called by + // `_handleCommands` with the expected parameters. + const pushIndexReceived = 12; + const senderID = "6d09f6c4-89b2-41b3-a0ac-e4c2502b5485"; + const remoteMessageIndex = 8; + const remoteMessages = [ + { + index: remoteMessageIndex, + data: { + command: COMMAND_SENDTAB, + payload: { + encrypted: {}, + }, + sender: senderID, + }, + }, + ]; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb({}); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + commands.sendTab.handle = (sender, data, reason) => { + return { + title: "testTitle", + uri: "https://testURI", + }; + }; + commands._fxai.device = { + refreshDeviceList: () => {}, + recentDeviceList: [ + { + id: senderID, + }, + ], + }; + const mockCommands = sinon.mock(commands); + mockCommands + .expects("_getReason") + .once() + .withExactArgs(pushIndexReceived, remoteMessageIndex); + mockCommands.expects("_notifyFxATabsReceived").once(); + await commands._handleCommands(remoteMessages, pushIndexReceived); + mockCommands.verify(); +}); + +add_task(async function test_commands_handleCommands_invalid_tab() { + // This test ensures that `_getReason` is being called by + // `_handleCommands` with the expected parameters. + const pushIndexReceived = 12; + const senderID = "6d09f6c4-89b2-41b3-a0ac-e4c2502b5485"; + const remoteMessageIndex = 8; + const remoteMessages = [ + { + index: remoteMessageIndex, + data: { + command: COMMAND_SENDTAB, + payload: { + encrypted: {}, + }, + sender: senderID, + }, + }, + ]; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb({}); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + commands.sendTab.handle = (sender, data, reason) => { + return { + title: "badUriTab", + uri: "file://path/to/pdf", + }; + }; + commands._fxai.device = { + refreshDeviceList: () => {}, + recentDeviceList: [ + { + id: senderID, + }, + ], + }; + const mockCommands = sinon.mock(commands); + mockCommands + .expects("_getReason") + .once() + .withExactArgs(pushIndexReceived, remoteMessageIndex); + // We shouldn't have tried to open a tab with an invalid uri + mockCommands.expects("_notifyFxATabsReceived").never(); + + await commands._handleCommands(remoteMessages, pushIndexReceived); + mockCommands.verify(); +}); + +add_task( + async function test_commands_pollDeviceCommands_push_local_state_empty() { + // Server state. + const remoteMessages = [ + { + index: 11, + data: {}, + }, + { + index: 12, + data: {}, + }, + ]; + const remoteIndex = 12; + + // Local state. + const pushIndexReceived = 11; + const accountState = { + data: { + device: {}, + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const mockCommands = sinon.mock(commands); + mockCommands.expects("_fetchDeviceCommands").once().withArgs(11).returns({ + index: remoteIndex, + messages: remoteMessages, + }); + mockCommands + .expects("_handleCommands") + .once() + .withArgs(remoteMessages, pushIndexReceived); + await commands.pollDeviceCommands(pushIndexReceived); + + mockCommands.verify(); + Assert.equal(accountState.data.device.lastCommandIndex, 12); + } +); + +add_task(async function test_commands_pollDeviceCommands_scheduled_local() { + // Server state. + const remoteMessages = [ + { + index: 11, + data: {}, + }, + { + index: 12, + data: {}, + }, + ]; + const remoteIndex = 12; + const pushIndexReceived = 0; + // Local state. + const accountState = { + data: { + device: { + lastCommandIndex: 10, + }, + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const mockCommands = sinon.mock(commands); + mockCommands.expects("_fetchDeviceCommands").once().withArgs(11).returns({ + index: remoteIndex, + messages: remoteMessages, + }); + mockCommands + .expects("_handleCommands") + .once() + .withArgs(remoteMessages, pushIndexReceived); + await commands.pollDeviceCommands(); + + mockCommands.verify(); + Assert.equal(accountState.data.device.lastCommandIndex, 12); +}); + +add_task( + async function test_commands_pollDeviceCommands_scheduled_local_state_empty() { + // Server state. + const remoteMessages = [ + { + index: 11, + data: {}, + }, + { + index: 12, + data: {}, + }, + ]; + const remoteIndex = 12; + const pushIndexReceived = 0; + // Local state. + const accountState = { + data: { + device: {}, + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + }; + const commands = new FxAccountsCommands(fxAccounts); + const mockCommands = sinon.mock(commands); + mockCommands.expects("_fetchDeviceCommands").once().withArgs(0).returns({ + index: remoteIndex, + messages: remoteMessages, + }); + mockCommands + .expects("_handleCommands") + .once() + .withArgs(remoteMessages, pushIndexReceived); + await commands.pollDeviceCommands(); + + mockCommands.verify(); + Assert.equal(accountState.data.device.lastCommandIndex, 12); + } +); + +add_task(async function test_send_tab_keys_regenerated_if_lost() { + const commands = { + _invokes: [], + invoke(cmd, device, payload) { + this._invokes.push({ cmd, device, payload }); + }, + }; + + // Local state. + const accountState = { + data: { + // Since the device object has no + // sendTabKeys, it will recover + // when we attempt to get the + // encryptedSendTabKeys + device: { + lastCommandIndex: 10, + }, + encryptedSendTabKeys: "keys", + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + async getUserAccountData(data) { + return accountState.getUserAccountData(data); + }, + telemetry: new TelemetryMock(), + }; + const sendTab = new SendTab(commands, fxAccounts); + let generateEncryptedKeysCalled = false; + sendTab._generateAndPersistEncryptedSendTabKey = async () => { + generateEncryptedKeysCalled = true; + }; + await sendTab.getEncryptedSendTabKeys(); + Assert.ok(generateEncryptedKeysCalled); +}); + +add_task(async function test_send_tab_keys_are_not_regenerated_if_not_lost() { + const commands = { + _invokes: [], + invoke(cmd, device, payload) { + this._invokes.push({ cmd, device, payload }); + }, + }; + + // Local state. + const accountState = { + data: { + // Since the device object has + // sendTabKeys, it will not try + // to regenerate them + // when we attempt to get the + // encryptedSendTabKeys + device: { + lastCommandIndex: 10, + sendTabKeys: "keys", + }, + encryptedSendTabKeys: "encrypted-keys", + }, + getUserAccountData() { + return this.data; + }, + updateUserAccountData(data) { + this.data = data; + }, + }; + + const fxAccounts = { + async withCurrentAccountState(cb) { + await cb(accountState); + }, + async getUserAccountData(data) { + return accountState.getUserAccountData(data); + }, + telemetry: new TelemetryMock(), + }; + const sendTab = new SendTab(commands, fxAccounts); + let generateEncryptedKeysCalled = false; + sendTab._generateAndPersistEncryptedSendTabKey = async () => { + generateEncryptedKeysCalled = true; + }; + await sendTab.getEncryptedSendTabKeys(); + Assert.ok(!generateEncryptedKeysCalled); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_credentials.js b/services/fxaccounts/tests/xpcshell/test_credentials.js new file mode 100644 index 0000000000..c3656f219d --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_credentials.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Credentials } = ChromeUtils.importESModule( + "resource://gre/modules/Credentials.sys.mjs" +); +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); + +var { hexToBytes: h2b, hexAsString: h2s, bytesAsHex: b2h } = CommonUtils; + +// Test vectors for the "onepw" protocol: +// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors +var vectors = { + "client stretch-KDF": { + email: h("616e6472c3a94065 78616d706c652e6f 7267"), + password: h("70c3a4737377c3b6 7264"), + quickStretchedPW: h( + "e4e8889bd8bd61ad 6de6b95c059d56e7 b50dacdaf62bd846 44af7e2add84345d" + ), + authPW: h( + "247b675ffb4c4631 0bc87e26d712153a be5e1c90ef00a478 4594f97ef54f2375" + ), + authSalt: h( + "00f0000000000000 0000000000000000 0000000000000000 0000000000000000" + ), + }, +}; + +// A simple test suite with no utf8 encoding madness. +add_task(async function test_onepw_setup_credentials() { + let email = "francine@example.org"; + let password = CommonUtils.encodeUTF8("i like pie"); + + let pbkdf2 = CryptoUtils.pbkdf2Generate; + let hkdf = CryptoUtils.hkdfLegacy; + + // quickStretch the email + let saltyEmail = Credentials.keyWordExtended("quickStretch", email); + + Assert.equal( + b2h(saltyEmail), + "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267" + ); + + let pbkdf2Rounds = 1000; + let pbkdf2Len = 32; + + let quickStretchedPW = await pbkdf2( + password, + saltyEmail, + pbkdf2Rounds, + pbkdf2Len + ); + let quickStretchedActual = + "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4"; + Assert.equal(b2h(quickStretchedPW), quickStretchedActual); + + // obtain hkdf info + let authKeyInfo = Credentials.keyWord("authPW"); + Assert.equal( + b2h(authKeyInfo), + "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057" + ); + + // derive auth password + let hkdfSalt = h2b("00"); + let hkdfLen = 32; + let authPW = await hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen); + + Assert.equal( + b2h(authPW), + "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342" + ); + + // derive unwrap key + let unwrapKeyInfo = Credentials.keyWord("unwrapBkey"); + let unwrapKey = await hkdf( + quickStretchedPW, + hkdfSalt, + unwrapKeyInfo, + hkdfLen + ); + + Assert.equal( + b2h(unwrapKey), + "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9" + ); +}); + +add_task(async function test_client_stretch_kdf() { + let expected = vectors["client stretch-KDF"]; + + let email = h2s(expected.email); + let password = h2s(expected.password); + + // Intermediate value from sjcl implementation in fxa-js-client + // The key thing is the c3a9 sequence in "andré" + let salt = Credentials.keyWordExtended("quickStretch", email); + Assert.equal( + b2h(salt), + "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267" + ); + + let options = { + stretchedPassLength: 32, + pbkdf2Rounds: 1000, + hkdfSalt: h2b("00"), + hkdfLength: 32, + }; + + let results = await Credentials.setup(email, password, options); + + Assert.equal( + expected.quickStretchedPW, + b2h(results.quickStretchedPW), + "quickStretchedPW is wrong" + ); + + Assert.equal(expected.authPW, b2h(results.authPW), "authPW is wrong"); +}); + +// End of tests +// Utility functions follow + +// turn formatted test vectors into normal hex strings +function h(hexStr) { + return hexStr.replace(/\s+/g, ""); +} diff --git a/services/fxaccounts/tests/xpcshell/test_device.js b/services/fxaccounts/tests/xpcshell/test_device.js new file mode 100644 index 0000000000..037db2b101 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_device.js @@ -0,0 +1,127 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { getFxAccountsSingleton } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const fxAccounts = getFxAccountsSingleton(); + +const { ON_NEW_DEVICE_ID, PREF_ACCOUNT_ROOT } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +function promiseObserved(topic) { + return new Promise(res => { + Services.obs.addObserver(res, topic); + }); +} + +_("Misc tests for FxAccounts.device"); + +fxAccounts.device._checkRemoteCommandsUpdateNeeded = async () => false; + +add_test(function test_default_device_name() { + // Note that head_helpers overrides getDefaultLocalName - this test is + // really just to ensure the actual implementation is sane - we can't + // really check the value it uses is correct. + // We are just hoping to avoid a repeat of bug 1369285. + let def = fxAccounts.device.getDefaultLocalName(); // make sure it doesn't throw. + _("default value is " + def); + ok(!!def.length); + + // This is obviously tied to the implementation, but we want early warning + // if any of these things fail. + // We really want one of these 2 to provide a value. + let hostname = Services.sysinfo.get("device") || Services.dns.myHostName; + _("hostname is " + hostname); + ok(!!hostname.length); + // the hostname should be in the default. + ok(def.includes(hostname)); + // We expect the following to work as a fallback to the above. + let fallback = Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ).oscpu; + _("UA fallback is " + fallback); + ok(!!fallback.length); + // the fallback should not be in the default + ok(!def.includes(fallback)); + + run_next_test(); +}); + +add_test(function test_migration() { + Services.prefs.clearUserPref("identity.fxaccounts.account.device.name"); + Services.prefs.setStringPref("services.sync.client.name", "my client name"); + // calling getLocalName() should move the name to the new pref and reset the old. + equal(fxAccounts.device.getLocalName(), "my client name"); + equal( + Services.prefs.getStringPref("identity.fxaccounts.account.device.name"), + "my client name" + ); + ok(!Services.prefs.prefHasUserValue("services.sync.client.name")); + run_next_test(); +}); + +add_test(function test_migration_set_before_get() { + Services.prefs.setStringPref("services.sync.client.name", "old client name"); + fxAccounts.device.setLocalName("new client name"); + equal(fxAccounts.device.getLocalName(), "new client name"); + run_next_test(); +}); + +add_task(async function test_reset() { + // We don't test the client name specifically here because the client name + // is set as part of signing the user in via the attempt to register the + // device. + const testPref = PREF_ACCOUNT_ROOT + "test-pref"; + Services.prefs.setStringPref(testPref, "whatever"); + let credentials = { + email: "foo@example.com", + uid: "1234@lcip.org", + sessionToken: "dead", + verified: true, + ...MOCK_ACCOUNT_KEYS, + }; + // FxA will try to register its device record in the background after signin. + const registerDevice = sinon + .stub(fxAccounts._internal.fxAccountsClient, "registerDevice") + .callsFake(async () => { + return { id: "foo" }; + }); + await fxAccounts._internal.setSignedInUser(credentials); + // wait for device registration to complete. + await promiseObserved(ON_NEW_DEVICE_ID); + ok(!Services.prefs.prefHasUserValue(testPref)); + // signing the user out should reset the name pref. + const namePref = PREF_ACCOUNT_ROOT + "device.name"; + ok(Services.prefs.prefHasUserValue(namePref)); + await fxAccounts.signOut(/* localOnly = */ true); + ok(!Services.prefs.prefHasUserValue(namePref)); + registerDevice.restore(); +}); + +add_task(async function test_name_sanitization() { + fxAccounts.device.setLocalName("emoji is valid \u2665"); + Assert.equal(fxAccounts.device.getLocalName(), "emoji is valid \u2665"); + + let invalid = "x\uFFFD\n\r\t" + "x".repeat(255); + let sanitized = "x\uFFFD\uFFFD\uFFFD\uFFFD" + "x".repeat(250); // 255 total. + + // If the pref already has the invalid value we still get the valid one back. + Services.prefs.setStringPref( + "identity.fxaccounts.account.device.name", + invalid + ); + Assert.equal(fxAccounts.device.getLocalName(), sanitized); + + // But if we explicitly set it to an invalid name, the sanitized value ends + // up in the pref. + fxAccounts.device.setLocalName(invalid); + Assert.equal(fxAccounts.device.getLocalName(), sanitized); + Assert.equal( + Services.prefs.getStringPref("identity.fxaccounts.account.device.name"), + sanitized + ); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_keys.js b/services/fxaccounts/tests/xpcshell/test_keys.js new file mode 100644 index 0000000000..6e650a1609 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_keys.js @@ -0,0 +1,182 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccountsKeys } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsKeys.sys.mjs" +); + +// Ref https://github.com/mozilla/fxa-crypto-relier/ for the details +// of these test vectors. + +add_task(async function test_derive_scoped_key_test_vector() { + const keys = new FxAccountsKeys(null); + const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; + const kB = "8b2e1303e21eee06a945683b8d495b9bf079ca30baa37eb8392d9ffa4767be45"; + const scopedKeyMetadata = { + identifier: "app_key:https%3A//example.com", + keyRotationTimestamp: 1510726317000, + keyRotationSecret: + "517d478cb4f994aa69930416648a416fdaa1762c5abf401a2acf11a0f185e98d", + }; + + const scopedKey = await keys._deriveScopedKey( + uid, + CommonUtils.hexToBytes(kB), + "app_key", + scopedKeyMetadata + ); + + Assert.deepEqual(scopedKey, { + kty: "oct", + kid: "1510726317-Voc-Eb9IpoTINuo9ll7bjA", + k: "Kkbk1_Q0oCcTmggeDH6880bQrxin2RLu5D00NcJazdQ", + }); +}); + +add_task(async function test_derive_legacy_sync_key_test_vector() { + const keys = new FxAccountsKeys(null); + const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; + const kB = "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9"; + const scopedKeyMetadata = { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationTimestamp: 1510726317123, + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + }; + + const scopedKey = await keys._deriveLegacyScopedKey( + uid, + CommonUtils.hexToBytes(kB), + "https://identity.mozilla.com/apps/oldsync", + scopedKeyMetadata + ); + + Assert.deepEqual(scopedKey, { + kty: "oct", + kid: "1510726317123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + }); +}); + +add_task(async function test_derive_multiple_keys_at_once() { + const keys = new FxAccountsKeys(null); + const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; + const kB = "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9"; + const scopedKeysMetadata = { + app_key: { + identifier: "app_key:https%3A//example.com", + keyRotationTimestamp: 1510726317000, + keyRotationSecret: + "517d478cb4f994aa69930416648a416fdaa1762c5abf401a2acf11a0f185e98d", + }, + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationTimestamp: 1510726318123, + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + }, + }; + + const scopedKeys = await keys._deriveScopedKeys( + uid, + CommonUtils.hexToBytes(kB), + scopedKeysMetadata + ); + + Assert.deepEqual(scopedKeys, { + app_key: { + kty: "oct", + kid: "1510726317-tUkxiR1lTlFrTgkF0tJidA", + k: "TYK6Hmj86PfKiqsk9DZmX61nxk9VsExGrwo94HP-0wU", + }, + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + }, + }); +}); + +add_task(async function test_rejects_bad_scoped_key_data() { + const keys = new FxAccountsKeys(null); + const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; + const kB = "8b2e1303e21eee06a945683b8d495b9bf079ca30baa37eb8392d9ffa4767be45"; + const scopedKeyMetadata = { + identifier: "app_key:https%3A//example.com", + keyRotationTimestamp: 1510726317000, + keyRotationSecret: + "517d478cb4f994aa69930416648a416fdaa1762c5abf401a2acf11a0f185e98d", + }; + + await Assert.rejects( + keys._deriveScopedKey( + uid.slice(0, -1), + CommonUtils.hexToBytes(kB), + "app_key", + scopedKeyMetadata + ), + /uid must be a 32-character hex string/ + ); + await Assert.rejects( + keys._deriveScopedKey( + uid.slice(0, -1) + "Q", + CommonUtils.hexToBytes(kB), + "app_key", + scopedKeyMetadata + ), + /uid must be a 32-character hex string/ + ); + await Assert.rejects( + keys._deriveScopedKey( + uid, + CommonUtils.hexToBytes(kB).slice(0, -1), + "app_key", + scopedKeyMetadata + ), + /kBbytes must be exactly 32 bytes/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + identifier: "foo", + }), + /identifier must be a string of length >= 10/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + identifier: {}, + }), + /identifier must be a string of length >= 10/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + keyRotationTimestamp: "xyz", + }), + /keyRotationTimestamp must be a number/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + keyRotationTimestamp: 12345, + }), + /keyRotationTimestamp must round to a 10-digit number/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + keyRotationSecret: scopedKeyMetadata.keyRotationSecret.slice(0, -1), + }), + /keyRotationSecret must be a 64-character hex string/ + ); + await Assert.rejects( + keys._deriveScopedKey(uid, CommonUtils.hexToBytes(kB), "app_key", { + ...scopedKeyMetadata, + keyRotationSecret: scopedKeyMetadata.keyRotationSecret.slice(0, -1) + "z", + }), + /keyRotationSecret must be a 64-character hex string/ + ); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js new file mode 100644 index 0000000000..5b80035418 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js @@ -0,0 +1,307 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests for FxAccounts, storage and the master password. + +// See verbose logging from FxAccounts.jsm +Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FXA_PWDMGR_HOST, FXA_PWDMGR_REALM } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +// Use a backstage pass to get at our LoginManagerStorage object, so we can +// mock the prototype. +var { LoginManagerStorage } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsStorage.sys.mjs" +); +var isLoggedIn = true; +LoginManagerStorage.prototype.__defineGetter__("_isLoggedIn", () => isLoggedIn); + +function setLoginMgrLoggedInState(loggedIn) { + isLoggedIn = loggedIn; +} + +initTestLogging("Trace"); + +async function getLoginMgrData() { + let logins = await Services.logins.searchLoginsAsync({ + origin: FXA_PWDMGR_HOST, + httpRealm: FXA_PWDMGR_REALM, + }); + if (!logins.length) { + return null; + } + Assert.equal(logins.length, 1, "only 1 login available"); + return logins[0]; +} + +function createFxAccounts() { + return new FxAccounts({ + _fxAccountsClient: { + async registerDevice() { + return { id: "deviceAAAAAA" }; + }, + async recoveryEmailStatus() { + return { verified: true }; + }, + async signOut() {}, + }, + updateDeviceRegistration() {}, + _getDeviceName() { + return "mock device name"; + }, + observerPreloads: [], + fxaPushService: { + async registerPushEndpoint() { + return { + endpoint: "http://mochi.test:8888", + getKey() { + return null; + }, + }; + }, + async unsubscribe() { + return true; + }, + }, + }); +} + +add_task(async function test_simple() { + let fxa = createFxAccounts(); + + let creds = { + uid: "abcd", + email: "test@example.com", + sessionToken: "sessionToken", + scopedKeys: { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + }, + verified: true, + }; + await fxa._internal.setSignedInUser(creds); + + // This should have stored stuff in both the .json file in the profile + // dir, and the login dir. + let path = PathUtils.join(PathUtils.profileDir, "signedInUser.json"); + let data = await IOUtils.readJSON(path); + + Assert.strictEqual( + data.accountData.email, + creds.email, + "correct email in the clear text" + ); + Assert.strictEqual( + data.accountData.sessionToken, + creds.sessionToken, + "correct sessionToken in the clear text" + ); + Assert.strictEqual( + data.accountData.verified, + creds.verified, + "correct verified flag" + ); + + Assert.ok( + !("scopedKeys" in data.accountData), + "scopedKeys not stored in clear text" + ); + + let login = await getLoginMgrData(); + Assert.strictEqual(login.username, creds.uid, "uid used for username"); + let loginData = JSON.parse(login.password); + Assert.strictEqual( + loginData.version, + data.version, + "same version flag in both places" + ); + Assert.deepEqual( + loginData.accountData.scopedKeys, + creds.scopedKeys, + "correct scoped keys in the login mgr" + ); + Assert.ok(!("email" in loginData), "email not stored in the login mgr json"); + Assert.ok( + !("sessionToken" in loginData), + "sessionToken not stored in the login mgr json" + ); + Assert.ok( + !("verified" in loginData), + "verified not stored in the login mgr json" + ); + + await fxa.signOut(/* localOnly = */ true); + Assert.strictEqual( + await getLoginMgrData(), + null, + "login mgr data deleted on logout" + ); +}); + +add_task(async function test_MPLocked() { + let fxa = createFxAccounts(); + + let creds = { + uid: "abcd", + email: "test@example.com", + sessionToken: "sessionToken", + scopedKeys: { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + }, + verified: true, + }; + + Assert.strictEqual( + await getLoginMgrData(), + null, + "no login mgr at the start" + ); + // tell the storage that the MP is locked. + setLoginMgrLoggedInState(false); + await fxa._internal.setSignedInUser(creds); + + // This should have stored stuff in the .json, and the login manager stuff + // will not exist. + let path = PathUtils.join(PathUtils.profileDir, "signedInUser.json"); + let data = await IOUtils.readJSON(path); + + Assert.strictEqual( + data.accountData.email, + creds.email, + "correct email in the clear text" + ); + Assert.strictEqual( + data.accountData.sessionToken, + creds.sessionToken, + "correct sessionToken in the clear text" + ); + Assert.strictEqual( + data.accountData.verified, + creds.verified, + "correct verified flag" + ); + + Assert.ok( + !("scopedKeys" in data.accountData), + "scopedKeys not stored in clear text" + ); + + Assert.strictEqual( + await getLoginMgrData(), + null, + "login mgr data doesn't exist" + ); + await fxa.signOut(/* localOnly = */ true); +}); + +add_task(async function test_consistentWithMPEdgeCases() { + setLoginMgrLoggedInState(true); + + let fxa = createFxAccounts(); + + let creds1 = { + uid: "uid1", + email: "test@example.com", + sessionToken: "sessionToken", + scopedKeys: { + [SCOPE_OLD_SYNC]: { + kid: "key id 1", + k: "key material 1", + kty: "oct", + }, + }, + verified: true, + }; + + let creds2 = { + uid: "uid2", + email: "test2@example.com", + sessionToken: "sessionToken2", + [SCOPE_OLD_SYNC]: { + kid: "key id 2", + k: "key material 2", + kty: "oct", + }, + verified: false, + }; + + // Log a user in while MP is unlocked. + await fxa._internal.setSignedInUser(creds1); + + // tell the storage that the MP is locked - this will prevent logout from + // being able to clear the data. + setLoginMgrLoggedInState(false); + + // now set the second credentials. + await fxa._internal.setSignedInUser(creds2); + + // We should still have creds1 data in the login manager. + let login = await getLoginMgrData(); + Assert.strictEqual(login.username, creds1.uid); + // and that we do have the first scopedKeys in the login manager. + Assert.deepEqual( + JSON.parse(login.password).accountData.scopedKeys, + creds1.scopedKeys, + "stale data still in login mgr" + ); + + // Make a new FxA instance (otherwise the values in memory will be used) + // and we want the login manager to be unlocked. + setLoginMgrLoggedInState(true); + fxa = createFxAccounts(); + + let accountData = await fxa.getSignedInUser(); + Assert.strictEqual(accountData.email, creds2.email); + // we should have no scopedKeys at all. + Assert.strictEqual( + accountData.scopedKeys, + undefined, + "stale scopedKey wasn't used" + ); + await fxa.signOut(/* localOnly = */ true); +}); + +// A test for the fact we will accept either a UID or email when looking in +// the login manager. +add_task(async function test_uidMigration() { + setLoginMgrLoggedInState(true); + Assert.strictEqual( + await getLoginMgrData(), + null, + "expect no logins at the start" + ); + + // create the login entry using email as a key. + let contents = { + scopedKeys: { + ...MOCK_ACCOUNT_KEYS.scopedKeys, + }, + }; + + let loginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" + ); + let login = new loginInfo( + FXA_PWDMGR_HOST, + null, // aFormActionOrigin, + FXA_PWDMGR_REALM, // aHttpRealm, + "foo@bar.com", // aUsername + JSON.stringify(contents), // aPassword + "", // aUsernameField + "" + ); // aPasswordField + await Services.logins.addLoginAsync(login); + + // ensure we read it. + let storage = new LoginManagerStorage(); + let got = await storage.get("uid", "foo@bar.com"); + Assert.deepEqual(got, contents); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_flow.js b/services/fxaccounts/tests/xpcshell/test_oauth_flow.js new file mode 100644 index 0000000000..ef5102ae17 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_oauth_flow.js @@ -0,0 +1,274 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* global crypto */ + +"use strict"; + +const { + FxAccountsOAuth, + ERROR_INVALID_SCOPES, + ERROR_INVALID_STATE, + ERROR_SYNC_SCOPE_NOT_GRANTED, + ERROR_NO_KEYS_JWE, + ERROR_OAUTH_FLOW_ABANDONED, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsOAuth.sys.mjs" +); + +const { SCOPE_PROFILE, FX_OAUTH_CLIENT_ID } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +ChromeUtils.defineESModuleGetters(this, { + jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", +}); + +initTestLogging("Trace"); + +add_task(function test_begin_oauth_flow() { + const oauth = new FxAccountsOAuth(); + add_task(async function test_begin_oauth_flow_invalid_scopes() { + try { + await oauth.beginOAuthFlow("foo,fi,fum", "foo"); + Assert.fail("Should have thrown error, scopes must be an array"); + } catch (e) { + Assert.equal(e.message, ERROR_INVALID_SCOPES); + } + try { + await oauth.beginOAuthFlow(["not-a-real-scope", SCOPE_PROFILE]); + Assert.fail("Should have thrown an error, must use a valid scope"); + } catch (e) { + Assert.equal(e.message, ERROR_INVALID_SCOPES); + } + }); + add_task(async function test_begin_oauth_flow_ok() { + const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; + const queryParams = await oauth.beginOAuthFlow(scopes); + + // First verify default query parameters + Assert.equal(queryParams.client_id, FX_OAUTH_CLIENT_ID); + Assert.equal(queryParams.action, "email"); + Assert.equal(queryParams.response_type, "code"); + Assert.equal(queryParams.access_type, "offline"); + Assert.equal(queryParams.scope, [SCOPE_PROFILE, SCOPE_OLD_SYNC].join(" ")); + + // Then, we verify that the state is a valid Base64 value + const state = queryParams.state; + ChromeUtils.base64URLDecode(state, { padding: "reject" }); + + // Then, we verify that the codeVerifier, can be used to verify the code_challenge + const code_challenge = queryParams.code_challenge; + Assert.equal(queryParams.code_challenge_method, "S256"); + const oauthFlow = oauth.getFlow(state); + const codeVerifierB64 = oauthFlow.verifier; + const expectedChallenge = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(codeVerifierB64) + ); + const expectedChallengeB64 = ChromeUtils.base64URLEncode( + expectedChallenge, + { pad: false } + ); + Assert.equal(expectedChallengeB64, code_challenge); + + // Then, we verify that something encrypted with the `keys_jwk`, can be decrypted using the private key + const keysJwk = queryParams.keys_jwk; + const decodedKeysJwk = JSON.parse( + new TextDecoder().decode( + ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" }) + ) + ); + const plaintext = "text to be encrypted and decrypted!"; + delete decodedKeysJwk.key_ops; + const jwe = await jwcrypto.generateJWE( + decodedKeysJwk, + new TextEncoder().encode(plaintext) + ); + const privateKey = oauthFlow.key; + const decrypted = await jwcrypto.decryptJWE(jwe, privateKey); + Assert.equal(new TextDecoder().decode(decrypted), plaintext); + + // Finally, we verify that we stored the requested scopes + Assert.deepEqual(oauthFlow.requestedScopes, scopes.join(" ")); + }); +}); + +add_task(function test_complete_oauth_flow() { + add_task(async function test_invalid_state() { + const oauth = new FxAccountsOAuth(); + const code = "foo"; + const state = "bar"; + const sessionToken = "01abcef12"; + try { + await oauth.completeOAuthFlow(sessionToken, code, state); + Assert.fail("Should have thrown an error"); + } catch (err) { + Assert.equal(err.message, ERROR_INVALID_STATE); + } + }); + add_task(async function test_sync_scope_not_authorized() { + const fxaClient = { + oauthToken: () => + Promise.resolve({ + access_token: "access_token", + refresh_token: "refresh_token", + // Note that the scope does not include the sync scope + scope: SCOPE_PROFILE, + }), + }; + const oauth = new FxAccountsOAuth(fxaClient); + const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; + const sessionToken = "01abcef12"; + const queryParams = await oauth.beginOAuthFlow(scopes); + try { + await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state); + Assert.fail( + "Should have thrown an error because the sync scope was not authorized" + ); + } catch (err) { + Assert.equal(err.message, ERROR_SYNC_SCOPE_NOT_GRANTED); + } + }); + add_task(async function test_jwe_not_returned() { + const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; + const fxaClient = { + oauthToken: () => + Promise.resolve({ + access_token: "access_token", + refresh_token: "refresh_token", + scope: scopes.join(" "), + }), + }; + const oauth = new FxAccountsOAuth(fxaClient); + const queryParams = await oauth.beginOAuthFlow(scopes); + const sessionToken = "01abcef12"; + try { + await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state); + Assert.fail( + "Should have thrown an error because we didn't get back a keys_nwe" + ); + } catch (err) { + Assert.equal(err.message, ERROR_NO_KEYS_JWE); + } + }); + add_task(async function test_complete_oauth_ok() { + // First, we initialize some fake values we would typically get + // from outside our system + const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; + const oauthCode = "fake oauth code"; + const sessionToken = "01abcef12"; + const plainTextScopedKeys = { + kid: "fake key id", + k: "fake key", + kty: "oct", + }; + const fakeAccessToken = "fake access token"; + const fakeRefreshToken = "fake refresh token"; + // Then, we initialize a fake http client, we'll add our fake oauthToken call + // once we have started the oauth flow (so we have the public keys!) + const fxaClient = {}; + // Then, we initialize our oauth object with the given client and begin a new flow + const oauth = new FxAccountsOAuth(fxaClient); + const queryParams = await oauth.beginOAuthFlow(scopes); + // Now that we have the public keys in `keys_jwk`, we use it to generate a JWE + // representing our scoped keys + const keysJwk = queryParams.keys_jwk; + const decodedKeysJwk = JSON.parse( + new TextDecoder().decode( + ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" }) + ) + ); + delete decodedKeysJwk.key_ops; + const jwe = await jwcrypto.generateJWE( + decodedKeysJwk, + new TextEncoder().encode(JSON.stringify(plainTextScopedKeys)) + ); + // We also grab the stored PKCE verifier that the oauth object stored internally + // to verify that we correctly send it as a part of our HTTP request + const storedVerifier = oauth.getFlow(queryParams.state).verifier; + + // To test what happens when more than one flow is completed simulatniously + // We mimic a slow network call on the first oauthToken call and let the second + // one win + let callCount = 0; + let slowResolve; + const resolveFn = (payload, resolve) => { + if (callCount === 1) { + // This is the second call + // lets resolve it so the second call wins + resolve(payload); + } else { + callCount += 1; + // This is the first call, let store our resolve function for later + // it will be resolved once the fast flow is fully completed + slowResolve = () => resolve(payload); + } + }; + + // Now we initialize our mock of the HTTP request, it verifies we passed in all the correct + // parameters and returns what we'd expect a healthy HTTP Response would look like + fxaClient.oauthToken = (sessionTokenHex, code, verifier, clientId) => { + Assert.equal(sessionTokenHex, sessionToken); + Assert.equal(code, oauthCode); + Assert.equal(verifier, storedVerifier); + Assert.equal(clientId, queryParams.client_id); + const response = { + access_token: fakeAccessToken, + refresh_token: fakeRefreshToken, + scope: scopes.join(" "), + keys_jwe: jwe, + }; + return new Promise(resolve => { + resolveFn(response, resolve); + }); + }; + + // Then, we call the completeOAuthFlow function, and get back our access token, + // refresh token and scopedKeys + + // To test what happens when multiple flows race, we create two flows, + // A slow one that will start first, but finish last + // And a fast one that will beat the slow one + const firstCompleteOAuthFlow = oauth + .completeOAuthFlow(sessionToken, oauthCode, queryParams.state) + .then(res => { + // To mimic the slow network connection on the slowCompleteOAuthFlow + // We resume the slow completeOAuthFlow once this one is complete + slowResolve(); + return res; + }); + const secondCompleteOAuthFlow = oauth + .completeOAuthFlow(sessionToken, oauthCode, queryParams.state) + .then(res => { + // since we can't fully gaurentee which oauth flow finishes first, we also resolve here + slowResolve(); + return res; + }); + + const { accessToken, refreshToken, scopedKeys } = await Promise.allSettled([ + firstCompleteOAuthFlow, + secondCompleteOAuthFlow, + ]).then(results => { + let fast; + let slow; + for (const result of results) { + if (result.status === "fulfilled") { + fast = result.value; + } else { + slow = result.reason; + } + } + // We make sure that we indeed have one slow flow that lost + Assert.equal(slow.message, ERROR_OAUTH_FLOW_ABANDONED); + return fast; + }); + + Assert.equal(accessToken, fakeAccessToken); + Assert.equal(refreshToken, fakeRefreshToken); + Assert.deepEqual(scopedKeys, plainTextScopedKeys); + + // Finally, we verify that all stored flows were cleared + Assert.equal(oauth.numOfFlows(), 0); + }); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js b/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js new file mode 100644 index 0000000000..798c439212 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js @@ -0,0 +1,180 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); + +// We grab some additional stuff via backstage passes. +var { AccountState } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +function promiseNotification(topic) { + return new Promise(resolve => { + let observe = () => { + Services.obs.removeObserver(observe, topic); + resolve(); + }; + Services.obs.addObserver(observe, topic); + }); +} + +// A storage manager that doesn't actually write anywhere. +function MockStorageManager() {} + +MockStorageManager.prototype = { + promiseInitialized: Promise.resolve(), + + initialize(accountData) { + this.accountData = accountData; + }, + + finalize() { + return Promise.resolve(); + }, + + getAccountData() { + return Promise.resolve(this.accountData); + }, + + updateAccountData(updatedFields) { + for (let [name, value] of Object.entries(updatedFields)) { + if (value == null) { + delete this.accountData[name]; + } else { + this.accountData[name] = value; + } + } + return Promise.resolve(); + }, + + deleteAccountData() { + this.accountData = null; + return Promise.resolve(); + }, +}; + +// Just enough mocks so we can avoid hawk etc. +function MockFxAccountsClient() { + this._email = "nobody@example.com"; + this._verified = false; + + this.accountStatus = function (uid) { + return Promise.resolve(!!uid && !this._deletedOnServer); + }; + + this.signOut = function () { + return Promise.resolve(); + }; + this.registerDevice = function () { + return Promise.resolve(); + }; + this.updateDevice = function () { + return Promise.resolve(); + }; + this.signOutAndDestroyDevice = function () { + return Promise.resolve(); + }; + this.getDeviceList = function () { + return Promise.resolve(); + }; + + FxAccountsClient.apply(this); +} + +MockFxAccountsClient.prototype = {}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); + +function MockFxAccounts(device = {}) { + return new FxAccounts({ + fxAccountsClient: new MockFxAccountsClient(), + newAccountState(credentials) { + // we use a real accountState but mocked storage. + let storage = new MockStorageManager(); + storage.initialize(credentials); + return new AccountState(storage); + }, + _getDeviceName() { + return "mock device name"; + }, + fxaPushService: { + registerPushEndpoint() { + return new Promise(resolve => { + resolve({ + endpoint: "http://mochi.test:8888", + }); + }); + }, + }, + }); +} + +async function createMockFxA() { + let fxa = new MockFxAccounts(); + let credentials = { + email: "foo@example.com", + uid: "1234@lcip.org", + sessionToken: "dead", + scopedKeys: { + [SCOPE_OLD_SYNC]: { + kid: "key id for sync key", + k: "key material for sync key", + kty: "oct", + }, + }, + verified: true, + }; + await fxa._internal.setSignedInUser(credentials); + return fxa; +} + +// The tests. + +add_task(async function testCacheStorage() { + let fxa = await createMockFxA(); + + // Hook what the impl calls to save to disk. + let cas = fxa._internal.currentAccountState; + let origPersistCached = cas._persistCachedTokens.bind(cas); + cas._persistCachedTokens = function () { + return origPersistCached().then(() => { + Services.obs.notifyObservers(null, "testhelper-fxa-cache-persist-done"); + }); + }; + + let promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done"); + let tokenData = { token: "token1", somethingelse: "something else" }; + let scopeArray = ["foo", "bar"]; + cas.setCachedToken(scopeArray, tokenData); + deepEqual(cas.getCachedToken(scopeArray), tokenData); + + deepEqual(cas.oauthTokens, { "bar|foo": tokenData }); + // wait for background write to complete. + await promiseWritten; + + // Check the token cache made it to our mocked storage. + deepEqual(cas.storageManager.accountData.oauthTokens, { + "bar|foo": tokenData, + }); + + // Drop the token from the cache and ensure it is removed from the json. + promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done"); + await cas.removeCachedToken("token1"); + deepEqual(cas.oauthTokens, {}); + await promiseWritten; + deepEqual(cas.storageManager.accountData.oauthTokens, {}); + + // sign out and the token storage should end up with null. + let storageManager = cas.storageManager; // .signOut() removes the attribute. + await fxa.signOut(/* localOnly = */ true); + deepEqual(storageManager.accountData, null); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js new file mode 100644 index 0000000000..82f174edd1 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js @@ -0,0 +1,255 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); +var { AccountState } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +function promiseNotification(topic) { + return new Promise(resolve => { + let observe = () => { + Services.obs.removeObserver(observe, topic); + resolve(); + }; + Services.obs.addObserver(observe, topic); + }); +} + +// Just enough mocks so we can avoid hawk and storage. +function MockStorageManager() {} + +MockStorageManager.prototype = { + promiseInitialized: Promise.resolve(), + + initialize(accountData) { + this.accountData = accountData; + }, + + finalize() { + return Promise.resolve(); + }, + + getAccountData() { + return Promise.resolve(this.accountData); + }, + + updateAccountData(updatedFields) { + for (let [name, value] of Object.entries(updatedFields)) { + if (value == null) { + delete this.accountData[name]; + } else { + this.accountData[name] = value; + } + } + return Promise.resolve(); + }, + + deleteAccountData() { + this.accountData = null; + return Promise.resolve(); + }, +}; + +function MockFxAccountsClient(activeTokens) { + this._email = "nobody@example.com"; + this._verified = false; + + this.accountStatus = function (uid) { + return Promise.resolve(!!uid && !this._deletedOnServer); + }; + + this.signOut = function () { + return Promise.resolve(); + }; + this.registerDevice = function () { + return Promise.resolve(); + }; + this.updateDevice = function () { + return Promise.resolve(); + }; + this.signOutAndDestroyDevice = function () { + return Promise.resolve(); + }; + this.getDeviceList = function () { + return Promise.resolve(); + }; + this.accessTokenWithSessionToken = function ( + sessionTokenHex, + clientId, + scope, + ttl + ) { + let token = `token${this.numTokenFetches}`; + if (ttl) { + token += `-ttl-${ttl}`; + } + this.numTokenFetches += 1; + this.activeTokens.add(token); + print("accessTokenWithSessionToken returning token", token); + return Promise.resolve({ access_token: token, ttl }); + }; + this.oauthDestroy = sinon.stub().callsFake((_clientId, token) => { + this.activeTokens.delete(token); + return Promise.resolve(); + }); + + // Test only stuff. + this.activeTokens = activeTokens; + this.numTokenFetches = 0; + + FxAccountsClient.apply(this); +} + +MockFxAccountsClient.prototype = {}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); + +function MockFxAccounts() { + // The FxA "auth" and "oauth" servers both share the same db of tokens, + // so we need to simulate the same here in the tests. + const activeTokens = new Set(); + return new FxAccounts({ + fxAccountsClient: new MockFxAccountsClient(activeTokens), + newAccountState(credentials) { + // we use a real accountState but mocked storage. + let storage = new MockStorageManager(); + storage.initialize(credentials); + return new AccountState(storage); + }, + _getDeviceName() { + return "mock device name"; + }, + fxaPushService: { + registerPushEndpoint() { + return new Promise(resolve => { + resolve({ + endpoint: "http://mochi.test:8888", + }); + }); + }, + }, + }); +} + +async function createMockFxA() { + let fxa = new MockFxAccounts(); + let credentials = { + email: "foo@example.com", + uid: "1234@lcip.org", + sessionToken: "dead", + scopedKeys: { + [SCOPE_OLD_SYNC]: { + kid: "key id for sync key", + k: "key material for sync key", + kty: "oct", + }, + }, + verified: true, + }; + + await fxa._internal.setSignedInUser(credentials); + return fxa; +} + +// The tests. + +add_task(async function testRevoke() { + let tokenOptions = { scope: "test-scope" }; + let fxa = await createMockFxA(); + let client = fxa._internal.fxAccountsClient; + + // get our first token and check we hit the mock. + let token1 = await fxa.getOAuthToken(tokenOptions); + equal(client.numTokenFetches, 1); + equal(client.activeTokens.size, 1); + ok(token1, "got a token"); + equal(token1, "token0"); + + // drop the new token from our cache. + await fxa.removeCachedOAuthToken({ token: token1 }); + ok(client.oauthDestroy.calledOnce); + + // the revoke should have been successful. + equal(client.activeTokens.size, 0); + // fetching it again hits the server. + let token2 = await fxa.getOAuthToken(tokenOptions); + equal(client.numTokenFetches, 2); + equal(client.activeTokens.size, 1); + ok(token2, "got a token"); + notEqual(token1, token2, "got a different token"); +}); + +add_task(async function testSignOutDestroysTokens() { + let fxa = await createMockFxA(); + let client = fxa._internal.fxAccountsClient; + + // get our first token and check we hit the mock. + let token1 = await fxa.getOAuthToken({ scope: "test-scope" }); + equal(client.numTokenFetches, 1); + equal(client.activeTokens.size, 1); + ok(token1, "got a token"); + + // get another + let token2 = await fxa.getOAuthToken({ scope: "test-scope-2" }); + equal(client.numTokenFetches, 2); + equal(client.activeTokens.size, 2); + ok(token2, "got a token"); + notEqual(token1, token2, "got a different token"); + + // FxA fires an observer when the "background" signout is complete. + let signoutComplete = promiseNotification("testhelper-fxa-signout-complete"); + // now sign out - they should be removed. + await fxa.signOut(); + await signoutComplete; + ok(client.oauthDestroy.calledTwice); + // No active tokens left. + equal(client.activeTokens.size, 0); +}); + +add_task(async function testTokenRaces() { + // Here we do 2 concurrent fetches each for 2 different token scopes (ie, + // 4 token fetches in total). + // This should provoke a potential race in the token fetching but we use + // a map of in-flight token fetches, so we should still only perform 2 + // fetches, but each of the 4 calls should resolve with the correct values. + let fxa = await createMockFxA(); + let client = fxa._internal.fxAccountsClient; + + let results = await Promise.all([ + fxa.getOAuthToken({ scope: "test-scope" }), + fxa.getOAuthToken({ scope: "test-scope" }), + fxa.getOAuthToken({ scope: "test-scope-2" }), + fxa.getOAuthToken({ scope: "test-scope-2" }), + ]); + + equal(client.numTokenFetches, 2, "should have fetched 2 tokens."); + + // Should have 2 unique tokens + results.sort(); + equal(results[0], results[1]); + equal(results[2], results[3]); + // should be 2 active. + equal(client.activeTokens.size, 2); + await fxa.removeCachedOAuthToken({ token: results[0] }); + equal(client.activeTokens.size, 1); + await fxa.removeCachedOAuthToken({ token: results[2] }); + equal(client.activeTokens.size, 0); + ok(client.oauthDestroy.calledTwice); +}); + +add_task(async function testTokenTTL() { + // This tests the TTL option passed into the method + let fxa = await createMockFxA(); + let token = await fxa.getOAuthToken({ scope: "test-ttl", ttl: 1000 }); + equal(token, "token0-ttl-1000"); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_pairing.js b/services/fxaccounts/tests/xpcshell/test_pairing.js new file mode 100644 index 0000000000..eac3112242 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_pairing.js @@ -0,0 +1,384 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { FxAccountsPairingFlow } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsPairing.sys.mjs" +); +const { EventEmitter } = ChromeUtils.importESModule( + "resource://gre/modules/EventEmitter.sys.mjs" +); +ChromeUtils.defineESModuleGetters(this, { + jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", +}); + +const CHANNEL_ID = "sW-UA97Q6Dljqen7XRlYPw"; +const CHANNEL_KEY = crypto.getRandomValues(new Uint8Array(32)); + +const SENDER_SUPP = { + ua: "Firefox Supp", + city: "Nice", + region: "PACA", + country: "France", + remote: "127.0.0.1", +}; +const UID = "abcd"; +const EMAIL = "foo@bar.com"; +const AVATAR = "https://foo.bar/avatar"; +const DISPLAY_NAME = "Foo bar"; +const DEVICE_NAME = "Foo's computer"; + +const PAIR_URI = "https://foo.bar/pair"; +const OAUTH_URI = "https://foo.bar/oauth"; +const KSYNC = "myksync"; +const SESSION = "mysession"; +const fxaConfig = { + promisePairingURI() { + return PAIR_URI; + }, + promiseOAuthURI() { + return OAUTH_URI; + }, +}; +const fxAccounts = { + getSignedInUser() { + return { + uid: UID, + email: EMAIL, + avatar: AVATAR, + displayName: DISPLAY_NAME, + }; + }, + async _withVerifiedAccountState(cb) { + return cb({ + async getUserAccountData() { + return { + sessionToken: SESSION, + }; + }, + }); + }, + _internal: { + keys: { + getKeyForScope(scope) { + return { + kid: "123456", + k: KSYNC, + kty: "oct", + }; + }, + }, + fxAccountsClient: { + async getScopedKeyData() { + return { + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationTimestamp: 12345678, + }, + }; + }, + async oauthAuthorize() { + return { code: "mycode", state: "mystate" }; + }, + }, + }, +}; +const weave = { + Service: { clientsEngine: { localName: DEVICE_NAME } }, +}; + +class MockPairingChannel extends EventTarget { + get channelId() { + return CHANNEL_ID; + } + + get channelKey() { + return CHANNEL_KEY; + } + + send(data) { + this.dispatchEvent( + new CustomEvent("send", { + detail: { data }, + }) + ); + } + + simulateIncoming(data) { + this.dispatchEvent( + new CustomEvent("message", { + detail: { data, sender: SENDER_SUPP }, + }) + ); + } + + close() { + this.closed = true; + } +} + +add_task(async function testFullFlow() { + const emitter = new EventEmitter(); + const pairingChannel = new MockPairingChannel(); + const pairingUri = await FxAccountsPairingFlow.start({ + emitter, + pairingChannel, + fxAccounts, + fxaConfig, + weave, + }); + Assert.equal( + pairingUri, + `${PAIR_URI}#channel_id=${CHANNEL_ID}&channel_key=${ChromeUtils.base64URLEncode( + CHANNEL_KEY, + { pad: false } + )}` + ); + + const flow = FxAccountsPairingFlow.get(CHANNEL_ID); + + const promiseSwitchToWebContent = emitter.once("view:SwitchToWebContent"); + const promiseMetadataSent = promiseOutgoingMessage(pairingChannel); + const epk = await generateEphemeralKeypair(); + + pairingChannel.simulateIncoming({ + message: "pair:supp:request", + data: { + client_id: "client_id_1", + state: "mystate", + keys_jwk: ChromeUtils.base64URLEncode( + new TextEncoder().encode(JSON.stringify(epk.publicJWK)), + { pad: false } + ), + scope: "profile https://identity.mozilla.com/apps/oldsync", + code_challenge: "chal", + code_challenge_method: "S256", + }, + }); + const sentAuthMetadata = await promiseMetadataSent; + Assert.deepEqual(sentAuthMetadata, { + message: "pair:auth:metadata", + data: { + email: EMAIL, + avatar: AVATAR, + displayName: DISPLAY_NAME, + deviceName: DEVICE_NAME, + }, + }); + const oauthUrl = await promiseSwitchToWebContent; + Assert.equal( + oauthUrl, + `${OAUTH_URI}?client_id=client_id_1&scope=profile+https%3A%2F%2Fidentity.mozilla.com%2Fapps%2Foldsync&email=foo%40bar.com&uid=abcd&channel_id=${CHANNEL_ID}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob%3Apair-auth-webchannel` + ); + + let pairSuppMetadata = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_supplicant_metadata" + ); + Assert.deepEqual( + { + ua: "Firefox Supp", + city: "Nice", + region: "PACA", + country: "France", + ipAddress: "127.0.0.1", + }, + pairSuppMetadata + ); + + const generateJWE = sinon.spy(jwcrypto, "generateJWE"); + const oauthAuthorize = sinon.spy( + fxAccounts._internal.fxAccountsClient, + "oauthAuthorize" + ); + const promiseOAuthParamsMsg = promiseOutgoingMessage(pairingChannel); + await simulateIncomingWebChannel(flow, "fxaccounts:pair_authorize"); + // We should have generated the expected JWE. + Assert.ok(generateJWE.calledOnce); + const generateArgs = generateJWE.firstCall.args; + Assert.deepEqual(generateArgs[0], epk.publicJWK); + Assert.deepEqual(JSON.parse(new TextDecoder().decode(generateArgs[1])), { + "https://identity.mozilla.com/apps/oldsync": { + kid: "123456", + k: KSYNC, + kty: "oct", + }, + }); + // We should have authorized an oauth code with expected parameters. + Assert.ok(oauthAuthorize.calledOnce); + const oauthCodeArgs = oauthAuthorize.firstCall.args[1]; + console.log(oauthCodeArgs); + Assert.ok(!oauthCodeArgs.keys_jwk); + Assert.deepEqual( + oauthCodeArgs.keys_jwe, + await generateJWE.firstCall.returnValue + ); + Assert.equal(oauthCodeArgs.client_id, "client_id_1"); + Assert.equal(oauthCodeArgs.access_type, "offline"); + Assert.equal(oauthCodeArgs.state, "mystate"); + Assert.equal( + oauthCodeArgs.scope, + "profile https://identity.mozilla.com/apps/oldsync" + ); + Assert.equal(oauthCodeArgs.code_challenge, "chal"); + Assert.equal(oauthCodeArgs.code_challenge_method, "S256"); + + const oAuthParams = await promiseOAuthParamsMsg; + Assert.deepEqual(oAuthParams, { + message: "pair:auth:authorize", + data: { code: "mycode", state: "mystate" }, + }); + + let heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(!heartbeat.suppAuthorized); + + await pairingChannel.simulateIncoming({ + message: "pair:supp:authorize", + }); + + heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(heartbeat.suppAuthorized); + + await simulateIncomingWebChannel(flow, "fxaccounts:pair_complete"); + // The flow should have been destroyed! + Assert.ok(!FxAccountsPairingFlow.get(CHANNEL_ID)); + Assert.ok(pairingChannel.closed); + generateJWE.restore(); + oauthAuthorize.restore(); +}); + +add_task(async function testUnknownPairingMessage() { + const emitter = new EventEmitter(); + const pairingChannel = new MockPairingChannel(); + await FxAccountsPairingFlow.start({ + emitter, + pairingChannel, + fxAccounts, + fxaConfig, + weave, + }); + const flow = FxAccountsPairingFlow.get(CHANNEL_ID); + const viewErrorObserved = emitter.once("view:Error"); + pairingChannel.simulateIncoming({ + message: "pair:boom", + }); + await viewErrorObserved; + let heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(heartbeat.err); +}); + +add_task(async function testUnknownWebChannelCommand() { + const emitter = new EventEmitter(); + const pairingChannel = new MockPairingChannel(); + await FxAccountsPairingFlow.start({ + emitter, + pairingChannel, + fxAccounts, + fxaConfig, + weave, + }); + const flow = FxAccountsPairingFlow.get(CHANNEL_ID); + const viewErrorObserved = emitter.once("view:Error"); + await simulateIncomingWebChannel(flow, "fxaccounts:boom"); + await viewErrorObserved; + let heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(heartbeat.err); +}); + +add_task(async function testPairingChannelFailure() { + const emitter = new EventEmitter(); + const pairingChannel = new MockPairingChannel(); + await FxAccountsPairingFlow.start({ + emitter, + pairingChannel, + fxAccounts, + fxaConfig, + weave, + }); + const flow = FxAccountsPairingFlow.get(CHANNEL_ID); + const viewErrorObserved = emitter.once("view:Error"); + sinon.stub(pairingChannel, "send").callsFake(() => { + throw new Error("Boom!"); + }); + pairingChannel.simulateIncoming({ + message: "pair:supp:request", + data: { + client_id: "client_id_1", + state: "mystate", + scope: "profile https://identity.mozilla.com/apps/oldsync", + code_challenge: "chal", + code_challenge_method: "S256", + }, + }); + await viewErrorObserved; + + let heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(heartbeat.err); +}); + +add_task(async function testFlowTimeout() { + const emitter = new EventEmitter(); + const pairingChannel = new MockPairingChannel(); + const viewErrorObserved = emitter.once("view:Error"); + await FxAccountsPairingFlow.start({ + emitter, + pairingChannel, + fxAccounts, + fxaConfig, + weave, + flowTimeout: 1, + }); + const flow = FxAccountsPairingFlow.get(CHANNEL_ID); + await viewErrorObserved; + + let heartbeat = await simulateIncomingWebChannel( + flow, + "fxaccounts:pair_heartbeat" + ); + Assert.ok(heartbeat.err.match(/Timeout/)); +}); + +async function simulateIncomingWebChannel(flow, command) { + return flow.onWebChannelMessage(command); +} + +async function promiseOutgoingMessage(pairingChannel) { + return new Promise(res => { + const onMessage = event => { + pairingChannel.removeEventListener("send", onMessage); + res(event.detail.data); + }; + pairingChannel.addEventListener("send", onMessage); + }); +} + +async function generateEphemeralKeypair() { + const keypair = await crypto.subtle.generateKey( + { name: "ECDH", namedCurve: "P-256" }, + true, + ["deriveKey"] + ); + const publicJWK = await crypto.subtle.exportKey("jwk", keypair.publicKey); + const privateJWK = await crypto.subtle.exportKey("jwk", keypair.privateKey); + delete publicJWK.key_ops; + return { + publicJWK, + privateJWK, + }; +} diff --git a/services/fxaccounts/tests/xpcshell/test_profile.js b/services/fxaccounts/tests/xpcshell/test_profile.js new file mode 100644 index 0000000000..f8137b5691 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_profile.js @@ -0,0 +1,677 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ON_PROFILE_CHANGE_NOTIFICATION, log } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +const { FxAccountsProfileClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsProfileClient.sys.mjs" +); +const { FxAccountsProfile } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsProfile.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +let mockClient = function (fxa) { + let options = { + serverURL: "http://127.0.0.1:1111/v1", + fxa, + }; + return new FxAccountsProfileClient(options); +}; + +const ACCOUNT_UID = "abc123"; +const ACCOUNT_EMAIL = "foo@bar.com"; +const ACCOUNT_DATA = { + uid: ACCOUNT_UID, + email: ACCOUNT_EMAIL, +}; + +let mockFxa = function () { + let fxa = { + // helpers to make the tests using this mock less verbose... + set _testProfileCache(profileCache) { + this._internal.currentAccountState._data.profileCache = profileCache; + }, + get _testProfileCache() { + return this._internal.currentAccountState._data.profileCache; + }, + }; + fxa._internal = Object.assign( + {}, + { + currentAccountState: Object.assign( + {}, + { + _data: Object.assign({}, ACCOUNT_DATA), + + get isCurrent() { + return true; + }, + + async getUserAccountData() { + return this._data; + }, + + async updateUserAccountData(data) { + this._data = Object.assign(this._data, data); + }, + } + ), + + withCurrentAccountState(cb) { + return cb(this.currentAccountState); + }, + + async _handleTokenError(err) { + // handleTokenError always rethrows. + throw err; + }, + } + ); + return fxa; +}; + +function CreateFxAccountsProfile(fxa = null, client = null) { + if (!fxa) { + fxa = mockFxa(); + } + let options = { + fxai: fxa._internal, + profileServerUrl: "http://127.0.0.1:1111/v1", + }; + if (client) { + options.profileClient = client; + } + return new FxAccountsProfile(options); +} + +add_test(function cacheProfile_change() { + let setProfileCacheCalled = false; + let fxa = mockFxa(); + fxa._internal.currentAccountState.updateUserAccountData = data => { + setProfileCacheCalled = true; + Assert.equal(data.profileCache.profile.avatar, "myurl"); + Assert.equal(data.profileCache.etag, "bogusetag"); + return Promise.resolve(); + }; + let profile = CreateFxAccountsProfile(fxa); + + makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { + Assert.equal(data, ACCOUNT_DATA.uid); + Assert.ok(setProfileCacheCalled); + run_next_test(); + }); + + return profile._cacheProfile({ + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myurl" }, + etag: "bogusetag", + }); +}); + +add_test(function fetchAndCacheProfile_ok() { + let client = mockClient(mockFxa()); + client.fetchProfile = function () { + return Promise.resolve({ body: { uid: ACCOUNT_UID, avatar: "myimg" } }); + }; + let profile = CreateFxAccountsProfile(null, client); + profile._cachedAt = 12345; + + profile._cacheProfile = function (toCache) { + Assert.equal(toCache.body.avatar, "myimg"); + return Promise.resolve(toCache.body); + }; + + return profile._fetchAndCacheProfile().then(result => { + Assert.equal(result.avatar, "myimg"); + Assert.notEqual(profile._cachedAt, 12345, "cachedAt has been bumped"); + run_next_test(); + }); +}); + +add_test(function fetchAndCacheProfile_always_bumps_cachedAt() { + let client = mockClient(mockFxa()); + client.fetchProfile = function () { + return Promise.reject(new Error("oops")); + }; + let profile = CreateFxAccountsProfile(null, client); + profile._cachedAt = 12345; + + return profile._fetchAndCacheProfile().then( + result => { + do_throw("Should not succeed"); + }, + err => { + Assert.notEqual(profile._cachedAt, 12345, "cachedAt has been bumped"); + run_next_test(); + } + ); +}); + +add_test(function fetchAndCacheProfile_sendsETag() { + let fxa = mockFxa(); + fxa._testProfileCache = { profile: {}, etag: "bogusETag" }; + let client = mockClient(fxa); + client.fetchProfile = function (etag) { + Assert.equal(etag, "bogusETag"); + return Promise.resolve({ + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }); + }; + let profile = CreateFxAccountsProfile(fxa, client); + + return profile._fetchAndCacheProfile().then(result => { + run_next_test(); + }); +}); + +// Check that a second profile request when one is already in-flight reuses +// the in-flight one. +add_task(async function fetchAndCacheProfileOnce() { + // A promise that remains unresolved while we fire off 2 requests for + // a profile. + let resolveProfile; + let promiseProfile = new Promise(resolve => { + resolveProfile = resolve; + }); + let numFetches = 0; + let client = mockClient(mockFxa()); + client.fetchProfile = function () { + numFetches += 1; + return promiseProfile; + }; + let fxa = mockFxa(); + let profile = CreateFxAccountsProfile(fxa, client); + + let request1 = profile._fetchAndCacheProfile(); + profile._fetchAndCacheProfile(); + await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise) + + // should be one request made to fetch the profile (but the promise returned + // by it remains unresolved) + Assert.equal(numFetches, 1); + + // resolve the promise. + resolveProfile({ + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }); + + // both requests should complete with the same data. + let got1 = await request1; + Assert.equal(got1.avatar, "myimg"); + let got2 = await request1; + Assert.equal(got2.avatar, "myimg"); + + // and still only 1 request was made. + Assert.equal(numFetches, 1); +}); + +// Check that sharing a single fetch promise works correctly when the promise +// is rejected. +add_task(async function fetchAndCacheProfileOnce() { + // A promise that remains unresolved while we fire off 2 requests for + // a profile. + let rejectProfile; + let promiseProfile = new Promise((resolve, reject) => { + rejectProfile = reject; + }); + let numFetches = 0; + let client = mockClient(mockFxa()); + client.fetchProfile = function () { + numFetches += 1; + return promiseProfile; + }; + let fxa = mockFxa(); + let profile = CreateFxAccountsProfile(fxa, client); + + let request1 = profile._fetchAndCacheProfile(); + let request2 = profile._fetchAndCacheProfile(); + await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise) + + // should be one request made to fetch the profile (but the promise returned + // by it remains unresolved) + Assert.equal(numFetches, 1); + + // reject the promise. + rejectProfile("oh noes"); + + // both requests should reject. + try { + await request1; + throw new Error("should have rejected"); + } catch (ex) { + if (ex != "oh noes") { + throw ex; + } + } + try { + await request2; + throw new Error("should have rejected"); + } catch (ex) { + if (ex != "oh noes") { + throw ex; + } + } + + // but a new request should works. + client.fetchProfile = function () { + return Promise.resolve({ + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }); + }; + + let got = await profile._fetchAndCacheProfile(); + Assert.equal(got.avatar, "myimg"); +}); + +add_test(function fetchAndCacheProfile_alreadyCached() { + let cachedUrl = "cachedurl"; + let fxa = mockFxa(); + fxa._testProfileCache = { + profile: { uid: ACCOUNT_UID, avatar: cachedUrl }, + etag: "bogusETag", + }; + let client = mockClient(fxa); + client.fetchProfile = function (etag) { + Assert.equal(etag, "bogusETag"); + return Promise.resolve(null); + }; + + let profile = CreateFxAccountsProfile(fxa, client); + profile._cacheProfile = function (toCache) { + do_throw("This method should not be called."); + }; + + return profile._fetchAndCacheProfile().then(result => { + Assert.equal(result, null); + Assert.equal(fxa._testProfileCache.profile.avatar, cachedUrl); + run_next_test(); + }); +}); + +// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the +// last one doesn't kick off a new request to check the cached copy is fresh. +add_task(async function fetchAndCacheProfileAfterThreshold() { + /* + * This test was observed to cause a timeout for... any timer precision reduction. + * Even 1 us. Exact reason is still undiagnosed. + */ + Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false); + + registerCleanupFunction(async () => { + Services.prefs.clearUserPref("privacy.reduceTimerPrecision"); + }); + + let numFetches = 0; + let client = mockClient(mockFxa()); + client.fetchProfile = async function () { + numFetches += 1; + return { + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }; + }; + let profile = CreateFxAccountsProfile(null, client); + profile.PROFILE_FRESHNESS_THRESHOLD = 1000; + + // first fetch should return null as we don't have data. + let p = await profile.getProfile(); + Assert.equal(p, null); + // ensure we kicked off a fetch. + Assert.notEqual(profile._currentFetchPromise, null); + // wait for that fetch to finish + await profile._currentFetchPromise; + Assert.equal(numFetches, 1); + Assert.equal(profile._currentFetchPromise, null); + + await profile.getProfile(); + Assert.equal(numFetches, 1); + Assert.equal(profile._currentFetchPromise, null); + + await new Promise(resolve => { + do_timeout(1000, resolve); + }); + + let origFetchAndCatch = profile._fetchAndCacheProfile; + let backgroundFetchDone = Promise.withResolvers(); + profile._fetchAndCacheProfile = async () => { + await origFetchAndCatch.call(profile); + backgroundFetchDone.resolve(); + }; + await profile.getProfile(); + await backgroundFetchDone.promise; + Assert.equal(numFetches, 2); +}); + +add_task(async function test_ensureProfile() { + let client = new FxAccountsProfileClient({ + serverURL: "http://127.0.0.1:1111/v1", + fxa: mockFxa(), + }); + let profile = CreateFxAccountsProfile(null, client); + + const testCases = [ + // profile retrieval when there is no cached profile info + { + threshold: 1000, + expectsCachedProfileReturned: false, + cachedProfile: null, + fetchedProfile: { + uid: ACCOUNT_UID, + email: ACCOUNT_EMAIL, + avatar: "myimg", + }, + }, + // profile retrieval when the cached profile is recent + { + // Note: The threshold for this test case is being set to an arbitrary value that will + // be greater than Date.now() so the retrieved cached profile will be deemed recent. + threshold: Date.now() + 5000, + expectsCachedProfileReturned: true, + cachedProfile: { + uid: `${ACCOUNT_UID}2`, + email: `${ACCOUNT_EMAIL}2`, + avatar: "myimg2", + }, + }, + // profile retrieval when the cached profile is old and a new profile is fetched + { + threshold: 1000, + expectsCachedProfileReturned: false, + cachedProfile: { + uid: `${ACCOUNT_UID}3`, + email: `${ACCOUNT_EMAIL}3`, + avatar: "myimg3", + }, + fetchAndCacheProfileResolves: true, + fetchedProfile: { + uid: `${ACCOUNT_UID}4`, + email: `${ACCOUNT_EMAIL}4`, + avatar: "myimg4", + }, + }, + // profile retrieval when the cached profile is old and a null profile is fetched + { + threshold: 1000, + expectsCachedProfileReturned: false, + cachedProfile: { + uid: `${ACCOUNT_UID}5`, + email: `${ACCOUNT_EMAIL}5`, + avatar: "myimg5", + }, + fetchAndCacheProfileResolves: true, + fetchedProfile: null, + }, + // profile retrieval when the cached profile is old and fetching a new profile errors + { + threshold: 1000, + expectsCachedProfileReturned: false, + cachedProfile: { + uid: `${ACCOUNT_UID}6`, + email: `${ACCOUNT_EMAIL}6`, + avatar: "myimg6", + }, + fetchAndCacheProfileResolves: false, + }, + // profile retrieval when we've cached a failure to fetch profile data + { + // Note: The threshold for this test case is being set to an arbitrary value that will + // be greater than Date.now() so the retrieved cached profile will be deemed recent. + threshold: Date.now() + 5000, + expectsCachedProfileReturned: false, + cachedProfile: null, + fetchedProfile: { + uid: `${ACCOUNT_UID}7`, + email: `${ACCOUNT_EMAIL}7`, + avatar: "myimg7", + }, + fetchAndCacheProfileResolves: true, + }, + // profile retrieval when the cached profile is old but staleOk is true. + { + threshold: 1000, + expectsCachedProfileReturned: true, + cachedProfile: { + uid: `${ACCOUNT_UID}8`, + email: `${ACCOUNT_EMAIL}8`, + avatar: "myimg8", + }, + fetchAndCacheProfileResolves: false, + options: { staleOk: true }, + }, + // staleOk but no cached profile + { + threshold: 1000, + expectsCachedProfileReturned: false, + cachedProfile: null, + fetchedProfile: { + uid: `${ACCOUNT_UID}9`, + email: `${ACCOUNT_EMAIL}9`, + avatar: "myimg9", + }, + options: { staleOk: true }, + }, + // fresh profile but forceFresh = true + { + // Note: The threshold for this test case is being set to an arbitrary value that will + // be greater than Date.now() so the retrieved cached profile will be deemed recent. + threshold: Date.now() + 5000, + expectsCachedProfileReturned: false, + fetchedProfile: { + uid: `${ACCOUNT_UID}10`, + email: `${ACCOUNT_EMAIL}10`, + avatar: "myimg10", + }, + options: { forceFresh: true }, + }, + ]; + + for (const tc of testCases) { + print(`test case: ${JSON.stringify(tc)}`); + let mockProfile = sinon.mock(profile); + mockProfile + .expects("_getProfileCache") + .once() + .returns( + tc.cachedProfile + ? { + profile: tc.cachedProfile, + } + : null + ); + profile.PROFILE_FRESHNESS_THRESHOLD = tc.threshold; + + let options = tc.options || {}; + if (tc.expectsCachedProfileReturned) { + mockProfile.expects("_fetchAndCacheProfile").never(); + let actualProfile = await profile.ensureProfile(options); + mockProfile.verify(); + Assert.equal(actualProfile, tc.cachedProfile); + } else if (tc.fetchAndCacheProfileResolves) { + mockProfile + .expects("_fetchAndCacheProfile") + .once() + .resolves(tc.fetchedProfile); + + let actualProfile = await profile.ensureProfile(options); + let expectedProfile = tc.fetchedProfile + ? tc.fetchedProfile + : tc.cachedProfile; + mockProfile.verify(); + Assert.equal(actualProfile, expectedProfile); + } else { + mockProfile.expects("_fetchAndCacheProfile").once().rejects(); + + let actualProfile = await profile.ensureProfile(options); + mockProfile.verify(); + Assert.equal(actualProfile, tc.cachedProfile); + } + } +}); + +// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the +// last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION +// is sent. +add_task(async function fetchAndCacheProfileBeforeThresholdOnNotification() { + let numFetches = 0; + let client = mockClient(mockFxa()); + client.fetchProfile = async function () { + numFetches += 1; + return { + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }; + }; + let profile = CreateFxAccountsProfile(null, client); + profile.PROFILE_FRESHNESS_THRESHOLD = 1000; + + // first fetch should return null as we don't have data. + let p = await profile.getProfile(); + Assert.equal(p, null); + // ensure we kicked off a fetch. + Assert.notEqual(profile._currentFetchPromise, null); + // wait for that fetch to finish + await profile._currentFetchPromise; + Assert.equal(numFetches, 1); + Assert.equal(profile._currentFetchPromise, null); + + Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION); + + let origFetchAndCatch = profile._fetchAndCacheProfile; + let backgroundFetchDone = Promise.withResolvers(); + profile._fetchAndCacheProfile = async () => { + await origFetchAndCatch.call(profile); + backgroundFetchDone.resolve(); + }; + await profile.getProfile(); + await backgroundFetchDone.promise; + Assert.equal(numFetches, 2); +}); + +add_test(function tearDown_ok() { + let profile = CreateFxAccountsProfile(); + + Assert.ok(!!profile.client); + Assert.ok(!!profile.fxai); + + profile.tearDown(); + Assert.equal(null, profile.fxai); + Assert.equal(null, profile.client); + + run_next_test(); +}); + +add_task(async function getProfile_ok() { + let cachedUrl = "myurl"; + let didFetch = false; + + let fxa = mockFxa(); + fxa._testProfileCache = { profile: { uid: ACCOUNT_UID, avatar: cachedUrl } }; + let profile = CreateFxAccountsProfile(fxa); + + profile._fetchAndCacheProfile = function () { + didFetch = true; + return Promise.resolve(); + }; + + let result = await profile.getProfile(); + + Assert.equal(result.avatar, cachedUrl); + Assert.ok(didFetch); +}); + +add_task(async function getProfile_no_cache() { + let fetchedUrl = "newUrl"; + let fxa = mockFxa(); + let profile = CreateFxAccountsProfile(fxa); + + profile._fetchAndCacheProfileInternal = function () { + return Promise.resolve({ uid: ACCOUNT_UID, avatar: fetchedUrl }); + }; + + await profile.getProfile(); // returns null. + let result = await profile._currentFetchPromise; + Assert.equal(result.avatar, fetchedUrl); +}); + +add_test(function getProfile_has_cached_fetch_deleted() { + let cachedUrl = "myurl"; + + let fxa = mockFxa(); + let client = mockClient(fxa); + client.fetchProfile = function () { + return Promise.resolve({ + body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: null }, + }); + }; + + let profile = CreateFxAccountsProfile(fxa, client); + fxa._testProfileCache = { + profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: cachedUrl }, + }; + + // instead of checking this in a mocked "save" function, just check after the + // observer + makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { + profile.getProfile().then(profileData => { + Assert.equal(null, profileData.avatar); + run_next_test(); + }); + }); + + return profile.getProfile().then(result => { + Assert.equal(result.avatar, "myurl"); + }); +}); + +add_test(function getProfile_fetchAndCacheProfile_throws() { + let fxa = mockFxa(); + fxa._testProfileCache = { + profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" }, + }; + let profile = CreateFxAccountsProfile(fxa); + + profile._fetchAndCacheProfile = () => Promise.reject(new Error()); + + return profile.getProfile().then(result => { + Assert.equal(result.avatar, "myimg"); + run_next_test(); + }); +}); + +add_test(function getProfile_email_changed() { + let fxa = mockFxa(); + let client = mockClient(fxa); + client.fetchProfile = function () { + return Promise.resolve({ + body: { uid: ACCOUNT_UID, email: "newemail@bar.com" }, + }); + }; + fxa._internal._handleEmailUpdated = email => { + Assert.equal(email, "newemail@bar.com"); + run_next_test(); + }; + + let profile = CreateFxAccountsProfile(fxa, client); + return profile._fetchAndCacheProfile(); +}); + +function makeObserver(aObserveTopic, aObserveFunc) { + let callback = function (aSubject, aTopic, aData) { + log.debug("observed " + aTopic + " " + aData); + if (aTopic == aObserveTopic) { + removeMe(); + aObserveFunc(aSubject, aTopic, aData); + } + }; + + function removeMe() { + log.debug("removing observer for " + aObserveTopic); + Services.obs.removeObserver(callback, aObserveTopic); + } + + Services.obs.addObserver(callback, aObserveTopic); + return removeMe; +} diff --git a/services/fxaccounts/tests/xpcshell/test_profile_client.js b/services/fxaccounts/tests/xpcshell/test_profile_client.js new file mode 100644 index 0000000000..22fcc293f8 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_profile_client.js @@ -0,0 +1,422 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + ERRNO_NETWORK, + ERRNO_PARSE, + ERRNO_UNKNOWN_ERROR, + ERROR_CODE_METHOD_NOT_ALLOWED, + ERROR_MSG_METHOD_NOT_ALLOWED, + ERROR_NETWORK, + ERROR_PARSE, + ERROR_UNKNOWN, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +const { FxAccountsProfileClient, FxAccountsProfileClientError } = + ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsProfileClient.sys.mjs" + ); + +const STATUS_SUCCESS = 200; + +/** + * Mock request responder + * @param {String} response + * Mocked raw response from the server + * @returns {Function} + */ +let mockResponse = function (response) { + let Request = function (requestUri) { + // Store the request uri so tests can inspect it + Request._requestUri = requestUri; + Request.ifNoneMatchSet = false; + return { + setHeader(header, value) { + if (header == "If-None-Match" && value == "bogusETag") { + Request.ifNoneMatchSet = true; + } + }, + async dispatch(method, payload) { + this.response = response; + return this.response; + }, + }; + }; + + return Request; +}; + +// A simple mock FxA that hands out tokens without checking them and doesn't +// expect tokens to be revoked. We have specific token tests further down that +// has more checks here. +let mockFxaInternal = { + getOAuthToken(options) { + Assert.equal(options.scope, "profile"); + return "token"; + }, +}; + +const PROFILE_OPTIONS = { + serverURL: "http://127.0.0.1:1111/v1", + fxai: mockFxaInternal, +}; + +/** + * Mock request error responder + * @param {Error} error + * Error object + * @returns {Function} + */ +let mockResponseError = function (error) { + return function () { + return { + setHeader() {}, + async dispatch(method, payload) { + throw error; + }, + }; + }; +}; + +add_test(function successfulResponse() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + headers: { etag: "bogusETag" }, + body: '{"email":"someone@restmail.net","uid":"0d5c1a89b8c54580b8e3e8adadae864a"}', + }; + + client._Request = new mockResponse(response); + client.fetchProfile().then(function (result) { + Assert.equal( + client._Request._requestUri, + "http://127.0.0.1:1111/v1/profile" + ); + Assert.equal(result.body.email, "someone@restmail.net"); + Assert.equal(result.body.uid, "0d5c1a89b8c54580b8e3e8adadae864a"); + Assert.equal(result.etag, "bogusETag"); + run_next_test(); + }); +}); + +add_test(function setsIfNoneMatchETagHeader() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + headers: {}, + body: '{"email":"someone@restmail.net","uid":"0d5c1a89b8c54580b8e3e8adadae864a"}', + }; + + let req = new mockResponse(response); + client._Request = req; + client.fetchProfile("bogusETag").then(function (result) { + Assert.equal( + client._Request._requestUri, + "http://127.0.0.1:1111/v1/profile" + ); + Assert.equal(result.body.email, "someone@restmail.net"); + Assert.equal(result.body.uid, "0d5c1a89b8c54580b8e3e8adadae864a"); + Assert.ok(req.ifNoneMatchSet); + run_next_test(); + }); +}); + +add_test(function successful304Response() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + headers: { etag: "bogusETag" }, + status: 304, + }; + + client._Request = new mockResponse(response); + client.fetchProfile().then(function (result) { + Assert.equal(result, null); + run_next_test(); + }); +}); + +add_test(function parseErrorResponse() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + success: true, + status: STATUS_SUCCESS, + body: "unexpected", + }; + + client._Request = new mockResponse(response); + client.fetchProfile().catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, STATUS_SUCCESS); + Assert.equal(e.errno, ERRNO_PARSE); + Assert.equal(e.error, ERROR_PARSE); + Assert.equal(e.message, "unexpected"); + run_next_test(); + }); +}); + +add_test(function serverErrorResponse() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + let response = { + status: 500, + body: '{ "code": 500, "errno": 100, "error": "Bad Request", "message": "Something went wrong", "reason": "Because the internet" }', + }; + + client._Request = new mockResponse(response); + client.fetchProfile().catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, 500); + Assert.equal(e.errno, 100); + Assert.equal(e.error, "Bad Request"); + Assert.equal(e.message, "Something went wrong"); + run_next_test(); + }); +}); + +// Test that we get a token, then if we get a 401 we revoke it, get a new one +// and retry. +add_test(function server401ResponseThenSuccess() { + // The last token we handed out. + let lastToken = -1; + // The number of times our removeCachedOAuthToken function was called. + let numTokensRemoved = 0; + + let mockFxaWithRemove = { + getOAuthToken(options) { + Assert.equal(options.scope, "profile"); + return "" + ++lastToken; // tokens are strings. + }, + removeCachedOAuthToken(options) { + // This test never has more than 1 token alive at once, so the token + // being revoked must always be the last token we handed out. + Assert.equal(parseInt(options.token), lastToken); + ++numTokensRemoved; + }, + }; + let profileOptions = { + serverURL: "http://127.0.0.1:1111/v1", + fxai: mockFxaWithRemove, + }; + let client = new FxAccountsProfileClient(profileOptions); + + // 2 responses - first one implying the token has expired, second works. + let responses = [ + { + status: 401, + body: '{ "code": 401, "errno": 100, "error": "Token expired", "message": "That token is too old", "reason": "Because security" }', + }, + { + success: true, + status: STATUS_SUCCESS, + headers: {}, + body: '{"avatar":"http://example.com/image.jpg","id":"0d5c1a89b8c54580b8e3e8adadae864a"}', + }, + ]; + + let numRequests = 0; + let numAuthHeaders = 0; + // Like mockResponse but we want access to headers etc. + client._Request = function (requestUri) { + return { + setHeader(name, value) { + if (name == "Authorization") { + numAuthHeaders++; + Assert.equal(value, "Bearer " + lastToken); + } + }, + async dispatch(method, payload) { + this.response = responses[numRequests]; + ++numRequests; + return this.response; + }, + }; + }; + + client.fetchProfile().then(result => { + Assert.equal(result.body.avatar, "http://example.com/image.jpg"); + Assert.equal(result.body.id, "0d5c1a89b8c54580b8e3e8adadae864a"); + // should have been exactly 2 requests and exactly 2 auth headers. + Assert.equal(numRequests, 2); + Assert.equal(numAuthHeaders, 2); + // and we should have seen one token revoked. + Assert.equal(numTokensRemoved, 1); + + run_next_test(); + }); +}); + +// Test that we get a token, then if we get a 401 we revoke it, get a new one +// and retry - but we *still* get a 401 on the retry, so the caller sees that. +add_test(function server401ResponsePersists() { + // The last token we handed out. + let lastToken = -1; + // The number of times our removeCachedOAuthToken function was called. + let numTokensRemoved = 0; + + let mockFxaWithRemove = { + getOAuthToken(options) { + Assert.equal(options.scope, "profile"); + return "" + ++lastToken; // tokens are strings. + }, + removeCachedOAuthToken(options) { + // This test never has more than 1 token alive at once, so the token + // being revoked must always be the last token we handed out. + Assert.equal(parseInt(options.token), lastToken); + ++numTokensRemoved; + }, + }; + let profileOptions = { + serverURL: "http://127.0.0.1:1111/v1", + fxai: mockFxaWithRemove, + }; + let client = new FxAccountsProfileClient(profileOptions); + + let response = { + status: 401, + body: '{ "code": 401, "errno": 100, "error": "It\'s not your token, it\'s you!", "message": "I don\'t like you", "reason": "Because security" }', + }; + + let numRequests = 0; + let numAuthHeaders = 0; + client._Request = function (requestUri) { + return { + setHeader(name, value) { + if (name == "Authorization") { + numAuthHeaders++; + Assert.equal(value, "Bearer " + lastToken); + } + }, + async dispatch(method, payload) { + this.response = response; + ++numRequests; + return this.response; + }, + }; + }; + + client.fetchProfile().catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, 401); + Assert.equal(e.errno, 100); + Assert.equal(e.error, "It's not your token, it's you!"); + // should have been exactly 2 requests and exactly 2 auth headers. + Assert.equal(numRequests, 2); + Assert.equal(numAuthHeaders, 2); + // and we should have seen both tokens revoked. + Assert.equal(numTokensRemoved, 2); + run_next_test(); + }); +}); + +add_test(function networkErrorResponse() { + let client = new FxAccountsProfileClient({ + serverURL: "http://domain.dummy", + fxai: mockFxaInternal, + }); + client.fetchProfile().catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, null); + Assert.equal(e.errno, ERRNO_NETWORK); + Assert.equal(e.error, ERROR_NETWORK); + run_next_test(); + }); +}); + +add_test(function unsupportedMethod() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + + return client._createRequest("/profile", "PUT").catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, ERROR_CODE_METHOD_NOT_ALLOWED); + Assert.equal(e.errno, ERRNO_NETWORK); + Assert.equal(e.error, ERROR_NETWORK); + Assert.equal(e.message, ERROR_MSG_METHOD_NOT_ALLOWED); + run_next_test(); + }); +}); + +add_test(function onCompleteRequestError() { + let client = new FxAccountsProfileClient(PROFILE_OPTIONS); + client._Request = new mockResponseError(new Error("onComplete error")); + client.fetchProfile().catch(function (e) { + Assert.equal(e.name, "FxAccountsProfileClientError"); + Assert.equal(e.code, null); + Assert.equal(e.errno, ERRNO_NETWORK); + Assert.equal(e.error, ERROR_NETWORK); + Assert.equal(e.message, "Error: onComplete error"); + run_next_test(); + }); +}); + +add_test(function constructorTests() { + validationHelper( + undefined, + "Error: Missing 'serverURL' configuration option" + ); + + validationHelper({}, "Error: Missing 'serverURL' configuration option"); + + validationHelper({ serverURL: "badUrl" }, "Error: Invalid 'serverURL'"); + + run_next_test(); +}); + +add_test(function errorTests() { + let error1 = new FxAccountsProfileClientError(); + Assert.equal(error1.name, "FxAccountsProfileClientError"); + Assert.equal(error1.code, null); + Assert.equal(error1.errno, ERRNO_UNKNOWN_ERROR); + Assert.equal(error1.error, ERROR_UNKNOWN); + Assert.equal(error1.message, null); + + let error2 = new FxAccountsProfileClientError({ + code: STATUS_SUCCESS, + errno: 1, + error: "Error", + message: "Something", + }); + let fields2 = error2._toStringFields(); + let statusCode = 1; + + Assert.equal(error2.name, "FxAccountsProfileClientError"); + Assert.equal(error2.code, STATUS_SUCCESS); + Assert.equal(error2.errno, statusCode); + Assert.equal(error2.error, "Error"); + Assert.equal(error2.message, "Something"); + + Assert.equal(fields2.name, "FxAccountsProfileClientError"); + Assert.equal(fields2.code, STATUS_SUCCESS); + Assert.equal(fields2.errno, statusCode); + Assert.equal(fields2.error, "Error"); + Assert.equal(fields2.message, "Something"); + + Assert.ok(error2.toString().includes("Something")); + run_next_test(); +}); + +/** + * Quick way to test the "FxAccountsProfileClient" constructor. + * + * @param {Object} options + * FxAccountsProfileClient constructor options + * @param {String} expected + * Expected error message + * @returns {*} + */ +function validationHelper(options, expected) { + // add fxai to options - that missing isn't what we are testing here. + if (options) { + options.fxai = mockFxaInternal; + } + try { + new FxAccountsProfileClient(options); + } catch (e) { + return Assert.equal(e.toString(), expected); + } + throw new Error("Validation helper error"); +} diff --git a/services/fxaccounts/tests/xpcshell/test_push_service.js b/services/fxaccounts/tests/xpcshell/test_push_service.js new file mode 100644 index 0000000000..0441888847 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_push_service.js @@ -0,0 +1,522 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests for the FxA push service. + +/* eslint-disable mozilla/use-chromeutils-generateqi */ + +const { + FXA_PUSH_SCOPE_ACCOUNT_UPDATE, + ONLOGOUT_NOTIFICATION, + ON_ACCOUNT_DESTROYED_NOTIFICATION, + ON_DEVICE_CONNECTED_NOTIFICATION, + ON_DEVICE_DISCONNECTED_NOTIFICATION, + ON_PASSWORD_CHANGED_NOTIFICATION, + ON_PASSWORD_RESET_NOTIFICATION, + ON_PROFILE_CHANGE_NOTIFICATION, + ON_PROFILE_UPDATED_NOTIFICATION, + ON_VERIFY_LOGIN_NOTIFICATION, + log, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +const { FxAccountsPushService } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsPush.sys.mjs" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "PushService", + "@mozilla.org/push/Service;1", + "nsIPushService" +); + +initTestLogging("Trace"); +log.level = Log.Level.Trace; + +const MOCK_ENDPOINT = "http://mochi.test:8888"; + +// tests do not allow external connections, mock the PushService +let mockPushService = { + pushTopic: PushService.pushTopic, + subscriptionChangeTopic: PushService.subscriptionChangeTopic, + subscribe(scope, principal, cb) { + cb(Cr.NS_OK, { + endpoint: MOCK_ENDPOINT, + }); + }, + unsubscribe(scope, principal, cb) { + cb(Cr.NS_OK, true); + }, +}; + +let mockFxAccounts = { + checkVerificationStatus() {}, + updateDeviceRegistration() {}, +}; + +let mockLog = { + trace() {}, + debug() {}, + warn() {}, + error() {}, +}; + +add_task(async function initialize() { + let pushService = new FxAccountsPushService(); + equal(pushService.initialize(), false); +}); + +add_task(async function registerPushEndpointSuccess() { + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: mockFxAccounts, + }); + + let subscription = await pushService.registerPushEndpoint(); + equal(subscription.endpoint, MOCK_ENDPOINT); +}); + +add_task(async function registerPushEndpointFailure() { + let failPushService = Object.assign(mockPushService, { + subscribe(scope, principal, cb) { + cb(Cr.NS_ERROR_ABORT); + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: failPushService, + fxai: mockFxAccounts, + }); + + let subscription = await pushService.registerPushEndpoint(); + equal(subscription, null); +}); + +add_task(async function unsubscribeSuccess() { + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: mockFxAccounts, + }); + + let result = await pushService.unsubscribe(); + equal(result, true); +}); + +add_task(async function unsubscribeFailure() { + let failPushService = Object.assign(mockPushService, { + unsubscribe(scope, principal, cb) { + cb(Cr.NS_ERROR_ABORT); + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: failPushService, + fxai: mockFxAccounts, + }); + + let result = await pushService.unsubscribe(); + equal(result, null); +}); + +add_test(function observeLogout() { + let customLog = Object.assign(mockLog, { + trace(msg) { + if (msg === "FxAccountsPushService unsubscribe") { + // logout means we unsubscribe + run_next_test(); + } + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + log: customLog, + }); + + pushService.observe(null, ONLOGOUT_NOTIFICATION); +}); + +add_test(function observePushTopicVerify() { + let emptyMsg = { + QueryInterface() { + return this; + }, + }; + let customAccounts = Object.assign(mockFxAccounts, { + checkVerificationStatus() { + // checking verification status on push messages without data + run_next_test(); + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: customAccounts, + }); + + pushService.observe( + emptyMsg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_test(function observePushTopicDeviceConnected() { + let msg = { + data: { + json: () => ({ + command: ON_DEVICE_CONNECTED_NOTIFICATION, + data: { + deviceName: "My phone", + }, + }), + }, + QueryInterface() { + return this; + }, + }; + let obs = (subject, topic, data) => { + Services.obs.removeObserver(obs, topic); + run_next_test(); + }; + Services.obs.addObserver(obs, ON_DEVICE_CONNECTED_NOTIFICATION); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: mockFxAccounts, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_task(async function observePushTopicDeviceDisconnected_current_device() { + const deviceId = "bogusid"; + let msg = { + data: { + json: () => ({ + command: ON_DEVICE_DISCONNECTED_NOTIFICATION, + data: { + id: deviceId, + }, + }), + }, + QueryInterface() { + return this; + }, + }; + + let signoutCalled = false; + let { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ); + const fxAccountsMock = new FxAccounts({ + newAccountState() { + return { + async getUserAccountData() { + return { device: { id: deviceId } }; + }, + }; + }, + signOut() { + signoutCalled = true; + }, + })._internal; + + const deviceDisconnectedNotificationObserved = new Promise(resolve => { + Services.obs.addObserver(function obs(subject, topic, data) { + Services.obs.removeObserver(obs, topic); + equal(data, JSON.stringify({ isLocalDevice: true })); + resolve(); + }, ON_DEVICE_DISCONNECTED_NOTIFICATION); + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: fxAccountsMock, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); + + await deviceDisconnectedNotificationObserved; + ok(signoutCalled); +}); + +add_task(async function observePushTopicDeviceDisconnected_another_device() { + const deviceId = "bogusid"; + let msg = { + data: { + json: () => ({ + command: ON_DEVICE_DISCONNECTED_NOTIFICATION, + data: { + id: deviceId, + }, + }), + }, + QueryInterface() { + return this; + }, + }; + + let signoutCalled = false; + let { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ); + const fxAccountsMock = new FxAccounts({ + newAccountState() { + return { + async getUserAccountData() { + return { device: { id: "thelocaldevice" } }; + }, + }; + }, + signOut() { + signoutCalled = true; + }, + })._internal; + + const deviceDisconnectedNotificationObserved = new Promise(resolve => { + Services.obs.addObserver(function obs(subject, topic, data) { + Services.obs.removeObserver(obs, topic); + equal(data, JSON.stringify({ isLocalDevice: false })); + resolve(); + }, ON_DEVICE_DISCONNECTED_NOTIFICATION); + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: fxAccountsMock, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); + + await deviceDisconnectedNotificationObserved; + ok(!signoutCalled); +}); + +add_test(function observePushTopicAccountDestroyed() { + const uid = "bogusuid"; + let msg = { + data: { + json: () => ({ + command: ON_ACCOUNT_DESTROYED_NOTIFICATION, + data: { + uid, + }, + }), + }, + QueryInterface() { + return this; + }, + }; + let customAccounts = Object.assign(mockFxAccounts, { + _handleAccountDestroyed() { + // checking verification status on push messages without data + run_next_test(); + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: customAccounts, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_test(function observePushTopicVerifyLogin() { + let url = "http://localhost/newLogin"; + let title = "bogustitle"; + let body = "bogusbody"; + let msg = { + data: { + json: () => ({ + command: ON_VERIFY_LOGIN_NOTIFICATION, + data: { + body, + title, + url, + }, + }), + }, + QueryInterface() { + return this; + }, + }; + let obs = (subject, topic, data) => { + Services.obs.removeObserver(obs, topic); + Assert.equal(data, JSON.stringify(msg.data.json().data)); + run_next_test(); + }; + Services.obs.addObserver(obs, ON_VERIFY_LOGIN_NOTIFICATION); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: mockFxAccounts, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_test(function observePushTopicProfileUpdated() { + let msg = { + data: { + json: () => ({ + command: ON_PROFILE_UPDATED_NOTIFICATION, + }), + }, + QueryInterface() { + return this; + }, + }; + let obs = (subject, topic, data) => { + Services.obs.removeObserver(obs, topic); + run_next_test(); + }; + Services.obs.addObserver(obs, ON_PROFILE_CHANGE_NOTIFICATION); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: mockFxAccounts, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_test(function observePushTopicPasswordChanged() { + let msg = { + data: { + json: () => ({ + command: ON_PASSWORD_CHANGED_NOTIFICATION, + }), + }, + QueryInterface() { + return this; + }, + }; + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + }); + + pushService._onPasswordChanged = function () { + run_next_test(); + }; + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_test(function observePushTopicPasswordReset() { + let msg = { + data: { + json: () => ({ + command: ON_PASSWORD_RESET_NOTIFICATION, + }), + }, + QueryInterface() { + return this; + }, + }; + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + }); + + pushService._onPasswordChanged = function () { + run_next_test(); + }; + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); + +add_task(async function commandReceived() { + let msg = { + data: { + json: () => ({ + command: "fxaccounts:command_received", + data: { + url: "https://api.accounts.firefox.com/auth/v1/account/device/commands?index=42&limit=1", + }, + }), + }, + QueryInterface() { + return this; + }, + }; + + let fxAccountsMock = {}; + const promiseConsumeRemoteMessagesCalled = new Promise(res => { + fxAccountsMock.commands = { + pollDeviceCommands() { + res(); + }, + }; + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: fxAccountsMock, + }); + + pushService.observe( + msg, + mockPushService.pushTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); + await promiseConsumeRemoteMessagesCalled; +}); + +add_test(function observeSubscriptionChangeTopic() { + let customAccounts = Object.assign(mockFxAccounts, { + updateDeviceRegistration() { + // subscription change means updating the device registration + run_next_test(); + }, + }); + + let pushService = new FxAccountsPushService({ + pushService: mockPushService, + fxai: customAccounts, + }); + + pushService.observe( + null, + mockPushService.subscriptionChangeTopic, + FXA_PUSH_SCOPE_ACCOUNT_UPDATE + ); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_storage_manager.js b/services/fxaccounts/tests/xpcshell/test_storage_manager.js new file mode 100644 index 0000000000..05c565d2f4 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_storage_manager.js @@ -0,0 +1,586 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests for the FxA storage manager. + +const { FxAccountsStorageManager } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsStorage.sys.mjs" +); +const { DATA_FORMAT_VERSION, log } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +initTestLogging("Trace"); +log.level = Log.Level.Trace; + +const DEVICE_REGISTRATION_VERSION = 42; + +// A couple of mocks we can use. +function MockedPlainStorage(accountData) { + let data = null; + if (accountData) { + data = { + version: DATA_FORMAT_VERSION, + accountData, + }; + } + this.data = data; + this.numReads = 0; +} +MockedPlainStorage.prototype = { + async get() { + this.numReads++; + Assert.equal(this.numReads, 1, "should only ever be 1 read of acct data"); + return this.data; + }, + + async set(data) { + this.data = data; + }, +}; + +function MockedSecureStorage(accountData) { + let data = null; + if (accountData) { + data = { + version: DATA_FORMAT_VERSION, + accountData, + }; + } + this.data = data; + this.numReads = 0; +} + +MockedSecureStorage.prototype = { + fetchCount: 0, + locked: false, + /* eslint-disable object-shorthand */ + // This constructor must be declared without + // object shorthand or we get an exception of + // "TypeError: this.STORAGE_LOCKED is not a constructor" + STORAGE_LOCKED: function () {}, + /* eslint-enable object-shorthand */ + async get(uid, email) { + this.fetchCount++; + if (this.locked) { + throw new this.STORAGE_LOCKED(); + } + this.numReads++; + Assert.equal( + this.numReads, + 1, + "should only ever be 1 read of unlocked data" + ); + return this.data; + }, + + async set(uid, contents) { + this.data = contents; + }, +}; + +function add_storage_task(testFunction) { + add_task(async function () { + print("Starting test with secure storage manager"); + await testFunction(new FxAccountsStorageManager()); + }); + add_task(async function () { + print("Starting test with simple storage manager"); + await testFunction(new FxAccountsStorageManager({ useSecure: false })); + }); +} + +// initialized without account data and there's nothing to read. Not logged in. +add_storage_task(async function checkInitializedEmpty(sm) { + if (sm.secureStorage) { + sm.secureStorage = new MockedSecureStorage(null); + } + await sm.initialize(); + Assert.strictEqual(await sm.getAccountData(), null); + await Assert.rejects( + sm.updateAccountData({ scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys } }), + /No user is logged in/ + ); +}); + +// Initialized with account data (ie, simulating a new user being logged in). +// Should reflect the initial data and be written to storage. +add_storage_task(async function checkNewUser(sm) { + let initialAccountData = { + uid: "uid", + email: "someone@somewhere.com", + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + device: { + id: "device id", + }, + }; + sm.plainStorage = new MockedPlainStorage(); + if (sm.secureStorage) { + sm.secureStorage = new MockedSecureStorage(null); + } + await sm.initialize(initialAccountData); + let accountData = await sm.getAccountData(); + Assert.equal(accountData.uid, initialAccountData.uid); + Assert.equal(accountData.email, initialAccountData.email); + Assert.deepEqual(accountData.scopedKeys, initialAccountData.scopedKeys); + Assert.deepEqual(accountData.device, initialAccountData.device); + + // and it should have been written to storage. + Assert.equal(sm.plainStorage.data.accountData.uid, initialAccountData.uid); + Assert.equal( + sm.plainStorage.data.accountData.email, + initialAccountData.email + ); + Assert.deepEqual( + sm.plainStorage.data.accountData.device, + initialAccountData.device + ); + // check secure + if (sm.secureStorage) { + Assert.deepEqual( + sm.secureStorage.data.accountData.scopedKeys, + initialAccountData.scopedKeys + ); + } else { + Assert.deepEqual( + sm.plainStorage.data.accountData.scopedKeys, + initialAccountData.scopedKeys + ); + } +}); + +// Initialized without account data but storage has it available. +add_storage_task(async function checkEverythingRead(sm) { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + device: { + id: "wibble", + registrationVersion: null, + }, + }); + if (sm.secureStorage) { + sm.secureStorage = new MockedSecureStorage(null); + } + await sm.initialize(); + let accountData = await sm.getAccountData(); + Assert.ok(accountData, "read account data"); + Assert.equal(accountData.uid, "uid"); + Assert.equal(accountData.email, "someone@somewhere.com"); + Assert.deepEqual(accountData.device, { + id: "wibble", + registrationVersion: null, + }); + // Update the data - we should be able to fetch it back and it should appear + // in our storage. + await sm.updateAccountData({ + verified: true, + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + device: { + id: "wibble", + registrationVersion: DEVICE_REGISTRATION_VERSION, + }, + }); + accountData = await sm.getAccountData(); + Assert.deepEqual(accountData.scopedKeys, MOCK_ACCOUNT_KEYS.scopedKeys); + Assert.deepEqual(accountData.device, { + id: "wibble", + registrationVersion: DEVICE_REGISTRATION_VERSION, + }); + // Check the new value was written to storage. + await sm._promiseStorageComplete; // storage is written in the background. + Assert.equal(sm.plainStorage.data.accountData.verified, true); + Assert.deepEqual(sm.plainStorage.data.accountData.device, { + id: "wibble", + registrationVersion: DEVICE_REGISTRATION_VERSION, + }); + // derive keys are secure + if (sm.secureStorage) { + Assert.deepEqual( + sm.secureStorage.data.accountData.scopedKeys, + MOCK_ACCOUNT_KEYS.scopedKeys + ); + } else { + Assert.deepEqual( + sm.plainStorage.data.accountData.scopedKeys, + MOCK_ACCOUNT_KEYS.scopedKeys + ); + } +}); + +add_storage_task(async function checkInvalidUpdates(sm) { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + if (sm.secureStorage) { + sm.secureStorage = new MockedSecureStorage(null); + } + await sm.initialize(); + + await Assert.rejects( + sm.updateAccountData({ uid: "another" }), + /Can't change uid/ + ); +}); + +add_storage_task(async function checkNullUpdatesRemovedUnlocked(sm) { + if (sm.secureStorage) { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + unwrapBKey: "unwrapBKey", + }); + } else { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + unwrapBKey: "unwrapBKey", + }); + } + await sm.initialize(); + + await sm.updateAccountData({ unwrapBKey: null }); + let accountData = await sm.getAccountData(); + Assert.ok(!accountData.unwrapBKey); + Assert.deepEqual(accountData.scopedKeys, MOCK_ACCOUNT_KEYS.scopedKeys); +}); + +add_storage_task(async function checkNullRemovesUnlistedFields(sm) { + // kA and kB are not listed in FXA_PWDMGR_*_FIELDS, but we still want to + // be able to delete them (migration case). + if (sm.secureStorage) { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ kA: "kA", kb: "kB" }); + } else { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + kA: "kA", + kb: "kB", + }); + } + await sm.initialize(); + + await sm.updateAccountData({ kA: null, kB: null }); + let accountData = await sm.getAccountData(); + Assert.ok(!accountData.kA); + Assert.ok(!accountData.kB); +}); + +add_storage_task(async function checkDelete(sm) { + if (sm.secureStorage) { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + } else { + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + } + await sm.initialize(); + + await sm.deleteAccountData(); + // Storage should have been reset to null. + Assert.equal(sm.plainStorage.data, null); + if (sm.secureStorage) { + Assert.equal(sm.secureStorage.data, null); + } + // And everything should reflect no user. + Assert.equal(await sm.getAccountData(), null); +}); + +// Some tests only for the secure storage manager. +add_task(async function checkNullUpdatesRemovedLocked() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + unwrapBKey: "unwrapBKey is another secure value", + }); + sm.secureStorage.locked = true; + await sm.initialize(); + + await sm.updateAccountData({ scopedKeys: null }); + let accountData = await sm.getAccountData(); + // No scopedKeys because it was removed. + Assert.ok(!accountData.scopedKeys); + // No unwrapBKey because we are locked + Assert.ok(!accountData.unwrapBKey); + + // now unlock - should still be no scopedKeys but unwrapBKey should appear. + sm.secureStorage.locked = false; + accountData = await sm.getAccountData(); + Assert.ok(!accountData.scopedKeys); + Assert.equal(accountData.unwrapBKey, "unwrapBKey is another secure value"); + // And secure storage should have been written with our previously-cached + // data. + Assert.strictEqual(sm.secureStorage.data.accountData.scopedKeys, undefined); + Assert.strictEqual( + sm.secureStorage.data.accountData.unwrapBKey, + "unwrapBKey is another secure value" + ); +}); + +add_task(async function checkEverythingReadSecure() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + await sm.initialize(); + + let accountData = await sm.getAccountData(); + Assert.ok(accountData, "read account data"); + Assert.equal(accountData.uid, "uid"); + Assert.equal(accountData.email, "someone@somewhere.com"); + Assert.deepEqual(accountData.scopedKeys, MOCK_ACCOUNT_KEYS.scopedKeys); +}); + +add_task(async function checkExplicitGet() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + await sm.initialize(); + + let accountData = await sm.getAccountData(["uid", "scopedKeys"]); + Assert.ok(accountData, "read account data"); + Assert.equal(accountData.uid, "uid"); + Assert.deepEqual(accountData.scopedKeys, MOCK_ACCOUNT_KEYS.scopedKeys); + // We didn't ask for email so shouldn't have got it. + Assert.strictEqual(accountData.email, undefined); +}); + +add_task(async function checkExplicitGetNoSecureRead() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + await sm.initialize(); + + Assert.equal(sm.secureStorage.fetchCount, 0); + // request 2 fields in secure storage - it should have caused a single fetch. + let accountData = await sm.getAccountData(["email", "uid"]); + Assert.ok(accountData, "read account data"); + Assert.equal(accountData.uid, "uid"); + Assert.equal(accountData.email, "someone@somewhere.com"); + Assert.strictEqual(accountData.scopedKeys, undefined); + Assert.equal(sm.secureStorage.fetchCount, 1); +}); + +add_task(async function checkLockedUpdates() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + unwrapBKey: "unwrapBKey", + }); + sm.secureStorage.locked = true; + await sm.initialize(); + + let accountData = await sm.getAccountData(); + // requesting scopedKeys will fail as storage is locked. + Assert.ok(!accountData.scopedKeys); + // While locked we can still update it and see the updated value. + sm.updateAccountData({ unwrapBKey: "new-unwrapBKey" }); + accountData = await sm.getAccountData(); + Assert.equal(accountData.unwrapBKey, "new-unwrapBKey"); + // unlock. + sm.secureStorage.locked = false; + accountData = await sm.getAccountData(); + // should reflect the value we updated and the one we didn't. + Assert.equal(accountData.unwrapBKey, "new-unwrapBKey"); + Assert.deepEqual(accountData.scopedKeys, MOCK_ACCOUNT_KEYS.scopedKeys); + // And storage should also reflect it. + Assert.deepEqual( + sm.secureStorage.data.accountData.scopedKeys, + MOCK_ACCOUNT_KEYS.scopedKeys + ); + Assert.strictEqual( + sm.secureStorage.data.accountData.unwrapBKey, + "new-unwrapBKey" + ); +}); + +// Some tests for the "storage queue" functionality. + +// A helper for our queued tests. It creates a StorageManager and then queues +// an unresolved promise. The tests then do additional setup and checks, then +// resolves or rejects the blocked promise. +async function setupStorageManagerForQueueTest() { + let sm = new FxAccountsStorageManager(); + sm.plainStorage = new MockedPlainStorage({ + uid: "uid", + email: "someone@somewhere.com", + }); + sm.secureStorage = new MockedSecureStorage({ + scopedKeys: { ...MOCK_ACCOUNT_KEYS.scopedKeys }, + }); + sm.secureStorage.locked = true; + await sm.initialize(); + + let resolveBlocked, rejectBlocked; + let blockedPromise = new Promise((resolve, reject) => { + resolveBlocked = resolve; + rejectBlocked = reject; + }); + + sm._queueStorageOperation(() => blockedPromise); + return { sm, blockedPromise, resolveBlocked, rejectBlocked }; +} + +// First the general functionality. +add_task(async function checkQueueSemantics() { + let { sm, resolveBlocked } = await setupStorageManagerForQueueTest(); + + // We've one unresolved promise in the queue - add another promise. + let resolveSubsequent; + let subsequentPromise = new Promise(resolve => { + resolveSubsequent = resolve; + }); + let subsequentCalled = false; + + sm._queueStorageOperation(() => { + subsequentCalled = true; + resolveSubsequent(); + return subsequentPromise; + }); + + // Our "subsequent" function should not have been called yet. + Assert.ok(!subsequentCalled); + + // Release our blocked promise. + resolveBlocked(); + + // Our subsequent promise should end up resolved. + await subsequentPromise; + Assert.ok(subsequentCalled); + await sm.finalize(); +}); + +// Check that a queued promise being rejected works correctly. +add_task(async function checkQueueSemanticsOnError() { + let { sm, blockedPromise, rejectBlocked } = + await setupStorageManagerForQueueTest(); + + let resolveSubsequent; + let subsequentPromise = new Promise(resolve => { + resolveSubsequent = resolve; + }); + let subsequentCalled = false; + + sm._queueStorageOperation(() => { + subsequentCalled = true; + resolveSubsequent(); + return subsequentPromise; + }); + + // Our "subsequent" function should not have been called yet. + Assert.ok(!subsequentCalled); + + // Reject our blocked promise - the subsequent operations should still work + // correctly. + rejectBlocked("oh no"); + + // Our subsequent promise should end up resolved. + await subsequentPromise; + Assert.ok(subsequentCalled); + + // But the first promise should reflect the rejection. + try { + await blockedPromise; + Assert.ok(false, "expected this promise to reject"); + } catch (ex) { + Assert.equal(ex, "oh no"); + } + await sm.finalize(); +}); + +// And some tests for the specific operations that are queued. +add_task(async function checkQueuedReadAndUpdate() { + let { sm, resolveBlocked } = await setupStorageManagerForQueueTest(); + // Mock the underlying operations + // _doReadAndUpdateSecure is queued by _maybeReadAndUpdateSecure + let _doReadCalled = false; + sm._doReadAndUpdateSecure = () => { + _doReadCalled = true; + return Promise.resolve(); + }; + + let resultPromise = sm._maybeReadAndUpdateSecure(); + Assert.ok(!_doReadCalled); + + resolveBlocked(); + await resultPromise; + Assert.ok(_doReadCalled); + await sm.finalize(); +}); + +add_task(async function checkQueuedWrite() { + let { sm, resolveBlocked } = await setupStorageManagerForQueueTest(); + // Mock the underlying operations + let __writeCalled = false; + sm.__write = () => { + __writeCalled = true; + return Promise.resolve(); + }; + + let writePromise = sm._write(); + Assert.ok(!__writeCalled); + + resolveBlocked(); + await writePromise; + Assert.ok(__writeCalled); + await sm.finalize(); +}); + +add_task(async function checkQueuedDelete() { + let { sm, resolveBlocked } = await setupStorageManagerForQueueTest(); + // Mock the underlying operations + let _deleteCalled = false; + sm._deleteAccountData = () => { + _deleteCalled = true; + return Promise.resolve(); + }; + + let resultPromise = sm.deleteAccountData(); + Assert.ok(!_deleteCalled); + + resolveBlocked(); + await resultPromise; + Assert.ok(_deleteCalled); + await sm.finalize(); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_telemetry.js b/services/fxaccounts/tests/xpcshell/test_telemetry.js new file mode 100644 index 0000000000..3b9d318404 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_telemetry.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { getFxAccountsSingleton } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const fxAccounts = getFxAccountsSingleton(); + +_("Misc tests for FxAccounts.telemetry"); + +const MOCK_HASHED_UID = "00112233445566778899aabbccddeeff"; +const MOCK_DEVICE_ID = "ffeeddccbbaa99887766554433221100"; + +add_task(function test_sanitized_uid() { + Services.prefs.deleteBranch( + "identity.fxaccounts.account.telemetry.sanitized_uid" + ); + + // Returns `null` by default. + Assert.equal(fxAccounts.telemetry.getSanitizedUID(), null); + + // Returns provided value if set. + fxAccounts.telemetry._setHashedUID(MOCK_HASHED_UID); + Assert.equal(fxAccounts.telemetry.getSanitizedUID(), MOCK_HASHED_UID); + + // Reverts to unset for falsey values. + fxAccounts.telemetry._setHashedUID(""); + Assert.equal(fxAccounts.telemetry.getSanitizedUID(), null); +}); + +add_task(function test_sanitize_device_id() { + Services.prefs.deleteBranch( + "identity.fxaccounts.account.telemetry.sanitized_uid" + ); + + // Returns `null` by default. + Assert.equal(fxAccounts.telemetry.sanitizeDeviceId(MOCK_DEVICE_ID), null); + + // Hashes with the sanitized UID if set. + // (test value here is SHA256(MOCK_DEVICE_ID + MOCK_HASHED_UID)) + fxAccounts.telemetry._setHashedUID(MOCK_HASHED_UID); + Assert.equal( + fxAccounts.telemetry.sanitizeDeviceId(MOCK_DEVICE_ID), + "dd7c845006df9baa1c6d756926519c8ce12f91230e11b6057bf8ec65f9b55c1a" + ); + + // Reverts to unset for falsey values. + fxAccounts.telemetry._setHashedUID(""); + Assert.equal(fxAccounts.telemetry.sanitizeDeviceId(MOCK_DEVICE_ID), null); +}); diff --git a/services/fxaccounts/tests/xpcshell/test_web_channel.js b/services/fxaccounts/tests/xpcshell/test_web_channel.js new file mode 100644 index 0000000000..48f043d0b9 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_web_channel.js @@ -0,0 +1,1380 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ON_PROFILE_CHANGE_NOTIFICATION, WEBCHANNEL_ID, log } = + ChromeUtils.importESModule("resource://gre/modules/FxAccountsCommon.sys.mjs"); +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); +const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } = + ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsWebChannel.sys.mjs" + ); + +const URL_STRING = "https://example.com"; + +const mockSendingContext = { + browsingContext: { top: { embedderElement: {} } }, + principal: {}, + eventTarget: {}, +}; + +add_test(function () { + validationHelper(undefined, "Error: Missing configuration options"); + + validationHelper( + { + channel_id: WEBCHANNEL_ID, + }, + "Error: Missing 'content_uri' option" + ); + + validationHelper( + { + content_uri: "bad uri", + channel_id: WEBCHANNEL_ID, + }, + /NS_ERROR_MALFORMED_URI/ + ); + + validationHelper( + { + content_uri: URL_STRING, + }, + "Error: Missing 'channel_id' option" + ); + + run_next_test(); +}); + +add_task(async function test_rejection_reporting() { + Services.prefs.setBoolPref( + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", + false + ); + + let mockMessage = { + command: "fxaccounts:login", + messageId: "1234", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + login(accountData) { + equal( + accountData.email, + "testuser@testuser.com", + "Should forward incoming message data to the helper" + ); + return Promise.reject(new Error("oops")); + }, + }, + }); + + let promiseSend = new Promise(resolve => { + channel._channel.send = (message, context) => { + resolve({ message, context }); + }; + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); + + let { message, context } = await promiseSend; + + equal(context, mockSendingContext, "Should forward the original context"); + equal( + message.command, + "fxaccounts:login", + "Should include the incoming command" + ); + equal(message.messageId, "1234", "Should include the message ID"); + equal( + message.data.error.message, + "Error: oops", + "Should convert the error message to a string" + ); + notStrictEqual( + message.data.error.stack, + null, + "Should include the stack for JS error rejections" + ); +}); + +add_test(function test_exception_reporting() { + let mockMessage = { + command: "fxaccounts:sync_preferences", + messageId: "5678", + data: { entryPoint: "fxa:verification_complete" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + openSyncPreferences(browser, entryPoint) { + equal( + entryPoint, + "fxa:verification_complete", + "Should forward incoming message data to the helper" + ); + throw new TypeError("splines not reticulated"); + }, + }, + }); + + channel._channel.send = (message, context) => { + equal(context, mockSendingContext, "Should forward the original context"); + equal( + message.command, + "fxaccounts:sync_preferences", + "Should include the incoming command" + ); + equal(message.messageId, "5678", "Should include the message ID"); + equal( + message.data.error.message, + "TypeError: splines not reticulated", + "Should convert the exception to a string" + ); + notStrictEqual( + message.data.error.stack, + null, + "Should include the stack for JS exceptions" + ); + + run_next_test(); + }; + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_error_message_remove_profile_path() { + const errors = { + windows: { + err: new Error( + "Win error 183 during operation rename on file C:\\Users\\Some Computer\\AppData\\Roaming\\" + + "Mozilla\\Firefox\\Profiles\\dbzjmzxa.default\\signedInUser.json (Cannot create a file)" + ), + expected: + "Error: Win error 183 during operation rename on file C:[REDACTED]signedInUser.json (Cannot create a file)", + }, + unix: { + err: new Error( + "Unix error 28 during operation write on file /Users/someuser/Library/Application Support/" + + "Firefox/Profiles/dbzjmzxa.default-release-7/signedInUser.json (No space left on device)" + ), + expected: + "Error: Unix error 28 during operation write on file [REDACTED]signedInUser.json (No space left on device)", + }, + netpath: { + err: new Error( + "Win error 32 during operation rename on file \\\\SVC.LOC\\HOMEDIRS$\\USERNAME\\Mozilla\\" + + "Firefox\\Profiles\\dbzjmzxa.default-release-7\\signedInUser.json (No space left on device)" + ), + expected: + "Error: Win error 32 during operation rename on file [REDACTED]signedInUser.json (No space left on device)", + }, + mount: { + err: new Error( + "Win error 649 during operation rename on file C:\\SnapVolumes\\MountPoints\\" + + "{9e399ec5-0000-0000-0000-100000000000}\\SVROOT\\Users\\username\\AppData\\Roaming\\Mozilla\\Firefox\\" + + "Profiles\\dbzjmzxa.default-release\\signedInUser.json (The create operation failed)" + ), + expected: + "Error: Win error 649 during operation rename on file C:[REDACTED]signedInUser.json " + + "(The create operation failed)", + }, + }; + const mockMessage = { + command: "fxaccounts:sync_preferences", + messageId: "1234", + }; + const channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + }); + + let testNum = 0; + const toTest = Object.keys(errors).length; + for (const key in errors) { + let error = errors[key]; + channel._channel.send = (message, context) => { + equal( + message.data.error.message, + error.expected, + "Should remove the profile path from the error message" + ); + testNum++; + if (testNum === toTest) { + run_next_test(); + } + }; + channel._sendError(error.err, mockMessage, mockSendingContext); + } +}); + +add_test(function test_profile_image_change_message() { + var mockMessage = { + command: "profile:change", + data: { uid: "foo" }, + }; + + makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { + Assert.equal(data, "foo"); + run_next_test(); + }); + + var channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_login_message() { + let mockMessage = { + command: "fxaccounts:login", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + login(accountData) { + Assert.equal(accountData.email, "testuser@testuser.com"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_oauth_login() { + const mockData = { + code: "oauth code", + state: "state parameter", + declinedSyncEngines: ["tabs", "creditcards"], + offeredSyncEngines: ["tabs", "creditcards", "history"], + }; + const mockMessage = { + command: "fxaccounts:oauth_login", + data: mockData, + }; + const channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + oauthLogin(data) { + Assert.deepEqual(data, mockData); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_logout_message() { + let mockMessage = { + command: "fxaccounts:logout", + data: { uid: "foo" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + logout(uid) { + Assert.equal(uid, "foo"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_delete_message() { + let mockMessage = { + command: "fxaccounts:delete", + data: { uid: "foo" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + logout(uid) { + Assert.equal(uid, "foo"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_can_link_account_message() { + let mockMessage = { + command: "fxaccounts:can_link_account", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + shouldAllowRelink(email) { + Assert.equal(email, "testuser@testuser.com"); + run_next_test(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_sync_preferences_message() { + let mockMessage = { + command: "fxaccounts:sync_preferences", + data: { entryPoint: "fxa:verification_complete" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + openSyncPreferences(browser, entryPoint) { + Assert.equal(entryPoint, "fxa:verification_complete"); + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + run_next_test(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_fxa_status_message() { + let mockMessage = { + command: "fxaccounts:fxa_status", + messageId: 123, + data: { + service: "sync", + context: "fx_desktop_v3", + }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + async getFxaStatus(service, sendingContext, isPairing, context) { + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + Assert.ok(!isPairing); + Assert.equal(context, "fx_desktop_v3"); + return { + signedInUser: { + email: "testuser@testuser.com", + sessionToken: "session-token", + uid: "uid", + verified: true, + }, + capabilities: { + engines: ["creditcards", "addresses"], + }, + }; + }, + }, + }); + + channel._channel = { + send(response, sendingContext) { + Assert.equal(response.command, "fxaccounts:fxa_status"); + Assert.equal(response.messageId, 123); + + let signedInUser = response.data.signedInUser; + Assert.ok(!!signedInUser); + Assert.equal(signedInUser.email, "testuser@testuser.com"); + Assert.equal(signedInUser.sessionToken, "session-token"); + Assert.equal(signedInUser.uid, "uid"); + Assert.equal(signedInUser.verified, true); + + deepEqual(response.data.capabilities.engines, [ + "creditcards", + "addresses", + ]); + + run_next_test(); + }, + }; + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_unrecognized_message() { + let mockMessage = { + command: "fxaccounts:unrecognized", + data: {}, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + }); + + // no error is expected. + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); + run_next_test(); +}); + +add_test(function test_helpers_should_allow_relink_same_email() { + let helpers = new FxAccountsWebChannelHelpers(); + + helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); + Assert.ok(helpers.shouldAllowRelink("testuser@testuser.com")); + + run_next_test(); +}); + +add_test(function test_helpers_should_allow_relink_different_email() { + let helpers = new FxAccountsWebChannelHelpers(); + + helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); + + helpers._promptForRelink = acctName => { + return acctName === "allowed_to_relink@testuser.com"; + }; + + Assert.ok(helpers.shouldAllowRelink("allowed_to_relink@testuser.com")); + Assert.ok(!helpers.shouldAllowRelink("not_allowed_to_relink@testuser.com")); + + run_next_test(); +}); + +add_task(async function test_helpers_login_without_customize_sync() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // verifiedCanLinkAccount should be stripped in the data. + Assert.equal(false, "verifiedCanLinkAccount" in accountData); + + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + }); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") + ); +}); + +add_task(async function test_helpers_login_set_previous_account_name_hash() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // previously signed in user preference is updated. + Assert.equal( + helpers.getPreviousAccountNameHashPref(), + CryptoUtils.sha256Base64("newuser@testuser.com") + ); + resolve(); + }); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "newuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + verified: true, + }); +}); + +add_task( + async function test_helpers_login_dont_set_previous_account_name_hash_for_unverified_emails() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // previously signed in user preference should not be updated. + Assert.equal( + helpers.getPreviousAccountNameHashPref(), + CryptoUtils.sha256Base64("lastuser@testuser.com") + ); + resolve(); + }); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "newuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + }); + } +); + +add_task(async function test_helpers_login_with_customize_sync() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // customizeSync should be stripped in the data. + Assert.equal(false, "customizeSync" in accountData); + + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + }); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") + ); +}); + +add_task( + async function test_helpers_login_with_customize_sync_and_declined_engines() { + let configured = false; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // customizeSync should be stripped in the data. + Assert.equal(false, "customizeSync" in accountData); + Assert.equal(false, "services" in accountData); + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.addons"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.bookmarks"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.history"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.passwords"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.prefs"), + true + ); + Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + services: { + sync: { + offeredEngines: [ + "addons", + "bookmarks", + "history", + "passwords", + "prefs", + ], + declinedEngines: ["addons", "prefs"], + }, + }, + }); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.addons"), + false + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.bookmarks"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.history"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.passwords"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.prefs"), + false + ); + Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + Assert.ok(configured, "sync was configured"); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith( + ["sync"], + "webchannel" + ) + ); + } +); + +add_task(async function test_helpers_login_with_offered_sync_engines() { + let helpers; + let configured = false; + const setSignedInUserCalled = new Promise(resolve => { + helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + async setSignedInUser(accountData) { + resolve(accountData); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + }); + + Services.prefs.setBoolPref("services.sync.engine.creditcards", false); + Services.prefs.setBoolPref("services.sync.engine.addresses", false); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + services: { + sync: { + declinedEngines: ["addresses"], + offeredEngines: ["creditcards", "addresses"], + }, + }, + }); + + const accountData = await setSignedInUserCalled; + + // ensure fxAccounts is informed of the new user being signed in. + equal(accountData.email, "testuser@testuser.com"); + + // services should be stripped in the data. + ok(!("services" in accountData)); + // credit cards was offered but not declined. + equal(Services.prefs.getBoolPref("services.sync.engine.creditcards"), true); + // addresses was offered and explicitely declined. + equal(Services.prefs.getBoolPref("services.sync.engine.addresses"), false); + ok(configured); +}); + +add_task(async function test_helpers_login_nothing_offered() { + let helpers; + let configured = false; + const setSignedInUserCalled = new Promise(resolve => { + helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + async setSignedInUser(accountData) { + resolve(accountData); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + }); + + // doesn't really matter if it's *all* engines... + const allEngines = [ + "addons", + "addresses", + "bookmarks", + "creditcards", + "history", + "passwords", + "prefs", + ]; + for (let name of allEngines) { + Services.prefs.clearUserPref("services.sync.engine." + name); + } + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + services: { + sync: {}, + }, + }); + + const accountData = await setSignedInUserCalled; + // ensure fxAccounts is informed of the new user being signed in. + equal(accountData.email, "testuser@testuser.com"); + + for (let name of allEngines) { + Assert.ok(!Services.prefs.prefHasUserValue("services.sync.engine." + name)); + } + Assert.ok(configured); +}); + +add_test(function test_helpers_open_sync_preferences() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: {}, + }); + + let mockBrowser = { + loadURI(uri) { + Assert.equal( + uri.spec, + "about:preferences?entrypoint=fxa%3Averification_complete#sync" + ); + run_next_test(); + }, + }; + + helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete"); +}); + +add_task(async function test_helpers_getFxAStatus_extra_engines() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + return Promise.resolve({ + email: "testuser@testuser.com", + sessionToken: "sessionToken", + uid: "uid", + verified: true, + }); + }, + }, + }, + privateBrowsingUtils: { + isBrowserPrivate: () => true, + }, + }); + + Services.prefs.setBoolPref( + "services.sync.engine.creditcards.available", + true + ); + // Not defining "services.sync.engine.addresses.available" on purpose. + + let fxaStatus = await helpers.getFxaStatus("sync", mockSendingContext); + ok(!!fxaStatus); + ok(!!fxaStatus.signedInUser); + deepEqual(fxaStatus.capabilities.engines, ["creditcards"]); +}); + +add_task(async function test_helpers_getFxaStatus_allowed_signedInUser() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve({ + email: "testuser@testuser.com", + sessionToken: "sessionToken", + uid: "uid", + verified: true, + }); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = (service, sendingContext) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + + return true; + }; + + return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.ok(!!fxaStatus.signedInUser); + let { signedInUser } = fxaStatus; + + Assert.equal(signedInUser.email, "testuser@testuser.com"); + Assert.equal(signedInUser.sessionToken, "sessionToken"); + Assert.equal(signedInUser.uid, "uid"); + Assert.ok(signedInUser.verified); + + // These properties are filtered and should not + // be returned to the requester. + Assert.equal(false, "scopedKeys" in signedInUser); + }); +}); + +add_task(async function test_helpers_getFxaStatus_allowed_no_signedInUser() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve(null); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = (service, sendingContext) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + + return true; + }; + + return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.equal(null, fxaStatus.signedInUser); + }); +}); + +add_task(async function test_helpers_getFxaStatus_not_allowed() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve(null); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = ( + service, + sendingContext, + isPairing, + context + ) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + Assert.ok(!isPairing); + Assert.equal(context, "fx_desktop_v3"); + + return false; + }; + + return helpers + .getFxaStatus("sync", mockSendingContext, false, "fx_desktop_v3") + .then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(!wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.equal(null, fxaStatus.signedInUser); + }); +}); + +add_task( + async function test_helpers_shouldAllowFxaStatus_sync_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "sync", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_desktop_context_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false, + "fx_desktop_v3" + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_no_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_sync_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "sync", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_desktop_context_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false, + "fx_desktop_v3" + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + false + ); + Assert.ok(!shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_pairing_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + true + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_no_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false + ); + Assert.ok(!shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { + let wasCalled = { + isBrowserPrivate: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + privateBrowsingUtils: { + isBrowserPrivate(browser) { + wasCalled.isBrowserPrivate = true; + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + return true; + }, + }, + }); + + let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); + Assert.ok(isPrivateBrowsingMode); + Assert.ok(wasCalled.isBrowserPrivate); +}); + +add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { + let wasCalled = { + isBrowserPrivate: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + privateBrowsingUtils: { + isBrowserPrivate(browser) { + wasCalled.isBrowserPrivate = true; + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + return false; + }, + }, + }); + + let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); + Assert.ok(!isPrivateBrowsingMode); + Assert.ok(wasCalled.isBrowserPrivate); +}); + +add_task(async function test_helpers_change_password() { + let wasCalled = { + updateUserAccountData: false, + updateDeviceRegistration: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + updateUserAccountData(credentials) { + return new Promise(resolve => { + Assert.ok(credentials.hasOwnProperty("email")); + Assert.ok(credentials.hasOwnProperty("uid")); + Assert.ok(credentials.hasOwnProperty("unwrapBKey")); + Assert.ok(credentials.hasOwnProperty("device")); + Assert.equal(null, credentials.device); + Assert.equal(null, credentials.encryptedSendTabKeys); + // "foo" isn't a field known by storage, so should be dropped. + Assert.ok(!credentials.hasOwnProperty("foo")); + wasCalled.updateUserAccountData = true; + + resolve(); + }); + }, + + updateDeviceRegistration() { + Assert.equal(arguments.length, 0); + wasCalled.updateDeviceRegistration = true; + return Promise.resolve(); + }, + }, + }, + }); + await helpers.changePassword({ + email: "email", + uid: "uid", + unwrapBKey: "unwrapBKey", + foo: "foo", + }); + Assert.ok(wasCalled.updateUserAccountData); + Assert.ok(wasCalled.updateDeviceRegistration); +}); + +add_task(async function test_helpers_change_password_with_error() { + let wasCalled = { + updateUserAccountData: false, + updateDeviceRegistration: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + updateUserAccountData() { + wasCalled.updateUserAccountData = true; + return Promise.reject(); + }, + + updateDeviceRegistration() { + wasCalled.updateDeviceRegistration = true; + return Promise.resolve(); + }, + }, + }, + }); + try { + await helpers.changePassword({}); + Assert.equal(false, "changePassword should have rejected"); + } catch (_) { + Assert.ok(wasCalled.updateUserAccountData); + Assert.ok(!wasCalled.updateDeviceRegistration); + } +}); + +function makeObserver(aObserveTopic, aObserveFunc) { + let callback = function (aSubject, aTopic, aData) { + log.debug("observed " + aTopic + " " + aData); + if (aTopic == aObserveTopic) { + removeMe(); + aObserveFunc(aSubject, aTopic, aData); + } + }; + + function removeMe() { + log.debug("removing observer for " + aObserveTopic); + Services.obs.removeObserver(callback, aObserveTopic); + } + + Services.obs.addObserver(callback, aObserveTopic); + return removeMe; +} + +function validationHelper(params, expected) { + try { + new FxAccountsWebChannel(params); + } catch (e) { + if (typeof expected === "string") { + return Assert.equal(e.toString(), expected); + } + return Assert.ok(e.toString().match(expected)); + } + throw new Error("Validation helper error"); +} diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.toml b/services/fxaccounts/tests/xpcshell/xpcshell.toml new file mode 100644 index 0000000000..7fc9c60006 --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/xpcshell.toml @@ -0,0 +1,49 @@ +[DEFAULT] +head = "head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js" +firefox-appdir = "browser" +skip-if = [ + "os == 'android'", + "appname == 'thunderbird'", +] +support-files = [ + "!/services/common/tests/unit/head_helpers.js", + "!/services/common/tests/unit/head_http.js", +] + +["test_accounts.js"] + +["test_accounts_config.js"] + +["test_accounts_device_registration.js"] + +["test_client.js"] + +["test_commands.js"] + +["test_credentials.js"] + +["test_device.js"] + +["test_keys.js"] + +["test_loginmgr_storage.js"] + +["test_oauth_flow.js"] + +["test_oauth_token_storage.js"] + +["test_oauth_tokens.js"] + +["test_pairing.js"] + +["test_profile.js"] + +["test_profile_client.js"] + +["test_push_service.js"] + +["test_storage_manager.js"] + +["test_telemetry.js"] + +["test_web_channel.js"] diff --git a/services/interfaces/moz.build b/services/interfaces/moz.build new file mode 100644 index 0000000000..18b570f63c --- /dev/null +++ b/services/interfaces/moz.build @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Sync") + +# Services interfaces are shared with other components (like Places and +# WebExtension storage), so we keep them in a separate folder and build them for +# all configurations, regardless of whether we build Sync. + +XPIDL_MODULE = "services" + +XPIDL_SOURCES += [ + "mozIAppServicesLogger.idl", + "mozIBridgedSyncEngine.idl", + "mozIInterruptible.idl", + "mozIServicesLogSink.idl", +] diff --git a/services/interfaces/mozIAppServicesLogger.idl b/services/interfaces/mozIAppServicesLogger.idl new file mode 100644 index 0000000000..17d8c87c3a --- /dev/null +++ b/services/interfaces/mozIAppServicesLogger.idl @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "mozIServicesLogSink.idl" + +[scriptable, uuid(446dd837-fbb0-41e4-8221-f740f672b20d)] +interface mozIAppServicesLogger : nsISupports { + void register(in AString target, in mozIServicesLogSink logger); +}; diff --git a/services/interfaces/mozIBridgedSyncEngine.idl b/services/interfaces/mozIBridgedSyncEngine.idl new file mode 100644 index 0000000000..72683abf22 --- /dev/null +++ b/services/interfaces/mozIBridgedSyncEngine.idl @@ -0,0 +1,160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozIServicesLogSink.idl" +#include "nsISupports.idl" + +interface nsIVariant; + +// A generic callback called with a result. Variants are automatically unboxed +// in JavaScript: for example, a `UTF8String` will be passed as a string +// argument; an `Int32` or `Int64` as a number. Methods that don't return a +// value, like `setLastSync` or `setUploaded`, will pass a `null` variant to +// `handleSuccess`. For all callback types in this file, either `handleSuccess` +// or `handleError` is guaranteed to be called once. +[scriptable, uuid(9b7dd2a3-df99-4469-9ea9-61b222098695)] +interface mozIBridgedSyncEngineCallback : nsISupports { + void handleSuccess(in nsIVariant result); + void handleError(in nsresult code, in AUTF8String message); +}; + +// A callback called after the engine applies incoming records. This is separate +// from `mozIBridgedSyncEngineCallback` because variants can't hold an +// `Array<T>` type. +[scriptable, uuid(2776cdd5-799a-4009-b2f3-356d940a5244)] +interface mozIBridgedSyncEngineApplyCallback : nsISupports { + // Notifies Sync that the bridged engine has finished applying incoming + // records, and has outgoing records. Sync encrypts and uploads these + // records, and notifies the engine that the upload succeeded by + // calling `engine.setUploaded(uploadedOutgoingRecordIds, ...)`. + void handleSuccess(in Array<AUTF8String> outgoingEnvelopesAsJSON); + + // Notifies Sync that the bridged engine failed to apply the staged records. + void handleError(in nsresult code, in AUTF8String message); +}; + +// A bridged engine is implemented in Rust. It handles storage internally, and +// exposes a minimal interface for the JS Sync code to control it. +[scriptable, uuid(3b2b80be-c30e-4498-8065-01809cfe8d47)] +interface mozIBridgedSyncEngine : nsISupports { + // The storage version for this engine's collection. If the version in the + // server's `meta/global` record is newer than ours, we'll refuse to sync, + // since we might not understand the data; if it's older, we'll wipe the + // collection on the server, and upload our data as if on a first sync. + readonly attribute long storageVersion; + + // Whether this engine tolerates skipped records, where a "skipped" record + // is one that would cause the server's published limits to be exceeded + // (for example, a single record where the payload is larger than the + // server accepts.) + // If this returns true, we will just skip the record without even attempting + // to upload. If this is false, we'll abort the entire batch. + // If the engine allows this, it will need to detect this scenario by noticing + // the ID is not in the 'success' records reported to `setUploaded`. + // (Note that this is not to be confused with the fact server's can currently + // reject records as part of a POST - but we hope to remove this ability from + // the server API. Note also that this is not bullet-proof - if the count of + // records is high, it's possible that we will have committed a previous + // batch before we hit the relevant limits, so things might have been written. + // We hope to fix this by ensuring batch limits are such that this is + // impossible) + readonly attribute boolean allowSkippedRecord; + + // Wires up the Sync logging machinery to the bridged engine. This can be + // `null`, in which case any logs from the engine will be discarded. + attribute mozIServicesLogSink logger; + + // Returns the last sync time, in milliseconds, for this engine's + // collection. This is used to build the collection URL for fetching + // incoming records, and as the initial value of the `X-I-U-S` header on + // upload. If the engine persists incoming records in a permanent (non-temp) + // table, `getLastSync` can return a "high water mark" that's the newer of + // the collection's last sync time, and the most recent record modification + // time. This avoids redownloading incoming records that were previously + // downloaded, but not applied. + void getLastSync(in mozIBridgedSyncEngineCallback callback); + + // Sets the last sync time, in milliseconds. This is used to fast-forward + // the last sync time for the engine's collection after fetching all + // records, and after each `setUploaded` call with the `X-L-M` header from + // the server. It may be called multiple times per sync. + void setLastSync(in long long lastSyncMillis, + in mozIBridgedSyncEngineCallback callback); + + // Returns the sync ID for this engine's collection. Used for testing; + // Sync only calls `ensureCurrentSyncId` and `resetSyncId`. On success, + // calls `callback.handleSuccess(in AUTF8String currentSyncId)`. + void getSyncId(in mozIBridgedSyncEngineCallback callback); + + // Generates a new sync ID for this engine, and resets all local Sync + // metadata, including the last sync time and any change flags, to start + // over as a first sync. On success, calls + // `callback.handleSuccess(newSyncId)`, where `newSyncId` is + // `AUTF8String` variant. Sync will upload the new sync ID in the + // `meta/global` record. + void resetSyncId(in mozIBridgedSyncEngineCallback callback); + + // Ensures that the local sync ID for the engine matches the sync ID for + // the collection on the server. On a mismatch, the engine can: + // 1. Reset all local Sync state, adopt `newSyncId` as the new sync ID, + // and call `callback.handleSuccess(newSyncId)`. Most engines should + // do this. + // 2. Ignore the given `newSyncId`, use its existing local sync ID + // without resetting any state, and call + // `callback.handleSuccess(existingSyncId)`. This is useful if, for + // example, the underlying database has been restored from a backup, + // and the engine would like to force a reset and first sync on all + // other devices. + // 3. Ignore the given `newSyncId`, reset all local Sync state, and + // generate a fresh sync ID, as if `resetSyncId`. This resets the + // engine's state everywhere, locally and on all other devices. + // If the callback is called with a different sync ID than `newSyncId`, + // Sync will reupload `meta/global` with the different ID. Otherwise, it + // will assume that the engine has adopted the `newSyncId`, and do nothing. + void ensureCurrentSyncId(in AUTF8String newSyncId, + in mozIBridgedSyncEngineCallback callback); + + // Notifies the engine that sync is starting. The engine can use this method + // to set up temp tables for merging, for example. This will only be called + // once per sync, and before any `storeIncoming` calls. + void syncStarted(in mozIBridgedSyncEngineCallback callback); + + // Stages a batch of incoming records, and calls the `callback` when + // done. This method may be called multiple times per sync, once per + // incoming batch, and always after `syncStarted`. Flushing incoming records + // more often incurs more writes to disk, but avoids redownloading and + // reapplying more records if syncing is interrupted. Typically, engines + // will stage incoming records in an SQLite temp table, and merge them with + // the local database when `apply` is called. + void storeIncoming(in Array<AUTF8String> incomingEnvelopesAsJSON, + in mozIBridgedSyncEngineCallback callback); + + // Applies all the staged records, and calls the `callback` with + // outgoing records to upload. This will always be called after + // `storeIncoming`, and only once per sync. Application should be atomic: + // either all incoming records apply successfully, or none. + void apply(in mozIBridgedSyncEngineApplyCallback callback); + + // Notifies the engine that Sync successfully uploaded the records with the + // given IDs. This method may be called multiple times per sync, once per + // batch upload. This will always be called after `apply`. + void setUploaded(in long long newTimestampMillis, + in Array<AUTF8String> uploadedIds, + in mozIBridgedSyncEngineCallback callback); + + // Notifies the engine that syncing has finished, and the engine shouldn't + // expect any more `setUploaded` calls. At this point, any outgoing records + // that weren't passed to `setUploaded` should be assumed failed. This is + // guaranteed to be called even if the sync fails. This will only be called + // once per sync. + void syncFinished(in mozIBridgedSyncEngineCallback callback); + + // Resets all local Sync metadata, including the sync ID, last sync time, + // and any change flags, but preserves all data. After a reset, the engine will + // sync as if for the first time. + void reset(in mozIBridgedSyncEngineCallback callback); + + // Erases all locally stored data and metadata for this engine. + void wipe(in mozIBridgedSyncEngineCallback callback); +}; diff --git a/services/interfaces/mozIInterruptible.idl b/services/interfaces/mozIInterruptible.idl new file mode 100644 index 0000000000..2894c428d6 --- /dev/null +++ b/services/interfaces/mozIInterruptible.idl @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +// Interrupts all pending operations on a data store, if possible. This is +// provided as a separate interface because the store may want to hide this +// implementation from callers, as interrupting a write can cause data loss. +[scriptable, uuid(1c06bfd3-76b1-46fa-a64a-db682d478374)] +interface mozIInterruptible : nsISupports { + void interrupt(); +}; diff --git a/services/interfaces/mozIServicesLogSink.idl b/services/interfaces/mozIServicesLogSink.idl new file mode 100644 index 0000000000..ea17750552 --- /dev/null +++ b/services/interfaces/mozIServicesLogSink.idl @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +// Adapts a `Log.sys.mjs` logger so that it can be used from native (Rust) code. +// The synced bookmarks mirror and bridged engines implement this interface +// to hook in to the services `LogManager` infrastructure. +[scriptable, uuid(c92bfe0d-50b7-4a7f-9686-fe5335a696b9)] +interface mozIServicesLogSink : nsISupports { + const short LEVEL_OFF = 0; + const short LEVEL_ERROR = 1; + const short LEVEL_WARN = 2; + const short LEVEL_INFO = 3; + const short LEVEL_DEBUG = 4; + const short LEVEL_TRACE = 5; + + attribute short maxLevel; + + void error(in AString message); + void warn(in AString message); + void debug(in AString message); + void trace(in AString message); + void info(in AString message); +}; diff --git a/services/moz.build b/services/moz.build new file mode 100644 index 0000000000..4ef95d88d2 --- /dev/null +++ b/services/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("moz.build"): + BUG_COMPONENT = ("Firefox Build System", "General") + +DIRS += [ + "common", + "crypto", + "interfaces", + "settings", +] + +# The automation dir is only included in nightlies and debug +if not CONFIG["RELEASE_OR_BETA"] or CONFIG["MOZ_DEBUG"]: + DIRS += ["automation"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + DIRS += [ + "fxaccounts", + ] + +if CONFIG["MOZ_SERVICES_SYNC"]: + DIRS += ["sync"] + +SPHINX_TREES["/services"] = "docs" diff --git a/services/settings/Attachments.sys.mjs b/services/settings/Attachments.sys.mjs new file mode 100644 index 0000000000..5ddc6bb046 --- /dev/null +++ b/services/settings/Attachments.sys.mjs @@ -0,0 +1,527 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + RemoteSettingsWorker: + "resource://services-settings/RemoteSettingsWorker.sys.mjs", + Utils: "resource://services-settings/Utils.sys.mjs", +}); + +class DownloadError extends Error { + constructor(url, resp) { + super(`Could not download ${url}`); + this.name = "DownloadError"; + this.resp = resp; + } +} + +class BadContentError extends Error { + constructor(path) { + super(`${path} content does not match server hash`); + this.name = "BadContentError"; + } +} + +class ServerInfoError extends Error { + constructor(error) { + super(`Server response is invalid ${error}`); + this.name = "ServerInfoError"; + this.original = error; + } +} + +// Helper for the `download` method for commonly used methods, to help with +// lazily accessing the record and attachment content. +class LazyRecordAndBuffer { + constructor(getRecordAndLazyBuffer) { + this.getRecordAndLazyBuffer = getRecordAndLazyBuffer; + } + + async _ensureRecordAndLazyBuffer() { + if (!this.recordAndLazyBufferPromise) { + this.recordAndLazyBufferPromise = this.getRecordAndLazyBuffer(); + } + return this.recordAndLazyBufferPromise; + } + + /** + * @returns {object} The attachment record, if found. null otherwise. + **/ + async getRecord() { + try { + return (await this._ensureRecordAndLazyBuffer()).record; + } catch (e) { + return null; + } + } + + /** + * @param {object} requestedRecord An attachment record + * @returns {boolean} Whether the requested record matches this record. + **/ + async isMatchingRequestedRecord(requestedRecord) { + const record = await this.getRecord(); + return ( + record && + record.last_modified === requestedRecord.last_modified && + record.attachment.size === requestedRecord.attachment.size && + record.attachment.hash === requestedRecord.attachment.hash + ); + } + + /** + * Generate the return value for the "download" method. + * + * @throws {*} if the record or attachment content is unavailable. + * @returns {Object} An object with two properties: + * buffer: ArrayBuffer with the file content. + * record: Record associated with the bytes. + **/ + async getResult() { + const { record, readBuffer } = await this._ensureRecordAndLazyBuffer(); + if (!this.bufferPromise) { + this.bufferPromise = readBuffer(); + } + return { record, buffer: await this.bufferPromise }; + } +} + +export class Downloader { + static get DownloadError() { + return DownloadError; + } + static get BadContentError() { + return BadContentError; + } + static get ServerInfoError() { + return ServerInfoError; + } + + constructor(...folders) { + this.folders = ["settings", ...folders]; + this._cdnURLs = {}; + } + + /** + * @returns {Object} An object with async "get", "set" and "delete" methods. + * The keys are strings, the values may be any object that + * can be stored in IndexedDB (including Blob). + */ + get cacheImpl() { + throw new Error("This Downloader does not support caching"); + } + + /** + * Download attachment and return the result together with the record. + * If the requested record cannot be downloaded and fallbacks are enabled, the + * returned attachment may have a different record than the input record. + * + * @param {Object} record A Remote Settings entry with attachment. + * If omitted, the attachmentId option must be set. + * @param {Object} options Some download options. + * @param {Number} options.retries Number of times download should be retried (default: `3`) + * @param {Boolean} options.checkHash Check content integrity (default: `true`) + * @param {string} options.attachmentId The attachment identifier to use for + * caching and accessing the attachment. + * (default: `record.id`) + * @param {Boolean} options.fallbackToCache Return the cached attachment when the + * input record cannot be fetched. + * (default: `false`) + * @param {Boolean} options.fallbackToDump Use the remote settings dump as a + * potential source of the attachment. + * (default: `false`) + * @throws {Downloader.DownloadError} if the file could not be fetched. + * @throws {Downloader.BadContentError} if the downloaded content integrity is not valid. + * @throws {Downloader.ServerInfoError} if the server response is not valid. + * @throws {NetworkError} if fetching the server infos and fetching the attachment fails. + * @returns {Object} An object with two properties: + * `buffer` `ArrayBuffer`: the file content. + * `record` `Object`: record associated with the attachment. + * `_source` `String`: identifies the source of the result. Used for testing. + */ + async download(record, options) { + let { + retries, + checkHash, + attachmentId = record?.id, + fallbackToCache = false, + fallbackToDump = false, + } = options || {}; + if (!attachmentId) { + // Check for pre-condition. This should not happen, but it is explicitly + // checked to avoid mixing up attachments, which could be dangerous. + throw new Error( + "download() was called without attachmentId or `record.id`" + ); + } + + const dumpInfo = new LazyRecordAndBuffer(() => + this._readAttachmentDump(attachmentId) + ); + const cacheInfo = new LazyRecordAndBuffer(() => + this._readAttachmentCache(attachmentId) + ); + + // Check if an attachment dump has been packaged with the client. + // The dump is checked before the cache because dumps are expected to match + // the requested record, at least shortly after the release of the client. + if (fallbackToDump && record) { + if (await dumpInfo.isMatchingRequestedRecord(record)) { + try { + return { ...(await dumpInfo.getResult()), _source: "dump_match" }; + } catch (e) { + // Failed to read dump: record found but attachment file is missing. + console.error(e); + } + } + } + + // Check if the requested attachment has already been cached. + if (record) { + if (await cacheInfo.isMatchingRequestedRecord(record)) { + try { + return { ...(await cacheInfo.getResult()), _source: "cache_match" }; + } catch (e) { + // Failed to read cache, e.g. IndexedDB unusable. + console.error(e); + } + } + } + + let errorIfAllFails; + + // There is no local version that matches the requested record. + // Try to download the attachment specified in record. + if (record && record.attachment) { + try { + const newBuffer = await this.downloadAsBytes(record, { + retries, + checkHash, + }); + const blob = new Blob([newBuffer]); + // Store in cache but don't wait for it before returning. + this.cacheImpl + .set(attachmentId, { record, blob }) + .catch(e => console.error(e)); + return { buffer: newBuffer, record, _source: "remote_match" }; + } catch (e) { + // No network, corrupted content, etc. + errorIfAllFails = e; + } + } + + // Unable to find an attachment that matches the record. Consider falling + // back to local versions, even if their attachment hash do not match the + // one from the requested record. + + // Unable to find a valid attachment, fall back to the cached attachment. + const cacheRecord = fallbackToCache && (await cacheInfo.getRecord()); + if (cacheRecord) { + const dumpRecord = fallbackToDump && (await dumpInfo.getRecord()); + if (dumpRecord?.last_modified >= cacheRecord.last_modified) { + // The dump can be more recent than the cache when the client (and its + // packaged dump) is updated. + try { + return { ...(await dumpInfo.getResult()), _source: "dump_fallback" }; + } catch (e) { + // Failed to read dump: record found but attachment file is missing. + console.error(e); + } + } + + try { + return { ...(await cacheInfo.getResult()), _source: "cache_fallback" }; + } catch (e) { + // Failed to read from cache, e.g. IndexedDB unusable. + console.error(e); + } + } + + // Unable to find a valid attachment, fall back to the packaged dump. + if (fallbackToDump && (await dumpInfo.getRecord())) { + try { + return { ...(await dumpInfo.getResult()), _source: "dump_fallback" }; + } catch (e) { + errorIfAllFails = e; + } + } + + if (errorIfAllFails) { + throw errorIfAllFails; + } + + throw new Downloader.DownloadError(attachmentId); + } + + /** + * Is the record downloaded? This does not check if it was bundled. + * + * @param record A Remote Settings entry with attachment. + * @returns {Promise<boolean>} + */ + isDownloaded(record) { + const cacheInfo = new LazyRecordAndBuffer(() => + this._readAttachmentCache(record.id) + ); + return cacheInfo.isMatchingRequestedRecord(record); + } + + /** + * Delete the record attachment downloaded locally. + * No-op if the attachment does not exist. + * + * @param record A Remote Settings entry with attachment. + * @param {Object} options Some options. + * @param {string} options.attachmentId The attachment identifier to use for + * accessing and deleting the attachment. + * (default: `record.id`) + */ + async deleteDownloaded(record, options) { + let { attachmentId = record?.id } = options || {}; + if (!attachmentId) { + // Check for pre-condition. This should not happen, but it is explicitly + // checked to avoid mixing up attachments, which could be dangerous. + throw new Error( + "deleteDownloaded() was called without attachmentId or `record.id`" + ); + } + return this.cacheImpl.delete(attachmentId); + } + + /** + * Clear the cache from obsolete downloaded attachments. + * + * @param {Array<String>} excludeIds List of attachments IDs to exclude from pruning. + */ + async prune(excludeIds) { + return this.cacheImpl.prune(excludeIds); + } + + /** + * @deprecated See https://bugzilla.mozilla.org/show_bug.cgi?id=1634127 + * + * Download the record attachment into the local profile directory + * and return a file:// URL that points to the local path. + * + * No-op if the file was already downloaded and not corrupted. + * + * @param {Object} record A Remote Settings entry with attachment. + * @param {Object} options Some download options. + * @param {Number} options.retries Number of times download should be retried (default: `3`) + * @throws {Downloader.DownloadError} if the file could not be fetched. + * @throws {Downloader.BadContentError} if the downloaded file integrity is not valid. + * @throws {Downloader.ServerInfoError} if the server response is not valid. + * @throws {NetworkError} if fetching the attachment fails. + * @returns {String} the absolute file path to the downloaded attachment. + */ + async downloadToDisk(record, options = {}) { + const { retries = 3 } = options; + const { + attachment: { filename, size, hash }, + } = record; + const localFilePath = PathUtils.join( + PathUtils.localProfileDir, + ...this.folders, + filename + ); + const localFileUrl = PathUtils.toFileURI(localFilePath); + + await this._makeDirs(); + + let retried = 0; + while (true) { + if ( + await lazy.RemoteSettingsWorker.checkFileHash(localFileUrl, size, hash) + ) { + return localFileUrl; + } + // File does not exist or is corrupted. + if (retried > retries) { + throw new Downloader.BadContentError(localFilePath); + } + try { + // Download and write on disk. + const buffer = await this.downloadAsBytes(record, { + checkHash: false, // Hash will be checked on file. + retries: 0, // Already in a retry loop. + }); + await IOUtils.write(localFilePath, new Uint8Array(buffer), { + tmpPath: `${localFilePath}.tmp`, + }); + } catch (e) { + if (retried >= retries) { + throw e; + } + } + retried++; + } + } + + /** + * Download the record attachment and return its content as bytes. + * + * @param {Object} record A Remote Settings entry with attachment. + * @param {Object} options Some download options. + * @param {Number} options.retries Number of times download should be retried (default: `3`) + * @param {Boolean} options.checkHash Check content integrity (default: `true`) + * @throws {Downloader.DownloadError} if the file could not be fetched. + * @throws {Downloader.BadContentError} if the downloaded content integrity is not valid. + * @returns {ArrayBuffer} the file content. + */ + async downloadAsBytes(record, options = {}) { + const { + attachment: { location, hash, size }, + } = record; + + const remoteFileUrl = (await this._baseAttachmentsURL()) + location; + + const { retries = 3, checkHash = true } = options; + let retried = 0; + while (true) { + try { + const buffer = await this._fetchAttachment(remoteFileUrl); + if (!checkHash) { + return buffer; + } + if ( + await lazy.RemoteSettingsWorker.checkContentHash(buffer, size, hash) + ) { + return buffer; + } + // Content is corrupted. + throw new Downloader.BadContentError(location); + } catch (e) { + if (retried >= retries) { + throw e; + } + } + retried++; + } + } + + /** + * @deprecated See https://bugzilla.mozilla.org/show_bug.cgi?id=1634127 + * + * Delete the record attachment downloaded locally. + * This is the counterpart of `downloadToDisk()`. + * Use `deleteDownloaded()` if `download()` was used to retrieve + * the attachment. + * + * No-op if the related file does not exist. + * + * @param record A Remote Settings entry with attachment. + */ + async deleteFromDisk(record) { + const { + attachment: { filename }, + } = record; + const path = PathUtils.join( + PathUtils.localProfileDir, + ...this.folders, + filename + ); + await IOUtils.remove(path); + await this._rmDirs(); + } + + async _baseAttachmentsURL() { + if (!this._cdnURLs[lazy.Utils.SERVER_URL]) { + const resp = await lazy.Utils.fetch(`${lazy.Utils.SERVER_URL}/`); + let serverInfo; + try { + serverInfo = await resp.json(); + } catch (error) { + throw new Downloader.ServerInfoError(error); + } + // Server capabilities expose attachments configuration. + const { + capabilities: { + attachments: { base_url }, + }, + } = serverInfo; + // Make sure the URL always has a trailing slash. + this._cdnURLs[lazy.Utils.SERVER_URL] = + base_url + (base_url.endsWith("/") ? "" : "/"); + } + return this._cdnURLs[lazy.Utils.SERVER_URL]; + } + + async _fetchAttachment(url) { + const headers = new Headers(); + headers.set("Accept-Encoding", "gzip"); + const resp = await lazy.Utils.fetch(url, { headers }); + if (!resp.ok) { + throw new Downloader.DownloadError(url, resp); + } + return resp.arrayBuffer(); + } + + async _readAttachmentCache(attachmentId) { + const cached = await this.cacheImpl.get(attachmentId); + if (!cached) { + throw new Downloader.DownloadError(attachmentId); + } + return { + record: cached.record, + async readBuffer() { + const buffer = await cached.blob.arrayBuffer(); + const { size, hash } = cached.record.attachment; + if ( + await lazy.RemoteSettingsWorker.checkContentHash(buffer, size, hash) + ) { + return buffer; + } + // Really unexpected, could indicate corruption in IndexedDB. + throw new Downloader.BadContentError(attachmentId); + }, + }; + } + + async _readAttachmentDump(attachmentId) { + async function fetchResource(resourceUrl) { + try { + return await fetch(resourceUrl); + } catch (e) { + throw new Downloader.DownloadError(resourceUrl); + } + } + const resourceUrlPrefix = + Downloader._RESOURCE_BASE_URL + "/" + this.folders.join("/") + "/"; + const recordUrl = `${resourceUrlPrefix}${attachmentId}.meta.json`; + const attachmentUrl = `${resourceUrlPrefix}${attachmentId}`; + const record = await (await fetchResource(recordUrl)).json(); + return { + record, + async readBuffer() { + return (await fetchResource(attachmentUrl)).arrayBuffer(); + }, + }; + } + + // Separate variable to allow tests to override this. + static _RESOURCE_BASE_URL = "resource://app/defaults"; + + async _makeDirs() { + const dirPath = PathUtils.join(PathUtils.localProfileDir, ...this.folders); + await IOUtils.makeDirectory(dirPath, { createAncestors: true }); + } + + async _rmDirs() { + for (let i = this.folders.length; i > 0; i--) { + const dirPath = PathUtils.join( + PathUtils.localProfileDir, + ...this.folders.slice(0, i) + ); + try { + await IOUtils.remove(dirPath); + } catch (e) { + // This could fail if there's something in + // the folder we're not permitted to remove. + break; + } + } + } +} diff --git a/services/settings/Database.sys.mjs b/services/settings/Database.sys.mjs new file mode 100644 index 0000000000..e8a89028bc --- /dev/null +++ b/services/settings/Database.sys.mjs @@ -0,0 +1,602 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + CommonUtils: "resource://services-common/utils.sys.mjs", + IDBHelpers: "resource://services-settings/IDBHelpers.sys.mjs", + ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", + Utils: "resource://services-settings/Utils.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log); + +/** + * Database is a tiny wrapper with the objective + * of providing major kinto-offline-client collection API. + * (with the objective of getting rid of kinto-offline-client) + */ +export class Database { + static destroy() { + return destroyIDB(); + } + + constructor(identifier) { + ensureShutdownBlocker(); + this.identifier = identifier; + } + + async list(options = {}) { + const { filters = {}, order = "" } = options; + let results = []; + try { + await executeIDB( + "records", + (store, rejectTransaction) => { + // Fast-path the (very common) no-filters case + if (lazy.ObjectUtils.isEmpty(filters)) { + const range = IDBKeyRange.only(this.identifier); + const request = store.index("cid").getAll(range); + request.onsuccess = e => { + results = e.target.result; + }; + return; + } + const request = store + .index("cid") + .openCursor(IDBKeyRange.only(this.identifier)); + const objFilters = transformSubObjectFilters(filters); + request.onsuccess = event => { + try { + const cursor = event.target.result; + if (cursor) { + const { value } = cursor; + if (lazy.Utils.filterObject(objFilters, value)) { + results.push(value); + } + cursor.continue(); + } + } catch (ex) { + rejectTransaction(ex); + } + }; + }, + { mode: "readonly" } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "list()", this.identifier); + } + // Remove IDB key field from results. + for (const result of results) { + delete result._cid; + } + return order ? lazy.Utils.sortObjects(order, results) : results; + } + + async importChanges(metadata, timestamp, records = [], options = {}) { + const { clear = false } = options; + const _cid = this.identifier; + try { + await executeIDB( + ["collections", "timestamps", "records"], + (stores, rejectTransaction) => { + const [storeMetadata, storeTimestamps, storeRecords] = stores; + + if (clear) { + // Our index is over the _cid and id fields. We want to remove + // all of the items in the collection for which the object was + // created, ie with _cid == this.identifier. + // We would like to just tell IndexedDB: + // store.index(IDBKeyRange.only(this.identifier)).delete(); + // to delete all records matching the first part of the 2-part key. + // Unfortunately such an API does not exist. + // While we could iterate over the index with a cursor, we'd do + // a roundtrip to PBackground for each item. Once you have 1000 + // items, the result is very slow because of all the overhead of + // jumping between threads and serializing/deserializing. + // So instead, we tell the store to delete everything between + // "our" _cid identifier, and what would be the next identifier + // (via lexicographical sorting). Unfortunately there does not + // seem to be a way to specify bounds for all items that share + // the same first part of the key using just that first part, hence + // the use of the hypothetical [] for the second part of the end of + // the bounds. + storeRecords.delete( + IDBKeyRange.bound([_cid], [_cid, []], false, true) + ); + } + + // Store or erase metadata. + if (metadata === null) { + storeMetadata.delete(_cid); + } else if (metadata) { + storeMetadata.put({ cid: _cid, metadata }); + } + // Store or erase timestamp. + if (timestamp === null) { + storeTimestamps.delete(_cid); + } else if (timestamp) { + storeTimestamps.put({ cid: _cid, value: timestamp }); + } + + if (!records.length) { + return; + } + + // Separate tombstones from creations/updates. + const toDelete = records.filter(r => r.deleted); + const toInsert = records.filter(r => !r.deleted); + lazy.console.debug( + `${_cid} ${toDelete.length} to delete, ${toInsert.length} to insert` + ); + // Delete local records for each tombstone. + lazy.IDBHelpers.bulkOperationHelper( + storeRecords, + { + reject: rejectTransaction, + completion() { + // Overwrite all other data. + lazy.IDBHelpers.bulkOperationHelper( + storeRecords, + { + reject: rejectTransaction, + }, + "put", + toInsert.map(item => ({ ...item, _cid })) + ); + }, + }, + "delete", + toDelete.map(item => [_cid, item.id]) + ); + }, + { desc: "importChanges() in " + _cid } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "importChanges()", _cid); + } + } + + async getLastModified() { + let entry = null; + try { + await executeIDB( + "timestamps", + store => { + store.get(this.identifier).onsuccess = e => (entry = e.target.result); + }, + { mode: "readonly" } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError( + e, + "getLastModified()", + this.identifier + ); + } + if (!entry) { + return null; + } + // Some distributions where released with a modified dump that did not + // contain timestamps for last_modified. Work around this here, and return + // the timestamp as zero, so that the entries should get updated. + if (isNaN(entry.value)) { + lazy.console.warn(`Local timestamp is NaN for ${this.identifier}`); + return 0; + } + return entry.value; + } + + async getMetadata() { + let entry = null; + try { + await executeIDB( + "collections", + store => { + store.get(this.identifier).onsuccess = e => (entry = e.target.result); + }, + { mode: "readonly" } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError( + e, + "getMetadata()", + this.identifier + ); + } + return entry ? entry.metadata : null; + } + + async getAttachment(attachmentId) { + let entry = null; + try { + await executeIDB( + "attachments", + store => { + store.get([this.identifier, attachmentId]).onsuccess = e => { + entry = e.target.result; + }; + }, + { mode: "readonly" } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError( + e, + "getAttachment()", + this.identifier + ); + } + return entry ? entry.attachment : null; + } + + async saveAttachment(attachmentId, attachment) { + try { + await executeIDB( + "attachments", + store => { + if (attachment) { + store.put({ cid: this.identifier, attachmentId, attachment }); + } else { + store.delete([this.identifier, attachmentId]); + } + }, + { desc: "saveAttachment(" + attachmentId + ") in " + this.identifier } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError( + e, + "saveAttachment()", + this.identifier + ); + } + } + + /** + * Delete all attachments which don't match any record. + * + * Attachments are linked to records, except when a fixed `attachmentId` is used. + * A record can be updated or deleted, potentially by deleting a record and restoring an updated version + * of the record with the same ID. Potentially leaving orphaned attachments in the database. + * Since we run the pruning logic after syncing, any attachment without a + * matching record can be discarded as they will be unreachable forever. + * + * @param {Array<String>} excludeIds List of attachments IDs to exclude from pruning. + */ + async pruneAttachments(excludeIds) { + const _cid = this.identifier; + let deletedCount = 0; + try { + await executeIDB( + ["attachments", "records"], + async (stores, rejectTransaction) => { + const [attachmentsStore, recordsStore] = stores; + + // List all stored attachments. + // All keys ≥ [_cid, ..] && < [_cid, []]. See comment in `importChanges()` + const rangeAllKeys = IDBKeyRange.bound( + [_cid], + [_cid, []], + false, + true + ); + const allAttachments = await new Promise((resolve, reject) => { + const request = attachmentsStore.getAll(rangeAllKeys); + request.onsuccess = e => resolve(e.target.result); + request.onerror = e => reject(e); + }); + if (!allAttachments.length) { + lazy.console.debug( + `${this.identifier} No attachments in IDB cache. Nothing to do.` + ); + return; + } + + // List all stored records. + const allRecords = await new Promise((resolve, reject) => { + const rangeAllIndexed = IDBKeyRange.only(_cid); + const request = recordsStore.index("cid").getAll(rangeAllIndexed); + request.onsuccess = e => resolve(e.target.result); + request.onerror = e => reject(e); + }); + + // Compare known records IDs to those stored along the attachments. + const currentRecordsIDs = new Set(allRecords.map(r => r.id)); + const attachmentsToDelete = allAttachments.reduce((acc, entry) => { + // Skip excluded attachments. + if (excludeIds.includes(entry.attachmentId)) { + return acc; + } + // Delete attachment if associated record does not exist. + if (!currentRecordsIDs.has(entry.attachment.record.id)) { + acc.push([_cid, entry.attachmentId]); + } + return acc; + }, []); + + // Perform a bulk delete of all obsolete attachments. + lazy.console.debug( + `${this.identifier} Bulk delete ${attachmentsToDelete.length} obsolete attachments` + ); + lazy.IDBHelpers.bulkOperationHelper( + attachmentsStore, + { + reject: rejectTransaction, + }, + "delete", + attachmentsToDelete + ); + deletedCount = attachmentsToDelete.length; + }, + { desc: "pruneAttachments() in " + this.identifier } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError( + e, + "pruneAttachments()", + this.identifier + ); + } + return deletedCount; + } + + async clear() { + try { + await this.importChanges(null, null, [], { clear: true }); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "clear()", this.identifier); + } + } + + /* + * Methods used by unit tests. + */ + + async create(record) { + if (!("id" in record)) { + record = { ...record, id: lazy.CommonUtils.generateUUID() }; + } + try { + await executeIDB( + "records", + store => { + store.add({ ...record, _cid: this.identifier }); + }, + { desc: "create() in " + this.identifier } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "create()", this.identifier); + } + return record; + } + + async update(record) { + try { + await executeIDB( + "records", + store => { + store.put({ ...record, _cid: this.identifier }); + }, + { desc: "update() in " + this.identifier } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "update()", this.identifier); + } + } + + async delete(recordId) { + try { + await executeIDB( + "records", + store => { + store.delete([this.identifier, recordId]); // [_cid, id] + }, + { desc: "delete() in " + this.identifier } + ); + } catch (e) { + throw new lazy.IDBHelpers.IndexedDBError(e, "delete()", this.identifier); + } + } +} + +let gDB = null; +let gDBPromise = null; + +/** + * This function attempts to ensure `gDB` points to a valid database value. + * If gDB is already a database, it will do no-op (but this may take a + * microtask or two). + * If opening the database fails, it will throw an IndexedDBError. + */ +async function openIDB() { + // We can be called multiple times in a race; always ensure that when + // we complete, `gDB` is no longer null, but avoid doing the actual + // IndexedDB work more than once. + if (!gDBPromise) { + // Open and initialize/upgrade if needed. + gDBPromise = lazy.IDBHelpers.openIDB(); + } + let db = await gDBPromise; + if (!gDB) { + gDB = db; + } +} + +const gPendingReadOnlyTransactions = new Set(); +const gPendingWriteOperations = new Set(); +/** + * Helper to wrap some IDBObjectStore operations into a promise. + * + * @param {IDBDatabase} db + * @param {String|String[]} storeNames - either a string or an array of strings. + * @param {function} callback + * @param {Object} options + * @param {String} options.mode + * @param {String} options.desc for shutdown tracking. + */ +async function executeIDB(storeNames, callback, options = {}) { + if (!gDB) { + // Check if we're shutting down. Services.startup.shuttingDown will + // be true sooner, but is never true in xpcshell tests, so we check + // both that and a bool we set ourselves when `profile-before-change` + // starts. + if (gShutdownStarted || Services.startup.shuttingDown) { + throw new lazy.IDBHelpers.ShutdownError( + "The application is shutting down", + "execute()" + ); + } + await openIDB(); + } else { + // Even if we have a db, wait a tick to avoid making IndexedDB sad. + // We should be able to remove this once bug 1626935 is fixed. + await Promise.resolve(); + } + + // Check for shutdown again as we've await'd something... + if (!gDB && (gShutdownStarted || Services.startup.shuttingDown)) { + throw new lazy.IDBHelpers.ShutdownError( + "The application is shutting down", + "execute()" + ); + } + + // Start the actual transaction: + const { mode = "readwrite", desc = "" } = options; + let { promise, transaction } = lazy.IDBHelpers.executeIDB( + gDB, + storeNames, + mode, + callback, + desc + ); + + // We track all readonly transactions and abort them at shutdown. + // We track all readwrite ones and await their completion at shutdown + // (to avoid dataloss when writes fail). + // We use a `.finally()` clause for this; it'll run the function irrespective + // of whether the promise resolves or rejects, and the promise it returns + // will resolve/reject with the same value. + let finishedFn; + if (mode == "readonly") { + gPendingReadOnlyTransactions.add(transaction); + finishedFn = () => gPendingReadOnlyTransactions.delete(transaction); + } else { + let obj = { promise, desc }; + gPendingWriteOperations.add(obj); + finishedFn = () => gPendingWriteOperations.delete(obj); + } + return promise.finally(finishedFn); +} + +async function destroyIDB() { + if (gDB) { + if (gShutdownStarted || Services.startup.shuttingDown) { + throw new lazy.IDBHelpers.ShutdownError( + "The application is shutting down", + "destroyIDB()" + ); + } + + // This will return immediately; the actual close will happen once + // there are no more running transactions. + gDB.close(); + const allTransactions = new Set([ + ...gPendingWriteOperations, + ...gPendingReadOnlyTransactions, + ]); + for (let transaction of Array.from(allTransactions)) { + try { + transaction.abort(); + } catch (ex) { + // Ignore errors to abort transactions, we'll destroy everything. + } + } + } + gDB = null; + gDBPromise = null; + return lazy.IDBHelpers.destroyIDB(); +} + +function makeNestedObjectFromArr(arr, val, nestedFiltersObj) { + const last = arr.length - 1; + return arr.reduce((acc, cv, i) => { + if (i === last) { + return (acc[cv] = val); + } else if (Object.prototype.hasOwnProperty.call(acc, cv)) { + return acc[cv]; + } + return (acc[cv] = {}); + }, nestedFiltersObj); +} + +function transformSubObjectFilters(filtersObj) { + const transformedFilters = {}; + for (const [key, val] of Object.entries(filtersObj)) { + const keysArr = key.split("."); + makeNestedObjectFromArr(keysArr, val, transformedFilters); + } + return transformedFilters; +} + +// We need to expose this wrapper function so we can test +// shutdown handling. +Database._executeIDB = executeIDB; + +let gShutdownStarted = false; +// Test-only helper to be able to test shutdown multiple times: +Database._cancelShutdown = () => { + gShutdownStarted = false; +}; + +let gShutdownBlocker = false; +Database._shutdownHandler = () => { + gShutdownStarted = true; + const NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR = 0x80660006; + // Duplicate the list (to avoid it being modified) and then + // abort all read-only transactions. + for (let transaction of Array.from(gPendingReadOnlyTransactions)) { + try { + transaction.abort(); + } catch (ex) { + // Ensure we don't throw/break, because either way we're in shutdown. + + // In particular, `transaction.abort` can throw if the transaction + // is complete, ie if we manage to get called in between the + // transaction completing, and our completion handler being called + // to remove the item from the set. We don't care about that. + if (ex.result != NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR) { + // Report any other errors: + console.error(ex); + } + } + } + if (gDB) { + // This will return immediately; the actual close will happen once + // there are no more running transactions. + gDB.close(); + gDB = null; + } + gDBPromise = null; + return Promise.allSettled( + Array.from(gPendingWriteOperations).map(op => op.promise) + ); +}; + +function ensureShutdownBlocker() { + if (gShutdownBlocker) { + return; + } + gShutdownBlocker = true; + lazy.AsyncShutdown.profileBeforeChange.addBlocker( + "RemoteSettingsClient - finish IDB access.", + Database._shutdownHandler, + { + fetchState() { + return Array.from(gPendingWriteOperations).map(op => op.desc); + }, + } + ); +} diff --git a/services/settings/IDBHelpers.sys.mjs b/services/settings/IDBHelpers.sys.mjs new file mode 100644 index 0000000000..6f2ef6937d --- /dev/null +++ b/services/settings/IDBHelpers.sys.mjs @@ -0,0 +1,212 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const DB_NAME = "remote-settings"; +const DB_VERSION = 3; + +/** + * Wrap IndexedDB errors to catch them more easily. + */ +class IndexedDBError extends Error { + constructor(error, method = "", identifier = "") { + if (typeof error == "string") { + error = new Error(error); + } + super(`IndexedDB: ${identifier} ${method} ${error && error.message}`); + this.name = error.name; + this.stack = error.stack; + } +} + +class ShutdownError extends IndexedDBError { + constructor(error, method = "", identifier = "") { + super(error, method, identifier); + } +} + +// We batch operations in order to reduce round-trip latency to the IndexedDB +// database thread. The trade-offs are that the more records in the batch, the +// more time we spend on this thread in structured serialization, and the +// greater the chance to jank PBackground and this thread when the responses +// come back. The initial choice of 250 was made targeting 2-3ms on a fast +// machine and 10-15ms on a slow machine. +// Every chunk waits for success before starting the next, and +// the final chunk's completion will fire transaction.oncomplete . +function bulkOperationHelper( + store, + { reject, completion }, + operation, + list, + listIndex = 0 +) { + try { + const CHUNK_LENGTH = 250; + const max = Math.min(listIndex + CHUNK_LENGTH, list.length); + let request; + for (; listIndex < max; listIndex++) { + request = store[operation](list[listIndex]); + } + if (listIndex < list.length) { + // On error, `transaction.onerror` is called. + request.onsuccess = bulkOperationHelper.bind( + null, + store, + { reject, completion }, + operation, + list, + listIndex + ); + } else if (completion) { + completion(); + } + // otherwise, we're done, and the transaction will complete on its own. + } catch (e) { + // The executeIDB callsite has a try... catch, but it will not catch + // errors in subsequent bulkOperationHelper calls chained through + // request.onsuccess above. We do want to catch those, so we have to + // feed them through manually. We cannot use an async function with + // promises, because if we wait a microtask after onsuccess fires to + // put more requests on the transaction, the transaction will auto-commit + // before we can add more requests. + reject(e); + } +} + +/** + * Helper to wrap some IDBObjectStore operations into a promise. + * + * @param {IDBDatabase} db + * @param {String|String[]} storeNames - either a string or an array of strings. + * @param {String} mode + * @param {function} callback + * @param {String} description of the operation for error handling purposes. + */ +function executeIDB(db, storeNames, mode, callback, desc) { + if (!Array.isArray(storeNames)) { + storeNames = [storeNames]; + } + const transaction = db.transaction(storeNames, mode); + let promise = new Promise((resolve, reject) => { + let stores = storeNames.map(name => transaction.objectStore(name)); + let result; + let rejectWrapper = e => { + reject(new IndexedDBError(e, desc || "execute()", storeNames.join(", "))); + try { + transaction.abort(); + } catch (ex) { + console.error(ex); + } + }; + // Add all the handlers before using the stores. + transaction.onerror = event => + reject(new IndexedDBError(event.target.error, desc || "execute()")); + transaction.onabort = event => + reject( + new IndexedDBError( + event.target.error || transaction.error || "IDBTransaction aborted", + desc || "execute()" + ) + ); + transaction.oncomplete = event => resolve(result); + // Simplify access to a single datastore: + if (stores.length == 1) { + stores = stores[0]; + } + try { + // Although this looks sync, once the callback places requests + // on the datastore, it can independently keep the transaction alive and + // keep adding requests. Even once we exit this try.. catch, we may + // therefore experience errors which should abort the transaction. + // This is why we pass the rejection handler - then the consumer can + // continue to ensure that errors are handled appropriately. + // In theory, exceptions thrown from onsuccess handlers should also + // cause IndexedDB to abort the transaction, so this is a belt-and-braces + // approach. + result = callback(stores, rejectWrapper); + } catch (e) { + rejectWrapper(e); + } + }); + return { promise, transaction }; +} + +/** + * Helper to wrap indexedDB.open() into a promise. + */ +async function openIDB(allowUpgrades = true) { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + request.onupgradeneeded = event => { + if (!allowUpgrades) { + reject( + new Error( + `IndexedDB: Error accessing ${DB_NAME} IDB at version ${DB_VERSION}` + ) + ); + return; + } + // When an upgrade is needed, a transaction is started. + const transaction = event.target.transaction; + transaction.onabort = event => { + const error = + event.target.error || + transaction.error || + new DOMException("The operation has been aborted", "AbortError"); + reject(new IndexedDBError(error, "open()")); + }; + + const db = event.target.result; + db.onerror = event => reject(new IndexedDBError(event.target.error)); + + if (event.oldVersion < 1) { + // Records store + const recordsStore = db.createObjectStore("records", { + keyPath: ["_cid", "id"], + }); + // An index to obtain all the records in a collection. + recordsStore.createIndex("cid", "_cid"); + // Last modified field + recordsStore.createIndex("last_modified", ["_cid", "last_modified"]); + // Timestamps store + db.createObjectStore("timestamps", { + keyPath: "cid", + }); + } + if (event.oldVersion < 2) { + // Collections store + db.createObjectStore("collections", { + keyPath: "cid", + }); + } + if (event.oldVersion < 3) { + // Attachment store + db.createObjectStore("attachments", { + keyPath: ["cid", "attachmentId"], + }); + } + }; + request.onerror = event => reject(new IndexedDBError(event.target.error)); + request.onsuccess = event => { + const db = event.target.result; + resolve(db); + }; + }); +} + +function destroyIDB() { + const request = indexedDB.deleteDatabase(DB_NAME); + return new Promise((resolve, reject) => { + request.onerror = event => reject(new IndexedDBError(event.target.error)); + request.onsuccess = () => resolve(); + }); +} + +export var IDBHelpers = { + bulkOperationHelper, + executeIDB, + openIDB, + destroyIDB, + IndexedDBError, + ShutdownError, +}; diff --git a/services/settings/RemoteSettings.worker.mjs b/services/settings/RemoteSettings.worker.mjs new file mode 100644 index 0000000000..66228f226e --- /dev/null +++ b/services/settings/RemoteSettings.worker.mjs @@ -0,0 +1,196 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * A worker dedicated to Remote Settings. + */ + +// These files are imported into the worker scope, and are not shared singletons +// with the main thread. +/* eslint-disable mozilla/reject-import-system-module-from-non-system */ +import { CanonicalJSON } from "resource://gre/modules/CanonicalJSON.sys.mjs"; +import { IDBHelpers } from "resource://services-settings/IDBHelpers.sys.mjs"; +import { SharedUtils } from "resource://services-settings/SharedUtils.sys.mjs"; +import { jsesc } from "resource://gre/modules/third_party/jsesc/jsesc.mjs"; +/* eslint-enable mozilla/reject-import-system-module-from-non-system */ + +const IDB_RECORDS_STORE = "records"; +const IDB_TIMESTAMPS_STORE = "timestamps"; + +let gShutdown = false; + +const Agent = { + /** + * Return the canonical JSON serialization of the specified records. + * It has to match what is done on the server (See Kinto/kinto-signer). + * + * @param {Array<Object>} records + * @param {String} timestamp + * @returns {String} + */ + async canonicalStringify(records, timestamp) { + // Sort list by record id. + let allRecords = records.sort((a, b) => { + if (a.id < b.id) { + return -1; + } + return a.id > b.id ? 1 : 0; + }); + // All existing records are replaced by the version from the server + // and deleted records are removed. + for (let i = 0; i < allRecords.length /* no increment! */; ) { + const rec = allRecords[i]; + const next = allRecords[i + 1]; + if ((next && rec.id == next.id) || rec.deleted) { + allRecords.splice(i, 1); // remove local record + } else { + i++; + } + } + const toSerialize = { + last_modified: "" + timestamp, + data: allRecords, + }; + return CanonicalJSON.stringify(toSerialize, jsesc); + }, + + /** + * If present, import the JSON file into the Remote Settings IndexedDB + * for the specified bucket and collection. + * (eg. blocklists/certificates, main/onboarding) + * @param {String} bucket + * @param {String} collection + * @returns {int} Number of records loaded from dump or -1 if no dump found. + */ + async importJSONDump(bucket, collection) { + const { data: records, timestamp } = await SharedUtils.loadJSONDump( + bucket, + collection + ); + if (records === null) { + // Return -1 if file is missing. + return -1; + } + if (gShutdown) { + throw new Error("Can't import when we've started shutting down."); + } + await importDumpIDB(bucket, collection, records, timestamp); + return records.length; + }, + + /** + * Check that the specified file matches the expected size and SHA-256 hash. + * @param {String} fileUrl file URL to read from + * @param {Number} size expected file size + * @param {String} size expected file SHA-256 as hex string + * @returns {boolean} + */ + async checkFileHash(fileUrl, size, hash) { + let resp; + try { + resp = await fetch(fileUrl); + } catch (e) { + // File does not exist. + return false; + } + const buffer = await resp.arrayBuffer(); + return SharedUtils.checkContentHash(buffer, size, hash); + }, + + async prepareShutdown() { + gShutdown = true; + // Ensure we can iterate and abort (which may delete items) by cloning + // the list. + let transactions = Array.from(gPendingTransactions); + for (let transaction of transactions) { + try { + transaction.abort(); + } catch (ex) { + // We can hit this case if the transaction has finished but + // we haven't heard about it yet. + } + } + }, + + _test_only_import(bucket, collection, records, timestamp) { + return importDumpIDB(bucket, collection, records, timestamp); + }, +}; + +/** + * Wrap worker invocations in order to return the `callbackId` along + * the result. This will allow to transform the worker invocations + * into promises in `RemoteSettingsWorker.sys.mjs`. + */ +self.onmessage = event => { + const { callbackId, method, args = [] } = event.data; + Agent[method](...args) + .then(result => { + self.postMessage({ callbackId, result }); + }) + .catch(error => { + console.log(`RemoteSettingsWorker error: ${error}`); + self.postMessage({ callbackId, error: "" + error }); + }); +}; + +let gPendingTransactions = new Set(); + +/** + * Import the records into the Remote Settings Chrome IndexedDB. + * + * Note: This duplicates some logics from `kinto-offline-client.js`. + * + * @param {String} bucket + * @param {String} collection + * @param {Array<Object>} records + * @param {Number} timestamp + */ +async function importDumpIDB(bucket, collection, records, timestamp) { + // Open the DB. It will exist since if we are running this, it means + // we already tried to read the timestamp in `remote-settings.sys.mjs` + const db = await IDBHelpers.openIDB(false /* do not allow upgrades */); + + // try...finally to ensure we always close the db. + try { + if (gShutdown) { + throw new Error("Can't import when we've started shutting down."); + } + + // Each entry of the dump will be stored in the records store. + // They are indexed by `_cid`. + const cid = bucket + "/" + collection; + // We can just modify the items in-place, as we got them from SharedUtils.loadJSONDump(). + records.forEach(item => { + item._cid = cid; + }); + // Store the collection timestamp. + let { transaction, promise } = IDBHelpers.executeIDB( + db, + [IDB_RECORDS_STORE, IDB_TIMESTAMPS_STORE], + "readwrite", + ([recordsStore, timestampStore], rejectTransaction) => { + // Wipe before loading + recordsStore.delete(IDBKeyRange.bound([cid], [cid, []], false, true)); + IDBHelpers.bulkOperationHelper( + recordsStore, + { + reject: rejectTransaction, + completion() { + timestampStore.put({ cid, value: timestamp }); + }, + }, + "put", + records + ); + } + ); + gPendingTransactions.add(transaction); + promise = promise.finally(() => gPendingTransactions.delete(transaction)); + await promise; + } finally { + // Close now that we're done. + db.close(); + } +} diff --git a/services/settings/RemoteSettingsClient.sys.mjs b/services/settings/RemoteSettingsClient.sys.mjs new file mode 100644 index 0000000000..7e95b9baab --- /dev/null +++ b/services/settings/RemoteSettingsClient.sys.mjs @@ -0,0 +1,1281 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +import { Downloader } from "resource://services-settings/Attachments.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + ClientEnvironmentBase: + "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs", + Database: "resource://services-settings/Database.sys.mjs", + IDBHelpers: "resource://services-settings/IDBHelpers.sys.mjs", + KintoHttpClient: "resource://services-common/kinto-http-client.sys.mjs", + ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", + RemoteSettingsWorker: + "resource://services-settings/RemoteSettingsWorker.sys.mjs", + SharedUtils: "resource://services-settings/SharedUtils.sys.mjs", + UptakeTelemetry: "resource://services-common/uptake-telemetry.sys.mjs", + Utils: "resource://services-settings/Utils.sys.mjs", +}); + +const TELEMETRY_COMPONENT = "remotesettings"; + +ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log); + +/** + * cacheProxy returns an object Proxy that will memoize properties of the target. + * @param {Object} target the object to wrap. + * @returns {Proxy} + */ +function cacheProxy(target) { + const cache = new Map(); + return new Proxy(target, { + get(target, prop, receiver) { + if (!cache.has(prop)) { + cache.set(prop, target[prop]); + } + return cache.get(prop); + }, + }); +} + +/** + * Minimalist event emitter. + * + * Note: we don't use `toolkit/modules/EventEmitter` because **we want** to throw + * an error when a listener fails to execute. + */ +class EventEmitter { + constructor(events) { + this._listeners = new Map(); + for (const event of events) { + this._listeners.set(event, []); + } + } + + /** + * Event emitter: will execute the registered listeners in the order and + * sequentially. + * + * @param {string} event the event name + * @param {Object} payload the event payload to call the listeners with + */ + async emit(event, payload) { + const callbacks = this._listeners.get(event); + let lastError; + for (const cb of callbacks) { + try { + await cb(payload); + } catch (e) { + lastError = e; + } + } + if (lastError) { + throw lastError; + } + } + + hasListeners(event) { + return this._listeners.has(event) && !!this._listeners.get(event).length; + } + + on(event, callback) { + if (!this._listeners.has(event)) { + throw new Error(`Unknown event type ${event}`); + } + this._listeners.get(event).push(callback); + } + + off(event, callback) { + if (!this._listeners.has(event)) { + throw new Error(`Unknown event type ${event}`); + } + const callbacks = this._listeners.get(event); + const i = callbacks.indexOf(callback); + if (i < 0) { + throw new Error(`Unknown callback`); + } else { + callbacks.splice(i, 1); + } + } +} + +class APIError extends Error {} + +class NetworkError extends APIError { + constructor(e) { + super(`Network error: ${e}`, { cause: e }); + this.name = "NetworkError"; + } +} + +class NetworkOfflineError extends APIError { + constructor() { + super("Network is offline"); + this.name = "NetworkOfflineError"; + } +} + +class ServerContentParseError extends APIError { + constructor(e) { + super(`Cannot parse server content: ${e}`, { cause: e }); + this.name = "ServerContentParseError"; + } +} + +class BackendError extends APIError { + constructor(e) { + super(`Backend error: ${e}`, { cause: e }); + this.name = "BackendError"; + } +} + +class BackoffError extends APIError { + constructor(e) { + super(`Server backoff: ${e}`, { cause: e }); + this.name = "BackoffError"; + } +} + +class TimeoutError extends APIError { + constructor(e) { + super(`API timeout: ${e}`, { cause: e }); + this.name = "TimeoutError"; + } +} + +class StorageError extends Error { + constructor(e) { + super(`Storage error: ${e}`, { cause: e }); + this.name = "StorageError"; + } +} + +class InvalidSignatureError extends Error { + constructor(cid, x5u) { + let message = `Invalid content signature (${cid})`; + if (x5u) { + const chain = x5u.split("/").pop(); + message += ` using '${chain}'`; + } + super(message); + this.name = "InvalidSignatureError"; + } +} + +class MissingSignatureError extends InvalidSignatureError { + constructor(cid) { + super(cid); + this.message = `Missing signature (${cid})`; + this.name = "MissingSignatureError"; + } +} + +class CorruptedDataError extends InvalidSignatureError { + constructor(cid) { + super(cid); + this.message = `Corrupted local data (${cid})`; + this.name = "CorruptedDataError"; + } +} + +class UnknownCollectionError extends Error { + constructor(cid) { + super(`Unknown Collection "${cid}"`); + this.name = "UnknownCollectionError"; + } +} + +class AttachmentDownloader extends Downloader { + constructor(client) { + super(client.bucketName, client.collectionName); + this._client = client; + } + + get cacheImpl() { + const cacheImpl = { + get: async attachmentId => { + return this._client.db.getAttachment(attachmentId); + }, + set: async (attachmentId, attachment) => { + return this._client.db.saveAttachment(attachmentId, attachment); + }, + delete: async attachmentId => { + return this._client.db.saveAttachment(attachmentId, null); + }, + prune: async excludeIds => { + return this._client.db.pruneAttachments(excludeIds); + }, + }; + Object.defineProperty(this, "cacheImpl", { value: cacheImpl }); + return cacheImpl; + } + + /** + * Download attachment and report Telemetry on failure. + * + * @see Downloader.download + */ + async download(record, options) { + try { + // Explicitly await here to ensure we catch a network error. + return await super.download(record, options); + } catch (err) { + // Report download error. + let status = lazy.UptakeTelemetry.STATUS.DOWNLOAD_ERROR; + if (lazy.Utils.isOffline) { + status = lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR; + } else if (/NetworkError/.test(err.message)) { + status = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR; + } + // If the file failed to be downloaded, report it as such in Telemetry. + await lazy.UptakeTelemetry.report(TELEMETRY_COMPONENT, status, { + source: this._client.identifier, + }); + throw err; + } + } + + /** + * Delete all downloaded records attachments. + * + * Note: the list of attachments to be deleted is based on the + * current list of records. + */ + async deleteAll() { + let allRecords = await this._client.db.list(); + return Promise.all( + allRecords + .filter(r => !!r.attachment) + .map(r => + Promise.all([this.deleteDownloaded(r), this.deleteFromDisk(r)]) + ) + ); + } +} + +export class RemoteSettingsClient extends EventEmitter { + static get APIError() { + return APIError; + } + static get NetworkError() { + return NetworkError; + } + static get NetworkOfflineError() { + return NetworkOfflineError; + } + static get ServerContentParseError() { + return ServerContentParseError; + } + static get BackendError() { + return BackendError; + } + static get BackoffError() { + return BackoffError; + } + static get TimeoutError() { + return TimeoutError; + } + static get StorageError() { + return StorageError; + } + static get InvalidSignatureError() { + return InvalidSignatureError; + } + static get MissingSignatureError() { + return MissingSignatureError; + } + static get CorruptedDataError() { + return CorruptedDataError; + } + static get UnknownCollectionError() { + return UnknownCollectionError; + } + + constructor( + collectionName, + { + bucketName = AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET, + signerName, + filterFunc, + localFields = [], + keepAttachmentsIds = [], + lastCheckTimePref, + } = {} + ) { + // Remote Settings cannot be used in child processes (no access to disk, + // easily killed, isolated observer notifications etc.). + // Since our goal here is to prevent consumers to instantiate while developing their + // feature, throwing in Nightly only is enough, and prevents unexpected crashes + // in release or beta. + if ( + !AppConstants.RELEASE_OR_BETA && + Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT + ) { + throw new Error( + "Cannot instantiate Remote Settings client in child processes." + ); + } + + super(["sync"]); // emitted events + + this.collectionName = collectionName; + // Client is constructed with the raw bucket name (eg. "main", "security-state", "blocklist") + // The `bucketName` will contain the `-preview` suffix if the preview mode is enabled. + this.bucketName = lazy.Utils.actualBucketName(bucketName); + this.signerName = signerName; + this.filterFunc = filterFunc; + this.localFields = localFields; + this.keepAttachmentsIds = keepAttachmentsIds; + this._lastCheckTimePref = lastCheckTimePref; + this._verifier = null; + this._syncRunning = false; + + // This attribute allows signature verification to be disabled, when running tests + // or when pulling data from a dev server. + this.verifySignature = AppConstants.REMOTE_SETTINGS_VERIFY_SIGNATURE; + + ChromeUtils.defineLazyGetter( + this, + "db", + () => new lazy.Database(this.identifier) + ); + + ChromeUtils.defineLazyGetter( + this, + "attachments", + () => new AttachmentDownloader(this) + ); + } + + /** + * Internal method to refresh the client bucket name after the preview mode + * was toggled. + * + * See `RemoteSettings.enabledPreviewMode()`. + */ + refreshBucketName() { + this.bucketName = lazy.Utils.actualBucketName(this.bucketName); + this.db.identifier = this.identifier; + } + + get identifier() { + return `${this.bucketName}/${this.collectionName}`; + } + + get lastCheckTimePref() { + return ( + this._lastCheckTimePref || + `services.settings.${this.bucketName}.${this.collectionName}.last_check` + ); + } + + httpClient() { + const api = new lazy.KintoHttpClient(lazy.Utils.SERVER_URL, { + fetchFunc: lazy.Utils.fetch, // Use fetch() wrapper. + }); + return api.bucket(this.bucketName).collection(this.collectionName); + } + + /** + * Retrieve the collection timestamp for the last synchronization. + * This is an opaque and comparable value assigned automatically by + * the server. + * + * @returns {number} + * The timestamp in milliseconds, returns -1 if retrieving + * the timestamp from the kinto collection fails. + */ + async getLastModified() { + let timestamp = -1; + try { + timestamp = await this.db.getLastModified(); + } catch (err) { + lazy.console.warn( + `Error retrieving the getLastModified timestamp from ${this.identifier} RemoteSettingsClient`, + err + ); + } + + return timestamp; + } + + /** + * Lists settings. + * + * @param {Object} options The options object. + * @param {Object} options.filters Filter the results (default: `{}`). + * @param {String} options.order The order to apply (eg. `"-last_modified"`). + * @param {boolean} options.dumpFallback Fallback to dump data if read of local DB fails (default: `true`). + * @param {boolean} options.emptyListFallback Fallback to empty list if no dump data and read of local DB fails (default: `true`). + * @param {boolean} options.loadDumpIfNewer Use dump data if it is newer than local data (default: `true`). + * @param {boolean} options.forceSync Always synchronize from server before returning results (default: `false`). + * @param {boolean} options.syncIfEmpty Synchronize from server if local data is empty (default: `true`). + * @param {boolean} options.verifySignature Verify the signature of the local data (default: `false`). + * @return {Promise} + */ + async get(options = {}) { + const { + filters = {}, + order = "", // not sorted by default. + dumpFallback = true, + emptyListFallback = true, + forceSync = false, + loadDumpIfNewer = true, + syncIfEmpty = true, + } = options; + let { verifySignature = false } = options; + + const hasParallelCall = !!this._importingPromise; + let data; + try { + let lastModified = forceSync ? null : await this.db.getLastModified(); + let hasLocalData = lastModified !== null; + + if (forceSync) { + if (!this._importingPromise) { + this._importingPromise = (async () => { + await this.sync({ sendEvents: false, trigger: "forced" }); + return true; // No need to re-verify signature after sync. + })(); + } + } else if (syncIfEmpty && !hasLocalData) { + // .get() was called before we had the chance to synchronize the local database. + // We'll try to avoid returning an empty list. + if (!this._importingPromise) { + // Prevent parallel loading when .get() is called multiple times. + this._importingPromise = (async () => { + const importedFromDump = lazy.Utils.LOAD_DUMPS + ? await this._importJSONDump() + : -1; + if (importedFromDump < 0) { + // There is no JSON dump to load, force a synchronization from the server. + // We don't want the "sync" event to be sent, since some consumers use `.get()` + // in "sync" callbacks. See Bug 1761953 + lazy.console.debug( + `${this.identifier} Local DB is empty, pull data from server` + ); + await this.sync({ loadDump: false, sendEvents: false }); + } + // Return `true` to indicate we don't need to `verifySignature`, + // since a trusted dump was loaded or a signature verification + // happened during synchronization. + return true; + })(); + } else { + lazy.console.debug(`${this.identifier} Awaiting existing import.`); + } + } else if (hasLocalData && loadDumpIfNewer) { + // Check whether the local data is older than the packaged dump. + // If it is, load the packaged dump (which overwrites the local data). + let lastModifiedDump = await lazy.Utils.getLocalDumpLastModified( + this.bucketName, + this.collectionName + ); + if (lastModified < lastModifiedDump) { + lazy.console.debug( + `${this.identifier} Local DB is stale (${lastModified}), using dump instead (${lastModifiedDump})` + ); + if (!this._importingPromise) { + // As part of importing, any existing data is wiped. + this._importingPromise = (async () => { + const importedFromDump = await this._importJSONDump(); + // Return `true` to skip signature verification if a dump was found. + // The dump can be missing if a collection is listed in the timestamps summary file, + // because its dump is present in the source tree, but the dump was not + // included in the `package-manifest.in` file. (eg. android, thunderbird) + return importedFromDump >= 0; + })(); + } else { + lazy.console.debug(`${this.identifier} Awaiting existing import.`); + } + } + } + + if (this._importingPromise) { + try { + if (await this._importingPromise) { + // No need to verify signature, because either we've just loaded a trusted + // dump (here or in a parallel call), or it was verified during sync. + verifySignature = false; + } + } catch (e) { + if (!hasParallelCall) { + // Sync or load dump failed. Throw. + throw e; + } + // Report error, but continue because there could have been data + // loaded from a parallel call. + console.error(e); + } finally { + // then delete this promise again, as now we should have local data: + delete this._importingPromise; + } + } + + // Read from the local DB. + data = await this.db.list({ filters, order }); + } catch (e) { + // If the local DB cannot be read (for unknown reasons, Bug 1649393) + // or sync failed, we fallback to the packaged data, and filter/sort in memory. + if (!dumpFallback) { + throw e; + } + console.error(e); + let { data } = await lazy.SharedUtils.loadJSONDump( + this.bucketName, + this.collectionName + ); + if (data !== null) { + lazy.console.info(`${this.identifier} falling back to JSON dump`); + } else if (emptyListFallback) { + lazy.console.info( + `${this.identifier} no dump fallback, return empty list` + ); + data = []; + } else { + // Obtaining the records failed, there is no dump, and we don't fallback + // to an empty list. Throw the original error. + throw e; + } + if (!lazy.ObjectUtils.isEmpty(filters)) { + data = data.filter(r => lazy.Utils.filterObject(filters, r)); + } + if (order) { + data = lazy.Utils.sortObjects(order, data); + } + // No need to verify signature on JSON dumps. + // If local DB cannot be read, then we don't even try to do anything, + // we return results early. + return this._filterEntries(data); + } + + lazy.console.debug( + `${this.identifier} ${data.length} records before filtering.` + ); + + if (verifySignature) { + lazy.console.debug( + `${this.identifier} verify signature of local data on read` + ); + const allData = lazy.ObjectUtils.isEmpty(filters) + ? data + : await this.db.list(); + const localRecords = allData.map(r => this._cleanLocalFields(r)); + const timestamp = await this.db.getLastModified(); + let metadata = await this.db.getMetadata(); + if (syncIfEmpty && lazy.ObjectUtils.isEmpty(metadata)) { + // No sync occured yet, may have records from dump but no metadata. + // We don't want the "sync" event to be sent, since some consumers use `.get()` + // in "sync" callbacks. See Bug 1761953 + await this.sync({ loadDump: false, sendEvents: false }); + metadata = await this.db.getMetadata(); + } + // Will throw MissingSignatureError if no metadata and `syncIfEmpty` is false. + await this._validateCollectionSignature( + localRecords, + timestamp, + metadata + ); + } + + // Filter the records based on `this.filterFunc` results. + const final = await this._filterEntries(data); + lazy.console.debug( + `${this.identifier} ${final.length} records after filtering.` + ); + return final; + } + + /** + * Synchronize the local database with the remote server. + * + * @param {Object} options See #maybeSync() options. + */ + async sync(options) { + if (lazy.Utils.shouldSkipRemoteActivityDueToTests) { + return; + } + + // We want to know which timestamp we are expected to obtain in order to leverage + // cache busting. We don't provide ETag because we don't want a 304. + const { changes } = await lazy.Utils.fetchLatestChanges( + lazy.Utils.SERVER_URL, + { + filters: { + collection: this.collectionName, + bucket: this.bucketName, + }, + } + ); + if (changes.length === 0) { + throw new RemoteSettingsClient.UnknownCollectionError(this.identifier); + } + // According to API, there will be one only (fail if not). + const [{ last_modified: expectedTimestamp }] = changes; + + await this.maybeSync(expectedTimestamp, { ...options, trigger: "forced" }); + } + + /** + * Synchronize the local database with the remote server, **only if necessary**. + * + * @param {int} expectedTimestamp the lastModified date (on the server) for the remote collection. + * This will be compared to the local timestamp, and will be used for + * cache busting if local data is out of date. + * @param {Object} options additional advanced options. + * @param {bool} options.loadDump load initial dump from disk on first sync (default: true if server is prod) + * @param {bool} options.sendEvents send `"sync"` events (default: `true`) + * @param {string} options.trigger label to identify what triggered this sync (eg. ``"timer"``, default: `"manual"`) + * @return {Promise} which rejects on sync or process failure. + */ + async maybeSync(expectedTimestamp, options = {}) { + // Should the clients try to load JSON dump? (mainly disabled in tests) + const { + loadDump = lazy.Utils.LOAD_DUMPS, + trigger = "manual", + sendEvents = true, + } = options; + + // Make sure we don't run several synchronizations in parallel, mainly + // in order to avoid race conditions in "sync" events listeners. + if (this._syncRunning) { + lazy.console.warn(`${this.identifier} sync already running`); + return; + } + + // Prevent network requests and IndexedDB calls to be initiated + // during shutdown. + if (Services.startup.shuttingDown) { + lazy.console.warn(`${this.identifier} sync interrupted by shutdown`); + return; + } + + this._syncRunning = true; + + let importedFromDump = []; + const startedAt = new Date(); + let reportStatus = null; + let thrownError = null; + try { + // If network is offline, we can't synchronize. + if (lazy.Utils.isOffline) { + throw new RemoteSettingsClient.NetworkOfflineError(); + } + + // Read last timestamp and local data before sync. + let collectionLastModified = await this.db.getLastModified(); + const allData = await this.db.list(); + // Local data can contain local fields, strip them. + let localRecords = allData.map(r => this._cleanLocalFields(r)); + const localMetadata = await this.db.getMetadata(); + + // If there is no data currently in the collection, attempt to import + // initial data from the application defaults. + // This allows to avoid synchronizing the whole collection content on + // cold start. + if (!collectionLastModified && loadDump) { + try { + const imported = await this._importJSONDump(); + // The worker only returns an integer. List the imported records to build the sync event. + if (imported > 0) { + lazy.console.debug( + `${this.identifier} ${imported} records loaded from JSON dump` + ); + importedFromDump = await this.db.list(); + // Local data is the data loaded from dump. We will need this later + // to compute the sync result. + localRecords = importedFromDump; + } + collectionLastModified = await this.db.getLastModified(); + } catch (e) { + // Report but go-on. + console.error(e); + } + } + let syncResult; + try { + // Is local timestamp up to date with the server? + if (expectedTimestamp == collectionLastModified) { + lazy.console.debug(`${this.identifier} local data is up-to-date`); + reportStatus = lazy.UptakeTelemetry.STATUS.UP_TO_DATE; + + // If the data is up-to-date but don't have metadata (records loaded from dump), + // we fetch them and validate the signature immediately. + if (this.verifySignature && lazy.ObjectUtils.isEmpty(localMetadata)) { + lazy.console.debug(`${this.identifier} pull collection metadata`); + const metadata = await this.httpClient().getData({ + query: { _expected: expectedTimestamp }, + }); + await this.db.importChanges(metadata); + // We don't bother validating the signature if the dump was just loaded. We do + // if the dump was loaded at some other point (eg. from .get()). + if (this.verifySignature && !importedFromDump.length) { + lazy.console.debug( + `${this.identifier} verify signature of local data` + ); + await this._validateCollectionSignature( + localRecords, + collectionLastModified, + metadata + ); + } + } + + // Since the data is up-to-date, if we didn't load any dump then we're done here. + if (!importedFromDump.length) { + return; + } + // Otherwise we want to continue with sending the sync event to notify about the created records. + syncResult = { + current: importedFromDump, + created: importedFromDump, + updated: [], + deleted: [], + }; + } else { + // Local data is either outdated or tampered. + // In both cases we will fetch changes from server, + // and make sure we overwrite local data. + syncResult = await this._importChanges( + localRecords, + collectionLastModified, + localMetadata, + expectedTimestamp + ); + if (sendEvents && this.hasListeners("sync")) { + // If we have listeners for the "sync" event, then compute the lists of changes. + // The records imported from the dump should be considered as "created" for the + // listeners. + const importedById = importedFromDump.reduce((acc, r) => { + acc.set(r.id, r); + return acc; + }, new Map()); + // Deleted records should not appear as created. + syncResult.deleted.forEach(r => importedById.delete(r.id)); + // Records from dump that were updated should appear in their newest form. + syncResult.updated.forEach(u => { + if (importedById.has(u.old.id)) { + importedById.set(u.old.id, u.new); + } + }); + syncResult.created = syncResult.created.concat( + Array.from(importedById.values()) + ); + } + + // When triggered from the daily timer, and if the sync was successful, and once + // all sync listeners have been executed successfully, we prune potential + // obsolete attachments that may have been left in the local cache. + if (trigger == "timer") { + const deleted = await this.attachments.prune( + this.keepAttachmentsIds + ); + if (deleted > 0) { + lazy.console.warn( + `${this.identifier} Pruned ${deleted} obsolete attachments` + ); + } + } + } + } catch (e) { + if (e instanceof InvalidSignatureError) { + // Signature verification failed during synchronization. + reportStatus = + e instanceof CorruptedDataError + ? lazy.UptakeTelemetry.STATUS.CORRUPTION_ERROR + : lazy.UptakeTelemetry.STATUS.SIGNATURE_ERROR; + // If sync fails with a signature error, it's likely that our + // local data has been modified in some way. + // We will attempt to fix this by retrieving the whole + // remote collection. + try { + lazy.console.warn( + `${this.identifier} Signature verified failed. Retry from scratch` + ); + syncResult = await this._importChanges( + localRecords, + collectionLastModified, + localMetadata, + expectedTimestamp, + { retry: true } + ); + } catch (e) { + // If the signature fails again, or if an error occured during wiping out the + // local data, then we report it as a *signature retry* error. + reportStatus = lazy.UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR; + throw e; + } + } else { + // The sync has thrown for other reason than signature verification. + // Obtain a more precise error than original one. + const adjustedError = this._adjustedError(e); + // Default status for errors at this step is SYNC_ERROR. + reportStatus = this._telemetryFromError(adjustedError, { + default: lazy.UptakeTelemetry.STATUS.SYNC_ERROR, + }); + throw adjustedError; + } + } + if (sendEvents) { + // Filter the synchronization results using `filterFunc` (ie. JEXL). + const filteredSyncResult = await this._filterSyncResult(syncResult); + // If every changed entry is filtered, we don't even fire the event. + if (filteredSyncResult) { + try { + await this.emit("sync", { data: filteredSyncResult }); + } catch (e) { + reportStatus = lazy.UptakeTelemetry.STATUS.APPLY_ERROR; + throw e; + } + } else { + lazy.console.info( + `All changes are filtered by JEXL expressions for ${this.identifier}` + ); + } + } + } catch (e) { + thrownError = e; + // Obtain a more precise error than original one. + const adjustedError = this._adjustedError(e); + // If browser is shutting down, then we can report a specific status. + // (eg. IndexedDB will abort transactions) + if (Services.startup.shuttingDown) { + reportStatus = lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR; + } + // If no Telemetry status was determined yet (ie. outside sync step), + // then introspect error, default status at this step is UNKNOWN. + else if (reportStatus == null) { + reportStatus = this._telemetryFromError(adjustedError, { + default: lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR, + }); + } + throw e; + } finally { + const durationMilliseconds = new Date() - startedAt; + // No error was reported, this is a success! + if (reportStatus === null) { + reportStatus = lazy.UptakeTelemetry.STATUS.SUCCESS; + } + // Report success/error status to Telemetry. + let reportArgs = { + source: this.identifier, + trigger, + duration: durationMilliseconds, + }; + // In Bug 1617133, we will try to break down specific errors into + // more precise statuses by reporting the JavaScript error name + // ("TypeError", etc.) to Telemetry on Nightly. + const channel = lazy.UptakeTelemetry.Policy.getChannel(); + if ( + thrownError !== null && + channel == "nightly" && + [ + lazy.UptakeTelemetry.STATUS.SYNC_ERROR, + lazy.UptakeTelemetry.STATUS.CUSTOM_1_ERROR, // IndexedDB. + lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR, + lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR, + ].includes(reportStatus) + ) { + // List of possible error names for IndexedDB: + // https://searchfox.org/mozilla-central/rev/49ed791/dom/base/DOMException.cpp#28-53 + reportArgs = { ...reportArgs, errorName: thrownError.name }; + } + + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + reportStatus, + reportArgs + ); + + lazy.console.debug(`${this.identifier} sync status is ${reportStatus}`); + this._syncRunning = false; + } + } + + /** + * Return a more precise error instance, based on the specified + * error and its message. + * @param {Error} e the original error + * @returns {Error} + */ + _adjustedError(e) { + if (/unparseable/.test(e.message)) { + return new RemoteSettingsClient.ServerContentParseError(e); + } + if (/NetworkError/.test(e.message)) { + return new RemoteSettingsClient.NetworkError(e); + } + if (/Timeout/.test(e.message)) { + return new RemoteSettingsClient.TimeoutError(e); + } + if (/HTTP 5??/.test(e.message)) { + return new RemoteSettingsClient.BackendError(e); + } + if (/Backoff/.test(e.message)) { + return new RemoteSettingsClient.BackoffError(e); + } + if ( + // Errors from kinto.js IDB adapter. + e instanceof lazy.IDBHelpers.IndexedDBError || + // Other IndexedDB errors (eg. RemoteSettingsWorker). + /IndexedDB/.test(e.message) + ) { + return new RemoteSettingsClient.StorageError(e); + } + return e; + } + + /** + * Determine the Telemetry uptake status based on the specified + * error. + */ + _telemetryFromError(e, options = { default: null }) { + let reportStatus = options.default; + + if (e instanceof RemoteSettingsClient.NetworkOfflineError) { + reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR; + } else if (e instanceof lazy.IDBHelpers.ShutdownError) { + reportStatus = lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR; + } else if (e instanceof RemoteSettingsClient.ServerContentParseError) { + reportStatus = lazy.UptakeTelemetry.STATUS.PARSE_ERROR; + } else if (e instanceof RemoteSettingsClient.NetworkError) { + reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR; + } else if (e instanceof RemoteSettingsClient.TimeoutError) { + reportStatus = lazy.UptakeTelemetry.STATUS.TIMEOUT_ERROR; + } else if (e instanceof RemoteSettingsClient.BackendError) { + reportStatus = lazy.UptakeTelemetry.STATUS.SERVER_ERROR; + } else if (e instanceof RemoteSettingsClient.BackoffError) { + reportStatus = lazy.UptakeTelemetry.STATUS.BACKOFF; + } else if (e instanceof RemoteSettingsClient.StorageError) { + reportStatus = lazy.UptakeTelemetry.STATUS.CUSTOM_1_ERROR; + } + + return reportStatus; + } + + /** + * Import the JSON files from services/settings/dump into the local DB. + */ + async _importJSONDump() { + lazy.console.info(`${this.identifier} try to restore dump`); + const result = await lazy.RemoteSettingsWorker.importJSONDump( + this.bucketName, + this.collectionName + ); + if (result < 0) { + lazy.console.debug(`${this.identifier} no dump available`); + } else { + lazy.console.info( + `${this.identifier} imported ${result} records from dump` + ); + } + return result; + } + + /** + * Fetch the signature info from the collection metadata and verifies that the + * local set of records has the same. + * + * @param {Array<Object>} records The list of records to validate. + * @param {int} timestamp The timestamp associated with the list of remote records. + * @param {Object} metadata The collection metadata, that contains the signature payload. + * @returns {Promise} + */ + async _validateCollectionSignature(records, timestamp, metadata) { + if (!metadata?.signature) { + throw new MissingSignatureError(this.identifier); + } + + if (!this._verifier) { + this._verifier = Cc[ + "@mozilla.org/security/contentsignatureverifier;1" + ].createInstance(Ci.nsIContentSignatureVerifier); + } + + // This is a content-signature field from an autograph response. + const { + signature: { x5u, signature }, + } = metadata; + const certChain = await (await lazy.Utils.fetch(x5u)).text(); + // Merge remote records with local ones and serialize as canonical JSON. + const serialized = await lazy.RemoteSettingsWorker.canonicalStringify( + records, + timestamp + ); + + lazy.console.debug(`${this.identifier} verify signature using ${x5u}`); + if ( + !(await this._verifier.asyncVerifyContentSignature( + serialized, + "p384ecdsa=" + signature, + certChain, + this.signerName, + lazy.Utils.CERT_CHAIN_ROOT_IDENTIFIER + )) + ) { + throw new InvalidSignatureError(this.identifier, x5u); + } + } + + /** + * This method is in charge of fetching data from server, applying the diff-based + * changes to the local DB, validating the signature, and computing a synchronization + * result with the list of creation, updates, and deletions. + * + * @param {Array<Object>} localRecords Current list of records in local DB. + * @param {int} localTimestamp Current timestamp in local DB. + * @param {Object} localMetadata Current metadata in local DB. + * @param {int} expectedTimestamp Cache busting of collection metadata + * @param {Object} options + * @param {bool} options.retry Whether this method is called in the + * retry situation. + * + * @returns {Promise<Object>} the computed sync result. + */ + async _importChanges( + localRecords, + localTimestamp, + localMetadata, + expectedTimestamp, + options = {} + ) { + const { retry = false } = options; + const since = retry || !localTimestamp ? undefined : `"${localTimestamp}"`; + + // Fetch collection metadata and list of changes from server. + lazy.console.debug( + `${this.identifier} Fetch changes from server (expected=${expectedTimestamp}, since=${since})` + ); + const { metadata, remoteTimestamp, remoteRecords } = + await this._fetchChangeset(expectedTimestamp, since); + + // We build a sync result, based on remote changes. + const syncResult = { + current: localRecords, + created: [], + updated: [], + deleted: [], + }; + // If data wasn't changed, return empty sync result. + // This can happen when we update the signature but not the data. + lazy.console.debug( + `${this.identifier} local timestamp: ${localTimestamp}, remote: ${remoteTimestamp}` + ); + if (localTimestamp && remoteTimestamp < localTimestamp) { + return syncResult; + } + + await this.db.importChanges(metadata, remoteTimestamp, remoteRecords, { + clear: retry, + }); + + // Read the new local data, after updating. + const newLocal = await this.db.list(); + const newRecords = newLocal.map(r => this._cleanLocalFields(r)); + // And verify the signature on what is now stored. + if (this.verifySignature) { + try { + await this._validateCollectionSignature( + newRecords, + remoteTimestamp, + metadata + ); + } catch (e) { + lazy.console.error( + `${this.identifier} Signature failed ${retry ? "again" : ""} ${e}` + ); + if (!(e instanceof InvalidSignatureError)) { + // If it failed for any other kind of error (eg. shutdown) + // then give up quickly. + throw e; + } + + // In order to distinguish signature errors that happen + // during sync, from hijacks of local DBs, we will verify + // the signature on the data that we had before syncing. + let localTrustworthy = false; + lazy.console.debug(`${this.identifier} verify data before sync`); + try { + await this._validateCollectionSignature( + localRecords, + localTimestamp, + localMetadata + ); + localTrustworthy = true; + } catch (sigerr) { + if (!(sigerr instanceof InvalidSignatureError)) { + // If it fails for other reason, keep original error and give up. + throw sigerr; + } + lazy.console.debug(`${this.identifier} previous data was invalid`); + } + + if (!localTrustworthy && !retry) { + // Signature failed, clear local DB because it contains + // bad data (local + remote changes). + lazy.console.debug(`${this.identifier} clear local data`); + await this.db.clear(); + // Local data was tampered, throw and it will retry from empty DB. + lazy.console.error(`${this.identifier} local data was corrupted`); + throw new CorruptedDataError(this.identifier); + } else if (retry) { + // We retried already, we will restore the previous local data + // before throwing eventually. + if (localTrustworthy) { + await this.db.importChanges( + localMetadata, + localTimestamp, + localRecords, + { + clear: true, // clear before importing. + } + ); + } else { + // Restore the dump if available (no-op if no dump) + const imported = await this._importJSONDump(); + // _importJSONDump() only clears DB if dump is available, + // therefore do it here! + if (imported < 0) { + await this.db.clear(); + } + } + } + throw e; + } + } else { + lazy.console.warn(`${this.identifier} has signature disabled`); + } + + if (this.hasListeners("sync")) { + // If we have some listeners for the "sync" event, + // Compute the changes, comparing records before and after. + syncResult.current = newRecords; + const oldById = new Map(localRecords.map(e => [e.id, e])); + for (const r of newRecords) { + const old = oldById.get(r.id); + if (old) { + oldById.delete(r.id); + if (r.last_modified != old.last_modified) { + syncResult.updated.push({ old, new: r }); + } + } else { + syncResult.created.push(r); + } + } + syncResult.deleted = syncResult.deleted.concat( + Array.from(oldById.values()) + ); + lazy.console.debug( + `${this.identifier} ${syncResult.created.length} created. ${syncResult.updated.length} updated. ${syncResult.deleted.length} deleted.` + ); + } + + return syncResult; + } + + /** + * Fetch information from changeset endpoint. + * + * @param expectedTimestamp cache busting value + * @param since timestamp of last sync (optional) + */ + async _fetchChangeset(expectedTimestamp, since) { + const client = this.httpClient(); + const { + metadata, + timestamp: remoteTimestamp, + changes: remoteRecords, + } = await client.execute( + { + path: `/buckets/${this.bucketName}/collections/${this.collectionName}/changeset`, + }, + { + query: { + _expected: expectedTimestamp, + _since: since, + }, + } + ); + return { + remoteTimestamp, + metadata, + remoteRecords, + }; + } + + /** + * Use the filter func to filter the lists of changes obtained from synchronization, + * and return them along with the filtered list of local records. + * + * If the filtered lists of changes are all empty, we return null (and thus don't + * bother listing local DB). + * + * @param {Object} syncResult Synchronization result without filtering. + * + * @returns {Promise<Object>} the filtered list of local records, plus the filtered + * list of created, updated and deleted records. + */ + async _filterSyncResult(syncResult) { + // Handle the obtained records (ie. apply locally through events). + // Build the event data list. It should be filtered (ie. by application target) + const { + current: allData, + created: allCreated, + updated: allUpdated, + deleted: allDeleted, + } = syncResult; + const [created, deleted, updatedFiltered] = await Promise.all( + [allCreated, allDeleted, allUpdated.map(e => e.new)].map( + this._filterEntries.bind(this) + ) + ); + // For updates, keep entries whose updated form matches the target. + const updatedFilteredIds = new Set(updatedFiltered.map(e => e.id)); + const updated = allUpdated.filter(({ new: { id } }) => + updatedFilteredIds.has(id) + ); + + if (!created.length && !updated.length && !deleted.length) { + return null; + } + // Read local collection of records (also filtered). + const current = await this._filterEntries(allData); + return { created, updated, deleted, current }; + } + + /** + * Filter entries for which calls to `this.filterFunc` returns null. + * + * @param {Array<Objet>} data + * @returns {Array<Object>} + */ + async _filterEntries(data) { + if (!this.filterFunc) { + return data; + } + const environment = cacheProxy(lazy.ClientEnvironmentBase); + const dataPromises = data.map(e => this.filterFunc(e, environment)); + const results = await Promise.all(dataPromises); + return results.filter(Boolean); + } + + /** + * Remove the fields from the specified record + * that are not present on server. + * + * @param {Object} record + */ + _cleanLocalFields(record) { + const keys = ["_status"].concat(this.localFields); + const result = { ...record }; + for (const key of keys) { + delete result[key]; + } + return result; + } +} diff --git a/services/settings/RemoteSettingsComponents.sys.mjs b/services/settings/RemoteSettingsComponents.sys.mjs new file mode 100644 index 0000000000..9f7687514f --- /dev/null +++ b/services/settings/RemoteSettingsComponents.sys.mjs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", +}); + +export var RemoteSettingsTimer = function () {}; +RemoteSettingsTimer.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]), + classID: Components.ID("{5e756573-234a-49ea-bbe4-59ec7a70657d}"), + contractID: "@mozilla.org/services/settings;1", + + // By default, this timer fires once every 24 hours. See the "services.settings.poll_interval" pref. + notify(timer) { + lazy.RemoteSettings.pollChanges({ trigger: "timer" }).catch(e => + console.error(e) + ); + }, +}; diff --git a/services/settings/RemoteSettingsWorker.sys.mjs b/services/settings/RemoteSettingsWorker.sys.mjs new file mode 100644 index 0000000000..57bfb3f3d5 --- /dev/null +++ b/services/settings/RemoteSettingsWorker.sys.mjs @@ -0,0 +1,233 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Interface to a dedicated thread handling for Remote Settings heavy operations. + */ +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gMaxIdleMilliseconds", + "services.settings.worker_idle_max_milliseconds", + 30 * 1000 // Default of 30 seconds. +); + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + SharedUtils: "resource://services-settings/SharedUtils.sys.mjs", +}); + +// Note: we currently only ever construct one instance of Worker. +// If it stops being a singleton, the AsyncShutdown code at the bottom +// of this file, as well as these globals, will need adjusting. +let gShutdown = false; +let gShutdownResolver = null; + +class RemoteSettingsWorkerError extends Error { + constructor(message) { + super(message); + this.name = "RemoteSettingsWorkerError"; + } +} + +class Worker { + constructor(source) { + if (gShutdown) { + console.error("Can't create worker once shutdown has started"); + } + this.source = source; + this.worker = null; + + this.callbacks = new Map(); + this.lastCallbackId = 0; + this.idleTimeoutId = null; + } + + async _execute(method, args = [], options = {}) { + // Check if we're shutting down. + if (gShutdown && method != "prepareShutdown") { + throw new RemoteSettingsWorkerError("Remote Settings has shut down."); + } + // Don't instantiate the worker to shut it down. + if (method == "prepareShutdown" && !this.worker) { + return null; + } + + const { mustComplete = false } = options; + // (Re)instantiate the worker if it was terminated. + if (!this.worker) { + this.worker = new ChromeWorker(this.source, { type: "module" }); + this.worker.onmessage = this._onWorkerMessage.bind(this); + this.worker.onerror = error => { + // Worker crashed. Reject each pending callback. + for (const { reject } of this.callbacks.values()) { + reject(error); + } + this.callbacks.clear(); + // And terminate it. + this.stop(); + }; + } + // New activity: reset the idle timer. + if (this.idleTimeoutId) { + clearTimeout(this.idleTimeoutId); + } + let identifier = method + "-"; + // Include the collection details in the importJSONDump case. + if (identifier == "importJSONDump-") { + identifier += `${args[0]}-${args[1]}-`; + } + return new Promise((resolve, reject) => { + const callbackId = `${identifier}${++this.lastCallbackId}`; + this.callbacks.set(callbackId, { resolve, reject, mustComplete }); + this.worker.postMessage({ callbackId, method, args }); + }); + } + + _onWorkerMessage(event) { + const { callbackId, result, error } = event.data; + // If we're shutting down, we may have already rejected this operation + // and removed its callback from our map: + if (!this.callbacks.has(callbackId)) { + return; + } + const { resolve, reject } = this.callbacks.get(callbackId); + if (error) { + reject(new RemoteSettingsWorkerError(error)); + } else { + resolve(result); + } + this.callbacks.delete(callbackId); + + // Terminate the worker when it's unused for some time. + // But don't terminate it if an operation is pending. + if (!this.callbacks.size) { + if (gShutdown) { + this.stop(); + if (gShutdownResolver) { + gShutdownResolver(); + } + } else { + this.idleTimeoutId = setTimeout(() => { + this.stop(); + }, lazy.gMaxIdleMilliseconds); + } + } + } + + /** + * Called at shutdown to abort anything the worker is doing that isn't + * critical. + */ + _abortCancelableRequests() { + // End all tasks that we can. + const callbackCopy = Array.from(this.callbacks.entries()); + const error = new Error("Shutdown, aborting read-only worker requests."); + for (const [id, { reject, mustComplete }] of callbackCopy) { + if (!mustComplete) { + this.callbacks.delete(id); + reject(error); + } + } + // There might be nothing left now: + if (!this.callbacks.size) { + this.stop(); + if (gShutdownResolver) { + gShutdownResolver(); + } + } + // If there was something left, we'll stop as soon as we get messages from + // those tasks, too. + // Let's hurry them along a bit: + this._execute("prepareShutdown"); + } + + stop() { + this.worker.terminate(); + this.worker = null; + this.idleTimeoutId = null; + } + + async canonicalStringify(localRecords, remoteRecords, timestamp) { + return this._execute("canonicalStringify", [ + localRecords, + remoteRecords, + timestamp, + ]); + } + + async importJSONDump(bucket, collection) { + return this._execute("importJSONDump", [bucket, collection], { + mustComplete: true, + }); + } + + async checkFileHash(filepath, size, hash) { + return this._execute("checkFileHash", [filepath, size, hash]); + } + + async checkContentHash(buffer, size, hash) { + // The implementation does little work on the current thread, so run the + // task on the current thread instead of the worker thread. + return lazy.SharedUtils.checkContentHash(buffer, size, hash); + } +} + +// Now, first add a shutdown blocker. If that fails, we must have +// shut down already. +// We're doing this here rather than in the Worker constructor because in +// principle having just 1 shutdown blocker for the entire file should be +// fine. If we ever start creating more than one Worker instance, this +// code will need adjusting to deal with that. +try { + lazy.AsyncShutdown.profileBeforeChange.addBlocker( + "Remote Settings profile-before-change", + async () => { + // First, indicate we've shut down. + gShutdown = true; + // Then, if we have no worker or no callbacks, we're done. + if ( + !RemoteSettingsWorker.worker || + !RemoteSettingsWorker.callbacks.size + ) { + return null; + } + // Otherwise, there's something left to do. Set up a promise: + let finishedPromise = new Promise(resolve => { + gShutdownResolver = resolve; + }); + + // Try to cancel most of the work: + RemoteSettingsWorker._abortCancelableRequests(); + + // Return a promise that the worker will resolve. + return finishedPromise; + }, + { + fetchState() { + const remainingCallbacks = RemoteSettingsWorker.callbacks; + const details = Array.from(remainingCallbacks.keys()).join(", "); + return `Remaining: ${remainingCallbacks.size} callbacks (${details}).`; + }, + } + ); +} catch (ex) { + console.error( + "Couldn't add shutdown blocker, assuming shutdown has started." + ); + console.error(ex); + // If AsyncShutdown throws, `profileBeforeChange` has already fired. Ignore it + // and mark shutdown. Constructing the worker will report an error and do + // nothing. + gShutdown = true; +} + +export var RemoteSettingsWorker = new Worker( + "resource://services-settings/RemoteSettings.worker.mjs" +); diff --git a/services/settings/SharedUtils.sys.mjs b/services/settings/SharedUtils.sys.mjs new file mode 100644 index 0000000000..1eeaf0bed9 --- /dev/null +++ b/services/settings/SharedUtils.sys.mjs @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Common logic shared by RemoteSettingsWorker.js (Worker) and the main thread. + */ + +export var SharedUtils = { + /** + * Check that the specified content matches the expected size and SHA-256 hash. + * @param {ArrayBuffer} buffer binary content + * @param {Number} size expected file size + * @param {String} size expected file SHA-256 as hex string + * @returns {boolean} + */ + async checkContentHash(buffer, size, hash) { + const bytes = new Uint8Array(buffer); + // Has expected size? (saves computing hash) + if (bytes.length !== size) { + return false; + } + // Has expected content? + const hashBuffer = await crypto.subtle.digest("SHA-256", bytes); + const hashBytes = new Uint8Array(hashBuffer); + const toHex = b => b.toString(16).padStart(2, "0"); + const hashStr = Array.from(hashBytes, toHex).join(""); + return hashStr == hash; + }, + + /** + * Load (from disk) the JSON file distributed with the release for this collection. + * @param {String} bucket + * @param {String} collection + */ + async loadJSONDump(bucket, collection) { + // When using the preview bucket, we still want to load the main dump. + // But we store it locally in the preview bucket. + const jsonBucket = bucket.replace("-preview", ""); + const fileURI = `resource://app/defaults/settings/${jsonBucket}/${collection}.json`; + let response; + try { + response = await fetch(fileURI); + } catch (e) { + // Return null if file is missing. + return { data: null, timestamp: null }; + } + // Will throw if JSON is invalid. + return response.json(); + }, +}; diff --git a/services/settings/SyncHistory.sys.mjs b/services/settings/SyncHistory.sys.mjs new file mode 100644 index 0000000000..f609eb26f7 --- /dev/null +++ b/services/settings/SyncHistory.sys.mjs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + KeyValueService: "resource://gre/modules/kvstore.sys.mjs", +}); + +/** + * A helper to keep track of synchronization statuses. + * + * We rely on a different storage backend than for storing Remote Settings data, + * because the eventual goal is to be able to detect `IndexedDB` issues and act + * accordingly. + */ +export class SyncHistory { + // Internal reference to underlying rkv store. + #store; + + /** + * @param {String} source the synchronization source (eg. `"settings-sync"`) + * @param {Object} options + * @param {int} options.size Maximum number of entries per source. + */ + constructor(source, { size } = { size: 100 }) { + this.source = source; + this.size = size; + } + + /** + * Store the synchronization status. The ETag is converted and stored as + * a millisecond epoch timestamp. + * The entries with the oldest timestamps will be deleted to maintain the + * history size under the configured maximum. + * + * @param {String} etag the ETag value from the server (eg. `"1647961052593"`) + * @param {String} status the synchronization status (eg. `"success"`) + * @param {Object} infos optional additional information to keep track of + */ + async store(etag, status, infos = {}) { + const rkv = await this.#init(); + const timestamp = parseInt(etag.replace('"', ""), 10); + if (Number.isNaN(timestamp)) { + throw new Error(`Invalid ETag value ${etag}`); + } + const key = `v1-${this.source}\t${timestamp}`; + const value = { timestamp, status, infos }; + await rkv.put(key, JSON.stringify(value)); + // Trim old entries. + const allEntries = await this.list(); + for (let i = this.size; i < allEntries.length; i++) { + let { timestamp } = allEntries[i]; + await rkv.delete(`v1-${this.source}\t${timestamp}`); + } + } + + /** + * Retrieve the stored history entries for a certain source, sorted by + * timestamp descending. + * + * @returns {Array<Object>} a list of objects + */ + async list() { + const rkv = await this.#init(); + const entries = []; + // The "from" and "to" key parameters to nsIKeyValueStore.enumerate() + // are inclusive and exclusive, respectively, and keys are tuples + // of source and datetime joined by a tab (\t), which is character code 9; + // so enumerating ["source", "source\n"), where the line feed (\n) + // is character code 10, enumerates all pairs with the given source. + for (const { value } of await rkv.enumerate( + `v1-${this.source}`, + `v1-${this.source}\n` + )) { + try { + const stored = JSON.parse(value); + entries.push({ ...stored, datetime: new Date(stored.timestamp) }); + } catch (e) { + // Ignore malformed entries. + console.error(e); + } + } + // Sort entries by `timestamp` descending. + entries.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1)); + return entries; + } + + /** + * Return the most recent entry. + */ + async last() { + // List is sorted from newer to older. + return (await this.list())[0]; + } + + /** + * Wipe out the **whole** store. + */ + async clear() { + const rkv = await this.#init(); + await rkv.clear(); + } + + /** + * Initialize the rkv store in the user profile. + * + * @returns {Object} the underlying `KeyValueService` instance. + */ + async #init() { + if (!this.#store) { + // Get and cache a handle to the kvstore. + const dir = PathUtils.join(PathUtils.profileDir, "settings"); + await IOUtils.makeDirectory(dir); + this.#store = await lazy.KeyValueService.getOrCreate(dir, "synchistory"); + } + return this.#store; + } +} diff --git a/services/settings/Utils.sys.mjs b/services/settings/Utils.sys.mjs new file mode 100644 index 0000000000..73c83e526b --- /dev/null +++ b/services/settings/Utils.sys.mjs @@ -0,0 +1,504 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +import { ServiceRequest } from "resource://gre/modules/ServiceRequest.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + SharedUtils: "resource://services-settings/SharedUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "CaptivePortalService", + "@mozilla.org/network/captive-portal-service;1", + "nsICaptivePortalService" +); +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gNetworkLinkService", + "@mozilla.org/network/network-link-service;1", + "nsINetworkLinkService" +); + +// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref. +// See LOG_LEVELS in Console.sys.mjs. Common examples: "all", "debug", "info", +// "warn", "error". +const log = (() => { + const { ConsoleAPI } = ChromeUtils.importESModule( + "resource://gre/modules/Console.sys.mjs" + ); + return new ConsoleAPI({ + maxLogLevel: "warn", + maxLogLevelPref: "services.settings.loglevel", + prefix: "services.settings", + }); +})(); + +ChromeUtils.defineLazyGetter(lazy, "isRunningTests", () => { + if (Services.env.get("MOZ_DISABLE_NONLOCAL_CONNECTIONS") === "1") { + // Allow to override the server URL if non-local connections are disabled, + // usually true when running tests. + return true; + } + return false; +}); + +// Overriding the server URL is normally disabled on Beta and Release channels, +// except under some conditions. +ChromeUtils.defineLazyGetter(lazy, "allowServerURLOverride", () => { + if (!AppConstants.RELEASE_OR_BETA) { + // Always allow to override the server URL on Nightly/DevEdition. + return true; + } + + if (lazy.isRunningTests) { + return true; + } + + if (Services.env.get("MOZ_REMOTE_SETTINGS_DEVTOOLS") === "1") { + // Allow to override the server URL when using remote settings devtools. + return true; + } + + if (lazy.gServerURL != AppConstants.REMOTE_SETTINGS_SERVER_URL) { + log.warn("Ignoring preference override of remote settings server"); + log.warn( + "Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment" + ); + } + + return false; +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gServerURL", + "services.settings.server", + AppConstants.REMOTE_SETTINGS_SERVER_URL +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gPreviewEnabled", + "services.settings.preview_enabled", + false +); + +function _isUndefined(value) { + return typeof value === "undefined"; +} + +export var Utils = { + get SERVER_URL() { + return lazy.allowServerURLOverride + ? lazy.gServerURL + : AppConstants.REMOTE_SETTINGS_SERVER_URL; + }, + + CHANGES_PATH: "/buckets/monitor/collections/changes/changeset", + + /** + * Logger instance. + */ + log, + + get shouldSkipRemoteActivityDueToTests() { + return ( + (lazy.isRunningTests || Cu.isInAutomation) && + this.SERVER_URL == "data:,#remote-settings-dummy/v1" + ); + }, + + get CERT_CHAIN_ROOT_IDENTIFIER() { + if (this.SERVER_URL == AppConstants.REMOTE_SETTINGS_SERVER_URL) { + return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot; + } + if (this.SERVER_URL.includes("allizom.")) { + return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot; + } + if (this.SERVER_URL.includes("dev.")) { + return Ci.nsIContentSignatureVerifier.ContentSignatureDevRoot; + } + if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) { + return Ci.nsIX509CertDB.AppXPCShellRoot; + } + return Ci.nsIContentSignatureVerifier.ContentSignatureLocalRoot; + }, + + get LOAD_DUMPS() { + // Load dumps only if pulling data from the production server, or in tests. + return ( + this.SERVER_URL == AppConstants.REMOTE_SETTINGS_SERVER_URL || + lazy.isRunningTests + ); + }, + + get PREVIEW_MODE() { + // We want to offer the ability to set preview mode via a preference + // for consumers who want to pull from the preview bucket on startup. + if (_isUndefined(this._previewModeEnabled) && lazy.allowServerURLOverride) { + return lazy.gPreviewEnabled; + } + return !!this._previewModeEnabled; + }, + + /** + * Internal method to enable pulling data from preview buckets. + * @param enabled + */ + enablePreviewMode(enabled) { + const bool2str = v => + // eslint-disable-next-line no-nested-ternary + _isUndefined(v) ? "unset" : v ? "enabled" : "disabled"; + this.log.debug( + `Preview mode: ${bool2str(this._previewModeEnabled)} -> ${bool2str( + enabled + )}` + ); + this._previewModeEnabled = enabled; + }, + + /** + * Returns the actual bucket name to be used. When preview mode is enabled, + * this adds the *preview* suffix. + * + * See also `SharedUtils.loadJSONDump()` which strips the preview suffix to identify + * the packaged JSON file. + * + * @param bucketName the client bucket + * @returns the final client bucket depending whether preview mode is enabled. + */ + actualBucketName(bucketName) { + let actual = bucketName.replace("-preview", ""); + if (this.PREVIEW_MODE) { + actual += "-preview"; + } + return actual; + }, + + /** + * Check if network is down. + * + * Note that if this returns false, it does not guarantee + * that network is up. + * + * @return {bool} Whether network is down or not. + */ + get isOffline() { + try { + return ( + Services.io.offline || + lazy.CaptivePortalService.state == + lazy.CaptivePortalService.LOCKED_PORTAL || + !lazy.gNetworkLinkService.isLinkUp + ); + } catch (ex) { + log.warn("Could not determine network status.", ex); + } + return false; + }, + + /** + * A wrapper around `ServiceRequest` that behaves like `fetch()`. + * + * Use this in order to leverage the `beConservative` flag, for + * example to avoid using HTTP3 to fetch critical data. + * + * @param input a resource + * @param init request options + * @returns a Response object + */ + async fetch(input, init = {}) { + return new Promise(function (resolve, reject) { + const request = new ServiceRequest(); + function fallbackOrReject(err) { + if ( + // At most one recursive Utils.fetch call (bypassProxy=false to true). + bypassProxy || + Services.startup.shuttingDown || + Utils.isOffline || + !request.isProxied || + !request.bypassProxyEnabled + ) { + reject(err); + return; + } + ServiceRequest.logProxySource(request.channel, "remote-settings"); + resolve(Utils.fetch(input, { ...init, bypassProxy: true })); + } + + request.onerror = () => + fallbackOrReject(new TypeError("NetworkError: Network request failed")); + request.ontimeout = () => + fallbackOrReject(new TypeError("Timeout: Network request failed")); + request.onabort = () => + fallbackOrReject(new DOMException("Aborted", "AbortError")); + request.onload = () => { + // Parse raw response headers into `Headers` object. + const headers = new Headers(); + const rawHeaders = request.getAllResponseHeaders(); + rawHeaders + .trim() + .split(/[\r\n]+/) + .forEach(line => { + const parts = line.split(": "); + const header = parts.shift(); + const value = parts.join(": "); + headers.set(header, value); + }); + + const responseAttributes = { + status: request.status, + statusText: request.statusText, + url: request.responseURL, + headers, + }; + resolve(new Response(request.response, responseAttributes)); + }; + + const { method = "GET", headers = {}, bypassProxy = false } = init; + + request.open(method, input, { bypassProxy }); + // By default, XMLHttpRequest converts the response based on the + // Content-Type header, or UTF-8 otherwise. This may mangle binary + // responses. Avoid that by requesting the raw bytes. + request.responseType = "arraybuffer"; + + for (const [name, value] of Object.entries(headers)) { + request.setRequestHeader(name, value); + } + + request.send(); + }); + }, + + /** + * Check if local data exist for the specified client. + * + * @param {RemoteSettingsClient} client + * @return {bool} Whether it exists or not. + */ + async hasLocalData(client) { + const timestamp = await client.db.getLastModified(); + return timestamp !== null; + }, + + /** + * Check if we ship a JSON dump for the specified bucket and collection. + * + * @param {String} bucket + * @param {String} collection + * @return {bool} Whether it is present or not. + */ + async hasLocalDump(bucket, collection) { + try { + await fetch( + `resource://app/defaults/settings/${bucket}/${collection}.json`, + { + method: "HEAD", + } + ); + return true; + } catch (e) { + return false; + } + }, + + /** + * Look up the last modification time of the JSON dump. + * + * @param {String} bucket + * @param {String} collection + * @return {int} The last modification time of the dump. -1 if non-existent. + */ + async getLocalDumpLastModified(bucket, collection) { + if (!this._dumpStats) { + if (!this._dumpStatsInitPromise) { + this._dumpStatsInitPromise = (async () => { + try { + let res = await fetch( + "resource://app/defaults/settings/last_modified.json" + ); + this._dumpStats = await res.json(); + } catch (e) { + log.warn(`Failed to load last_modified.json: ${e}`); + this._dumpStats = {}; + } + delete this._dumpStatsInitPromise; + })(); + } + await this._dumpStatsInitPromise; + } + const identifier = `${bucket}/${collection}`; + let lastModified = this._dumpStats[identifier]; + if (lastModified === undefined) { + const { timestamp: dumpTimestamp } = await lazy.SharedUtils.loadJSONDump( + bucket, + collection + ); + // Client recognize -1 as missing dump. + lastModified = dumpTimestamp ?? -1; + this._dumpStats[identifier] = lastModified; + } + return lastModified; + }, + + /** + * Fetch the list of remote collections and their timestamp. + * ``` + * { + * "timestamp": 1486545678, + * "changes":[ + * { + * "host":"kinto-ota.dev.mozaws.net", + * "last_modified":1450717104423, + * "bucket":"blocklists", + * "collection":"certificates" + * }, + * ... + * ], + * "metadata": {} + * } + * ``` + * @param {String} serverUrl The server URL (eg. `https://server.org/v1`) + * @param {int} expectedTimestamp The timestamp that the server is supposed to return. + * We obtained it from the Megaphone notification payload, + * and we use it only for cache busting (Bug 1497159). + * @param {String} lastEtag (optional) The Etag of the latest poll to be matched + * by the server (eg. `"123456789"`). + * @param {Object} filters + */ + async fetchLatestChanges(serverUrl, options = {}) { + const { expectedTimestamp, lastEtag = "", filters = {} } = options; + + let url = serverUrl + Utils.CHANGES_PATH; + const params = { + ...filters, + _expected: expectedTimestamp ?? 0, + }; + if (lastEtag != "") { + params._since = lastEtag; + } + if (params) { + url += + "?" + + Object.entries(params) + .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) + .join("&"); + } + const response = await Utils.fetch(url); + + if (response.status >= 500) { + throw new Error(`Server error ${response.status} ${response.statusText}`); + } + + const is404FromCustomServer = + response.status == 404 && + Services.prefs.prefHasUserValue("services.settings.server"); + + const ct = response.headers.get("Content-Type"); + if (!is404FromCustomServer && (!ct || !ct.includes("application/json"))) { + throw new Error(`Unexpected content-type "${ct}"`); + } + + let payload; + try { + payload = await response.json(); + } catch (e) { + payload = e.message; + } + + if (!payload.hasOwnProperty("changes")) { + // If the server is failing, the JSON response might not contain the + // expected data. For example, real server errors (Bug 1259145) + // or dummy local server for tests (Bug 1481348) + if (!is404FromCustomServer) { + throw new Error( + `Server error ${url} ${response.status} ${ + response.statusText + }: ${JSON.stringify(payload)}` + ); + } + } + + const { changes = [], timestamp } = payload; + + let serverTimeMillis = Date.parse(response.headers.get("Date")); + // Since the response is served via a CDN, the Date header value could have been cached. + const cacheAgeSeconds = response.headers.has("Age") + ? parseInt(response.headers.get("Age"), 10) + : 0; + serverTimeMillis += cacheAgeSeconds * 1000; + + // Age of data (time between publication and now). + const ageSeconds = (serverTimeMillis - timestamp) / 1000; + + // Check if the server asked the clients to back off. + let backoffSeconds; + if (response.headers.has("Backoff")) { + const value = parseInt(response.headers.get("Backoff"), 10); + if (!isNaN(value)) { + backoffSeconds = value; + } + } + + return { + changes, + currentEtag: `"${timestamp}"`, + serverTimeMillis, + backoffSeconds, + ageSeconds, + }; + }, + + /** + * Test if a single object matches all given filters. + * + * @param {Object} filters The filters object. + * @param {Object} entry The object to filter. + * @return {Boolean} + */ + filterObject(filters, entry) { + return Object.entries(filters).every(([filter, value]) => { + if (Array.isArray(value)) { + return value.some(candidate => candidate === entry[filter]); + } else if (typeof value === "object") { + return Utils.filterObject(value, entry[filter]); + } else if (!Object.prototype.hasOwnProperty.call(entry, filter)) { + console.error(`The property ${filter} does not exist`); + return false; + } + return entry[filter] === value; + }); + }, + + /** + * Sorts records in a list according to a given ordering. + * + * @param {String} order The ordering, eg. `-last_modified`. + * @param {Array} list The collection to order. + * @return {Array} + */ + sortObjects(order, list) { + const hasDash = order[0] === "-"; + const field = hasDash ? order.slice(1) : order; + const direction = hasDash ? -1 : 1; + return list.slice().sort((a, b) => { + if (a[field] && _isUndefined(b[field])) { + return direction; + } + if (b[field] && _isUndefined(a[field])) { + return -direction; + } + if (_isUndefined(a[field]) && _isUndefined(b[field])) { + return 0; + } + return a[field] > b[field] ? direction : -direction; + }); + }, +}; diff --git a/services/settings/components.conf b/services/settings/components.conf new file mode 100644 index 0000000000..fbc4df9bfc --- /dev/null +++ b/services/settings/components.conf @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{5e756573-234a-49ea-bbe4-59ec7a70657d}', + 'contract_ids': ['@mozilla.org/services/settings;1'], + 'esModule': 'resource://services-settings/RemoteSettingsComponents.sys.mjs', + 'constructor': 'RemoteSettingsTimer', + }, +] diff --git a/services/settings/docs/index.rst b/services/settings/docs/index.rst new file mode 100644 index 0000000000..fb9cdc1f49 --- /dev/null +++ b/services/settings/docs/index.rst @@ -0,0 +1,552 @@ +.. _services/remotesettings: + +=============== +Remote Settings +=============== + +The :searchfox:`remote-settings.sys.mjs <services/settings/remote-settings.sys.mjs>` module offers the ability to fetch remote settings that are kept in sync with Mozilla servers. + + +Usage +===== + +The ``get()`` method returns the list of entries for a specific key. Each entry can have arbitrary attributes, and can only be modified on the server. + +.. code-block:: js + + const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.sys.mjs"); + + const data = await RemoteSettings("a-key").get(); + + /* + data == [ + {label: "Yahoo", enabled: true, weight: 10, id: "d0782d8d", last_modified: 1522764475905}, + {label: "Google", enabled: true, weight: 20, id: "8883955f", last_modified: 1521539068414}, + {label: "Ecosia", enabled: false, weight: 5, id: "337c865d", last_modified: 1520527480321}, + ] + */ + + for (const entry of data) { + // Do something with entry... + // await InternalAPI.load(entry.id, entry.label, entry.weight); + }); + +.. note:: + The ``id`` and ``last_modified`` (timestamp) attributes are assigned by the server. + + +Empty local database +-------------------- + +On new user profiles or for recently added use-cases, the local database will be empty until a synchronization with the server happens. Synchronizations are managed internally, and can sometimes be triggered minutes after browser starts. + +By default, if ``.get()`` is called before the local database had the chance to be synchronized, and if no initial data was provided (:ref:`see below <services/initial-data>`), then the settings will be pulled from the server in order to avoid returning an empty list. In that case, the first call to ``.get()`` will thus take longer than the following ones. + +This behaviour can be disabled using the ``syncIfEmpty`` option. + +.. important:: + + If the implicit synchronization fails (e.g network is not available) then errors are silent and **an empty list is returned**. :ref:`Uptake Telemetry <services/settings/uptake-telemetry>` status is sent though. + + +Options +------- + +* ``filters``, ``order``: The list can optionally be filtered or ordered: + + .. code-block:: js + + const subset = await RemoteSettings("a-key").get({ + filters: { + property: "value" + }, + order: "-weight" + }); + +* ``syncIfEmpty``: if ``true`` and no local data is present, then look for a packaged dump to load. If none, then pull records from network. + Set it to ``false`` to skip loading of packaged dump and network activity. Use this only if your use-case can tolerate an empty list until the first synchronization happens (default: ``true``). + + .. code-block:: js + + await RemoteSettings("a-key").get({ syncIfEmpty: false }); + +* ``verifySignature``: verify the content signature of the local data (default: ``false``). + An error is thrown if the local data was altered. This hurts performance, but can be used if your use case needs to be secure from local tampering. + +* ``emptyListFallback``: return an empty list if obtaining the records fails (default: ``true``). + If an error occurs during the reading of local data, or during synchronization, then an empty list is returned. + + +Events +------ + +The ``on()`` function registers handlers to be triggered when events occur. + +The ``"sync"`` event allows to be notified when the remote settings are changed on the server side. Your handler is given an ``event`` object that contains a ``data`` attribute that has information about the changes: + +- ``current``: current list of entries (after changes were applied); +- ``created``, ``updated``, ``deleted``: list of entries that were created/updated/deleted respectively. + +.. code-block:: js + + RemoteSettings("a-key").on("sync", event => { + const { data: { current } } = event; + for (const entry of current) { + // Do something with entry... + // await InternalAPI.reload(entry.id, entry.label, entry.weight); + } + }); + +.. note:: + + Currently, the synchronization of remote settings is triggered via push notifications, and also by its own timer every 24H (see the preference ``services.settings.poll_interval`` ). + + +File attachments +---------------- + +When an entry has a file attached to it, it has an ``attachment`` attribute, which contains the file related information (url, hash, size, mimetype, etc.). + +Remote files are not downloaded automatically. In order to keep attachments in sync, the provided helper can be leveraged like this: + +.. code-block:: js + + const client = RemoteSettings("a-key"); + + client.on("sync", async ({ data: { created, updated, deleted } }) => { + const toDelete = deleted.filter(d => d.attachment); + const toDownload = created + .concat(updated.map(u => u.new)) + .filter(d => d.attachment); + + // Remove local files of deleted records + await Promise.all( + toDelete.map(record => client.attachments.deleteDownloaded(record)) + ); + + // Download new attachments + const fileContents = await Promise.all( + toDownload.map(async record => { + const { buffer } = await client.attachments.download(record); + return buffer; + }); + ); + }); + +The provided helper will: + - fetch the remote binary content + - write the file in the local IndexedDB + - check the file size + - check the content SHA256 hash + - do nothing if the attachment was already present and sound locally. + +.. important:: + + The following aspects are not taken care of (yet! help welcome): + + - check available disk space + - preserve bandwidth + - resume downloads of large files + +.. note:: + + The ``download()`` method supports the following options: + + - ``retries`` (default: ``3``): number of retries on network errors + - ``fallbackToCache`` (default: ``false``): allows callers to fall back to the cached file and record, if the requested record's attachment fails to download. + This enables callers to always have a valid pair of attachment and record, + provided that the attachment has been retrieved at least once. + - ``fallbackToDump`` (default: ``false``): activates a fallback to a dump that has been + packaged with the client, when other ways to load the attachment have failed. + See :ref:`services/packaging-attachments <services/packaging-attachments>` for more information. + +.. note:: + + A ``downloadAsBytes()`` method returning an ``ArrayBuffer`` is also available, if writing the attachment locally is not necessary. + + Some ``downloadToDisk()`` and ``deleteFromDisk()`` methods are also available but generally discouraged, since they are prone to leaving extraneous files + in the profile directory (see `Bug 1634127 <https://bugzilla.mozilla.org/show_bug.cgi?id=1634127>`_). + + +.. _services/initial-data: + +Initial data +------------ + +It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet. + +The JSON dump will serve as the default dataset for ``.get()``, instead of doing a round-trip to pull the latest data. It will also reduce the amount of data to be downloaded on the first synchronization. + +#. Place the JSON dump of the server records in the ``services/settings/dumps/main/`` folder. Using this command: + :: + + CID="your-collection" + curl "https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/${CID}/changeset?_expected=0" | jq '{"data": .changes, "timestamp": .timestamp}' > services/settings/dumps/main/${CID}.json`` + +#. Add the filename to the ``FINAL_TARGET_FILES`` list in ``services/settings/dumps/main/moz.build`` +#. Add the filename to the ``[browser]`` section of ``mobile/android/installer/package-manifest.in`` IF the file should be bundled with Android. + +Now, when ``RemoteSettings("some-key").get()`` is called from an empty profile, the ``some-key.json`` file is going to be loaded before the results are returned. + +JSON dumps in the tree are periodically updated by ``taskcluster/docker/periodic-updates/scripts/periodic_file_updates.sh``. +If your collection's in-tree dump should not be kept up to date by this automation, place the JSON file in ``services/settings/static-dumps/`` instead. + +.. note:: + + The example above uses "main" because that's the default bucket name. + If you have customized the bucket name, use the actual bucket name instead of "main". + +.. _services/packaging-attachments: + +Packaging attachments +~~~~~~~~~~~~~~~~~~~~~ + +Attachments are not included in the JSON dumps by default. You may choose to package the attachment +with the client, for example if it is important to have the data available at the first startup +without requiring network activity. Or if most users would download the attachment anyway. +Only package attachments if needed, since they increase the file size of the Firefox installer. + +To package an attachment for consumers of the `download()` method: + +#. Select the desired attachment record from the JSON dump of the server records, and place it at + ``services/settings/dumps/<bucket name>/<collection name>/<attachment id>.meta.json``. + The ``<attachment id>`` defaults to the ``id`` field of the record. If this ``id`` is not fixed, + you must choose a custom ID that can be relied upon as a long-term attachment identifier. See + the notes below for more details. +#. Download the attachment associated with the record, and place it at + ``services/settings/dumps/<bucket name>/<collection name>/<attachment id>``. +#. Update ``taskcluster/docker/periodic-updates/scripts/periodic_file_updates.sh`` and add the attachment, + by editing the ``compare_remote_settings_files`` function and describing the attachment. + Unlike JSON dumps, attachments must explicitly be listed in that update script, because the + attachment selection logic needs to be codified in a ``jq`` filter in the script. + For an example, see `Bug 1636158 <https://bugzilla.mozilla.org/show_bug.cgi?id=1636158>`_. +#. Register the location of the ``<attachment id>.meta.json`` and ``<attachment id>`` in the + ``moz.build`` file of the collection folder, and possibly ``package-manifest.in``, + as described in `the previous section about registering JSON dumps <services/initial-data>`. + +.. note:: + + ``<attachment id>`` is used to derive the file names of the packaged attachment dump, and as the + key for the cache where attachment updates from the network are saved. + The attachment identifier is expected to be fixed across client application updates. + If that expectation cannot be met, the ``attachmentId`` option of the ``download`` method of the + attachment downloader should be used to override the attachment ID with a custom (stable) value. + In order to keep track of the cached attachment, and prevent it from being pruned automatically, + the attachment identifier will have to be explicitly listed in the ``keepAttachmentsIds = [<attachment id>]`` + option of the RemoteSettings client constructor. + +.. note:: + + The contents of the ``.meta.json`` file is already contained within the records, but separated + from the main set of records to ensure the availability of the original record with the data, + independently of the packaged or downloaded records. + This file may become optional in a future update, see `Bug 1640059 <https://bugzilla.mozilla.org/show_bug.cgi?id=1640059>`_. + + +Synchronization Process +======================= + +The synchronization process consists in pulling the recent changes, merging them with the local data, and verifying the integrity of the result. + +.. image:: synchronization-flow.svg + +.. Source of diagram +.. https://mermaid-js.github.io/mermaid-live-editor/ +.. When using this tool, please remove xlink prefix from attributes in the resulting SVG file. +.. See bug 1481470. +.. +.. graph TD +.. 0[Sync] --> pull; +.. pull[Pull changes] --> merge[Merge with local] +.. merge --> valid{Is signature valid?}; +.. valid -->|Yes| Success; +.. valid -->|No| retried{Retried?}; +.. retried --> |Yes| validchanges{Valid without changes?}; +.. retried --> |No| valid2{Valid without changes?}; +.. validchanges -->|Yes| restoredata[Restore previous data]; +.. validchanges -->|No| clear[Clear local]; +.. restore --> Failure; +.. valid2 --> |No| clear2[Clear local]; +.. valid2 --> |Yes| Retry; +.. Retry --> |Retry| pull; +.. clear2 --> Retry; +.. clear --> restore[Restore packaged dump]; +.. restoredata --> Failure; +.. style 0 fill:#00ff00; +.. style Success fill:#00ff00; +.. style Failure fill:#ff0000; + +.. important:: + + As shown above, we can end-up in situations where synchronization fails and will leave the local DB in an empty state. + + +Targets and A/B testing +======================= + +In order to deliver settings to subsets of the population, you can set targets on entries (platform, language, channel, version range, preferences values, samples, etc.) when editing records on the server. + +From the client API standpoint, this is completely transparent: the ``.get()`` method — as well as the event data — will always filter the entries on which the target matches. + +.. note:: + + The remote settings targets follow the same approach as the :ref:`Normandy recipe client <components/normandy>` (ie. JEXL filter expressions). + + +.. _services/settings/uptake-telemetry: + +Uptake Telemetry +================ + +Some :ref:`uptake telemetry <telemetry/collection/uptake>` is collected in order to monitor how remote settings are propagated. + +It is submitted to a single :ref:`keyed histogram <histogram-type-keyed>` whose id is ``UPTAKE_REMOTE_CONTENT_RESULT_1`` and the keys are prefixed with ``main/`` (eg. ``main/a-key`` in the above example). + + +Create new remote settings +========================== + +Staff members can create new kinds of remote settings, following `this documentation <https://remote-settings.readthedocs.io/en/latest/getting-started.html>`_. + +It basically consists in: + +#. Choosing a key (eg. ``search-providers``) +#. Assigning collaborators to editors and reviewers groups +#. (*optional*) Define a JSONSchema to validate entries +#. (*optional*) Allow attachments on entries + +And once done: + +#. Create, modify or delete entries and let reviewers approve the changes +#. Wait for Firefox to pick-up the changes for your settings key + + +Global Notifications +==================== + +The polling for changes process sends two notifications that observers can register to: + +* ``remote-settings:changes-poll-start``: Polling for changes is starting. triggered either by the scheduled timer or a push broadcast. +* ``remote-settings:changes-poll-end``: Polling for changes has ended +* ``remote-settings:sync-error``: A synchronization error occurred. Notification subject provides information about the error and affected + collection in the ``wrappedJSObject`` attribute. +* ``remote-settings:broken-sync-error``: Synchronization seems to be consistently failing. Profile is at risk. + +.. code-block:: javascript + + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "remote-settings:changes-poll-start"); + + const { expectedTimestamp } = JSON.parse(aData); + console.log("Polling started", expectedTimestamp ? "from push broadcast" : "by scheduled trigger"); + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-start"); + + +Advanced Options +================ + +``localFields``: records fields that remain local +------------------------------------------------- + +During synchronization, the local database is compared with the server data. Any difference will be overwritten by the remote version. + +In some use-cases it's necessary to store some state using extra attributes on records. The ``localFields`` options allows to specify which records field names should be preserved on records during synchronization. + +.. code-block:: javascript + + const client = RemoteSettings("a-collection", { + localFields: [ "userNotified", "userResponse" ], + }); + + +``filterFunc``: custom filtering function +----------------------------------------- + +By default, the entries returned by ``.get()`` are filtered based on the JEXL expression result from the ``filter_expression`` field. The ``filterFunc`` option allows to execute a custom filter (async) function, that should return the record (modified or not) if kept or a falsy value if filtered out. + +.. code-block:: javascript + + const client = RemoteSettings("a-collection", { + filterFunc: (record, environment) => { + const { enabled, ...entry } = record; + return enabled ? entry : null; + } + }); + + +Debugging and manual testing +============================ + +Logging +------- + +In order to enable verbose logging, set the log level preference to ``debug``. + +.. code-block:: javascript + + Services.prefs.setStringPref("services.settings.loglevel", "debug"); + +Remote Settings Dev Tools +------------------------- + +The Remote Settings Dev Tools extension provides some tooling to inspect synchronization statuses, to change the remote server or to switch to *preview* mode in order to sign-off pending changes. `More information on the dedicated repository <https://github.com/mozilla/remote-settings-devtools>`_. + +Preview Mode +------------ + +Enable the preview mode in order to preview changes to be reviewed on the server. This can be achieved using the *Remote Settings Dev Tools*, or programmatically with: + +.. code-block:: javascript + + RemoteSettings.enablePreviewMode(true); + +In order to pull preview data **on startup**, or in order to persist it across restarts, set ``services.settings.preview_enabled`` to ``true`` in the profile preferences (ie. ``user.js``). +For release and ESR, for security reasons, you would have to run the application with the ``MOZ_REMOTE_SETTINGS_DEVTOOLS=1`` environment variable for the preference to be taken into account. Note that toggling the preference won't have any effect until restart. + +Trigger a synchronization manually +---------------------------------- + +The synchronization of every known remote settings clients can be triggered manually with ``pollChanges()``: + +.. code-block:: js + + await RemoteSettings.pollChanges() + +In order to ignore last synchronization status during polling for changes, set the ``full`` option: + +.. code-block:: js + + await RemoteSettings.pollChanges({ full: true }) + +The synchronization of a single client can be forced with the ``.sync()`` method: + +.. code-block:: js + + await RemoteSettings("a-key").sync(); + +.. important:: + + The above methods are only relevant during development or debugging and should never be called in production code. + + +Inspect local data +------------------ + +The internal IndexedDB of Remote Settings can be accessed via the Storage Inspector in the `browser toolbox <https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox>`_. + +For example, the local data of the ``"key"`` collection can be accessed in the ``remote-settings`` database at *Browser Toolbox* > *Storage* > *IndexedDB* > *chrome*, in the ``records`` store. + + +Delete all local data +--------------------- + +All local data, of **every collection**, including downloaded attachments, can be deleted with: + +.. code-block:: js + + await RemoteSettings.clearAll(); + + +Unit Tests +========== + +As a foreword, we would like to underline the fact that your tests should not test Remote Settings itself. Your tests should assume Remote Settings works, and should only run assertions on the integration part. For example, if you see yourself mocking the server responses, your tests may go over their responsibility. + +If your code relies on the ``"sync"`` event, you are likely to be interested in faking this event and make sure your code runs as expected. If it relies on ``.get()``, you will probably want to insert some fake local data. + + +Simulate ``"sync"`` events +-------------------------- + +You can forge a ``payload`` that contains the events attributes as described above, and emit it :) + +.. code-block:: js + + const payload = { + current: [{ id: "abc", age: 43 }], + created: [], + updated: [{ old: { id: "abc", age: 42 }, new: { id: "abc", age: 43 }}], + deleted: [], + }; + + await RemoteSettings("a-key").emit("sync", { data: payload }); + + +Manipulate local data +--------------------- + +A handle on the underlying database can be obtained through the ``.db`` attribute. + +.. code-block:: js + + const db = RemoteSettings("a-key").db; + +And records can be created manually (as if they were synchronized from the server): + +.. code-block:: js + + const record = await db.create({ + id: "a-custom-string-or-uuid", + domain: "website.com", + usernameSelector: "#login-account", + passwordSelector: "#pass-signin", + }); + +If no timestamp is set, any call to ``.get()`` will trigger the load of initial data (JSON dump) if any, or a synchronization will be triggered. To avoid that, store a fake timestamp. We use ``Date.now()`` instead of an arbitrary number, to make sure it's higher than the dump's, and thus prevent its load from the test. + +.. code-block:: js + + await db.importChanges({}, Date.now()); + +In order to bypass the potential target filtering of ``RemoteSettings("key").get()``, the low-level listing of records can be obtained with ``collection.list()``: + +.. code-block:: js + + const { data: subset } = await db.list({ + filters: { + "property": "value" + } + }); + +The local data can be flushed with ``clear()``: + +.. code-block:: js + + await db.clear() + + +Misc +==== + +We host more documentation on https://remote-settings.readthedocs.io/, on how to run a server locally, manage attachments, or use the REST API etc. + +About blocklists +---------------- + +The security settings, as well as addons, plugins, and GFX blocklists were the first use-cases of remote settings, and thus have some specificities. + +For example, they leverage advanced customization options (bucket, content-signature certificate, target filtering etc.). In order to get a reference to these clients, their initialization code must be executed first. + +.. code-block:: js + + const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm"); + + RemoteSecuritySettings.init(); + + + const {BlocklistPrivate} = ChromeUtils.import("resource://gre/modules/Blocklist.jsm"); + + BlocklistPrivate.ExtensionBlocklistRS._ensureInitialized(); + BlocklistPrivate.PluginBlocklistRS._ensureInitialized(); + BlocklistPrivate.GfxBlocklistRS._ensureInitialized(); + +Then, in order to access a specific client instance, the ``bucketName`` must be specified: + +.. code-block:: js + + const client = RemoteSettings("onecrl", { bucketName: "security-state" }); + +And in the storage inspector, the IndexedDB internal store will be prefixed with ``security-state`` instead of ``main`` (eg. ``security-state/onecrl``). diff --git a/services/settings/docs/synchronization-flow.svg b/services/settings/docs/synchronization-flow.svg new file mode 100644 index 0000000000..f8cd66e42e --- /dev/null +++ b/services/settings/docs/synchronization-flow.svg @@ -0,0 +1,4 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this file, +You can obtain one at http://mozilla.org/MPL/2.0/. --> +<svg id="mermaid-1614267332297" width="100%" xmlns="http://www.w3.org/2000/svg" height="1191.02001953125" style="max-width: 681.097900390625px;" viewBox="0 0 681.097900390625 1191.02001953125"><style>#mermaid-1614267332297{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-1614267332297 .error-icon{fill:#552222;}#mermaid-1614267332297 .error-text{fill:#552222;stroke:#552222;}#mermaid-1614267332297 .edge-thickness-normal{stroke-width:2px;}#mermaid-1614267332297 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-1614267332297 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-1614267332297 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-1614267332297 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-1614267332297 .marker{fill:#333333;stroke:#333333;}#mermaid-1614267332297 .marker.cross{stroke:#333333;}#mermaid-1614267332297 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-1614267332297 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-1614267332297 .cluster-label text{fill:#333;}#mermaid-1614267332297 .cluster-label span{color:#333;}#mermaid-1614267332297 .label text,#mermaid-1614267332297 span{fill:#333;color:#333;}#mermaid-1614267332297 .node rect,#mermaid-1614267332297 .node circle,#mermaid-1614267332297 .node ellipse,#mermaid-1614267332297 .node polygon,#mermaid-1614267332297 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-1614267332297 .node .label{text-align:center;}#mermaid-1614267332297 .node.clickable{cursor:pointer;}#mermaid-1614267332297 .arrowheadPath{fill:#333333;}#mermaid-1614267332297 .edgePath .path{stroke:#333333;stroke-width:1.5px;}#mermaid-1614267332297 .flowchart-link{stroke:#333333;fill:none;}#mermaid-1614267332297 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-1614267332297 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-1614267332297 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-1614267332297 .cluster text{fill:#333;}#mermaid-1614267332297 .cluster span{color:#333;}#mermaid-1614267332297 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80,100%,96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-1614267332297:root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-1614267332297 flowchart{fill:apa;}</style><g><g class="output"><g class="clusters"></g><g class="edgePaths"><g class="edgePath LS-0 LE-pull" style="opacity: 1;" id="L-0-pull"><path class="path" d="M342.5449962615967,48L342.5449962615967,73L342.5449962615967,98" marker-end="url(#arrowhead2240)" style="fill:none"></path><defs><marker id="arrowhead2240" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-pull LE-merge" style="opacity: 1;" id="L-pull-merge"><path class="path" d="M312.50888548956976,138L274.96374702453613,163L274.96374702453613,188" marker-end="url(#arrowhead2241)" style="fill:none"></path><defs><marker id="arrowhead2241" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-merge LE-valid" style="opacity: 1;" id="L-merge-valid"><path class="path" d="M274.96374702453613,228L274.96374702453613,253L275.46374702453613,278.5000038146973" marker-end="url(#arrowhead2242)" style="fill:none"></path><defs><marker id="arrowhead2242" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-valid LE-Success" style="opacity: 1;" id="L-valid-Success"><path class="path" d="M244.2903903057476,415.77665167354536L204.9066619873047,481.45001220703125L204.9066619873047,549.5725135803223" marker-end="url(#arrowhead2243)" style="fill:none"></path><defs><marker id="arrowhead2243" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-valid LE-retried" style="opacity: 1;" id="L-valid-retried"><path class="path" d="M306.6371009195334,415.7766468679424L345.0208320617676,481.45001220703125L345.5208320617674,516.9500122070311" marker-end="url(#arrowhead2244)" style="fill:none"></path><defs><marker id="arrowhead2244" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-retried LE-validchanges" style="opacity: 1;" id="L-retried-validchanges"><path class="path" d="M314.3468395079885,592.021022399834L219.85833358764648,657.6950149536133L220.35833358764648,693.1950157165527" marker-end="url(#arrowhead2245)" style="fill:none"></path><defs><marker id="arrowhead2245" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-retried LE-valid2" style="opacity: 1;" id="L-retried-valid2"><path class="path" d="M380.78403175221416,587.931815263167L519.0187530517578,657.6950149536133L519.5187530517578,693.1950157165533" marker-end="url(#arrowhead2246)" style="fill:none"></path><defs><marker id="arrowhead2246" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-validchanges LE-restoredata" style="opacity: 1;" id="L-validchanges-restoredata"><path class="path" d="M266.73461943413736,847.1437237665461L336.4000015258789,928.0200119018555L336.4000015258789,983.0200119018555L336.4000015258789,1028.0200119018555L336.4000015258789,1053.0200119018555" marker-end="url(#arrowhead2247)" style="fill:none"></path><defs><marker id="arrowhead2247" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-validchanges LE-clear" style="opacity: 1;" id="L-validchanges-clear"><path class="path" d="M173.98204703465768,847.1437245859271L103.31666564941406,928.0200119018555L103.31666564941406,963.0200119018555" marker-end="url(#arrowhead2248)" style="fill:none"></path><defs><marker id="arrowhead2248" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-restore LE-Failure" style="opacity: 1;" id="L-restore-Failure"><path class="path" d="M103.31666564941406,1093.0200119018555L103.31666564941406,1118.0200119018555L184.9666633605957,1149.5473614070174" marker-end="url(#arrowhead2249)" style="fill:none"></path><defs><marker id="arrowhead2249" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-valid2 LE-clear2" style="opacity: 1;" id="L-valid2-clear2"><path class="path" d="M535.6179849609663,877.4207777038293L544.904167175293,928.0200119018555L544.904167175293,963.0200119018555" marker-end="url(#arrowhead2250)" style="fill:none"></path><defs><marker id="arrowhead2250" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-valid2 LE-Retry" style="opacity: 1;" id="L-valid2-Retry"><path class="path" d="M471.3317676967353,845.3330257838934L393.7083320617676,928.0200119018555L393.7083320617676,983.0200119018555L393.7083320617676,1028.0200119018555L541.872917175293,1065.6716907714963" marker-end="url(#arrowhead2251)" style="fill:none"></path><defs><marker id="arrowhead2251" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-Retry LE-pull" style="opacity: 1;" id="L-Retry-pull"><path class="path" d="M599.7062454223633,1057.4159367943996L654.1812515258789,1028.0200119018555L654.1812515258789,983.0200119018555L654.1812515258789,928.0200119018555L654.1812515258789,792.8575134277344L654.1812515258789,657.6950149536133L654.1812515258789,569.5725135803223L654.1812515258789,481.45001220703125L654.1812515258789,362.2250061035156L654.1812515258789,253L654.1812515258789,208L654.1812515258789,163L397.03666496276855,125.86854882938192" marker-end="url(#arrowhead2252)" style="fill:none"></path><defs><marker id="arrowhead2252" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-clear2 LE-Retry" style="opacity: 1;" id="L-clear2-Retry"><path class="path" d="M544.904167175293,1003.0200119018555L544.904167175293,1028.0200119018555L559.2849527994791,1053.0200119018555" marker-end="url(#arrowhead2253)" style="fill:none"></path><defs><marker id="arrowhead2253" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-clear LE-restore" style="opacity: 1;" id="L-clear-restore"><path class="path" d="M103.31666564941406,1003.0200119018555L103.31666564941406,1028.0200119018555L103.31666564941406,1053.0200119018555" marker-end="url(#arrowhead2254)" style="fill:none"></path><defs><marker id="arrowhead2254" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g><g class="edgePath LS-restoredata LE-Failure" style="opacity: 1;" id="L-restoredata-Failure"><path class="path" d="M336.4000015258789,1093.0200119018555L336.4000015258789,1118.0200119018555L254.75000381469727,1149.5473614070174" marker-end="url(#arrowhead2255)" style="fill:none"></path><defs><marker id="arrowhead2255" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1px; stroke-dasharray: 1px, 0px;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-0-pull" class="edgeLabel L-LS-0' L-LE-pull"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-pull-merge" class="edgeLabel L-LS-pull' L-LE-merge"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-merge-valid" class="edgeLabel L-LS-merge' L-LE-valid"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(204.9066619873047,481.45001220703125)"><g transform="translate(-11.324996948242188,-10)" class="label"><rect rx="0" ry="0" width="22.649993896484375" height="20"></rect><foreignObject width="22.649993896484375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-valid-Success" class="edgeLabel L-LS-valid' L-LE-Success">Yes</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(345.0208320617676,481.45001220703125)"><g transform="translate(-9.399993896484375,-10)" class="label"><rect rx="0" ry="0" width="18.79998779296875" height="20"></rect><foreignObject width="18.79998779296875" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-valid-retried" class="edgeLabel L-LS-valid' L-LE-retried">No</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(219.85833358764648,657.6950149536133)"><g transform="translate(-11.324996948242188,-10)" class="label"><rect rx="0" ry="0" width="22.649993896484375" height="20"></rect><foreignObject width="22.649993896484375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-retried-validchanges" class="edgeLabel L-LS-retried' L-LE-validchanges">Yes</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(519.0187530517578,657.6950149536133)"><g transform="translate(-9.399993896484375,-10)" class="label"><rect rx="0" ry="0" width="18.79998779296875" height="20"></rect><foreignObject width="18.79998779296875" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-retried-valid2" class="edgeLabel L-LS-retried' L-LE-valid2">No</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(336.4000015258789,983.0200119018555)"><g transform="translate(-11.324996948242188,-10)" class="label"><rect rx="0" ry="0" width="22.649993896484375" height="20"></rect><foreignObject width="22.649993896484375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-validchanges-restoredata" class="edgeLabel L-LS-validchanges' L-LE-restoredata">Yes</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(103.31666564941406,928.0200119018555)"><g transform="translate(-9.399993896484375,-10)" class="label"><rect rx="0" ry="0" width="18.79998779296875" height="20"></rect><foreignObject width="18.79998779296875" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-validchanges-clear" class="edgeLabel L-LS-validchanges' L-LE-clear">No</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-restore-Failure" class="edgeLabel L-LS-restore' L-LE-Failure"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(544.904167175293,928.0200119018555)"><g transform="translate(-9.399993896484375,-10)" class="label"><rect rx="0" ry="0" width="18.79998779296875" height="20"></rect><foreignObject width="18.79998779296875" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-valid2-clear2" class="edgeLabel L-LS-valid2' L-LE-clear2">No</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(393.7083320617676,983.0200119018555)"><g transform="translate(-11.324996948242188,-10)" class="label"><rect rx="0" ry="0" width="22.649993896484375" height="20"></rect><foreignObject width="22.649993896484375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-valid2-Retry" class="edgeLabel L-LS-valid2' L-LE-Retry">Yes</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform="translate(654.1812515258789,569.5725135803223)"><g transform="translate(-18.916671752929688,-10)" class="label"><rect rx="0" ry="0" width="37.833343505859375" height="20"></rect><foreignObject width="37.833343505859375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-Retry-pull" class="edgeLabel L-LS-Retry' L-LE-pull">Retry</span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-clear2-Retry" class="edgeLabel L-LS-clear2' L-LE-Retry"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-clear-restore" class="edgeLabel L-LS-clear' L-LE-restore"></span></div></foreignObject></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span id="L-L-restoredata-Failure" class="edgeLabel L-LS-restoredata' L-LE-Failure"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node default" style="opacity: 1;" id="flowchart-0-2553" transform="translate(342.5449962615967,28)"><rect rx="0" ry="0" x="-26.125" y="-20" width="52.25" height="40" class="label-container" style="fill:#00ff00;"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-16.125,-10)"><foreignObject width="32.25" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Sync</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-pull-2554" transform="translate(342.5449962615967,118)"><rect rx="0" ry="0" x="-54.491668701171875" y="-20" width="108.98333740234375" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-44.491668701171875,-10)"><foreignObject width="88.98333740234375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Pull changes</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-merge-2556" transform="translate(274.96374702453613,208)"><rect rx="0" ry="0" x="-69.30000305175781" y="-20" width="138.60000610351562" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-59.30000305175781,-10)"><foreignObject width="118.60000610351562" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Merge with local</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-valid-2558" transform="translate(274.96374702453613,362.2250061035156)"><polygon points="84.22500228881836,0 168.45000457763672,-84.22500228881836 84.22500228881836,-168.45000457763672 0,-84.22500228881836" transform="translate(-84.22500228881836,84.22500228881836)" class="label-container"></polygon><g class="label" transform="translate(0,0)"><g transform="translate(-63.583335876464844,-10)"><foreignObject width="127.16667175292969" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Is signature valid?</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-Success-2560" transform="translate(204.9066619873047,569.5725135803223)"><rect rx="0" ry="0" x="-36.991668701171875" y="-20" width="73.98333740234375" height="40" class="label-container" style="fill:#00ff00;"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-26.991668701171875,-10)"><foreignObject width="53.98333740234375" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Success</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-retried-2562" transform="translate(345.0208320617676,569.5725135803223)"><polygon points="53.122501373291016,0 106.24500274658203,-53.122501373291016 53.122501373291016,-106.24500274658203 0,-53.122501373291016" transform="translate(-53.122501373291016,53.122501373291016)" class="label-container"></polygon><g class="label" transform="translate(0,0)"><g transform="translate(-29.025001525878906,-10)"><foreignObject width="58.05000305175781" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Retried?</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-validchanges-2564" transform="translate(219.85833358764648,792.8575134277344)"><polygon points="100.16249771118164,0 200.3249954223633,-100.16249771118164 100.16249771118164,-200.3249954223633 0,-100.16249771118164" transform="translate(-100.16249771118164,100.16249771118164)" class="label-container"></polygon><g class="label" transform="translate(0,0)"><g transform="translate(-81.29166412353516,-10)"><foreignObject width="162.5833282470703" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Valid without changes?</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-valid2-2566" transform="translate(519.0187530517578,792.8575134277344)"><polygon points="100.16249771118164,0 200.3249954223633,-100.16249771118164 100.16249771118164,-200.3249954223633 0,-100.16249771118164" transform="translate(-100.16249771118164,100.16249771118164)" class="label-container"></polygon><g class="label" transform="translate(0,0)"><g transform="translate(-81.29166412353516,-10)"><foreignObject width="162.5833282470703" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Valid without changes?</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-restoredata-2568" transform="translate(336.4000015258789,1073.0200119018555)"><rect rx="0" ry="0" x="-87.76667022705078" y="-20" width="175.53334045410156" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-77.76667022705078,-10)"><foreignObject width="155.53334045410156" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Restore previous data</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-clear-2570" transform="translate(103.31666564941406,983.0200119018555)"><rect rx="0" ry="0" x="-48.39167022705078" y="-20" width="96.78334045410156" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-38.39167022705078,-10)"><foreignObject width="76.78334045410156" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Clear local</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-restore-2571" transform="translate(103.31666564941406,1073.0200119018555)"><rect rx="0" ry="0" x="-95.31666564941406" y="-20" width="190.63333129882812" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-85.31666564941406,-10)"><foreignObject width="170.63333129882812" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Restore packaged dump</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-Failure-2572" transform="translate(219.85833358764648,1163.0200119018555)"><rect rx="0" ry="0" x="-34.89167022705078" y="-20" width="69.78334045410156" height="40" class="label-container" style="fill:#ff0000;"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-24.89167022705078,-10)"><foreignObject width="49.78334045410156" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Failure</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-clear2-2574" transform="translate(544.904167175293,983.0200119018555)"><rect rx="0" ry="0" x="-48.39167022705078" y="-20" width="96.78334045410156" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-38.39167022705078,-10)"><foreignObject width="76.78334045410156" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Clear local</div></foreignObject></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-Retry-2576" transform="translate(570.7895812988281,1073.0200119018555)"><rect rx="0" ry="0" x="-28.916664123535156" y="-20" width="57.83332824707031" height="40" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-18.916664123535156,-10)"><foreignObject width="37.83332824707031" height="20"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;">Retry</div></foreignObject></g></g></g></g></g></g></svg>
\ No newline at end of file diff --git a/services/settings/dumps/blocklists/addons-bloomfilters.json b/services/settings/dumps/blocklists/addons-bloomfilters.json new file mode 100644 index 0000000000..b7fadac629 --- /dev/null +++ b/services/settings/dumps/blocklists/addons-bloomfilters.json @@ -0,0 +1,243 @@ +{ + "data": [ + { + "stash": { + "blocked": [ + "{c897d01a-2f32-4d90-b583-0db861a92a4d}:1.3" + ], + "unblocked": [] + }, + "schema": 1710851757588, + "key_format": "{guid}:{version}", + "stash_time": 1710938104519, + "id": "80fa9b68-a5a3-45b1-9e95-0efd98906b8c", + "last_modified": 1710938159550 + }, + { + "stash": { + "blocked": [ + "{d2a9c975-7b89-4227-8baf-72dbaa9bffe2}:1.0.2", + "{d2a9c975-7b89-4227-8baf-72dbaa9bffe2}:1.0.1", + "{d2a9c975-7b89-4227-8baf-72dbaa9bffe2}:1.0.0" + ], + "unblocked": [] + }, + "schema": 1710837932773, + "key_format": "{guid}:{version}", + "stash_time": 1710851704213, + "id": "becac4a1-48bb-4202-99f8-e3d479e6d6cd", + "last_modified": 1710851757468 + }, + { + "stash": { + "blocked": [ + "queapps@dev:2024.3.7.1" + ], + "unblocked": [] + }, + "schema": 1710776572419, + "key_format": "{guid}:{version}", + "stash_time": 1710786904359, + "id": "cc6d970f-1272-403a-9682-ff9885b9a17d", + "last_modified": 1710786957409 + }, + { + "stash": { + "blocked": [ + "{e2780f13-3c0d-4551-8080-c53e88ea7fe2}:1.2" + ], + "unblocked": [] + }, + "schema": 1709717395157, + "key_format": "{guid}:{version}", + "stash_time": 1709836504187, + "id": "07fb40eb-63ea-4e7e-aafa-bbc91270eadd", + "last_modified": 1709836557282 + }, + { + "stash": { + "blocked": [ + "{7bc8e402-0d0f-4daa-b639-12638167b89b}:1.0.0", + "{7ffc9179-b13b-43c7-a629-e912d1bd2d41}:1.0.1", + "{0bfd044e-fd33-43c5-963e-79bc37677ef0}:1.0", + "{9081d218-23b6-4fb0-b0e2-9584cfd9ec67}:1.0.2", + "{8a0f8be7-462c-4ac4-9aaa-e6942ceba2de}:1.0.1", + "{947e6a31-2445-4943-a470-759a4503c677}:1.0.1", + "adblockdefender@ext:1.0.1", + "{0a9351b5-c0a5-4e05-9f95-bb76da94599d}:1.0.1", + "{49b5c131-1f79-4917-b6a7-aa210cd686b0}:1.0.1", + "cinematic-compass@addon:1.0", + "rank-master@addon:1.0.4", + "rank-master@addon:1.0.1", + "{4a25756a-7fc7-44ac-82b3-5afc323e80d0}:1.0.1", + "{7f7ac8a6-57ec-4d91-99dc-8674cf1e367b}:1.0.3", + "rank-tracker@addon:1.0.1", + "{ff21bd20-17e1-48da-ba2b-9d8f1f7b3bed}:1.0.0", + "{947e6a31-2445-4943-a470-759a4503c677}:1.0.0", + "{e3894e65-24c3-4a49-ab0f-a3cd87eedd35}:1.0.1", + "{a1e65c0a-c949-4a85-9b33-e9c6c0f249be}:1.2.1", + "{9081d218-23b6-4fb0-b0e2-9584cfd9ec67}:1.0.0", + "{3e55091d-709e-4b91-a4ac-09bbbf169bff}:1.0.1", + "adlockernet@extension:1.0.0", + "adsoff@addon:1.0.0", + "{c8d534aa-5add-4870-8925-34d39f4ffd2c}:1.0.0", + "adblckhero@ext:1.0.0", + "{40014e6f-37c2-4e84-8b0f-219464496aec}:1.0.0", + "adblockdefender@ext:1.0.0", + "{0a9351b5-c0a5-4e05-9f95-bb76da94599d}:1.0.0", + "{13db8986-7bd0-41b1-a9de-af3541ec5df1}:1.1.1", + "{105b0afa-c723-4347-b945-7cf283dc69da}:1.0.0", + "{13db8986-7bd0-41b1-a9de-af3541ec5df1}:1.0.1", + "cinematic-compass@addon:1.0.3", + "streamflicks@addon:1.0", + "{ff21bd20-17e1-48da-ba2b-9d8f1f7b3bed}:1.0.1", + "{0a9351b5-c0a5-4e05-9f95-bb76da94599d}:1.0.2", + "rank-master@addon:1.0.3", + "{bc426494-9c10-4462-aeb0-9bfba371ac1c}:1.0", + "{2dc2a242-51eb-4c47-a257-2df052e83390}:1.0.1", + "rank-master@addon:1.0.2", + "{a401a93a-c77a-4677-ad91-70ffd90a116b}:1.0", + "musicz@addons:1.0.0", + "{d3a98f6b-f202-4735-ac51-21fb6249af4b}:1.0.1" + ], + "unblocked": [] + }, + "schema": 1708713360872, + "key_format": "{guid}:{version}", + "stash_time": 1709037304339, + "id": "e84f82b8-12d4-44de-b392-47c90d07e20c", + "last_modified": 1709037359688 + }, + { + "stash": { + "blocked": [ + "{baf760b3-2a5b-4e38-878a-139bf10380c2}:1.0.1", + "purebrowse@ext:1.0.1", + "purebrowse@ext:1.0.0", + "purebrowse@ext:1.0.2", + "autoadsoff@addon:1.0.0", + "purebrowse@ext:1.0.3", + "{798674d6-2dd2-43e3-bccc-2080301379bb}:1.0.0" + ], + "unblocked": [] + }, + "schema": 1708699544620, + "key_format": "{guid}:{version}", + "stash_time": 1708713304742, + "id": "82b01b1f-d543-4966-b48d-0fac1fec9b06", + "last_modified": 1708713360745 + }, + { + "stash": { + "blocked": [ + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.36", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.55", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.31", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.70", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.11", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.56", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.77", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.7", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.57", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.51", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.5", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.63", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.62", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.42", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.49", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.25", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.13", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.52", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.76", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.8", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.1", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.35", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.54", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.15", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.21", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.38", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.18", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.64", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.46", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.58", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.53", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.3", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.30", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.71", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.41", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.37", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.66", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.72", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.24", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.10", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.60", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.40", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.12", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.4", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.39", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.22", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.43", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.9", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.23", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.28", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.59", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.48", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.2", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.17", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.69", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.6", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.68", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.20", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.44", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.29", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.19", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.14", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.50", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.61", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.67", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.65", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.16", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.47", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.27", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.73", + "Aternity-WebExt-12.2.3@aternity.com:12.2.3.45" + ], + "unblocked": [] + }, + "schema": 1707904676002, + "key_format": "{guid}:{version}", + "stash_time": 1708000504358, + "id": "be2e380d-39cf-4ccc-8050-b7171f4ecd11", + "last_modified": 1708000561099 + }, + { + "stash": { + "blocked": [ + "{626b88aa-0df6-4213-a87c-8cbfc77a820b}:1.5.9" + ], + "unblocked": [] + }, + "schema": 1707395854884, + "key_format": "{guid}:{version}", + "stash_time": 1707482104424, + "id": "69852edb-d1a7-4383-8906-5fd2fa09de22", + "last_modified": 1707482157549 + }, + { + "schema": 1707395854274, + "attachment": { + "hash": "3cd2ee400a959dc53fd60776cbbe220aa752903b658b262788d2be974f341fc8", + "size": 828655, + "filename": "filter.bin", + "location": "staging/addons-bloomfilters/a7c51df8-f2fb-4367-a086-46dc6c3cf3f8.bin", + "mimetype": "application/octet-stream" + }, + "key_format": "{guid}:{version}", + "attachment_type": "bloomfilter-base", + "generation_time": 1707395704435, + "id": "ebce7331-8908-45c6-bc19-652e3eff840d", + "last_modified": 1707395854769 + } + ], + "timestamp": 1710938159550 +} diff --git a/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin b/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin Binary files differnew file mode 100644 index 0000000000..f0273eef6e --- /dev/null +++ b/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin diff --git a/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin.meta.json b/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin.meta.json new file mode 100644 index 0000000000..45bcfea771 --- /dev/null +++ b/services/settings/dumps/blocklists/addons-bloomfilters/addons-mlbf.bin.meta.json @@ -0,0 +1 @@ +{"schema":1707395854274,"attachment":{"hash":"3cd2ee400a959dc53fd60776cbbe220aa752903b658b262788d2be974f341fc8","size":828655,"filename":"filter.bin","location":"staging/addons-bloomfilters/a7c51df8-f2fb-4367-a086-46dc6c3cf3f8.bin","mimetype":"application/octet-stream"},"key_format":"{guid}:{version}","attachment_type":"bloomfilter-base","generation_time":1707395704435,"id":"ebce7331-8908-45c6-bc19-652e3eff840d","last_modified":1707395854769}
\ No newline at end of file diff --git a/services/settings/dumps/blocklists/addons.json b/services/settings/dumps/blocklists/addons.json new file mode 100644 index 0000000000..ae72c837d5 --- /dev/null +++ b/services/settings/dumps/blocklists/addons.json @@ -0,0 +1,21961 @@ +{ + "data": [ + { + "guid": "{b557cf1f-1399-4beb-b395-71e03757e427}", + "prefs": [], + "schema": 1604933286063, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1676184", + "why": "This add-on violates Mozilla's add-on policies by not providing the necessary information to facilitate a review.", + "name": "super-sync-env" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7b2ff754-e3fd-4c7d-8d6f-b4fd87c71473", + "last_modified": 1604940558744 + }, + { + "guid": "/^((\\{00894eeb-db15-499e-98cc-9d352ee42198\\})|(\\{30e6bd98-94c0-41b7-b085-119effe56451\\})|(\\{5f4b8d5b-f8e8-4caf-86e6-151bbff7dead\\})|(\\{6e97c492-7efa-43f2-8ed8-c20c0275cc99\\})|(\\{a04a7d52-1e5a-4608-9d89-6f3a408b92c1\\})|(\\{afc030c0-261a-478e-8074-6e1aeeb5ec45\\}))$/", + "prefs": [], + "schema": 1604396204131, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674888", + "why": "These add-ons violate Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Multiple crypto wallets" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "394eae28-5619-45b9-b4bd-b34f58a44015", + "last_modified": 1604503468321 + }, + { + "guid": "{d4e528a9-a6bf-4e35-82b5-cd1ad4b7d331}", + "prefs": [], + "schema": 1604497062898, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1675201", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "TronWallet" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c786e676-ff43-409d-83ea-abf222058e11", + "last_modified": 1604503468313 + }, + { + "guid": "/^((\\{cc8d4d26-b615-4880-863a-586af45dc7e7\\})|(\\{ebd1d9ad-d43b-4105-b1f7-c78a7772b431\\}))$/", + "prefs": [], + "schema": 1604324581859, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674798", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Coin Add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ca05039e-fd10-4587-979b-d0415c727580", + "last_modified": 1604396203679 + }, + { + "guid": "sourcegraph-for-firefox@sourcegraph.com", + "prefs": [], + "schema": 1604163572646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1541010", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Sourcegraph for Firefox" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "20.5.20.1516", + "minVersion": "0" + } + ], + "id": "9fde5729-9be6-4955-9627-67742c5ed62a", + "last_modified": 1604324581391 + }, + { + "guid": "{446c7519-9e32-4f9a-b562-447f4421ec9a}", + "prefs": [], + "schema": 1604059105250, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671618", + "why": "This add-on violates Mozilla's add-on policies by severely circumventing user consent or control.", + "name": "Search add-ons without consent" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "64adb8cf-42d2-4a72-9ad4-df09db0d0d7c", + "last_modified": 1604089130722 + }, + { + "guid": "/^((\\{3d66c55b-ed06-47fe-a823-d49301568ad3\\})|(\\{527d060d-9eaa-4670-8dea-7c152f0b8dcd\\})|(\\{ab64584a-98db-4661-b14a-d6286aed36b2\\})|(\\{b0a0f872-a93b-439d-a783-44690ee6ba4a\\})|(\\{fd299ce1-1602-4490-b659-f45504f9324c\\})|(\\{0362578d-c9c2-4a85-8a37-eab60242c5bb\\})|(\\{2451ecb9-6260-4564-a546-8532f04b587a\\})|(\\{39790485-930b-40a5-8268-69222363ff80\\}))$/", + "prefs": [], + "schema": 1604011389667, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671618", + "why": "This add-on, up to version 1.0.4, violates Mozilla's add-on policies by severely circumventing user consent or control.", + "name": "Search add-ons without consent" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.4", + "minVersion": "0" + } + ], + "id": "2109c966-8e2f-49df-9df0-3126e5a2bbb7", + "last_modified": 1604089130714 + }, + { + "guid": "/^((\\{827fad0c-8f73-449b-8376-31f69d4e56cf\\})|(\\{a98d27c2-e251-11ea-b905-02429d9dd6dd\\}))$/", + "prefs": [], + "schema": 1603927364672, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674014", + "why": "These add-ons violate Mozilla's add-on policies by making use of obfuscated code.", + "name": "Go HTTPS" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "acbea144-d931-46b5-9043-44e3ab71d9bf", + "last_modified": 1603931811186 + }, + { + "guid": "/^((\\{328a3fee-69c3-45b8-9281-73b7ca637dd3\\})|(\\{59a08eca-2078-4b2b-b529-3515987bb7ae\\}))$/", + "prefs": [], + "schema": 1603284433270, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1672733", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake coin add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "415d9c2a-ed6f-4729-87b7-664127435a53", + "last_modified": 1603467453042 + }, + { + "guid": "/^((\\{19b0820f-5a37-4f9e-a38e-842f88fe8a15\\})|(\\{30315127-a73b-4f9e-84a2-d04c85f40843\\})|(\\{346c7c17-98da-4351-aa66-989c0cbbe2f6\\})|(\\{3cfffe30-e5f2-4e59-a9ec-e4c74e9f0774\\})|(\\{4f93dedc-ce3b-4c2e-9f74-f75dd14707ca\\})|(\\{73ec9218-d475-4149-a855-228dba2daad3\\})|(\\{7f0b3e60-10c8-4450-a3b6-88d0073238f4\\})|(\\{823da1a3-c9c6-4d52-a569-bf5a0bfd31f0\\})|(\\{86b96feb-ac7b-44a4-b4c6-e506a84b6653\\})|(\\{91f7f1e6-62f8-46c6-834b-75bcdeffa944\\})|(\\{9815d7d2-a195-4efa-afa9-f662dc113bc5\\})|(\\{998c5862-e728-4c11-87e8-24215c8eb605\\})|(\\{9cf8c6c9-098e-44e6-b7f0-d4ef29f52706\\})|(\\{9f683ae4-ed81-4a1f-9564-89aac4fe1c4e\\})|(\\{b27881fc-1af3-4c7b-b803-53b2589ba169\\})|(\\{c90706d8-783c-427a-9c9e-cade21ef2e10\\})|(\\{cfd7c21b-31d7-4265-8981-210ba4f639ea\\})|(\\{d4a427f8-d0e5-42eb-bbe1-589f583d8d23\\})|(\\{e254b1aa-eea6-4323-b425-643b69c80f5b\\}))$/", + "prefs": [], + "schema": 1603395700564, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1672973", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "494eab50-b308-4fb1-8fa4-acd779328567", + "last_modified": 1603467453036 + }, + { + "guid": "/^((blog@link)|(ft@ext\\.xpi)|(hometab@ext)|(open@tab)|(tabpro@ext\\.xpi)|(zen@tab))$/", + "prefs": [], + "schema": 1603456450633, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1672981", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Addons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0d63cebc-6b95-46f0-a998-2d8ada2dea47", + "last_modified": 1603467453031 + }, + { + "guid": "/^((\\{3c22a946-d2ae-45cd-8c47-e3c5bd960fcf\\})|(\\{4495e9ca-17d2-4256-8051-b0a3a376d273\\})|(\\{e7b217ec-d7b0-4e68-a24e-498763c10a46\\}))$/", + "prefs": [], + "schema": 1603458435498, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1673005", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search-hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b216ddd0-009f-4ae1-ab41-d4fec96f2fcc", + "last_modified": 1603467453025 + }, + { + "guid": "/^((\\{27320f04-1c21-4ff1-97da-4f70ba73c37c\\})|(\\{49cd942b-8184-43fd-ab21-b707e16fd954\\})|(\\{5052bd1c-67ec-42d2-bf25-1c0a82fcf144\\})|(\\{879b285f-8675-4039-bce1-bd9500d67983\\})|(\\{9f1e7e3d-f866-45f2-80e8-8e416b10fce2\\})|(\\{a7eb39ab-244d-4073-bd91-64967d433e35\\})|(\\{d23a2e71-cce2-4c1a-8333-25dd5603725a\\})|(\\{d6afd1d6-c0e6-46a6-a976-02aa376872f5\\})|(\\{e69e46e9-fed2-4398-83e6-506631c0eed5\\})|(\\{f99f5e77-53ff-4242-9cc4-cd17b5c75f38\\}))$/", + "prefs": [], + "schema": 1603115672935, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1672114", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Ledger Clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7e2ac67b-1287-4786-a742-26b364887026", + "last_modified": 1603186371797 + }, + { + "guid": "/^((\\{931a969d-88fa-4daf-a309-281d3f9e2604\\})|(\\{dcac30a2-6b81-48af-b02c-50cf66d80acd\\}))$/", + "prefs": [], + "schema": 1602704491067, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671417", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Coin Add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2cdc427f-3627-45fe-aaf2-adb2d8bf941d", + "last_modified": 1602783995620 + }, + { + "guid": "{4c6939d7-8a28-419b-b300-fc3aaa713e00}", + "prefs": [], + "schema": 1602531694521, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671127", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Metamask Fake" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b3f49864-78e1-4165-943d-903decebf0ec", + "last_modified": 1602697713590 + }, + { + "guid": "/^((\\{02258e92-9873-4b42-9c94-4321194d4e1d\\})|(\\{0cb5f899-2778-4cc6-b35e-d0cdd6f66d21\\})|(\\{520cb697-d3d0-4ee4-ad77-8830ec5743c0\\})|(\\{53cdb998-5b38-4fb0-96ac-f27e315e4555\\})|(\\{c28e5913-ad85-4bee-8d42-55fc61d36157\\})|(\\{d5e0d76c-8c2f-4e61-b20c-3a20a4278d51\\}))$/", + "prefs": [], + "schema": 1602189331410, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670237", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "facebookBot" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "472284d1-e68c-4379-be93-f8ba2419742a", + "last_modified": 1602255261265 + }, + { + "guid": "{ff2867d1-4757-4bd2-820c-8332fa19e8b1}", + "prefs": [], + "schema": 1602249386322, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670239", + "why": "This add-on violates Mozilla's Add-ons No Surprises policy by opening unrelated tabs on startup.", + "name": "allPhoneParnker" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2d23761c-c258-41fd-9a12-4b3b19cb49af", + "last_modified": 1602255261259 + }, + { + "guid": "{5ed32cb9-2517-4657-95bc-4a2b8bd5b66c}", + "prefs": [], + "schema": 1602249714157, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670249", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "MetaMask Wallet (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "34839545-1520-4dda-b518-78f6ae2efad2", + "last_modified": 1602255261254 + }, + { + "guid": "/^((\\{072bfa23-cfd6-495a-a496-aa80a7348680\\})|(\\{08bd8f24-c413-456a-a3e3-bb66c9003211\\})|(\\{08da5c03-3368-4bef-b5a6-cfe406c1f986\\})|(\\{0ab42cee-b0a0-472a-bc97-00f433e6f26b\\})|(\\{0dbd16ac-07d9-497b-af3e-c3be9acffa6e\\})|(\\{0ddbd50b-e700-4f32-94d4-9623ebf84bde\\})|(\\{0f31d364-724b-4f06-863c-7173bae99f1d\\})|(\\{12a4ae26-9afd-4ed2-aa82-e3b113c5af2b\\})|(\\{12c9a302-9089-49eb-84c9-04b4f518b9a3\\})|(\\{1414b2ab-288f-4b8d-9883-bae6690c5119\\})|(\\{162223de-faf1-4db9-b134-3f1030f470a3\\})|(\\{170b50e1-8905-49c5-8f4a-0fa383a0fad6\\})|(\\{17972267-d32b-4120-b5a5-5b5d5e0c0acc\\})|(\\{1af1881a-0898-45cc-84fb-87e446c369bb\\})|(\\{1b46ff0a-b620-487f-b302-f4a76c30cee7\\})|(\\{1ca1dc51-e2d0-4922-900d-21170716a502\\})|(\\{1d1b6b1c-a893-4f13-a1dd-16644bded07e\\})|(\\{2018eab4-7eaa-4c52-9bdd-bc6ec30af027\\})|(\\{24c0190e-dcd6-4074-836e-7dcc635a7a1a\\})|(\\{252306c3-ab62-494c-a3a5-4adc99983e44\\})|(\\{25b663b0-17b6-4c44-b72c-bbd545bd7f53\\})|(\\{268fd854-0d41-4f21-89ef-60705f936de2\\})|(\\{29a19e2d-cf27-465b-b47c-3020a930dc4a\\})|(\\{2a3af548-93ff-4537-abf9-5dae508cd38a\\})|(\\{2c541d57-f4b3-4dbd-ad96-6a3a305f88fc\\})|(\\{33e09c15-2c23-40ea-9a0f-80d4646ced60\\})|(\\{34bf0a97-bee0-42d4-bd97-d229505f44ee\\})|(\\{426d7bd1-9332-4d05-b44a-c3547fddb7d3\\})|(\\{476ba741-1dd1-498d-9a28-4d99f8348bfb\\})|(\\{4a7e74da-21a1-4306-a97e-5b37016f7c32\\})|(\\{4e86cc61-036f-45a8-991f-928d4c54999b\\})|(\\{52b9d108-872e-499a-8af1-e96a6d944bf0\\})|(\\{53eeba51-50b6-4348-b931-51460f7425cc\\})|(\\{545bf194-8006-4166-9732-375f517e35fb\\})|(\\{582ecf42-ef62-425d-a572-b8c21a08b22e\\})|(\\{5985e7a1-6449-4a8d-87e5-fab210574ad6\\})|(\\{5c015f49-99a1-4896-aaec-40c7f6094a91\\})|(\\{5c1529b9-ebc6-4b9b-aad4-299a676b5c6b\\})|(\\{6029d93b-183c-4874-b139-5fd392d7f60f\\})|(\\{64542449-566e-4fa6-a17f-4d6d288dba2b\\})|(\\{64c6a93c-20dc-4de7-86d5-aa02349f959c\\})|(\\{6da98135-d3c2-4c69-a828-c7172af6b59d\\})|(\\{6f41d41e-47db-472e-8dc9-6c1a2f660bfd\\})|(\\{70862264-783e-4ee7-910e-de2d0884e450\\})|(\\{72dae3b7-ebdc-4156-8ef3-e3b11ca6cfcd\\})|(\\{766718d2-1d1b-48ea-b932-dbb9ccf48498\\})|(\\{78595767-9185-4ae6-858c-f2e8539d733f\\})|(\\{785ce386-1eca-4822-9fc0-640cc4a508ba\\})|(\\{79f46e5c-9c5c-45a2-8d36-8c1cd17f5f79\\})|(\\{7a3113f6-b0cf-4a95-a1d6-58918413fc22\\})|(\\{7d13eed3-2c81-4b11-a70a-82287ce7a39f\\})|(\\{7dbd2526-6727-4b50-aa5d-d73ea86282cc\\})|(\\{8796a946-9814-4517-89c9-fc4c61874449\\})|(\\{88138f10-2e6c-4675-84c7-0f68f9725c42\\})|(\\{88d167c3-0a08-4386-ba52-4f3917da2c8f\\})|(\\{899c1fe8-ae17-4fb3-9c46-62006c0d5d62\\})|(\\{8b0f590f-f229-4625-be1b-81c0106eb273\\})|(\\{8b2f1e97-0df9-4954-860b-22e4f8bb17d9\\})|(\\{8e1bccf6-ac2b-4aaa-b8dc-4146efd97f13\\})|(\\{93fed05d-ea35-46d4-9cf7-72e95dbe28e7\\})|(\\{95c16470-d73a-487e-a1af-2a6aa5dd6e0c\\})|(\\{97ac99c5-a95f-4525-a69e-95c5dcdd2790\\})|(\\{9aea9e5b-a595-421b-a894-41ac52351ae1\\})|(\\{a0376dfe-bc1b-48fb-a875-edf30f7c4fcf\\})|(\\{a0802a5e-733b-4b4c-b8d4-d53d9fad856b\\})|(\\{a0ff6d88-e4ec-4ff4-b362-cbd62defe16e\\})|(\\{a664c3f7-0ae4-481d-8fa3-0a2d1e27d1a0\\})|(\\{a98ec8fa-3a5c-44c7-80e1-b8de2f4b873f\\})|(\\{aace152a-3f14-4535-b521-5a4360895531\\})|(\\{b27d23bb-1116-4797-a56c-99f2c5308850\\})|(\\{b3729dc0-5b58-408c-9d22-1c0eaadbaea3\\})|(\\{b4114612-0f65-470c-8574-0d28ad13df22\\})|(\\{b5113e76-b8ca-41e1-b347-2dc779e3aa80\\})|(\\{b566b404-c1a5-4ff1-84a2-85beb77d9449\\})|(\\{b82fcfb4-b315-41a3-a4b6-d3fb7512a31a\\})|(\\{b93d15a8-03b7-4cbb-9a26-d053246bb219\\})|(\\{b95ae003-d6b9-4d69-926d-268b379492e5\\})|(\\{be90a8c8-22b2-4e71-bd79-61d9c8ab1bec\\})|(\\{bf075676-a801-4014-98e2-a407024b4c9a\\})|(\\{c14a942d-c79d-4cce-bfc6-8d0631e93012\\})|(\\{c1dd47cb-1dfb-441c-91ed-a8dbb12f4339\\})|(\\{c2c0c68c-4543-41cb-be8a-615a809aed31\\})|(\\{c4003f83-334f-47b5-8cf1-46ac290fe6d5\\})|(\\{c4026f1a-92ce-4d71-96a3-6fc55992228a\\})|(\\{c45453cc-21c5-42db-957d-4dc9d404838a\\})|(\\{d255390c-8f21-4831-8dd9-7b1a3a9cf732\\})|(\\{d4773452-076e-4221-be65-e4316e82cc62\\})|(\\{d4ef967f-c59a-465e-950a-97c3dd49b19c\\})|(\\{d5330c50-d27f-46bf-a4be-b0b8635493d4\\})|(\\{d8aeafa1-1ae7-4167-9857-88bf92f912ca\\})|(\\{dc197378-0fdf-4b7c-825d-4fefc59e32a5\\})|(\\{e02e7533-d8ee-45b9-bbdd-b152e4198f9a\\})|(\\{e30f227a-7483-4dfe-a699-4bfaaaaf4cfc\\})|(\\{e4b15bb3-bafa-4008-9d67-81333ee1b25a\\})|(\\{e5840f1b-be29-4414-a9db-aef4880d20c5\\})|(\\{e65b6df1-d21f-410f-a817-6bf2da6fc759\\})|(\\{e9301ed6-99dc-49b3-a3b8-6e059fed0065\\})|(\\{ea4d1685-1ecd-45e0-857e-c1f5416bbd75\\}))$/", + "prefs": [], + "schema": 1602253151136, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670250", + "why": "These add-ons determined likely to be malicious becuase they are related to confirmed malicious add-ons.", + "name": "Add-ons related to malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "be966a82-a780-4ec5-9877-a5fbd9869068", + "last_modified": 1602255261248 + }, + { + "guid": "/^((\\{efa1c1ab-d827-4041-a5d2-aa7bf99d4915\\})|(\\{f33c604f-9ef6-4d65-9c7b-55bbdc394c88\\})|(\\{f386f96c-8bf5-4b56-b6e0-b4986ef42628\\})|(\\{f598902c-da33-439f-be3d-bd6a2c8d7066\\})|(\\{fa0075c0-c068-4461-bbf2-7d4410afa074\\})|(\\{fac05277-33a8-4843-94f6-5df6979a2f20\\})|(\\{fba54691-cb5b-4902-97a3-088290ef4b7f\\})|(\\{fc79e85a-a9aa-473c-a384-6297a70efcc4\\})|(\\{ffad0d38-b7a2-4d84-942c-73972159df29\\}))$/", + "prefs": [], + "schema": 1602253400574, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670250", + "why": "These add-ons determined likely to be malicious becuase they are related to confirmed malicious add-ons.", + "name": "Add-ons related to malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e7b803b9-5c91-4fe3-b151-331a30f00e87", + "last_modified": 1602255261242 + }, + { + "guid": "{e0e12577-ed05-484c-8e18-0f6f07ad5c32}", + "prefs": [], + "schema": 1602187122542, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1670101", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Cookie Manager Spoc" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b85f4cb4-5f09-4d98-89d4-8c90136ab83a", + "last_modified": 1602189330975 + }, + { + "guid": "/^((sp@FindingFormsPro)|(sp@WatchTVOnline)|(sp@QuickEmailAccess)|(sp@QuickNEasyRecipes)|(sp@MyFlightFinder)|(sp@BreakingNewsPlus)|(sp@WeatherDiscover)|(sp@MyRecipeFinder)|(sp@AppDiscoveryTools)|(sp@FileConversionNow)|(sp@TemplatesHere)|(sp@SpeedCheckerPlus)|(sp@MapsNDirectionHub)|(sp@FormsHere)|(sp@ConvertPDFHub)|(sp@ProPDFConverters)|(sp@DLOfficeTools)|(sp@MapNDirectionHub)|(sp@MyLoginHub)|(sp@LocalForecast)|(sp@GoGamesHub)|(sp@LiveRadioPro)|(sp@MyOnlineCoupons)|(sp@TrackYourPackages)|(sp@MapsProHub)|(sp@ShipmentTrackers)|(sp@ConvertPDFTo)|(sp@EmailProHub)|(sp@MySweeps)|(sp@MyHoroscope)|(sp@MyVideoConverter)|(sp@EasyClassifieds)|(sp@WatchSportsHere))$/", + "prefs": [], + "schema": 1602081787999, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618984", + "why": "The add-ons violate our no surprises policy", + "name": "Various add-ons violating no surprises" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.*", + "minVersion": "0" + } + ], + "id": "3fc5e417-c0bd-4bdd-9226-5047bf0db7d6", + "last_modified": 1602152338028 + }, + { + "guid": "/^((_l4Membersttab03_@www\\.quicktemplatefinder\\.com)|(_l6Membersttab03_@www\\.propdfconverter\\.com)|(_lbMembersttab03_@free\\.worldofnotes\\.com)|(_ozMembersttab03_@free\\.newnotecenter\\.com)|(_ptMembersttab03_@www\\.simplepackagefinder\\.com)|(_pvMembersttab03_@www\\.mapmywayfree\\.com)|(_q1Membersttab03_@free\\.everydaymemo\\.com)|(_q8Membersttab03_@www\\.getformsfree\\.com)|(_qfMembersttab03_@www\\.formfinderfree\\.com)|(_qwMembersttab03_@free\\.shoppingdealslive\\.com)|(_r7Membersttab03_@free\\.onlineformsdirect\\.com)|(_rsMembersttab03_@www\\.freeauctionfinder\\.com)|(_rwMembersttab03_@free\\.getlyricsonline\\.com)|(_s0Membersttab03_@free\\.funnyjokesnow\\.com)|(_s2Membersttab03_@free\\.mybabyboomerhub\\.com)|(_s6Membersttab03_@free\\.celebgossiponline\\.com))$/", + "prefs": [], + "schema": 1602083005272, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1669737", + "why": "The add-ons violate our no surprises and data consent policies. Please upgrade to their latest version (9.x or above) to continue.", + "name": "Misleading search add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "8.*", + "minVersion": "0" + } + ], + "id": "a8f0fe57-e340-4171-a4e7-f79e32034878", + "last_modified": 1602152338022 + }, + { + "guid": "/^((_0dMembersttab03_@www\\.myaudiotab\\.com)|(_12Membersttab03_@free\\.myscrapnook\\.com)|(_29Membersttab03_@www\\.headlinealley\\.com)|(_2jMembersttab03_@www\\.recipekart\\.com)|(_2vMembersttab03_@www\\.dailybibleguide\\.com)|(_39Membersttab03_@www\\.mapsgalaxy\\.com)|(_4jMembersttab03_@www\\.radiorage\\.com)|(_57Membersttab03_@free\\.marineaquariumfree\\.com)|(_5eMembersttab03_@www\\.translationbuddy\\.com)|(_5pMembersttab03_@www\\.metrowhiz\\.com)|(_64Membersttab03_@www\\.televisionfanatic\\.com)|(_65Membersttab03_@download\\.fromdoctopdf\\.com)|(_69Membersttab03_@www\\.packagetracer\\.com)|(_8eMembersttab03_@download\\.howtosimplified\\.com)|(_8iMembersttab03_@download\\.audiotoaudio\\.com)|(_8jMembersttab03_@download\\.myimageconverter\\.com)|(_94Membersttab03_@www\\.motitags\\.com)|(_9pMembersttab03_@free\\.onlinemapfinder\\.com)|(_b7Membersttab03_@free\\.mytransitguide\\.com)|(_beMembersttab03_@free\\.dailylocalguide\\.com)|(_brMembersttab03_@free\\.yourtemplatefinder\\.com)|(_ceMembersttab03_@free\\.easypdfcombine\\.com)|(_d1Membersttab03_@free\\.mysocialshortcut\\.com)|(_dbMembersttab03_@free\\.getformsonline\\.com)|(_dgMembersttab03_@free\\.trackapackage\\.net)|(_diMembersttab03_@www\\.easymaillogin\\.com)|(_dnMembersttab03_@www\\.webmailworld\\.com)|(_dpMembersttab03_@free\\.findyourmaps\\.com)|(_dqMembersttab03_@www\\.downspeedtest\\.com)|(_drMembersttab03_@free\\.downloadinboxnow\\.com)|(_dsMembersttab03_@free\\.internetspeedutility\\.net)|(_dxMembersttab03_@www\\.download-freemaps\\.com)|(_dzMembersttab03_@www\\.pconverter\\.com)|(_e1Membersttab03_@free\\.actionclassicgames\\.com)|(_e5Membersttab03_@www\\.productivityboss\\.com)|(_eaMembersttab03_@www\\.mynewsguide\\.com)|(_ebMembersttab03_@download\\.metrohotspot\\.com)|(_ecMembersttab03_@www\\.instantradioplay\\.com)|(_edMembersttab03_@free\\.myradioaccess\\.com)|(_eeMembersttab03_@download\\.freeradiocast\\.com)|(_efMembersttab03_@free\\.funcustomcreations\\.com)|(_ehMembersttab03_@free\\.dailyrecipeguide\\.com)|(_eiMembersttab03_@www\\.100sofrecipes\\.com)|(_esMembersttab03_@free\\.downloadmanagernow\\.com)|(_euMembersttab03_@free\\.filesendsuite\\.com)|(_ewMembersttab03_@free\\.mergedocsonline\\.com)|(_exMembersttab03_@free\\.easydocmerge\\.com)|(_eyMembersttab03_@free\\.convertdocsnow\\.com)|(_f7Membersttab03_@download\\.smsfrombrowser\\.com)|(_fkMembersttab03_@free\\.getflightinfo\\.com)|(_foMembersttab03_@free\\.flightsearchapp\\.com)|(_frMembersttab03_@free\\.testforspeed\\.com)|(_fsMembersttab03_@free\\.pdfconverterhq\\.com)|(_fwMembersttab03_@free\\.howtosuite\\.com)|(_fxMembersttab03_@free\\.mytransitplanner\\.com)|(_gcMembersttab03_@www\\.weatherblink\\.com)|(_gtMembersttab03_@free\\.gamingwonderland\\.com)|(_hgMembersttab03_@free\\.atozmanuals\\.com)|(_hmMembersttab03_@free\\.easyweatheralert\\.com)|(_hnMembersttab03_@free\\.quickweatheralert\\.com)|(_hoMembersttab03_@free\\.directionsbuilder\\.com)|(_hpMembersttab03_@free\\.easyfileconvert\\.com)|(_hxMembersttab03_@free\\.mergedocsnow\\.com)|(_hyMembersttab03_@free\\.formfetcherpro\\.com)|(_hzMembersttab03_@free\\.televisionace\\.com)|(_i2Membersttab03_@free\\.streamlineddiy\\.com)|(_i4Membersttab03_@free\\.fileconvertonline\\.com)|(_i6Membersttab03_@free\\.dailyproductivitytools\\.com)|(_ijMembersttab03_@www\\.freedirectionsonline\\.com)|(_iuMembersttab03_@free\\.productmanualsfinder\\.com)|(_iwMembersttab03_@free\\.allinonedocs\\.com)|(_j5Membersttab03_@ext\\.ask\\.com)|(_j6Membersttab03_@www\\.freemanualsindex\\.com)|(_j7Membersttab03_@www\\.convertdocsonline\\.com)|(_jaMembersttab03_@www\\.testonlinespeed\\.com)|(_jbMembersttab03_@www\\.onlinemapsearch\\.com)|(_jiMembersttab03_@www\\.searchformsonline\\.com)|(_jjMembersttab03_@www\\.onlineroutefinder\\.com)|(_jnMembersttab03_@www\\.pdfconverttools\\.com)|(_joMembersttab03_@www\\.onlineformfinder\\.com)|(_jpMembersttab03_@www\\.directionswhiz\\.com)|(_jqMembersttab03_@www\\.convertpdfsnow\\.com)|(_k8Membersttab03_@www\\.mymapsexpress\\.com)|(_koMembersttab03_@www\\.quickpdfmerger\\.com)|(_krMembersttab03_@www\\.easyemailsuite\\.com)|(_kwMembersttab03_@www\\.productmanualspro\\.com)|(_kxMembersttab03_@www\\.smarteasymaps\\.com)|(_kyMembersttab03_@www\\.quickflighttracker\\.com)|(_kzMembersttab03_@www\\.productmanualsguide\\.com)|(_l1Membersttab03_@www\\.videoconverterhd\\.com)|(_l3Membersttab03_@www\\.watchmytvshows\\.com))$/", + "prefs": [], + "schema": 1602083056710, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1669737", + "why": "The add-ons violate our no surprises and data consent policies. Please upgrade to their latest version (9.x or above) to continue.", + "name": "Misleading search add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "8.*", + "minVersion": "0" + } + ], + "id": "4262a74b-e514-45b3-a6f3-df869d369d6d", + "last_modified": 1602152338016 + }, + { + "guid": "/^((test@WhatsMySpeed)|(ext@630e3b49-decc-4d5e-8a9e-242df04e8ba1)|(ext@6bbf8f72-c54d-4731-b455-e164685c5914)|(sp@EmailAccessHere)|(sp@WatchTelevision)|(sp@GamerHubPro)|(sp@FreeConverterHub)|(sp@AccessFreeTemplates)|(sp@ViewOnlineRecipes)|(sp@SatelliteandEarthMaps)|(sp@SatelliteandEarthMap)|(sp@PackageTrackingOnline)|(sp@DirectionsandMapsNow)|(sp@ClassifiedsListApp)|(sp@meinemailzentrum)|(web@meinemailzentrum)|(web@meinemailzentrum.com))$/", + "prefs": [], + "schema": 1602081916747, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618984", + "why": "The add-ons violate our no surprises policy", + "name": "Various add-ons violating no surprises" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "076d3556-68cb-4a5d-83cc-e4cca29d3f48", + "last_modified": 1602152338007 + }, + { + "guid": "{9f94439a-292e-4b2f-9144-3a84c35021ec}", + "prefs": [], + "schema": 1601840499677, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1669431", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "old-layout (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "43df375b-832e-49db-934c-ada0e0ea8b64", + "last_modified": 1601975517286 + }, + { + "guid": "/^((\\{05548773-8d3f-4dbc-908c-91c9801723ab\\})|(\\{09fa5118-58e7-4a2c-85aa-3d299aadca2d\\})|(\\{0d83297b-ee0f-4470-b047-ea1eb5161e0a\\})|(\\{19638976-64c1-4bb5-815b-64a3a6a0a767\\})|(\\{196ca185-6582-9e63-8a6f-5d2141598bdc\\})|(\\{2371fa74-b56a-5198-a162-ad1d7db6f8ae\\})|(\\{2a2dae8a-a18c-4e0f-ac31-f5609dab6b99\\})|(\\{3596f810-bf50-47e2-b54a-2128ebdc5179\\})|(\\{4cac7d9e-fa55-4ac8-9a6c-f5797e598584\\})|(\\{598ca189-9e63-43d2-8a2f-5d5141598bdc\\})|(\\{69db3d9a-5273-4147-aabe-299f40869fb4\\})|(\\{83a7ca08-c5a3-43bb-8110-903f7ba60e52\\})|(\\{8da42f72-5f9e-49e0-8821-9e152b472cbc\\})|(\\{8e6a181e-40ce-43ff-9436-c68e42e5aef7\\})|(\\{913af60c-3d60-4400-9cac-e4090de6787c\\})|(\\{9e6f886c-70ea-4d6d-9e10-0961f3dc5093\\})|(\\{a1eac861-2bf1-4cf2-82dd-63ae7cc674eb\\})|(\\{a596e1fb-12ac-bb96-67ea-f11aab7dea9a\\})|(\\{ba316506-0b16-4e72-9edc-145646ac9e2c\\})|(\\{bc684719-7ec8-40b5-ba7f-a971fe3ef269\\}))$/", + "prefs": [], + "schema": 1601645194883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1668856", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e0349a95-2bfa-4682-872f-e9a4e947e9ca", + "last_modified": 1601656026437 + }, + { + "guid": "/^((\\{536f3cba-58ea-4855-a3a0-b636da88fa3e\\})|(\\{d16f4aff-dabf-42cb-a491-7fe87e957c43\\}))$/", + "prefs": [], + "schema": 1601494897591, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1668468", + "why": "These add-ons violate Mozilla’s add-on policies by collecting ancillary user data.", + "name": "OldLayout (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3e39aaa3-1567-4498-a736-64fb68d956cc", + "last_modified": 1601559385012 + }, + { + "guid": "/^((v-down@addons-firefox)|(\\{667ef836-c20c-4a01-bfa4-af9b3e17299b\\}))$/", + "prefs": [], + "schema": 1600704040590, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1666440", + "why": "These add-ons violate Mozilla's add-on policies by making use of obfuscated code.", + "name": "Video Download" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "efc66283-f419-43fa-b656-361bde591874", + "last_modified": 1600772875317 + }, + { + "guid": "/^((mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified--117756830)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified--1216575438)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified--2088645733)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified--573512243)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1260029560)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1519886836)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1557628910)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1704698220)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1939627508)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-1969522249)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-2169863222)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-2340954546)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-2944855766)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-2994830326)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-307914412)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-313645365)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-3184548427)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-3206263432)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-3817143579)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-4214504777)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-4238340145)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-464522215)|(mekpfngodchodemgmkhinohkfjefjeea@chrome-store-foxified-655613955)|(mekpfngodchodemgmkhinohkfjefjeea@chromeStoreFoxified-3562932078)|(\\{4a635075-14e6-4bdb-84ce-ff3954342266\\})|(\\{5f795d1b-9337-4ee6-b98e-04cbcb632223\\})|(\\{6d5d77b0-33e5-4b98-89ef-45a6f35eca8e\\})|(\\{80d07d4a-99a5-49f4-9a0e-8f9bc0f265de\\})|(\\{b154832e-410c-43d7-9d82-d606fa8e225c\\}))$/", + "prefs": [], + "schema": 1600630898542, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1666220", + "why": "These add-ons violate Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons colecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "40322df1-6c76-43f5-add4-d5a083ac53b0", + "last_modified": 1600704040099 + }, + { + "guid": "/^((\\{57c9b8b0-e41b-7c9b-4890-f6ce48908edb\\})|(\\{840ee0e6-ac43-4ace-a0b6-ba8e89ad6e6b\\})|(\\{945a90fa-404b-435e-ad48-537590c31f8c\\})|(\\{a8f161ef-4fcc-4a0c-b919-9eaa3441d45d\\})|(\\{ba97a455-d45e-4585-a59b-dad16ad81280\\})|(\\{bd7264fb-248a-476c-8746-2b51793c04ec\\}))$/", + "prefs": [], + "schema": 1600685522118, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1666225", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7a40eef1-ed69-4053-b148-c87a24ceec69", + "last_modified": 1600704040091 + }, + { + "guid": "{b98a2aba-4044-443f-99c7-4d4137d0454e}", + "prefs": [], + "schema": 1600199325165, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1665345", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Akap Internet security for all" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7b4ca57b-c057-4aa0-bd7e-bd386e08dff3", + "last_modified": 1600284413867 + }, + { + "guid": "pro@page-pref", + "prefs": [], + "schema": 1599766900172, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1664295", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Page Preferences" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "14ff0365-5b4e-4f8b-9c99-d9b6aea9f044", + "last_modified": 1599817087652 + }, + { + "guid": "/^((add-on@bookmark-this)|(extension@tab-modify))$/", + "prefs": [], + "schema": 1599421296553, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1663608", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "60737247-2edc-4c90-9938-eff22f1b06e4", + "last_modified": 1599557323077 + }, + { + "guid": "{aea0443d-e1eb-40c3-937f-7b0986b0f6f2}", + "prefs": [], + "schema": 1598902900782, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1662384", + "why": "This add-on violates Mozilla’s add-on policies by collecting user data without disclosure or consent.", + "name": "Tano Wallet" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6307ddc1-3cb0-4797-882b-71a16615d4ae", + "last_modified": 1598995479473 + }, + { + "guid": "ext@new-custom-tab", + "prefs": [], + "schema": 1598968498912, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1662476", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "New Custom Tab" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ebf000c5-59ae-4aad-a0df-131d79fc94c8", + "last_modified": 1598995479465 + }, + { + "guid": "{859fa04b-4d37-476e-a3b7-2e0306723e0c}", + "prefs": [], + "schema": 1597769413922, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1659777", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "FaWave" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "db4a6dbe-3669-4f6d-9642-2daaa03e2883", + "last_modified": 1597781922506 + }, + { + "guid": "{145d4851-34fb-4fcb-8b0d-9260fa911f54}", + "prefs": [], + "schema": 1597220003337, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1658731", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Addons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f456a9c8-338d-4ca1-9c41-f5c43ab1c6af", + "last_modified": 1597241887211 + }, + { + "guid": "{706b4435-fb0c-471e-be66-0c132e47640d}", + "prefs": [], + "schema": 1596656494784, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1657728", + "why": "This add-on violates Mozilla's add-on policies by tricking users into executing remote code.", + "name": "Netflix (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e21a5638-a89a-4103-b76c-5f0ea797b79e", + "last_modified": 1596746780409 + }, + { + "guid": "/^((\\{1f4742c1-0b89-4fa1-915c-1d732ab813c9\\})|(\\{612697f6-846a-4d55-a707-9f6c8da3f29e\\}))$/", + "prefs": [], + "schema": 1596138097245, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1656512", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fa514474-9450-48fb-a6f4-25e0faa565ff", + "last_modified": 1596206660495 + }, + { + "guid": "/^((\\{5e717dad-4d46-450a-9001-6d87fa2764a6\\})|(\\{a585b03f-b8cf-40ea-aef9-05049dfc1946\\}))$/", + "prefs": [], + "schema": 1595101293613, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1653947", + "why": "These add-ons violate Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9abcd6ed-4678-495d-8c9f-94a6626e3011", + "last_modified": 1595258518841 + }, + { + "guid": "/^((\\{33d203ab-419c-4ef6-a512-4b9e59767000\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767001\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767002\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767960\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767961\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767962\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767963\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767970\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767971\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767972\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767973\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767974\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767975\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767981\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767982\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767983\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767984\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767985\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767990\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767996\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767997\\})|(\\{33d203ab-419c-4ef6-a512-4b9e59767999\\})|(\\{39000c59-8aaa-4d18-b97f-11945e519e11\\})|(\\{39000c59-8aaa-4d18-b97f-11945e519ebd\\}))$/", + "prefs": [], + "schema": 1594909351966, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1653269", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7c502209-c720-4db5-83fe-b444f1e4368b", + "last_modified": 1594981206721 + }, + { + "guid": "/^((\\{5a3eefeb-3e92-474e-81f2-d27e973e93b8\\})|(\\{60a10b62-b9da-46b8-ad61-ae64852afa7f\\})|(\\{807e06e3-2e66-4cb0-b745-28093644c18a\\})|(\\{c0bcd3e2-28c1-4359-bf50-e1cb51691a23\\})|(\\{f9dd990d-5aa9-4b28-9da4-814ea4531776\\})|(\\{fc1aa9a4-1029-4978-854d-9adfcfdeae43\\}))$/", + "prefs": [], + "schema": 1594909465358, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1653286", + "why": "These add-ons violate Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3d5548b1-a192-4ac0-9946-39950b8df310", + "last_modified": 1594981206716 + }, + { + "guid": "/^((adultwebsiteblocker@lipocodes)|(DownloadYoutubeNow@lipocodes\\.com)|(GermanSpellingDictionary@lipocodes)|(websiteblocker@lipocodes)|(website_pdf@lipoapps))$/", + "prefs": [], + "schema": 1594582895881, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1652742", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6e2e1c90-b816-42a7-ae77-203de8ff3fc2", + "last_modified": 1594751132789 + }, + { + "guid": "/^((AmericanEnglishSpellingChecker@lipocodes)|(SpanishSpellingChecker@lipocodes))$/", + "prefs": [], + "schema": 1594733542218, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1652745", + "why": "These add-ons violate Mozilla’s add-on policies by collecting user data without disclosure or consent.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ffeae2b1-cf81-417d-ab86-c857a2402f10", + "last_modified": 1594751132784 + }, + { + "guid": "{d52ea726-df3e-48c5-b931-da90c3ae6dd9}", + "prefs": [], + "schema": 1593783157352, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650585", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Yoroi" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d89bdc3b-bb85-4613-828c-2bab532b1578", + "last_modified": 1594066991967 + }, + { + "guid": "/^((\\{bb627a45-8f46-4555-98da-097cdab69615\\})|(\\{bce4a211-11a1-41a1-9827-50f18d258e11\\}))$/", + "prefs": [], + "schema": 1593899325072, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650836", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e2785820-173c-4057-860a-a6444637cc98", + "last_modified": 1594066991961 + }, + { + "guid": "{e8984717-d520-4aa5-b4ef-1cdd14c109ce}", + "prefs": [], + "schema": 1594053530845, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650840", + "why": "This add-on violates Mozilla’s add-on policies by collecting user data without disclosure or consent.", + "name": "YouTube Downloader Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "82de35ac-b7dd-4e30-bae8-5ea3d5485843", + "last_modified": 1594066991955 + }, + { + "guid": "/^((advanced_srch@ext\\.xpi)|(blooom@ext\\.xpi)|(bookamarks@ext\\.xpi)|(borrower@ext\\.xpi)|(cbutton@ext\\.xpi)|(charger@ext\\.xpi)|(commit@ext\\.xpi)|(dbutton@ext\\.xpi)|(dendi@ext\\.xpi)|(emoji@finder\\.xpi)|(emoji_box@ext\\.xpi)|(emoji_finder@ext\\.xpi)|(git@ext\\.xpi)|(giver@ext\\.xpi)|(header@ext\\.xpi)|(home-ext@main\\.xpi)|(homegy@ext\\.xpi)|(homepage@ext\\.xpi)|(import@page\\.xpi)|(incognitowindow@ww\\.xpi)|(local@ext\\.xpi)|(main@ext\\.xpi)|(my_emoji@ext\\.xpi)|(newtab-for-ff@ext\\.xpi)|(oscoboteam@gmail\\.com)|(pagesetup@v1\\.xpi)|(pdf-tab-converter@ext\\.xpi)|(pdf_converter@ext\\.xpi)|(pdf_editor@ext\\.xpi)|(pdf_online@ext\\.xpi)|(pdf_saver@ext\\.xpi)|(pdf_tab@ext\\.xpi)|(pdf_tab_converter@ext\\.xpi)|(pdf_wizzard@ext\\.xpi)|(preferences@ext\\.xpi)|(private_browsing@ext\\.xpi)|(private_search@ext\\.xpi)|(pull@ext\\.xpi)|(ring@ext\\.xpi)|(sviftjoe@ext\\.xpi)|(windows-tab@ext\\.xpi)|(\\{4de43d5a-f57c-4425-a354-93a9ce2fe861\\})|(\\{a1bb4007-473d-4203-ab7d-7de9ae878279\\})|(\\{bfaabd10-b172-4140-8946-429d7bbb6fb8\\}))$/", + "prefs": [], + "schema": 1594053935800, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650841", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search-hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "21acca65-e7ed-4c0a-b3cd-f05f4df3584e", + "last_modified": 1594066991949 + }, + { + "guid": "/^((fedemmchkhdelojjjgdkopmplmjiengg@chrome-store-foxified--823560021)|(fedemmchkhdelojjjgdkopmplmjiengg@chrome-store-foxified-1217776487)|(fedemmchkhdelojjjgdkopmplmjiengg@chrome-store-foxified-77529707)|(fggpapnokdmcagooedemcgfhpcidnnbc@chrome-store-foxified-56486150)|(jmeodlkabhcmdebfikcgnhdneigffoag@chrome-store-foxified--940653973)|(jmeodlkabhcmdebfikcgnhdneigffoag@chrome-store-foxified--970176466)|(\\{029d0500-68ad-4b02-aa34-c3be28d3c952\\})|(\\{0fe88455-850f-4f9e-b34e-c64747ac3274\\})|(\\{12d2c374-69c2-4747-b606-482586e56fb9\\})|(\\{18c16fe6-918b-4303-900c-ff86339574df\\})|(\\{1d5a4c53-81b3-40d9-a5e5-f915b26f879b\\})|(\\{213994e8-a858-4f3f-8c1c-c3906d10370f\\})|(\\{23e1aa4d-5061-406e-aba3-907974a9baa6\\})|(\\{33b0a817-bb94-4ff8-90d5-54d7519ef143\\})|(\\{3c16da40-3c81-4e78-b62b-831ad3b9d7bc\\})|(\\{441432c1-4336-4c00-8ad0-9cb3374432dc\\})|(\\{49f51783-7154-48d9-98d6-d48fab6f3f49\\})|(\\{4a1556b3-498e-4d59-97ca-09069ab3e2e0\\})|(\\{5b9264e8-609d-445f-993d-9d35c6cbe372\\})|(\\{62bfcada-a107-41ab-9aa3-91ccc804e984\\})|(\\{6b0891fb-3664-456f-92ab-ad6956d26986\\})|(\\{708b40d9-43ed-4ce4-95e2-093b2245ec71\\})|(\\{8175e95e-fe95-48ae-aa4f-d73807237ae6\\})|(\\{8eb80b1a-7abb-4a88-bd1f-51403bdc1a8c\\})|(\\{95b56317-db14-4c77-b6b3-d300710224ef\\})|(\\{9973a0b4-356a-470a-9b8f-9f89e74c7840\\})|(\\{b357faad-ec9a-4c75-ad87-9ecc876c9ff8\\})|(\\{d0d90a87-03de-44da-8dc4-d432bd4a8ae0\\})|(\\{d479fc03-87a9-4ffe-98fb-d20738d4aece\\})|(\\{d93f2300-3fb0-4d12-9d4a-39dc379e35ae\\})|(\\{e6796495-a93f-4307-8bff-f937287b0fc2\\})|(\\{f14840e4-196b-4ef6-9c85-84d9203c2924\\})|(\\{f3126a97-3a2c-487a-86bb-18bd426edcc1\\}))$/", + "prefs": [], + "schema": 1593599784537, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650138", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6e7a021d-f995-40f1-8f1b-aab451089613", + "last_modified": 1593783156743 + }, + { + "guid": "/^((bahkljhhdeciiaodlkppoonappfnheoi@chromeStoreFoxified-3761781728)|(dbfmnekepjoapopniengjbcpnbljalfg@chrome-store-foxified-unsigned)|(fgigfliokgjiicoladjbodllohgkolbe@chrome-store-foxified--1744075311)|(fgigfliokgjiicoladjbodllohgkolbe@chrome-store-foxified-1686967414)|(generated-6qpuwmerrkb0nutc18kzls@chrome-store-foxified-181411724)|(llgiblikeclfoebojkplbcmnicgcabhg@chrome-store-foxified--759157022)|(llgiblikeclfoebojkplbcmnicgcabhg@chrome-store-foxified-3251930310)|(llgiblikeclfoebojkplbcmnicgcabhg@chrome-store-foxified-4051630149)|(safebrowsing\\.firefox@trustnav\\.com)|(\\{05fdaf75-d3e4-4434-aa8b-183d491d54d6\\})|(\\{a37e731d-7644-4fed-8612-1ee0474950f9\\})|(\\{a54b0c1d-6c98-4ac4-9e9e-d623aba08921\\}))$/", + "prefs": [], + "schema": 1593705201653, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650141", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Remote script injection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4947073b-381f-4267-89f4-9591e38b29c3", + "last_modified": 1593783156738 + }, + { + "guid": "/^((bahkljhhdeciiaodlkppoonappfnheoi@chrome-store-foxified-1375878516)|(vidmate@india\\.com)|(\\{02315b1d-6e91-46bc-92a9-4611eac43692\\})|(\\{023cc38a-e7cb-4098-9564-0bc2fc434ed0\\})|(\\{05b3ec50-df36-4667-ad87-8e7eb4b25091\\})|(\\{05bba9cd-6547-48b2-86f2-454fd09309f9\\})|(\\{05dae426-4c6a-47da-affd-adff1f19025b\\})|(\\{061e6cd3-40c9-427b-b3f4-1e3246bb898a\\})|(\\{063DA41A-2561-401B-91FA-AC75E460F4EC\\})|(\\{06a4929b-48d6-4c29-9fbf-8283c7af6817\\})|(\\{0ae6a4d7-7a2f-4c91-9937-a0b209dd9929\\})|(\\{0f26adb8-8084-4925-b111-ffa1240a3f36\\})|(\\{13f3d29f-b148-4fdb-822f-6bad39befc92\\})|(\\{16b0e9d0-5292-4161-86fc-e8d4f938bede\\})|(\\{16ca0ebf-0870-418d-a4ff-aa65b93cd962\\})|(\\{19758c54-d52b-4b96-9a29-52d67cfdcd3a\\})|(\\{19d569ff-4d0c-4cff-bf7f-3fc7a44b47af\\})|(\\{1ac34f1e-c584-4d2e-80ec-bc7f6c97e214\\})|(\\{1e089af9-9474-40e4-8767-a0f142af429e\\})|(\\{1f4ff519-5941-4e0c-b4b0-bd7205b5dce1\\})|(\\{20175382-1566-4a4a-8073-a8ad39b71a3d\\})|(\\{20dd4a66-ad07-4b45-b5bb-8c7d325e4235\\})|(\\{2136a796-5f22-4dd9-be01-07eafab1204a\\})|(\\{23b6469e-28a5-4396-8094-df88860847a5\\})|(\\{24436206-088d-4a1a-8d0e-cf93ca7a2d23\\})|(\\{2743421e-e752-48de-92a4-dbc0bc17218a\\})|(\\{27f35034-8adb-4070-8a2d-19874d869627\\})|(\\{29a70ebf-55de-4d0d-b32b-f593c40beafe\\})|(\\{2b5dee4d-6fa2-4544-988e-4b6796fcc2dd\\})|(\\{2b8d4e01-8cd7-41d1-a780-88f6487be76e\\})|(\\{2c8d7a96-a6ee-46be-8a1b-1132eb217107\\})|(\\{2d56442e-48ed-43fc-8a3d-f1287948b85e\\})|(\\{2eb5f6dc-b13f-4e61-8d22-8dd67c6eaa41\\})|(\\{3054365f-84fb-46a4-894c-4e27c0befe35\\})|(\\{30b4646c-e1db-4dee-a397-6a2531d0225f\\})|(\\{3120fef3-3693-420b-8aba-b62a40be2e99\\})|(\\{31c38bb1-3da7-4145-afa8-3f930a8d23ea\\})|(\\{323ee1c1-04de-4fcd-8e23-fb80ea246433\\})|(\\{33904608-2fa2-42ff-9521-a8f8b96b3df6\\})|(\\{34859f18-afa0-4fbb-aa6e-ef6b2c2dd7b8\\})|(\\{34d06de6-3074-4ab1-a9da-6448fc1e2b29\\})|(\\{35cb8472-646b-4b2a-8cdd-3206681be6fa\\})|(\\{3625f370-b607-45eb-b997-8e1808047bb6\\})|(\\{372b22d8-55a3-4f9a-98e5-a22fe1af7d4d\\})|(\\{3777ceb4-b4d8-4155-b86c-86b08e509898\\})|(\\{381d3696-0422-41aa-b606-12cc9ebe8b59\\})|(\\{3c9f8dc4-eb25-405c-b0be-db3c087d1e5c\\})|(\\{3cecb889-7f55-4b51-abc1-2ef68f03e755\\})|(\\{3db9b4e7-a4ae-4f85-80e8-8804a3e65a43\\})|(\\{3eba841e-3ed1-49b0-a676-17a8044f396f\\})|(\\{3ec01cb3-b730-4536-b1f2-b1737f7f76a5\\})|(\\{3f13b2ea-8523-4296-81e9-d41b34a028a6\\})|(\\{3f4ce7be-f18c-4809-a048-8915b6fab570\\})|(\\{3f8b1172-bae4-49b6-97dd-56610d9be9f0\\})|(\\{403ec2e1-66a1-4fa6-9dc7-fb7ec85df601\\})|(\\{4322d31f-91ca-4138-ab6a-976338d1cd3b\\})|(\\{4a3d4001-73a0-4038-aeb6-1229dded0523\\})|(\\{4a703694-8daa-43de-979a-e5194ae98444\\})|(\\{4d584a35-da8c-459a-9adb-dab88d27ebe7\\})|(\\{4e2b1fc7-6298-4982-a61a-25d678636cb1\\})|(\\{4f39a102-574e-4f74-93b3-51ca7958ad22\\})|(\\{4f3debf1-0e74-4dc5-a7ab-448280ce6461\\})|(\\{5117db36-6a2e-485d-a1a8-97f75af00bc1\\})|(\\{51311486-15c0-4f65-a40b-9561ba07e215\\})|(\\{56495d47-f44e-4d98-9434-7bc2f5525823\\})|(\\{56786bf6-645f-419d-b913-d0ca3859fa33\\})|(\\{5947c59b-69df-414c-9c10-017f4196a309\\})|(\\{5afee4b9-98f8-443f-935b-9a207620db3f\\})|(\\{5ca2a8a6-ace0-41da-b842-e2a42f2e1cd4\\})|(\\{5cd491dd-bd65-49c9-8152-08d474ad34be\\})|(\\{5eee10be-a741-455f-95d3-4d03215c9690\\})|(\\{5f68bcac-eebc-4671-b75a-9dd6cea18a15\\})|(\\{5f9713ff-6dc2-4adf-a9f3-47f80cd85dfb\\})|(\\{62e37e5b-4701-4a1c-8a82-029432691f40\\})|(\\{634462e7-3f71-4150-b108-69b51780e68c\\})|(\\{65e7d7b7-7e48-4568-988c-2d189d9ff5ff\\})|(\\{675812c6-1b4f-4c56-af85-3bda760719f6\\})|(\\{67c45529-cf0b-4c5c-b24f-280d9349c909\\})|(\\{68da33b7-e3d9-4afe-b8ab-765093be602f\\})|(\\{69387715-933a-45d9-9ddf-8a727c9dbcf3\\})|(\\{6ae0f1b6-be29-4790-8721-ca36bd738876\\})|(\\{6afe3849-bf08-476e-a451-2aee47b5aea4\\})|(\\{6ca71d05-3cc8-4e33-8ae4-fbbee89de965\\})|(\\{6d0404a3-40a0-4b6b-a530-e8d045ae0ee4\\})|(\\{6d743625-7a7f-4eec-9b44-c6465325d0a3\\})|(\\{702cc617-a851-44fc-8b58-46ad6e640ecc\\})|(\\{708cc305-3067-4094-bb39-b068fb990c61\\})|(\\{7107fef7-513e-4921-a60e-17d0fb90e25c\\})|(\\{724e248e-97dd-4b87-9a22-22b3a4e90d7c\\})|(\\{732f68a6-d9b5-4ff8-aada-707fa43b19d5\\})|(\\{737dc7a8-df2d-4dfa-92ee-0c8959cae647\\})|(\\{7469196e-2de4-485e-aaf4-ebdf2e792348\\})|(\\{759701de-a587-4b50-aaaa-258c5e1bddb8\\})|(\\{75ff68d2-c459-4433-86ce-a9c241cb3e73\\})|(\\{765eb1ff-8106-428d-8d4c-c57f1e33049f\\})|(\\{777d46f8-890d-47b6-8079-d55104b2856c\\})|(\\{7bdc2314-02d1-4b33-ad6f-79569ab3674c\\})|(\\{7c510db5-9da4-4529-985b-98cb042bc6ff\\}))$/", + "prefs": [], + "schema": 1593705366484, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650144", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search Overrides" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "38040bfd-8701-43da-ad69-3b210f7a658c", + "last_modified": 1593783156732 + }, + { + "guid": "/^((\\{7d6ae6df-1c04-43a5-869e-8a23d3934737\\})|(\\{7de74298-3d21-41a2-a178-370132faa85d\\})|(\\{7ed82570-bbf3-402f-a5c4-31ac0530293f\\})|(\\{81622d67-2ba6-425c-8175-4014ec821c84\\})|(\\{817b743d-685f-436f-9104-2718370f0f14\\})|(\\{852eb947-3f78-427e-96f6-bae8c7d45480\\})|(\\{8551b8c2-d137-4d78-b6de-8f99e612ad6b\\})|(\\{85d62722-1d90-4e52-bd9a-c75cc8c53c62\\})|(\\{8a384a87-1b7b-485d-b032-ccd3156c8ef7\\})|(\\{8da0dac4-b157-4741-aa49-c99430a9aa7e\\})|(\\{8e0f770c-3087-4055-abd1-839e138e145a\\})|(\\{8e74f640-5158-476b-a039-d9b16adc23e3\\})|(\\{8e95aea7-ba10-4665-8f99-39e673ef3aba\\})|(\\{8f8c089f-1adc-46de-ba27-f03d8aa77507\\})|(\\{90d88023-4dc1-46bc-bf8c-09f0e563864c\\})|(\\{9248343d-6415-410f-bdc2-3e73b2dd4c25\\})|(\\{93de7c3e-1021-4ae0-8825-19a55c9a00a9\\})|(\\{95eaf46e-7a60-496e-a52c-ca119fe404ef\\})|(\\{964e4ab3-7043-4cda-8ec9-1041f4b683ec\\})|(\\{99e34243-0ab2-488d-8cf4-476a39342875\\})|(\\{9c5be5d9-0f11-4f1f-92d6-e000576b8bf7\\})|(\\{9d6b0c0c-c502-4ee2-9ee3-907dab4dc1d9\\})|(\\{9e1eeb8e-6627-4c59-b686-7df31d9ff62a\\})|(\\{9eb48825-7932-442b-84a5-de2fd2800306\\})|(\\{9f8cca9a-2bcf-4b04-b796-562b3302e433\\})|(\\{a1ce0a8c-1177-4049-b5ba-3a798d9f0279\\})|(\\{a1eb42fd-6c3b-4c63-81e0-4904806243d9\\})|(\\{a451ebb4-8c80-4684-a32f-6b331c3e9390\\})|(\\{a4e8e10c-898e-462b-8db1-220044c3f4b3\\})|(\\{a5949142-2af4-4957-8ed9-64d4e06d586d\\})|(\\{a792db23-156d-437f-9ed5-d3319cfb82a5\\})|(\\{a85a38fc-011e-4f2c-9e8a-ff2963f76955\\})|(\\{a8a5035c-0cef-4504-8c0d-a26997e84d93\\})|(\\{ac12dcbf-f835-48db-b4fc-56c145b3bb59\\})|(\\{ae49d937-7e75-40b0-a784-635363f68bcc\\})|(\\{b38ebbc1-fd37-4644-8d2e-67f11a2018fe\\})|(\\{b6bae9e6-6940-4698-8673-8b5cdc4ac5b9\\})|(\\{b6e3d5ab-e8bc-40d8-8eab-f1836caf1052\\})|(\\{b753389d-d85e-4125-98f8-bf19c0511089\\})|(\\{b7897131-2bf5-4024-8c41-701d1c6890ef\\})|(\\{b84de506-80f0-4b73-afe0-0b930da27ce7\\})|(\\{bcaa879f-60b5-4dcc-8da9-8b97a8db4c2c\\})|(\\{bd9a5038-8fec-454d-8f72-d14315618852\\})|(\\{bd9bc86e-5f14-4463-8163-255f694599f2\\})|(\\{c074cd4b-bcf7-4cb7-965d-e03a5ba6c978\\})|(\\{c1c04f48-b24a-4966-9876-b75095372491\\})|(\\{c39c50d0-c7ea-474e-9d2a-10d9eb663299\\})|(\\{c7fd903d-e8bd-4991-ac5c-51606f3df5ad\\})|(\\{cc4dc32e-d435-42fd-a6ec-5971218b86a7\\})|(\\{cc54a433-beda-449e-9125-821479dd2bbf\\})|(\\{cd1f7c79-46b5-457a-a2ae-e82131eb473c\\})|(\\{ce9fbeb1-ee7f-4e87-a8e2-8229317833c4\\})|(\\{cee22b0d-3f3d-4e89-ae79-0e21dc1bb4b1\\})|(\\{cf35a04b-3b15-44fd-8cb7-8eed586bb23c\\})|(\\{d0e39061-7f95-4ee2-a258-206aae9b4301\\})|(\\{d0f31cdc-518b-4277-be2a-0f85c9b66fd7\\})|(\\{d496d961-2fbe-445d-a56d-058db7826492\\})|(\\{d49a9d4d-b997-4292-a0ca-4d2f7a0c61c8\\})|(\\{d4a8ee97-9864-4676-ac91-2683a604d633\\})|(\\{d4a96684-0e76-4269-b08b-00f9f2f7cf29\\})|(\\{d5d00f6f-a94c-4f61-8881-e9e8aabd6732\\})|(\\{d748943d-da9b-4cc9-aa0b-4f9ba5e4108c\\})|(\\{d7b4cdcc-4779-4ab9-b2ee-f0be10219a20\\})|(\\{d7ba7e93-14e0-49b0-a743-6480bcd44671\\})|(\\{d8d8cb23-1963-4544-a496-881d9a1d821c\\})|(\\{d9aafd87-d1df-4459-b09a-48f9c66ee9d6\\})|(\\{da77b261-64cc-466a-ab08-b810ac8b16f1\\})|(\\{dbcf27a9-4bb2-4ad7-82d2-f4781f4fa54d\\})|(\\{dcd3cb2e-235d-4451-a3e5-f1531f5b7b69\\})|(\\{de592dfa-2529-411c-a4b3-80c6e9fcfe84\\})|(\\{df63c4ff-9080-4bad-bb40-a362d02fe047\\})|(\\{e0888bc5-861b-4062-9601-f621b4ededd3\\})|(\\{e1aaa683-ecb9-4673-af6a-0522b25def1a\\})|(\\{e2b719ca-bb2a-4f8f-85fc-ed8ee5a711eb\\})|(\\{e30bad75-2798-4dbf-bf0a-c0238d4bc72e\\})|(\\{e40c5cd7-dfc2-4eb6-ab5c-8f931728dc07\\})|(\\{e4cb2302-9fc9-415c-af9d-4f30114b7e76\\})|(\\{e52d8f33-3f82-4225-8bca-dd2d8d507b02\\})|(\\{e6a9830c-cf6b-41c7-9a7d-394cbfb0f561\\})|(\\{e764acf2-dec7-4a8e-9654-1cff3d47f81b\\})|(\\{e803c340-021f-4df9-b35a-b57081d12126\\})|(\\{e9679072-420c-479f-a269-1a2b82d07668\\})|(\\{e9e71e6c-976f-4a65-b6ac-86aec1f76800\\})|(\\{ea044650-db5d-4665-aee8-5fd5c0019baa\\})|(\\{eac2a1ea-a38e-430d-9e7c-2f05b0145dff\\})|(\\{ebf3d8e2-e805-4d03-bdb0-e3cd5e6818d3\\})|(\\{ec57dcba-d2bb-4c96-8df0-2c98811afdbb\\})|(\\{eca1ec4c-4131-4ac7-b6dc-5506a3aae09a\\})|(\\{f0408c68-1d51-4d18-befe-bfd8de73b1fe\\})|(\\{f31775a2-1753-442b-bd38-34015c893dd2\\})|(\\{f51ebc22-0e0b-4ab8-a998-2eda93ebb11c\\})|(\\{f6ef3800-5101-42a2-9e55-7e2c5e3562fd\\})|(\\{f8c9209d-7c46-400b-80dd-554522a3045d\\})|(\\{f90fa605-2954-43b0-989c-9c7cdb844eb5\\})|(\\{f96ee6a2-21a1-4f00-98f4-0ec791e0f8d4\\})|(\\{f97baacd-156b-47e6-b518-c6beb06905b2\\})|(\\{fad66d2f-514e-4c13-98c0-338713d37838\\})|(\\{faf9c0dc-ef49-44d6-a116-f4fc3de27969\\}))$/", + "prefs": [], + "schema": 1593705486294, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650144", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search Overrides" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "66152682-a9fd-4ce6-ae62-17ec4b9d5da7", + "last_modified": 1593783156727 + }, + { + "guid": "/^((\\{fc872e3f-443a-4b3e-87b1-1c413e1e2dc4\\})|(\\{fd51546c-b08b-4682-b52e-fbde47aede45\\})|(\\{fda53749-d26a-4007-876e-dc10cee6fcf9\\}))$/", + "prefs": [], + "schema": 1593705487230, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1650144", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search Overrides" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "07b84d5b-33c0-4e4b-ae86-b10bf0045bd1", + "last_modified": 1593783156722 + }, + { + "guid": "/^((addon@kravi\\.online)|(\\{0f3b3fb6-a971-40b8-aa64-fa79b9d2018d\\})|(\\{24d770e0-959a-4838-a436-c0f6269936a7\\})|(\\{2f5817b9-1b88-4632-a008-958b3039557d\\})|(\\{520e3e3b-92e7-4ca5-845d-6a3b106bca6e\\})|(\\{5aff28a3-74b6-4317-84ee-6936392fc6ab\\})|(\\{9bc27edf-9f4f-48ed-ba96-7df380747065\\})|(\\{aa50ee26-de9d-4dbd-a837-64f6923f025e\\}))$/", + "prefs": [], + "schema": 1593373295253, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1649271", + "why": "These add-ons violate our no surprises and monetization policies.", + "name": "FireX affiliate promotion add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6c06f7be-952e-4dae-8d48-3ed7eb94e3a5", + "last_modified": 1593599783923 + }, + { + "guid": "/^((_ejMembersttab03_@free\\.downloadrecipesearch\\.com)|(_flMembersttab03_@free\\.myformsfinder\\.com)|(_psMembersttab03_@www\\.freetemplatefinder\\.com))$/", + "prefs": [], + "schema": 1592682094497, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1627249", + "why": "The add-ons violate our no surprises and data consent policies. Please upgrade to their latest version (9.x or above) to continue.", + "name": "Misleading search add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "8.*", + "minVersion": "0" + } + ], + "id": "e016df6f-c253-441c-a867-386d213b228b", + "last_modified": 1592846474156 + }, + { + "guid": "/^((suisserien90@outlook\\.com)|(\\{04f34321-900d-49e8-b2ba-85642e1d2167\\})|(\\{13305730-6d06-4d29-8a50-103b55b41f94\\})|(\\{23730c01-2bd6-47fd-869d-bed842f0819a\\})|(\\{31f54302-17b7-4d2e-875f-921193ea34bb\\})|(\\{33d8aeab-57b3-4942-92da-e2cb9f7d7d7e\\})|(\\{4953bde8-216d-4524-aba6-9d84b7cf4ea8\\})|(\\{4a7c8ec5-2899-4730-9742-d111a55c2194\\})|(\\{670319d6-3d45-4f44-99e2-0b23a2d8bccc\\})|(\\{7cbfee61-804f-4c49-a79b-556502ce9fc9\\})|(\\{88fa0036-482d-48f4-aefd-bebd1bfc6945\\})|(\\{8a25959e-6268-4e71-8959-3a3b09d3c9f7\\})|(\\{8e07001b-157e-4c18-8aac-178e8e42a944\\})|(\\{92bdb180-7b26-4ec4-b0bd-de6f8b8a0d59\\})|(\\{9de2201d-2a5a-4cf3-bcdc-e3679dce250f\\})|(\\{db252e2a-cf4c-457e-b134-c0af8b5970fc\\})|(\\{ef2940f7-bdd4-49be-948f-469777641893\\}))$/", + "prefs": [], + "schema": 1592309193166, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1646070", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e161917c-387c-4ebb-905c-02e923525780", + "last_modified": 1592386739136 + }, + { + "guid": "/^((\\{6244689f-c58f-45ea-a86f-b270862d52d1\\})|(\\{91c831bf-f2a4-4189-b745-da48f1c0d7bd\\}))$/", + "prefs": [], + "schema": 1591126893821, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1642976", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2380a178-4230-46a1-a64b-d084daa7a03d", + "last_modified": 1591217936777 + }, + { + "guid": "{00353fe1-9fe1-402d-91cd-0603672b24aa}", + "prefs": [], + "schema": 1589797244276, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632212", + "why": "This add-on violates Mozilla's add-on policies by using a misleading name.", + "name": "MediaPlayer10 Search Powered by Yahoo" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.20.213", + "minVersion": "0" + } + ], + "id": "380f49e3-8fc0-4698-be45-ebe5e2d43620", + "last_modified": 1589882159554 + }, + { + "guid": "/^((\\{8da217c7-3ad0-4bca-8dbc-20c9f190a49f\\})|(\\{3dc7ec06-f769-4213-9f78-d7f212b97306\\})|(\\{8b4a6f7a-35fa-4e19-a967-f2bf58680c9c\\})|(\\{7dcbc355-56f7-47cd-8f39-02dfab691d5c\\})|(\\{3cd1ee27-b930-41d3-940f-5ce6e5f351bb\\})|(\\{4e8cb340-8b4c-4930-a474-475401d4acef\\})|(\\{70dd2db8-a59f-4107-8158-d03535ff3e5b\\})|(\\{3143c250-b65a-47e2-a412-76b06d514898\\})|(\\{d7f4f007-98bc-402b-b03e-abac1fa889e7\\})|(\\{bc1fd5d5-1984-4dfe-801c-c52d4621b38d\\})|(\\{56ab7b94-c5bd-4353-9ab0-88a9ee203ae3\\})|(\\{e9762aad-4543-458d-a374-f8034b09c4fa\\})|(\\{8f451db8-02fa-46ed-a51d-89101a8f7c05\\})|(\\{5b8dc6d6-0b5f-4d69-9a96-cc7c4ec2c910\\})|(\\{d964e6e6-ee57-4f85-939f-a68385f91172\\})|(\\{aee53115-a386-4c0a-b55c-501fb0be4a95\\})|(\\{6e3fcb82-0bff-40c7-af05-13557ea52f7c\\})|(\\{ea408188-701e-4ebe-bbaa-78f31841ae0b\\})|(\\{1fd9b109-4b8a-45b7-a6cf-e8c548f4ed7d\\})|(\\{d34bc0ad-1c64-48ab-b99a-40528b323335\\})|(\\{db16d458-324c-4c76-af08-1097cd66400a\\})|(\\{bfeb5542-e12d-44d3-bda6-67c6c587a46a\\})|(\\{744878d2-fc78-44dd-bc23-9f2a60666f4d\\})|(\\{c8433eeb-7e77-4f6c-85a3-97d94a80b5f9\\})|(\\{1a30df6c-d08a-4528-9c61-7de5617ef920\\})|(\\{387b8bf2-6d33-4036-a383-57eedd44eed2\\})|(\\{77e0efb7-6d23-465f-8e44-144b64444e61\\})|(\\{383f5d11-e6ec-4c2d-a10d-9dc4e0b3f33e\\})|(\\{daf7b378-d0bd-4555-a36d-2a72c530fe2f\\})|(\\{d55ca4be-4bcc-4e50-bb8a-2e7b9ea7cdd6\\})|(\\{6770b2e1-ff5e-44ce-ad31-582d2846b379\\})|(\\{cd426d98-f41c-41c7-844a-68b2914e6c45\\})|(\\{2cd538ae-ddcd-4997-9a61-e1f7b5cbd671\\})|(\\{7c22cd77-84dc-4535-93b1-82d8cb7c17c2\\})|(\\{8ca4136e-49af-4466-869f-d7f3375d4eff\\})|(\\{21ce3c32-578e-4ad0-91c4-b024987c1675\\})|(\\{1b533e15-0e04-4fa8-92ed-211d7689000f\\})|(\\{19ec95c4-5b74-475d-a6df-dd8b0ed59268\\})|(\\{31908468-e35d-4716-992f-6b8e781709f6\\})|(\\{883a0770-0148-414c-b0e8-ee54f62325fe\\})|(\\{51440879-e72b-4de9-b171-3566f0d050be\\})|(\\{f6369c61-a8a9-4f23-a443-247f5f708c9f\\})|(\\{3de42493-eae0-47f3-8201-42379770e58a\\})|(\\{d5b0375d-df44-4740-8627-e47c4590742f\\}))$/", + "prefs": [], + "schema": 1589793046978, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632212", + "why": "This add-on violates Mozilla's add-on policies by executing remote scripts, collecting search terms going to a third-party provider or expsoing add-on settings to web pages.", + "name": "Add-ons violating Mozilla's add-on policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "33b46813-8910-46b3-92d1-47d022a66294", + "last_modified": 1589882159550 + }, + { + "guid": "firefox-night@mode.ubk", + "prefs": [], + "schema": 1589285002744, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1637571", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Night Shift mode" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7790cbae-2ba6-43d9-8496-0ce311cecd00", + "last_modified": 1589450770496 + }, + { + "guid": "/^((2020-adblock-for@youtube\\.com)|(@save-from-youtube)|(@youtube-converter-addon-for-firefox)|(@youtube-converter-video-music-downloader)|(@youtube-download-helper-addon)|(@youtube-mp3-download-addon)|(@youtube-mp3-downloader-addon)|(@youtube-video-download)|(@youtube\\.download\\.helper)|(@YouTube1)|(@youtubehdVideoDownloader)|(adblock-for@youtube\\.com)|(addon@youtube-adblock)|(addon@youtube-mp3-downloader)|(addons-mozilla@flappy-bird)|(addons-mozilla@youtube-hd-download)|(addons@youtube-mp3-download)|(addons@youtube-video-download)|(autoyoutubehdquality@developer\\.org)|(convert-to-mp3@youtube)|(download\\.helper@youtube\\.com)|(downloader@youtube\\.com)|(google\\.translator\\.addon@addons\\.mozilla\\.org)|(google\\.translator@my\\.addons)|(gtranslate@addons\\.mozilla\\.org)|(info@video\\.download-lagu-mp3\\.com)|(info@youtubemp3music\\.com)|(info@youtubemp3music\\.info)|(info@yt-download\\.org)|(info@yt2mp3s\\.me)|(instagram-download-addon@addons\\.mozilla\\.org)|(instagram-downloader-pro@firefox)|(mp3\\.download@youtube\\.com)|(mp3\\.downloader@youtube\\.com)|(open-my-page-button1@mozilla\\.org)|(video-download@downloader-addon)|(ydh-addon@youtube\\.com)|(youtube-to-mp3@downloader)|(youtube\\.downlaoder\\.update\\.2019@addons\\.mozilla\\.org)|(youtube\\.download\\.helper@youtube\\.com)|(youtube\\.downloader@professional\\.com)|(youtube@tomp3)|(youtubedownloaderhd\\.addon@mozilla)|(youtubehd@developer\\.org)|(youtubetomp3@addon)|(youtubetomp3@youtube\\.com)|(youtudbe\\.download@addons\\.mozilla\\.org)|(yputube-video-download@addons-mozilla)|(yt-download-addon@addons\\.mozilla\\.org))$/", + "prefs": [], + "schema": 1589369260661, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1637588", + "why": "This add-on violates Mozilla's add-on policies by loading surprising content without disclosure or consent.", + "name": "Add-ons violating No Surprises policy" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "864a2a91-e341-4cca-bc22-00300255604b", + "last_modified": 1589450770493 + }, + { + "guid": "{e8f8555b-c4c4-4be6-b0ee-00b452e06e8d}", + "prefs": [], + "schema": 1589375112158, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1637590", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Screen Capture Lite" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ea706d4a-34ae-4fc6-b67c-2e4dc941437c", + "last_modified": 1589450770486 + }, + { + "guid": "new_tab@ext.xpi", + "prefs": [], + "schema": 1589375251912, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1637710", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "New Tab" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "79b35553-78d1-4de4-aff3-8b744b2cbe82", + "last_modified": 1589450770482 + }, + { + "guid": "/^((@pdf-software)|(@pdf-tools)|(\\{3f5de3f0-2a4a-4310-adc9-aa320caceac1\\})|(\\{cbeba114-49a8-45aa-9b20-58d744301a7b\\})|(\\{ef94edee-053f-4d62-9101-cbc8550608df\\}))$/", + "prefs": [], + "schema": 1589053292831, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1636933", + "why": "This add-on violates Mozilla's add-on policies by including remote content in the newtab page.", + "name": "Add-ons using remote content on the newtab page" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f9afaa85-b3fc-48d0-a696-0a916844281e", + "last_modified": 1589285002325 + }, + { + "guid": "/^((@gotransitx)|(@new-tab)|(@new-tab-ff-sideload)|(@new-tab-may)|(@new-tab-poc)|(@new-tab-side)|(@new-tab-sideload)|(@new-tab-test)|(@newtab-ext)|(@newtab-omni)|(@s_search2)|(\\{283f0ce4-ae17-4f72-8400-a6f455b5b278\\})|(\\{4318af2c-eccf-40aa-bee3-9f53a840baa3\\})|(\\{c24b53b4-1925-4b55-a955-0f24e807c33c\\})|(\\{c39923fb-30d8-49cb-8036-c692df03b282\\}))$/", + "prefs": [], + "schema": 1589201469333, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1636935", + "why": "This add-on violates Mozilla's add-on policies by collecting user search terms.", + "name": "Add-ons collecting search terms" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3b5fe370-f122-4b12-b4f0-81b5e32c347d", + "last_modified": 1589285002321 + }, + { + "guid": "/^((@adaware_webprotection)|(@adaware_webprotection_test)|(@browser-safety)|(@new-tab-ff-addon)|(@torrent-scanner)|(@waterfox-monitor\\.xpi)|(\\{65c3d1c8-6a6b-4f25-ae84-3770846ded13\\})|(\\{baf80d95-ba88-4506-9b17-d03d2fd188e7\\})|(\\{e789c2ad-91e5-4621-85f0-5e7e43036233\\}))$/", + "prefs": [], + "schema": 1589201612918, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1636936", + "why": "This add-on violates Mozilla's add-on policies by collecting user data without disclosure or consent.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "943dc1de-7edb-4a52-91d3-c1043e69f658", + "last_modified": 1589285002317 + }, + { + "guid": "/^((@browser-safety-inline)|(newtab@lavasoft\\.com)|(\\{a34f4b0d-2813-4e64-a72b-59998488e866\\}))$/", + "prefs": [], + "schema": 1589201762208, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1636937", + "why": "This add-on violates Mozilla's 'No Surprises' add-on policy.", + "name": "Add-ons including surprising functionality" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b4ca1419-20df-4c27-969f-883f3e9fc05f", + "last_modified": 1589285002313 + }, + { + "guid": "firefox@browser-addon.xyz", + "prefs": [], + "schema": 1589226093582, + "details": { + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Adblock Premium" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "eafece33-b993-443e-b944-8bcefb4389ec", + "last_modified": 1589285002309 + }, + { + "guid": "/^((\\{4172b9c5-86b5-4a94-9c6d-fc9c586e00e1\\})|(\\{6e4137fb-4840-47e4-953f-9a5a59acd2f0\\})|(\\{7ed913b5-a7b6-48db-903c-a9014341b672\\})|(\\{74300757-a623-4b34-ac23-a24d5f4b932e\\}))$/", + "prefs": [], + "schema": 1588757238897, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1635950", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "NEX Extension Trade" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "067bea22-d677-4f45-9e6b-c05bf4137e29", + "last_modified": 1588871670403 + }, + { + "guid": "/^((\\{6eb37565-44ae-4512-b847-dd731b4e89b7\\})|(\\{70cbaf25-0acd-43eb-88bd-d1d867e8a5fc\\}))$/", + "prefs": [], + "schema": 1588801398036, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1636082", + "why": "This add-on violates Mozilla's add-on policies by executing remote code or violating the No Surprises policy.", + "name": "Various youtube download add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8d9347d8-d30f-4251-bfbb-ad94f57bfa04", + "last_modified": 1588871670397 + }, + { + "guid": "push@ext.xpi", + "prefs": [], + "schema": 1588016491847, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633857", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Push (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "22ed13b0-bb78-4b59-9eac-b938f1d6db24", + "last_modified": 1588178650319 + }, + { + "guid": "/^((dind@ext\\.xpi)|(dolen@ext\\.xpi)|(dom@ext\\.xpi)|(fynal@ext\\.xpi)|(fyster@ext\\.xpi)|(grabber@ext\\.xpi)|(homly@ext\\.xpi)|(info@ext\\.xpi)|(kibbi@ext\\.xpi)|(konned@ext\\.xpi)|(oryole@ext\\.xpi)|(plac@ext\\.xpi)|(primary@ext\\.xpi)|(privor@ext\\.xpi)|(remote@ext\\.xpi)|(reture@ext\\.xpi)|(rurel@ext\\.xpi)|(sarc@ext\\.xpi)|(shakk@ext\\.xpi)|(sicq@ext\\.xpi)|(sidebar@ext\\.xpi)|(stolan@ext\\.xpi)|(tofik@ext\\.xpi)|(unwyre@ext\\.xpi)|(ynto@ext\\.xpi)|(zoom@ext\\.xpi))$/", + "prefs": [], + "schema": 1588156830156, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1634015", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1c42622f-7093-4bae-95e7-333122964ad0", + "last_modified": 1588178650310 + }, + { + "guid": "/^((\\{d7f46ca0-899d-11da-a72b-0800200c9a65\\})|(\\{d041a782-a59e-44a9-9496-02bfc5eb24b5\\}))$/", + "prefs": [], + "schema": 1587843690891, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633380", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e211c71e-144c-4b23-a33c-d698e398416a", + "last_modified": 1588004938454 + }, + { + "guid": "/^((\\{66b636a9-2c5e-462e-83f6-89650caa7049\\})|(\\{70f04df5-d98b-473a-b394-664db1ccf6f3\\})|(chi_tab@ext\\.xpi)|(emoji_tab@ext\\.xpi)|(my_pdf_tab@ext\\.xpi)|(calm_tab@ext\\.xpi)|(nice_tab@ext\\.xpi)|(relaxing_tab@ext\\.xpi))$/", + "prefs": [], + "schema": 1587989793275, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633381", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0d6b3ced-45b7-47b0-9b1a-901eb87f3c28", + "last_modified": 1588004938450 + }, + { + "guid": "{84c1d4fc-641f-4910-800b-b538d6f7273c}", + "prefs": [], + "schema": 1587989848219, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633382", + "why": "This add-on violates Mozilla's add-on policies by collecting search data going to third-party search engines.", + "name": "Add-ons collecting search datta" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b46a37dd-528d-4d79-8abe-51d4fb190de6", + "last_modified": 1588004938446 + }, + { + "guid": "/^((abnmaapdjgmmnlodmpmjdebnklepecok@chrome-store-foxified-2396027552)|(abnmaapdjgmmnlodmpmjdebnklepecok@chrome-store-foxified-2768870236)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified-3862814240)|(abnmaapdjgmmnlodmpmjdebnklepecok@chrome-store-foxified-1995059603)|(ddbcnfgbjcicbjdblfafohkpodcnnehi@chrome-store-foxified-749309492)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified-1874441907)|(mekjacpfdgboianikjjonilmgfiekmic@chrome-store-foxified-1955510596)|(mpnamocnciebhgnpcnmoodclmocfcdig@chrome-store-foxified-1063516441)|(mpnamocnciebhgnpcnmoodclmocfcdig@chrome-store-foxified--20246131)|(mpnamocnciebhgnpcnmoodclmocfcdig@chrome-store-foxified-1884689962)|(kgcglkchocglabfcpdiepkifnfgffipe@chrome-store-foxified-1741550361)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified-1214727723)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified--1260504401)|(mpnamocnciebhgnpcnmoodclmocfcdig@chrome-store-foxified--1159015990)|(jellydsgliker@gmail\\.com)|(eoejpaafchckabdajohgeejbijdcgekh@chrome-store-foxified-140400165)|(coonecdghnepgiblpccbbihiahajndda@chrome-store-foxified--1373559051)|(mdaabhnjlpeemhcdbpopjfpjhbahgljl@chrome-store-foxified--411767831)|(pojgfhefohapcfnfbpmhkheejmfdkoap@chrome-store-foxified--1625696403)|(ghbdhifaekeljelljigndababkcmnkbc@chrome-store-foxified--381631510)|(eeocglpgjdpaefaedpblffpeebgmgddk@chrome-store-foxified-329350023)|(eeocglpgjdpaefaedpblffpeebgmgddk@chrome-store-foxified-1137326342)|(pojgfhefohapcfnfbpmhkheejmfdkoap@chrome-store-foxified-499858012)|(eeocglpgjdpaefaedpblffpeebgmgddk@chrome-store-foxified-541423900)|(inglpdjejkleleiikjkankoliodjihfd@chrome-store-foxified-1717509943)|(inglpdjejkleleiikjkankoliodjihfd@chrome-store-foxified--463564538)|(mdaabhnjlpeemhcdbpopjfpjhbahgljl@chrome-store-foxified-1665879186)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--1720842629)|(lplaiehenloheihooakfjkigmkbmmhon@chrome-store-foxified-1634056625)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--690580557)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified--1733601123)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified-626655243)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--1696752497)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified--2146203701)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-1831123651)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified-1767565081)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-2083020687)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified-1426582348)|(nknfhhmhoflkcijaodalbncnmidocced@chrome-store-foxified--1290320498)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--1121442903)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-951765545)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified--1520388245)|(mdaabhnjlpeemhcdbpopjfpjhbahgljl@chrome-store-foxified-2096851818)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-1268339820)|(mkbgdfopfbhcdnoccicgpcpgghhkgocf@chrome-store-foxified--120390217)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-1144806275)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--1474463899)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified--2139994037)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified-610190214)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified--745418008)|(djijfbpaknhcpkmmdpjehlohnfjignne@chrome-store-foxified-1752420038)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-2147224839)|(\\{d15c2e66-7d4a-42d2-ad26-7145307a085a\\})|(lplaiehenloheihooakfjkigmkbmmhon@chrome-store-foxified-208664670)|(lplaiehenloheihooakfjkigmkbmmhon@chrome-store-foxified--1439727444)|(\\{12ee1eeb-7788-444f-8430-4edd085ab7e6\\})|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified--1731227161)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified--1624543284)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-1394660953)|(mhhlegoabmmlmmafmepadpdnncknjdid@chrome-store-foxified-1394660953)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-1935240009)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified-400250101)|(lmnialfbncmdjnnlkieehpbbgaoiihdc@chrome-store-foxified--80073038))$/", + "prefs": [], + "schema": 1587989982847, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633390", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote script" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "952d5b57-1105-4e57-aa3a-a0c63d554c6f", + "last_modified": 1588004938442 + }, + { + "guid": "/^((coonecdghnepgiblpccbbihiahajndda@chrome-store-foxified--168270995)|(\\{aecccccf-16fa-4fad-b8e3-add05aaa9111\\})|(\\{bc5a705b-7b2d-4bb0-9cf9-360387b682eb\\})|(\\{7ab16f1b-627c-4ac3-ba35-e3545a9090cc\\})|(\\{fcca50f6-5318-4eb3-b737-73dd3b9e6045\\})|(\\{f0990433-f30e-4943-8fb7-e1de2b913d48\\})|(fjekneelhekaolbldhmokjfjlfdlbfcp@chrome-store-foxified--681203570)|(mhhlegoabmmlmmafmepadpdnncknjdid@chrome-store-foxified-1094969866)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified--1871365729)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified--1356642283)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified-1428700807)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified--2007683861)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified-2015138387)|(kbfppphbbfnnfbpbgilecdildnckbcam@chrome-store-foxified--1508372373)|(phbfhmldomeohhegllnephiclooafjdj@chrome-store-foxified--1178514589)|(bikpdjjalmcdjoglbgfhmdjclblhfjei@chrome-store-foxified--1178514589)|(debaadifgajofjmemiiphodjgamjaebh@chrome-store-foxified-219347684)|(fpocmbppcinhpeholdacbakebdkijfdp@chrome-store-foxified--493366227)|(pllaimjanehlenjlohniomaplpchdpea@chrome-store-foxified--606969408)|(fpocmbppcinhpeholdacbakebdkijfdp@chrome-store-foxified-2133428645)|(debaadifgajofjmemiiphodjgamjaebh@chrome-store-foxified--2071337123)|(@vkmediadownloader)|(\\{80d083fc-8d0c-43b9-bb21-9c1545797019\\})|(@fud)|(@facebookcolor)|(\\{6f72fdeb-a77c-4626-94bd-80d2966d67f9\\})|(\\{5b9ba2c6-e069-431c-b1a0-6013cb2668ae\\})|(image-search-reverse@4\\.90)|(nokpebgkfckhkmiejkpokjgeaigopbmo@chrome-store-foxified-unsigned)|(generated-hgzdu0x7vzdjuxwf4p8twj@chrome-store-foxified-864201011)|(\\{ea43dc99-0607-481e-b82c-f32769aa691e\\})|(\\{2981b4f4-25a6-429a-a3a8-18fd228f20bc\\})|(lite-vpn4\\.1@gmail\\.com)|(\\{61a36b8b-6c7c-4dbc-ba7a-2b58d74eedd2\\})|(\\{580d5662-cfca-4314-95ea-d0eee823a540\\})|(\\{9643ee9d-e783-40ee-9dd5-fb7219e82ea9\\})|(\\{a7376abe-343c-4205-99c8-8871ab87fa6f\\})|(\\{c5283dde-419a-405a-a837-4ed56761c1c9\\})|(\\{91b25e6e-b160-44f0-8342-ba36049fc336\\}))$/", + "prefs": [], + "schema": 1587991952607, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633390", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote script" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7b12233a-69c3-4e98-9337-7df0f010b939", + "last_modified": 1588004938434 + }, + { + "guid": "/^((jellydsg@gmail\\.com)|(FreeZvoni@kust24181002)|(FreeZvoni@kust24181002vat)|(\\{c982c69c-aa50-48b4-b09f-1d2da9019668\\}))$/", + "prefs": [], + "schema": 1587992188104, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1633393", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1d6aadb9-6bff-4050-a040-34dcf255de8d", + "last_modified": 1588004938424 + }, + { + "guid": "adblock-6.6@addons.mozilla.org", + "prefs": [], + "schema": 1587736485695, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1629925", + "why": "This add-on violates Mozilla's No Surprises add-on policy", + "name": "Adblock by Adblock" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9e5d0c15-a204-4e08-8342-069927ca1cb9", + "last_modified": 1587746716524 + }, + { + "guid": "/^((\\{101e4d89-674e-4284-8c4c-8aac12048410\\})|(youtubedownloaderhd\\.addon@mozi0lla)|(\\{65960fa3-6ea9-4c57-b7d4-2e7af34772ff\\})|(\\{0fa50bbb-b7d2-4616-9603-1194a1198a4d\\}))$/", + "prefs": [], + "schema": 1587670892686, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632826", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d7cac810-a22a-45e0-94d4-acdcbd5f4076", + "last_modified": 1587736485219 + }, + { + "guid": "fvd@download", + "prefs": [], + "schema": 1587733828378, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632827", + "why": "This add-on violates Mozilla's No Surprises add-on policy.", + "name": "fvd@download" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8701caa3-4210-478f-8e60-d829e1b63cac", + "last_modified": 1587736485214 + }, + { + "guid": "/^((lsoares1995@gmail\\.com)|(amqp-dwn-all-vd@michael\\.slidan)|(\\{cfd42eed-18e4-42e0-a0ff-3ec6ecc8f806\\})|(\\{ed73da0b-36bd-432b-9215-f778760c7053\\})|(\\{84d3da4e-7e3b-4712-8e3e-5cd30a4a6546\\}))$/", + "prefs": [], + "schema": 1587733948804, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632828", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "faca9e29-818a-4897-bd98-bae27046dc56", + "last_modified": 1587736485210 + }, + { + "guid": "/^((\\{fb07cc6e-491c-4826-951a-65f60e85c81b\\})|(\\{11dfb55e-0ded-4a7d-be63-e57a5dd67d5e\\})|(\\{349526f1-bac0-459d-b2d0-ee5a27b54ba2\\})|(\\{3a8fc1bc-866d-4531-b0d9-6b36bc8a8884\\})|(\\{773df6a2-1d9c-4b98-aef8-4c44c92b7ea4\\})|(\\{a0774577-adeb-4d0a-b191-70762ac80fb4\\})|(hbehjppehnealjnhnoopckhmhahgacac@chrome-store-foxified-13858238)|(\\{d93ef4bf-3fe8-4400-86a5-886f2a058786\\})|(\\{e985e17f-7073-45dd-addc-4b1ec9ba1f01\\})|(\\{9a6f884b-3b8a-492a-8f7e-a975e2f6ec20\\})|(\\{bf441351-19c6-4245-8f83-b4a66ccb43fb\\})|(\\{dedf3f79-4629-4628-a4c6-695a4f03b424\\})|(\\{20b844c3-411b-4632-897f-c47070f72403\\})|(\\{81dcf023-18c0-457c-a883-6951269402d6\\})|(\\{81dcf023-18c0-457c-aa83-6951269402d6\\})|(\\{75ca571c-556c-4901-8c17-0fd7a2d845b0\\})|(\\{f2155d36-a230-4ae3-a743-3d146648c45d\\})|(\\{9fe202c1-1cb5-4645-a9aa-c3034f9f3ec2\\})|(\\{0e064fb4-7cdf-429b-9739-8d8aca9055af\\})|(\\{40cb7315-3bd5-4b9e-99e4-3bfabbb7a554\\})|(\\{f9a897f7-2ce7-4eb0-948a-218855b16bdf\\})|(\\{b038d2dc-46f7-4f96-b159-0fee836bbe76\\})|(\\{b954032e-3a94-4b85-b212-d0cd29f23750\\})|(\\{6072ab6b-4987-44b1-97c8-a26ede805966\\})|(\\{e6ccbdbd-3644-4ccf-b442-c5e733e68a41\\})|(\\{89e9630a-36f9-454d-8c88-58681e679d84\\})|(\\{3e95cd89-4058-4059-97fb-80fe2c6bd608\\})|(\\{e3b78ccc-10eb-4022-9fc2-9bfacdf2d042\\})|(\\{8bca4db2-2139-4410-b7a2-af6c21dd5c2e\\})|(\\{63a6a8e8-918e-4b51-a616-1e0125302eb1\\})|(\\{8c65c4b7-6680-4d7d-a2c3-3b6ea8d7cd44\\})|(\\{2a968879-9525-4a34-bf8b-bc173f9c4c35\\})|(\\{cb4d0255-eba7-4ee6-8a4e-ba586acb4d2a\\})|(\\{84a859ae-c6fe-4adf-a779-8a08e9028778\\})|(\\{bb1ac3b5-96b9-437e-8d34-39ebd37aea42\\})|(\\{f3fdd60b-f051-48b5-8c6f-9294ac7ddf8b\\})|(\\{159fad04-dfe7-49f1-bae7-8b4b15f28ca8\\})|(\\{2a2f7c8d-faf8-4faf-aa39-c8149db9a18d\\})|(\\{cd0ebad1-7a39-40a4-a88e-343abb6fe6f2\\})|(\\{be0d4d36-f71e-41a7-8a4d-56c0b885dead\\})|(\\{17cb6906-27f8-4b17-bbaa-983939949c67\\})|(\\{3ab14e91-cbe8-4a83-8c82-41210e237673\\})|(\\{5bd93da0-9900-4e78-a6d3-1b9de3ce471e\\})|(\\{e22aeabf-fe0f-4ea6-a5da-124e733a6856\\})|(\\{ee85b096-d78a-4123-b077-8d74d2c86331\\})|(\\{8f908c81-ac8a-4d0a-ad0c-e8f26df841a0\\})|(\\{23b304bb-3887-4854-8867-a0cafcb021b9\\})|(\\{af79e4cc-af3b-4712-a803-169c4556c578\\})|(\\{d7d93c78-ecf8-4727-8ad0-de0706e81e6b\\})|(\\{02ae80ae-8ad2-4882-87cf-3cab6d037aac\\})|(\\{6bb6d370-8dec-4e2a-a8fa-065871304ff2\\})|(\\{898a9073-415c-4300-b569-5ce013da6bd7\\})|(\\{a3d7c2e5-7803-4348-bea4-5dedccd64a50\\})|(\\{6f16edea-ef55-48f1-99fc-75eca7439412\\})|(\\{7d24c39b-9291-48d8-b5bd-81628c397228\\})|(\\{a44b19a5-8e1a-432b-bab3-17e760c7b747\\})|(\\{d7dee400-d09f-46f5-b702-0d81ef94976e\\})|(\\{f618b6b5-faef-4a6d-917f-07b1f95f1489\\})|(\\{2ccedff5-407f-4194-9eca-c2563a4b51ee\\})|(\\{43d9105a-020e-4137-b137-ada41fc6d05d\\})|(\\{af15cd0d-feb4-441a-8692-6832a5cece5f\\})|(\\{3629dde6-0659-449d-8bfc-3d09bcfd1bf3\\})|(\\{a586c658-b570-4d43-97c9-f78f99d0e99d\\})|(\\{05f3ec76-9a51-4687-8b74-ad7c4143ca01\\})|(\\{7b6939db-dc52-4a55-b8ae-5aa53ed408d0\\})|(\\{261a9b0b-c37a-47e5-b779-06595b35679d\\})|(\\{d8ba0ffc-e4a1-4ef8-8f5a-fa6e03305faa\\})|(\\{eb2bb212-9b5b-4b3c-a864-41a15d88098d\\})|(\\{95a84bda-ea71-4c0e-aacf-6123e2f710f5\\})|(\\{2c17b106-4da9-4e45-ac9e-c6afe258cd79\\})|(\\{be877377-f2c7-40ed-904e-5a0573fb5bf0\\})|(\\{f7f6572d-8107-486f-9dba-ea8aa5577d68\\})|(\\{69592649-9f3f-4013-86cc-add248067d1f\\})|(\\{d5d73232-b950-4a37-8300-b66c084061c7\\})|(\\{a1b0aa8c-546b-4a41-a9d5-de8c97638235\\})|(\\{e422f1b1-c290-413b-85cd-17ba4a994e40\\})|(\\{aff1e2a5-450e-4406-a90a-07e1e2a6af99\\})|(\\{2da2bbe4-b8a4-4b86-a340-8b4973e7acbb\\})|(\\{67671ea3-34ca-4cf3-8a92-f1ea15fe05d5\\})|(\\{7eae263a-29a4-4cf4-8ae6-651dae80f3f0\\})|(\\{1e5a12b3-6ab3-4de4-af96-19204f611aaa\\})|(\\{32d829ea-7c44-4510-b199-a212400315c5\\})|(\\{a949a26f-941c-4925-bde6-6530315038eb\\})|(\\{94216c39-e7b9-4cba-85de-ece3cbc99392\\})|(\\{6f95b9c5-7271-48f9-bf5d-e1f2475b246d\\})|(\\{9f3560af-0dde-4462-9c94-fd3eb666cab9\\})|(\\{e7efc2fd-15b3-4883-8ab8-d5f6b032719f\\})|(\\{bc29a9c7-87ef-4fe9-b4a3-716450825df6\\})|(\\{acdec1eb-b577-46ba-afca-7083a4f435a5\\})|(\\{523d358d-5c8c-4ab9-b450-72c9612ee451\\})|(\\{ce5e1f02-eaff-47cd-9244-e62972cb6527\\})|(\\{821bed9b-7d77-4096-91e3-e85908748969\\})|(\\{51a062cc-a286-4a29-b4f4-be266be03b28\\})|(\\{665dc8f5-b56b-40f1-8afe-7c6ef7b563c0\\})|(\\{93387229-dc8f-42bc-841c-6d95b1b05ca9\\})|(\\{dc46f742-4dad-435f-8827-205c1455fde8\\})|(\\{0b5d0ca4-df53-412a-a575-9cda5174fb7e\\})|(\\{9b3fb49b-90a0-4558-80ce-5357b1ea0886\\}))$/", + "prefs": [], + "schema": 1587498092559, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1632212", + "why": "This add-on violates Mozilla's add-on policies by executing remote scripts, collecting search terms going to a third-party provider or expsoing add-on settings to web pages.", + "name": "Add-ons violating Mozilla's add-on policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "00d26716-a2d9-41cc-b588-9d27b572ad19", + "last_modified": 1587573362859 + }, + { + "guid": "/^((\\{d8cf9bb9-3569-4639-8862-c7ddb82d5f1a\\})|(\\{bc3a1465-8945-454e-bc5d-a0619782e141\\})|(\\{8eaab1d0-29f9-4a36-af6d-5e2ed425c900\\})|(\\{947567da-3457-4eab-ff56-987452456322\\})|(\\{d35cbb3c-3c67-4e07-8386-966c86318f8b\\})|(\\{d065767f-8bcf-4990-bd5a-68bcf0a07fb5\\})|(\\{7b34b6f9-b93e-4dda-975c-4ae2b8496eb2\\})|(\\{bf5c4a48-5096-4e13-a14f-b21f707e7c09\\})|(\\{0cd0d26a-303a-4a60-b164-05973b7c2181\\})|(\\{ab3736f5-0a14-4747-ab9e-7f73ce289e11\\})|(\\{40e868db-380b-4564-aa42-d8efb0448f9a\\})|(\\{56637a41-c7c4-4046-b2bc-2afebdc45f82\\})|(\\{7bf9bb0e-449b-4864-8bf8-1e9584e0df75\\})|(\\{96f0a636-16ed-4150-afa7-2dea4004d42e\\})|(\\{8d52e6a6-733e-467c-a31c-1626e62d3dd9\\})|(\\{a8ccbb2e-699c-4e0d-a6c5-a4b2538dc59e\\})|(\\{c9dfecd3-8dfa-44e8-b57b-62cf6b9eaf8d\\})|(\\{b90bd179-d092-47d6-9247-7b80081490e0\\})|(\\{a52db3fe-a21b-41be-8db0-c522c918e24a\\})|(\\{0bbb2670-a281-46b1-9b94-7b0f1a5094c1\\})|(\\{18bd2488-e103-4f15-b59c-eb1a5e037166\\})|(\\{58d1ae16-88c1-4b53-848f-6148e7b9b24c\\})|(\\{d76b83aa-bbd1-4370-818d-e8d332015451\\})|(\\{d7dddf2f-e1bf-4475-bde1-fa7fdac7d8e8\\})|(\\{5cba1e13-f8d7-442a-b705-663c764b0dd1\\})|(\\{12ea2aa2-261a-4fe2-b137-abdbbf6f90a3\\})|(\\{69e2be54-a38e-434d-a26e-052105153f97\\})|(\\{4582aaa7-9688-4038-a9ef-06345fa0f400\\})|(\\{d4a978ec-2aa5-40cf-9970-0c4b425041de\\})|(\\{29eb0733-95e1-4540-b88b-f7d95f5c89fc\\})|(\\{230dc693-8671-4c93-95e9-273a1cd0d637\\})|(\\{2e214782-9160-4c24-a234-0bf9f7a7d7d9\\})|(\\{3e5e63bc-323f-4d46-b757-2a47dd6079fd\\})|(\\{5baeae2e-d152-49ea-ad10-ec124abaa5a9\\})|(\\{cd75a0fe-4d14-4ac9-9633-868c45145ad5\\})|(\\{3bc54878-fb86-4696-9e95-bad0da699159\\})|(\\{1a55f734-685d-47cd-8077-7223f5719042\\})|(\\{7e92bba1-1f5b-476b-af4c-66a3fb87c883\\})|(\\{6b34fc9e-74a8-4172-8e03-a18b3df33507\\})|(\\{261918a4-11ce-4ade-9c00-a38340e76c8a\\})|(\\{3f245e99-96b8-4902-a9a6-905f8f3b7bd6\\})|(\\{a927e0b6-c765-4117-9204-44559abbb760\\})|(\\{3b725149-d305-44e5-81e2-5a65193d98bc\\})|(\\{c9ec7390-e443-42c7-81d3-0c04a21ccb6d\\})|(\\{e67c019c-88ce-410f-a193-f315eef5b307\\})|(\\{2560ecd5-d3b5-497c-b2bd-3655ce95c7c8\\})|(\\{c9182ac6-2d90-45ae-b9d2-8b1630fc2ab9\\})|(\\{7d6e8e6e-e8a3-4109-aed3-807404275242\\})|(\\{ffee5e17-64c7-46fe-9e0a-3b5163913a09\\})|(\\{cbeeff4e-d66f-4e69-8faf-e76e388cea55\\})|(\\{40000ae1-c1dd-4edd-9939-4361c51ac36d\\})|(\\{ddd9f4a1-bb11-4f9a-aa25-bbf294ee77d3\\})|(\\{70a5d868-74a5-4579-bf6a-9306c7d878bf\\})|(\\{1c88d738-5203-4b61-8a47-47e0c2f870c4\\})|(\\{b397e8f1-5133-40d9-ab94-c50acf74602a\\})|(\\{bec4f263-f7ea-4fa7-b1d6-b86ac5c599fb\\})|(\\{a9d6068b-5264-4e42-9d19-902e84a63225\\})|(\\{40b7988b-531f-4653-bc24-46c5fc1e828e\\})|(\\{f2ed03be-0923-4a76-b9ac-2b9bed2a0898\\})|(\\{8bcc5597-0617-4032-ab9f-2c588fbde1e0\\})|(\\{c5f476fb-9767-4a47-a277-41752c588022\\})|(\\{2ddb229d-27c0-4a03-8672-5bca0ef65e60\\})|(\\{1738bd80-2569-4d7b-8c15-d73af8a7c974\\})|(\\{93aca1b6-fbf4-4a2a-b7c4-05a908af4d55\\})|(\\{d68eb2a0-a533-467e-be53-0551c7b2b14e\\})|(\\{d47f20ed-b6ba-4ede-8568-747f0ab6b110\\})|(\\{03570c25-3fc2-40ad-acb7-35d5544faddd\\})|(\\{67d0bded-ebd6-4173-b10e-c288319478ec\\})|(\\{c331baaf-b89e-4ec6-9636-fb18d259cb90\\})|(\\{c1ef737c-c8e6-4577-9c34-66c1c31d2ca8\\})|(\\{389218e4-e2b3-4a14-af5c-80917883dff9\\})|(\\{65d8b6a9-7109-4f60-bb56-c22a360435b7\\})|(\\{f469578c-3712-469f-b377-51ccf97d268d\\})|(123search6010@coinup\\.org)|(123search6011@coinup\\.org)|(123search6012@coinup\\.org)|(6013@coinup\\.org)|(\\{9413e9e0-24c3-4e7b-8f33-5e4270ae0921\\})|(\\{a68392cb-63ba-4f93-adef-bb170ffedddc\\})|(\\{f10c2e43-884f-48f7-ad36-7627880b53d0\\})|(lookmoviedssxml@coinup\\.org)|(coinupsearch6013@coinup\\.org)|(newlook03@7thsense\\.media)|(animeio6015@coinup\\.org)|(picto7100@coinup\\.org))$/", + "prefs": [], + "schema": 1587325291483, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1631422", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "53340de1-6a6d-4528-a587-f17216c6a32a", + "last_modified": 1587485550892 + }, + { + "guid": "/^((\\{e79975e9-e40d-452c-a995-5895c8d110d5\\})|(\\{a36d5211-070b-4021-bca1-1b73b2ce4d73\\}))$/", + "prefs": [], + "schema": 1587388543147, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1631425", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "52e5afe6-e024-40b4-948d-d190aac71b5f", + "last_modified": 1587485550889 + }, + { + "guid": "/^((@realonlineradio)|(@searchanonymo)|(\\{947567da-3457-4eab-ff56-987452456444\\})|(\\{be6be898-315f-48df-b515-6004129ce31d\\})|(\\{9c1b818d-45f7-4c4c-8ad4-5eff683c4607\\})|(\\{55220873-2dfc-4037-b09f-bd50101ba7d8\\})|(\\{0fe62f39-10f3-4a15-8ce3-2e789db17ce3\\})|(\\{124a5ba8-a611-4e9c-889b-ce94e3903ff7\\})|(n18TMqBF@nature-wallpapers\\.com)|(\\{4b1b9905-a554-468f-a780-7b1f498428da\\})|(\\{c4c0caf7-856c-4b33-bd5a-c2e4b8ca20bb\\})|(\\{993da426-2f79-4337-8da3-f06f1001e6ef\\})|(streamplus0305@coinup\\.org)|(streamplus1100@coinup\\.org)|(access1100@coinup\\.org)|(\\{c6ee999f-a247-4077-8229-20c8f13de509\\})|(access1100-33vidia@coinup\\.org)|(prime@valid-install\\.com)|(\\{0a4082a4-82ad-4ba3-94a2-5b0dba7e0953\\}))$/", + "prefs": [], + "schema": 1587389169680, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1631746", + "why": "This add-on violates Mozilla's Add-on Policies by executing remote scripts, collecting user data without disclosure or consent or collecting ancillary data.", + "name": "Add-ons violating Mozilla'S add-on policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2b0c70da-21e1-491e-a186-4fd5af268d7d", + "last_modified": 1587485550885 + }, + { + "guid": "{612dfdb7-81eb-459e-8d81-dc4ecb62dc9b}", + "prefs": [], + "schema": 1587475321531, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1631789", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Best Youtube Downloader Free (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e241d95d-cb5d-4220-a4d7-f2bf89844f96", + "last_modified": 1587485550880 + }, + { + "guid": "/^((\\{529b261b-df0b-4e3b-bf42-07b462da0ee8\\})|(\\{8f4fa810-9c36-4b81-8e72-de97c8f29d67\\}))$/", + "prefs": [], + "schema": 1586987031329, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1630427", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.\n", + "name": "Universal Bypass" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "13.8.4", + "minVersion": "12.0" + }, + { + "severity": 3, + "maxVersion": "13.10.2", + "minVersion": "13.9", + "targetApplication": [] + } + ], + "id": "59b96bbc-20bc-4433-8596-79990aa0f670", + "last_modified": 1587125503251 + }, + { + "guid": "/^((\\{329fc2d4-30d7-48b7-8247-4a4bb7682da8\\})|(\\{5738de0c-38f0-4e38-a01e-8553ec09235c\\})|(\\{bc775a74-c002-4e99-bf4d-ad9a806263dd\\})|(\\{1272ff2b-0339-4d10-9297-cb0e90a2be0e\\})|(\\{e5771c28-c725-43b0-b8d4-5d4acd072b7d\\}))$/", + "prefs": [], + "schema": 1586806901368, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1629790", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6a02c6a6-ef2e-4b46-ab67-49157ed9e49c", + "last_modified": 1586977183602 + }, + { + "guid": "/^((\\{39262546-d73e-4259-ba57-b772473106a3\\})|(\\{f6e51fb0-9724-4c86-aa95-12d20c77a7fa\\})|(\\{805466a4-90a7-48a3-85bb-bad244244a85\\})|(\\{60cfdc21-74c4-42bb-9b9f-9688f1a22e17\\})|(\\{c96f35f7-9ca8-4119-8d08-aaf1f5231fa7\\})|(\\{057b25cd-d923-4faf-828b-47d36f069a40\\})|(\\{1c5a9f06-a63b-49c4-9d66-563c7a9ed059\\})|(\\{61e204ba-f49f-41e4-8d38-0cbf003e3ea8\\})|(\\{139b5a73-e8ae-496a-b0eb-2bc4de518fc6\\})|(\\{59b82710-8c63-4d82-bc16-64f6bda5cd20\\})|(\\{bfe32796-ee0b-4271-a5d2-81938374395f\\})|(\\{ef5353b7-cf92-40e9-8f5c-93210015b15f\\}))$/", + "prefs": [], + "schema": 1586956158067, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1630241", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Cryptowallet Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "17d89613-a8c6-4e49-a5c7-e9eac64133f1", + "last_modified": 1586977183599 + }, + { + "guid": "/^((spylog@cfsoft\\.com)|(\\{7cb9bddc-8c0e-4b4e-92df-3735e3db909a\\})|(\\{8c64047e-20da-45f2-998d-f852e0886c70\\})|(\\{684a1175-31b9-478a-845f-ab332d0c0ec3\\})|(vni@vietbacsecurity\\.com)|(\\{95e9e74d-4318-4f14-939d-24ec61ed6657\\})|(blockpopup@vietbacsecurity\\.com)|(blocallpopup@vietbacsecurity\\.com)|(Vanga@vietbacsecurity\\.com)|(sontung@vietbacsecurity\\.com)|(TiengViet@vietbacsecurity\\.com)|(Pomodorotimer@vietbacsecurity\\.com)|(Clock@vietbacsecurity\\.com)|(alarmclock@vietbacsecurity\\.com)|(gotiengviet@vietbacsecurity\\.com)|(location@vietbacsecurity\\.com)|(Amlich@vietbacsecurity\\.com)|(lich@vietbacsecurity\\.com)|(lichx@vietbacsecurity\\.com)|(Bitcoin@vietbacsecurity\\.com)|(thoitiet@vietbacsecurity\\.com)|(Bitcointovnd@vietbacsecurity\\.com)|(font@vietbacsecurity\\.com)|(porn@vietbacsecurity\\.com)|(TiengVietkhongdau@vietbacsecurity\\.com)|(Calendar@vietbacsecurity\\.com)|(007@vietbacsecurity\\.com)|(spymyself@vietbacsecurity\\.com)|(youtubedl@vietbacsecurity\\.com)|(adminaaa@vietbacsecurity\\.com)|(adminaaaq@vietbacsecurity\\.com)|(adminaaan@vietbacsecurity\\.com)|(adminaaank@vietbacsecurity\\.com)|(admindsa@vietbacsecurity\\.com)|(admindsak@vietbacsecurity\\.com)|(admdin@vietbacsecurity\\.com)|(admins@vietbacsecurity\\.com)|(cotuong@vietbacsecurity\\.com)|(\\{a127afcb-7284-40aa-aac2-cbe0fdd4491d\\})|(\\{9c07f8ad-57dc-459f-b07c-96900e8a3806\\})|(\\{c2c59ff7-83e6-4988-8c1d-473d8ac76bba\\})|(\\{8f0e655a-22ee-43bb-97fc-80af74a4dc34\\})|(\\{49253a6c-eeb9-465c-bcf9-258f731dad9e\\})|(\\{c5542677-064b-4fce-876b-3e1ba30371a4\\})|(\\{7b786f87-2016-4752-a781-5f8b4c63241e\\})|(\\{4b768602-8729-4852-8587-d55169ce67e9\\})|(\\{04c37c01-32bd-4e4a-94bf-36d8175b0555\\})|(\\{743aeddb-e150-40e0-8091-e8b52e7225a4\\})|(\\{1b7f9ca4-0a14-4ef9-8ec3-3cd05efb683c\\})|(\\{e843a1e9-21a2-43ad-a1ff-34e430eae000\\})|(\\{92a420a0-5075-4126-8318-2ce8b4ef8746\\})|(\\{f204e4e2-c9d0-4a12-bbdf-20ae0346171e\\})|(\\{045504f3-13d3-420d-a8a2-74e6e548f5f8\\})|(\\{44c67012-6467-4925-9284-1ad3785e02fd\\})|(\\{709baa43-e321-4d09-80ad-bf41c93d49d5\\})|(\\{7679dbca-1b6e-4e9a-ae70-8fab1e541051\\})|(\\{46d6f718-bbf8-42de-85bf-07c7e2ee3db1\\})|(\\{9b4d83fa-395f-4c86-a897-7927ce9383e3\\})|(chudaibi@vietbacsecurity\\.com)|(\\{f55e480d-37ff-403c-9601-1380c8119b7a\\})|(\\{1c52bac9-b511-47e6-9492-51832b6d73ae\\})|(\\{f09be92f-8d2a-4294-b787-dee323ca52ef\\})|(\\{14e65683-b5ee-4e41-8083-c8138b0a30ed\\})|(\\{1c65312f-bbf4-4447-9fe5-433234eb4562\\})|(\\{f7d105c4-d341-4d17-b0ee-257cb1473a07\\})|(\\{12b5579f-eb13-4399-a902-2f36c3ad3447\\})|(\\{e3d1a35f-50eb-4125-9b0f-d26a59f88823\\})|(minh@41batrieu\\.com)|(\\{abb3fe45-b511-4d21-aa0c-0cd1fed6dffb\\})|(tocao@vietbacsecurity\\.com)|(\\{87cb22c4-7395-4658-bf65-a2c27c8f1e8c\\})|(\\{9324c255-2e6e-4b27-b4fa-5af7f6e167c6\\})|(\\{02d3a0c5-1f60-4824-b37f-9dc2c67ef9f2\\})|(\\{711e1a95-bc52-44de-bb17-ea24ed493e65\\})|(\\{c2fff0c1-601f-42e4-878f-c1cdd624c279\\}))$/", + "prefs": [], + "schema": 1586962120860, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1630297", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1c016cb6-d9b3-4a24-a887-1649b3ac2134", + "last_modified": 1586977183595 + }, + { + "guid": "{ab2186b0-8c0b-4921-a2d4-95e6e05c0e3c}", + "prefs": [], + "schema": 1586115691726, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1628040", + "why": "This ad-on violates Mozilla's Add-on policies by collecting user data without disclosure or consent.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "baf700c9-b165-4569-a247-8ced9b94fab1", + "last_modified": 1586290176418 + }, + { + "guid": "/^((\\{ddf1ef2e-af32-dd2f-0640-85ef6d10fb02\\})|(\\{b511c8c7-d036-7302-a1cf-4aefb16e8658\\})|(\\{1e939f19-1391-b8ba-3790-5d4404c43bb1\\})|(\\{7da26e3b-a032-bad1-857f-d19328d0ee12\\})|(\\{ff4cdb7a-d968-140e-6709-eea6c7e0d1fc\\})|(\\{63a8cb2c-5a8f-d848-272d-310e846a7bfb\\})|(\\{07b11f4d-d53b-f8df-1bc8-10773768a2d6\\})|(\\{e74124a5-c58e-a28d-9e96-d0fcb73e6905\\})|(\\{4c9f5729-16af-e8ff-5e07-7ee7ccc06665\\})|(\\{76f93d6c-ace1-28af-5e1b-d09442925b1a\\})|(\\{694226c5-3426-466d-d826-4dd70cc4260c\\})|(\\{ea403745-59f6-af83-4190-ab3b9fafd369\\})|(\\{cbe1950f-9a99-9d66-4060-12b9e85b675b\\})|(\\{874a192b-50de-af0f-53c0-76b54b4c5b76\\})|(\\{13bf414f-8444-9f88-e05a-5fb8be3bef6a\\})|(\\{7aaab407-a72e-c07a-5971-77a238b093e1\\})|(\\{d4d1feb4-71b9-a8e4-089f-699483413d32\\})|(\\{c7eef1d4-370d-8005-26e7-67ee1b76ef4d\\})|(\\{941e84f4-e93f-2bd3-be34-efc9be9f8b66\\})|(\\{256ac5f6-5fcb-44ac-eeb8-c65f4470c7e1\\})|(\\{84a0bb93-9422-8d02-b607-fd98f854837a\\})|(\\{c987166c-d73d-b9c1-0e21-51e9ea4d2df6\\})|(\\{8cc0f1c4-990a-5fb4-eb3b-bb455da8c268\\})|(\\{5b6512ff-d5fe-bebb-3862-43a9d7a7bcb4\\})|(\\{6885fd10-acd9-2987-0a27-583828c051df\\})|(\\{945b997e-4cf8-1476-76e8-4c0e489d57b8\\})|(\\{284e1fd3-5f68-c175-1cd5-9f8340543d61\\})|(\\{3b5f5ac5-9b7e-0d10-678c-100f39949103\\})|(\\{e2eda9e6-76b1-c5e0-6ab5-4a119db1f094\\})|(\\{e7c89b14-f86f-8f9e-4aa7-bf93f058c403\\})|(\\{57711d16-ab2e-7841-ddcc-3d245c9e53f4\\})|(\\{30017eef-489f-1557-cd23-bd588933e30d\\})|(\\{a376fa1e-0214-3b11-3260-f464aa202ff0\\})|(\\{ab64c65f-a4d5-ffee-61ec-635208ee910c\\})|(\\{c31b8bd4-6ba5-414f-5929-3b108372cfb5\\})|(\\{ef96f34d-6b17-687f-fdc2-b2286ef7c68e\\})|(\\{8238128c-7659-fb9c-a29e-ce5844273852\\})|(\\{27bbe28b-181e-6e1c-be31-bdd6404004b6\\})|(\\{d70e5451-61bb-9c6d-0913-4767c47d156a\\})|(\\{9c705bfa-89f2-a7c0-329d-d7dc4ad0b32b\\})|(\\{4af055a2-e1f3-6c2c-484a-dd12d651085d\\})|(\\{46a61b09-8b11-2334-9c1d-d057ac644702\\})|(\\{cca52e1f-857c-3654-3f8f-ba32136d0160\\})|(\\{04a32ec2-76c5-a8e0-49ac-aa51b36cf91c\\})|(\\{fc4bfc3d-666e-2687-5f77-2514854794ac\\})|(\\{8f11842c-4a65-d7b5-c12d-6fab43a5f51f\\})|(\\{8d08e6d2-e82f-ede4-089b-23184112b0b5\\})|(\\{7dd7b266-a9da-451a-47e7-b56fb5c77a40\\})|(\\{55636e59-2fac-a595-6a3d-42f4359b7f3a\\})|(\\{a6b75d85-0607-a71a-087e-6ecd5cd525ac\\})|(\\{b39ab5a0-3902-15c3-6fc1-ce45924dcf22\\})|(\\{90f781a4-3d9f-a1c1-c4a6-b446223bee6d\\})|(\\{047bdfe9-b256-d910-a2ff-aaef394e37bc\\})|(\\{3496aee8-1e4a-4a2d-d935-e9c60a1007da\\})|(\\{a197c072-4b06-440e-391e-d60a64bdcd6a\\})|(\\{13fd2e82-d19d-6ad0-b77a-3caaab77ee00\\})|(\\{0ac1543f-5b68-c8b8-9268-af4b51a0233d\\})|(\\{c3f2a253-e235-d612-9d44-e1d5a28fd883\\})|(\\{5ed4b053-1445-dd12-2118-9d697d40736b\\})|(\\{7c2c341e-c8a7-7806-cf00-0ed3a16496c9\\})|(\\{c1d30a2e-4b27-3730-da00-93c350122f57\\})|(\\{63b32739-1c43-d874-19db-5dd4d1ef2abc\\})|(\\{92bae4b7-ea7d-3fdb-b34c-20293af43734\\})|(\\{5fd29856-2296-49cc-37ee-4d0e4dc738ef\\})|(\\{c4487a30-f901-f358-8a05-23ac3cfa9469\\})|(\\{4a2169e8-1a23-378a-bc4b-5d3b817193a5\\})|(\\{5917dee3-e834-7514-6768-2324f39ebe90\\})|(\\{2986179a-af19-6571-79f8-1bb75de24f88\\})|(\\{8cfbe90c-eeb7-30b2-31ce-da719c1a63cf\\})|(\\{4617e32e-8a17-1fe9-d89c-7e8fffeca945\\})|(\\{de3d433a-af12-a27c-8886-113668435e8d\\})|(\\{52326034-27a5-2035-473b-46cf569866b9\\})|(\\{58172e8e-3cfb-9a0d-90a7-d9ae64537326\\})|(\\{530c001f-94f1-f453-dbc6-b89c30c36cf9\\})|(\\{38ea3f7d-6f09-2f03-e75f-de2bad37e84a\\})|(\\{82e77989-fe96-1895-c958-6a9a7c8955c5\\})|(\\{1baf2f92-1872-b460-7d6c-8fc1ccacedc9\\})|(\\{6f8c3be7-5264-bf34-b488-8991ebccb7b5\\})|(\\{82f650b6-57b2-3160-843b-c6b9c0aafd28\\})|(\\{ce0fda96-6059-e2bd-b61c-7e0ea9426c48\\})|(\\{77fe55df-763b-c6e1-6d5d-540d86a3f4d5\\})|(\\{3028912a-4002-ee0e-4a5a-ea060ddc1ed0\\})|(\\{8049c65e-6536-1556-f5a0-82745ba3a711\\})|(\\{43962ebd-ae57-d2d2-7660-8b501c66090c\\})|(\\{3be51fdd-4325-9022-ce8a-94fc9406d0ff\\})|(\\{4bbf76c6-0cda-6b55-a82d-6a02869ba851\\})|(\\{627f651e-58c1-4f10-81d9-8fc4a2b8a6d4\\})|(\\{b3d9f42b-0cf6-5f80-0645-6870904c6d99\\})|(\\{f24bbc96-20bc-4510-a275-442a604aa12c\\})|(\\{a97e6c70-d890-4a69-b7ae-ff97bb25808e\\})|(\\{95674029-cc9f-4e46-9a64-b7a4b3e39277\\})|(\\{f8f40912-e963-48d3-b176-fe7223e54d94\\})|(\\{8a1eed18-914e-4a58-aac5-791272bc5c26\\})|(\\{3267ca67-cf6c-451b-8ba9-311d3b954e8e\\})|(\\{00969488-23d9-4291-a6d7-abfbb9d75fd7\\}))$/", + "prefs": [], + "schema": 1585730548980, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1626592", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "df94545c-e570-4efe-afd4-11ab0e4f9ddf", + "last_modified": 1585930042641 + }, + { + "guid": "{7fa7ce90-e40e-44dc-8b79-84337f3de987}", + "prefs": [], + "schema": 1585743918356, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1626597", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Decodex" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ab0258b9-63bc-425a-8dc4-53250d2f27e2", + "last_modified": 1585930042636 + }, + { + "guid": "{6f62927a-e380-401a-8c9e-c485b7d87f0d}", + "prefs": [], + "schema": 1585744575197, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1626602", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Lookbox" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3486ee79-b128-4496-985d-6f5d9dc2c340", + "last_modified": 1585930042632 + }, + { + "guid": "{68d4ca8c-c012-43a7-8f22-d569a5fe3eb6}", + "prefs": [], + "schema": 1585745131075, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1626603", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Maximize" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "39e5eda4-c9a8-4a8e-a6d5-fd3d0d3b0215", + "last_modified": 1585930042628 + }, + { + "guid": "{171cdcfa-d6d3-4bc2-8e5d-dfcbb67c7695}", + "prefs": [], + "schema": 1585597291445, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1625509", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Sushkom AV" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a51c888f-234f-4f0b-a76c-c0a036bfa6ca", + "last_modified": 1585730548574 + }, + { + "guid": "{b7410e57-0452-47d5-a7cf-a1a91e7fd0b7}", + "prefs": [], + "schema": 1585424490920, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1625922", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "eaa5e3c2-1be6-4eb9-9357-f2d0686448be", + "last_modified": 1585575900465 + }, + { + "guid": "/^((\\{e2e34cff-c7d2-4c87-8195-d206ac928969\\})|(\\{aea3f9d0-909d-4c39-9282-25b11aa29d61\\})|(\\{ec5c4eed-0275-4148-a7f0-b21c05121703\\}))$/", + "prefs": [], + "schema": 1585158977245, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1625025", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name or collecting ancillary user data.", + "name": "Deceptive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "034951cb-7614-4202-af35-18123476eb03", + "last_modified": 1585238797372 + }, + { + "guid": "/^((\\{927f6d6a-104f-4e69-b99a-e62e856899d9\\})|(\\{edf2d18c-ba07-4f30-bd59-85520b7259d8\\})|(\\{2128e9bc-aa97-4f9e-9327-bdce611538fb\\})|(\\{41a71134-b81e-49c5-be44-154c1b981153\\})|(\\{e00696fa-7731-40c6-81f1-5507d0435347\\})|(\\{f6dee727-c6cf-4d4a-b108-d59c3d9723ce\\})|(\\{48935666-26c5-484a-ade3-1660eca6a219\\})|(\\{06b9fcaa-45df-4fb3-a8fa-775d68690f5b\\})|(jid1-4P0kohSJxU1qGa@jetpack)|(jid1-93WyvptyvzGATw@jetpack)|(addon@ytdownloader1\\.info)|(\\{f4817c52-46ac-4b98-b682-f900701c5778\\}))$/", + "prefs": [], + "schema": 1585177209604, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1625186", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "abcf90de-7322-4232-a59c-06e4b3a33c68", + "last_modified": 1585238797365 + }, + { + "guid": "/^ext@bettersurfplus$/", + "prefs": [], + "schema": 1585140126652, + "blockID": "i506", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=939254", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware and is installed silently in violation of the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BetterSurf (malware)", + "created": "2013-12-10T15:10:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b4da06d2-a0fd-09b6-aadb-7e3b29c3be3a", + "last_modified": 1585158976536 + }, + { + "guid": "/^((\\{7aeae561-714b-45f6-ace3-4a8aed6e227b\\})|(\\{01e86e69-a2f8-48a0-b068-83869bdba3d0\\})|(\\{77f5fe49-12e3-4cf5-abb4-d993a0164d9e\\}))$/", + "prefs": [], + "schema": 1585140060946, + "blockID": "i436", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=891606", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, changing Firefox default settings and not reverting them on uninstall. If you want to continue using this add-on, it can be enabled in the Add-ons Manager.", + "name": "Visual Bee", + "created": "2013-08-09T15:04:44Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ad6dc811-ab95-46fa-4bff-42186c149980", + "last_modified": 1585158976531 + }, + { + "guid": "/^((\\{0b24cf69-02b8-407d-83db-e7af04fc1f3e\\})|(\\{6feed48d-41d4-49b8-b7d6-ef78cc7a7cd7\\})|(\\{8a0699a0-09c3-4cf1-b38d-fec25441650c\\}))$/", + "prefs": [], + "schema": 1585138327934, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1434759", + "why": "These add-ons use remote scripts to alter popular sites like Google or Amazon.", + "name": "Malicious remote script add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "32ffc62d-40c4-43ac-aa3f-7240978d0ad0", + "last_modified": 1585158976522 + }, + { + "guid": "/^(({41c14ab8-9958-44bf-b74e-af54c1f169a6})|({78054cb2-e3e8-4070-a8ad-3fd69c8e4707})|({0089b179-8f3d-44d9-bb18-582843b0757a})|({f44ddcb4-4cc0-4866-92fa-eefda60c6720})|({1893d673-7953-4870-8069-baac49ce3335})|({fb28cac0-c2aa-4e0c-a614-cf3641196237})|({d7dee150-da14-45ba-afca-02c7a79ad805})|(RandomNameTest@RandomNameTest\\.com)|(corpsearchengine@mail\\.ru)|(support@work\\.org))$/", + "prefs": [], + "schema": 1585145454357, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458330", + "why": "These are malicious add-ons that inject remote scripts and use deceptive names.", + "name": "\"Table\" add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3a123214-b4b6-410c-a061-bbaf0d168d31", + "last_modified": 1585158976514 + }, + { + "guid": "/^((@mixclouddownloader)|(all-down@james\\.burrow)|(d\\.lehr@chello\\.at)|(easy-video-downloader@addonsmash)|(easy-youtube-mp3@james\\.burrow)|(gid@addonsmash)|(gmail_panel@addon_clone)|(idm@addonsmash)|(image-picka@addonsmash)|(instant-idm@addon\\.host)|(jdm@awesome\\.addons)|(open-in-idm@addonsmash)|(open-in-idm@james\\.burrow)|(open-in-vlc@awesome\\.addons)|(saveimage@addonsmash)|(thundercross@addonsmash)|(vk-download@addon\\.host)|(vk-music-downloader@addonsmash)|(whatsapp_popup@addons\\.clone)|(ytb-down@james\\.burrow)|(ytb-mp3-downloader@james\\.burrow)|(\\{0df8d631-7d88-401e-ba7e-af1425dded8a\\})|(\\{3c74e141-1993-4c04-b755-a66dd491bb47\\})|(\\{5cdd95c7-5d92-40c5-8e2a-8c52c90191d9\\})|(\\{40efedc0-8e48-404a-a779-f4016b25c0e6\\})|(\\{53d605ce-599b-4352-8a06-5e594b3d1822\\})|(\\{3697c1e8-27d7-4c63-a27e-ac16191a1545\\})|(\\{170503FA-3349-4F17-BC86-001888A5C8E2\\})|(\\{649558df-9461-4824-ad18-f2d4d4845ac8\\})|(\\{27875553-afd5-4365-86dc-019bcd60594c\\})|(\\{27875553-afd5-4365-86dc-019bcd60594c\\})|(\\{6e7624fa-7f70-4417-93db-1ec29c023275\\})|(\\{b1aea1f1-6bed-41ef-9679-1dfbd7b2554f\\})|(\\{b9acc029-d62b-4d23-b921-8e7aea34266a\\})|(\\{b9b59e13-4ac5-4eff-8dbe-c345b7619b3c\\})|(\\{b0186d2d-3126-4537-9186-a6f198547901\\})|(\\{b3e8fde8-6d97-4ac3-95e0-57b797f4c56b\\})|(\\{e6a9a96e-4a08-4719-b9bd-0e91c35aaabc\\})|(\\{e69a36e6-ee12-4fe6-87ca-66b77fc0ffbf\\})|(\\{ee3601f1-78ab-48bf-89ae-0cfe4aed1f2e\\})|(\\{f4ce48b3-ad14-4900-86cb-4604474c5b08\\})|(\\{f5c1262d-b1e8-44a4-b820-a834f0f6d605\\}))$/", + "prefs": [], + "schema": 1585149409880, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1476020", + "why": "Add-ons repeatedly violated several of review policies.", + "name": "Several youtube downloading add-ons and others" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ae8ae617-590d-430b-86d4-16364372b67f", + "last_modified": 1585158976511 + }, + { + "guid": "/^(({a4d84dae-7906-4064-911b-3ad2b1ec178b})|({d7e388c5-1cd0-4aa6-8888-9172f90951fb})|({a67f4004-855f-4e6f-8ef0-2ac735614967})|({25230eb3-db35-4613-8c03-e9a3912b7004})|({37384122-9046-4ff9-a31f-963767d9fe33})|({f1479b0b-0762-4ba2-97fc-010ea9dd4e73})|({53804e15-69e5-4b24-8883-c8f68bd98cf6})|({0f2aec80-aade-46b8-838c-54eeb595aa96})|({b65d6378-6840-4da6-b30e-dee113f680aa})|({e8fc3f33-14b7-41aa-88a1-d0d7b5641a50})|({c49ee246-d3d2-4e88-bfdb-4a3b4de9f974}))$/", + "prefs": [], + "schema": 1585138540335, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484536", + "why": "Add-ons that don't respect user choice by overriding search.", + "name": "Search hijacking add-ons (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "01c22882-868b-43e1-bb23-29d5dc7bc11b", + "last_modified": 1585158976508 + }, + { + "guid": "/^((\\{0b47ef96-f7c9-4017-97b6-51c1280eaf6e\\})|(\\{2c65ed54-5215-4f10-a7bd-39445a6450fd\\})|(\\{80dbc30a-168b-4a18-bd5b-51f9b1807d7f\\})|(\\{93a106e3-e70b-48af-9282-30e3d6c87af9\\})|(\\{2120dc72-6040-45ed-9655-aaabed57fc93\\})|(\\{c3c9f1ee-4192-4d5a-b753-a62c19b16c98\\})|(\\{d0fc8cf9-66aa-4f08-8c96-3f882c2e9c9b\\})|(\\{da69b9e2-c2d1-4b90-93be-4cc3976e452d\\})|(\\{eb70585e-76bb-4eae-9f06-7fc5efbc877e\\})|(fairshare-unlock@burstworks-test\\.com)|(fairshare-unlock@burstworks\\.com)|(freevideodownloader-hosted@funnerapps\\.com)|(freevideodownloader-test-hosted@funnerapps\\.com)|(gmo-panel@ddmr\\.com)|(helper-sig@savefrom\\.net)|(ihmgiclibbndffejedjimfjmfoabpcke@chrome-store-foxified--?\\d+)|(ihmgiclibbndffejedjimfjmfoabpcke@chromeStoreFoxified--?\\d+)|(panel-branded@ddmr\\.com)|(panel-canadaTalkNow@ddmr\\.com)|(panel-community@ddmr\\.com)|(panel-digaYgane@ddmr\\.com)|(panel-ecglobal@ddmr\\.com)|(panel-fusionCash@ddmr\\.com)|(panel-grindaBuck@ddmr\\.com)|(panel-measurement@ddmr\\.com)|(panel-mysoapbox@ddmr\\.com)|(panel-ofernation@ddmr\\.com)|(panel-rewarding@ddmr\\.com)|(panel-superpay@ddmr\\.com)|(panel-zippy@ddmr\\.com)|(test_fairshare-unlock@burstworks\\.com)|(test-fairshare-unlock@burstworks\\.com)|(vindale@ddmr\\.com))$/", + "prefs": [], + "schema": 1585151995695, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562965", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "04c27082-2b78-4005-a6c5-8e0a13f83288", + "last_modified": 1585158976505 + }, + { + "guid": "/^((\\{3c8970fa-1340-45ad-82fe-81f3beccfbdc\\})|(\\{4ab99b95-4d05-438c-8a3e-adb1b3fe8d81\\})|(\\{7f87a05d-dba7-448e-9af2-ee0f4a294c01\\})|(\\{59a219a8-45cd-458d-9b3e-8d86c19dfc31\\})|(\\{79f4bfc7-b1da-4dc4-85cc-ecbcc5dd152e\\})|(\\{484dc5ad-4d6a-4ee4-91b7-b5b8166e6b3d\\})|(\\{2643d75f-9d64-47ef-9c23-78f0f055c7b8\\})|(\\{76399bf2-8354-4b11-bf43-6c863b195b1d\\})|(\\{110791c0-2883-4301-8214-90be7549df43\\})|(\\{a33e004d-2ac0-4d77-8e14-50780bc231a3\\})|(\\{aaaa5840-6b3b-49d8-92c2-9696798c4e2a\\})|(\\{bfc55377-7210-4e7a-828f-6fdb9df02847\\})|(\\{c6c78b9a-370d-49c5-b9c6-96d7e38861c5\\})|(\\{c115eb3a-4746-472b-8f1f-d8596c49b3b6\\})|(\\{deaa22e5-33ed-440f-a734-c3175e6228a7\\})|(\\{e34d5840-6b3b-49d8-92c2-9696798c4e2a\\})|(aapbdbdomjkkjkaonfhkkikfgjllcleb@[cC]hrome-?[sS]tore-?[fF]oxified--?((\\d+)|(unsigned)))|(babelfox_client@rami)|(blndkmebkmenignoajhoemebccmmfjib@chrome-store-foxified--?\\d+)|(bridge-translate-app@chrome-store-foxified--?\\d+)|(dephbpajmknbniclommefdlnflkfnpgh@chrome-store-foxified--?\\d+)|(extension@newtab\\.biz)|(generated-74o6bact7xu7y32fvfju4s@chrome-store-foxified--?\\d+)|(generated-axbwzwbksnnig1ug9v5dly@chrome-store-foxified--?\\d+)|(googletranslateelement@developer\\.org)|(icdahkkjdchifpnbebileaelbcgipepe@chrome-store-foxified--?\\d+)|(ifgljfjnflaadalpmkkgdailepedeehd@chrome-store-foxified--?\\d+)|(knpgbkpddpcepnloiijojmgbdhihkjkl@chrome-store-foxified--?\\d+)|(translate-4@chrome-store-foxified--?\\d+))$/", + "prefs": [], + "schema": 1585157952678, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1593243", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "11fd123a-e67d-44ab-909f-b776ea2e8d0a", + "last_modified": 1585158976501 + }, + { + "guid": "/^((graph-helper@thegraph\\.co)|(lab@uneg\\.edu\\.ve)|(ponyhoofbeta@ponyhoof\\.little\\.my)|(\\{75080f0e-3dbe-455d-a1d1-33850f36e2ff\\})|(\\{27883109-b35d-4b6b-9e5b-89b26496fb5e\\})|(\\{cf407827-6739-4514-ad2e-8af25c0e1d97\\})|(\\{c9efb61c-f0bb-42a2-a3e5-ca75da67007c\\})|(\\{c671463f-7bd2-4a35-839c-2097aa52b1d2\\})|(\\{83d16411-a3d6-4268-a95f-1e9abcbdd521\\})|(\\{3835f36e-a8a8-4247-a731-6e1ed7d52c5f\\})|(\\{8d13fb0d-e6b6-4301-9865-3c438a1a347f\\})|(\\{21262c5a-3c8e-46a1-b504-7d16ccddf656\\})|(\\{3e1f3207-8b6c-493b-91f3-0eb43326e273\\})|(\\{9fe1f0bb-c24d-4eab-90f5-9471905132f6\\})|(\\{8b189632-155f-4478-bf51-285c3a54de8c\\})|(\\{3854ac9e-7c1c-4d2a-84f7-edc8656ac7ae\\})|(\\{ec82c419-3cc7-4789-bad0-a069ef80211e\\})|(\\{C0D03FFD-391A-41CE-ACA2-64557D9440CC\\})|(\\{D4A86045-8722-4289-93C4-7C80970F0E7D\\})|(\\{5f23a0d5-ff0e-4e18-8afc-cf50c468ea1d\\})|(\\{6b67941f-2544-468d-b371-ad5c2d99687b\\})|(\\{d6a07a0c-cad7-4e08-987a-099c1add5fc4\\})|(web-signer@softplan\\.com\\.br)|(\\{251eec9f-108a-4864-960f-e07cee35a991\\})|(\\{9cd44027-9d26-4cbd-bfd9-da7eaa495261\\})|(\\{4244357f-568b-477b-802a-db9d6551a719\\})|(\\{1ed0819f-e210-4089-8e24-af2dba6451bb\\})|(\\{e02dba27-b975-4dcb-9adb-ea74ed6cd632\\})|(\\{1240d113-0410-418b-b99f-047aad6ddae3\\})|(\\{115d13bd-f95d-4a61-9dd7-fb3ab816a8dd\\})|(mydesires@mydesires\\.com)|(mesenvies@mesenvies\\.fr)|(\\{0f250607-4465-49bb-a56c-12c8514a3e7d\\})|(\\{12a290cc-2b60-4efb-96f0-dde41f27769d\\})|(\\{9ac03a7d-f80f-4868-8b95-35074f8cdf62\\})|(\\{ce1da0e5-133b-4828-8cbb-66c5ea97e78e\\})|(\\{0da8fe99-30f7-462c-85d0-db66f4f70a97\\})|(\\{9aca9f23-d9aa-4680-9dcb-837291ff6854\\})|(\\{d86d1438-56ef-4294-ab9b-ac1f2c6420e6\\})|(\\{6280d3e5-a444-4823-be7d-317090d3b175\\})|(\\{faccc7b3-b0c7-4fcc-9d4d-b12721ea7f0d\\})|(\\{b84f4632-26f1-4fb5-9082-1f2ca7685afa\\})|(\\{a543f841-3613-4a52-bce8-58bd6e7eb100\\})|(\\{5b1c9b32-9dd3-461c-85c6-bbac4ef2af19\\})|(\\{0207f077-7f95-47ba-b0f9-b356d456f914\\})|(teste@addon\\.com)|(\\{6da58723-1757-42a4-a2db-849f6159c880\\})|(\\{4a122670-da9d-4257-b2bc-27440af385af\\})|(\\{f39035e7-e7f6-4dc1-b13b-04d7a365ab41\\})|(\\{3b3201ca-d7a1-48ab-a89b-ad97cd688590\\})|(\\{bb0179bf-3435-4924-8ae5-19f35e7f1a7f\\})|(\\{d2f6e317-3957-4931-b9b1-6b3ebef25aab\\})|(luckypdf@luckypdfconverter\\.com)|(\\{755ae77a-2901-4177-99aa-ac3fe15974e0\\})|(\\{6e5cab8f-78da-45fe-90af-4cfaf3d95d0e\\})|(\\{cd0c26ad-5150-42cb-bec1-2da0db715a90\\})|(\\{357d3c5d-5c68-4943-b49e-df20c6ff931e\\})|(\\{8b5d6a2c-d18c-47b6-9b13-cebab7425838\\})|(\\{d26b38f2-1ec0-43d8-8d05-8e14a09955bc\\})|(\\{d3b4d997-cf97-4ffe-9401-3efcbc816fd9\\})|(\\{506848ee-e612-4d2f-8645-70c26e577efd\\})|(\\{c0272d15-248c-4f93-80cd-684c0f360da1\\})|(\\{7a478659-43ba-4054-8d67-9ad75f66f0f6\\})|(\\{351b37fb-6d84-4820-8dbe-a517d8f856ce\\})|(\\{f0031513-5b7e-4cc6-9a16-616c82e79d48\\})|(\\{adf2ee6b-ef1c-4350-b35f-369e6b0dc64a\\})|(\\{06a6425d-0e31-4703-a0e2-72d4a552477c\\})|(\\{6f8a85a2-d83b-4a2c-ac38-d06bf3fb1b92\\})|(\\{9d96147e-9418-4493-bedb-380bf065f8b0\\})|(\\{3055992b-3fda-44bf-9065-8514ea2c2acb\\})|(\\{602ade61-49fc-4a40-9287-53949c8f0462\\})|(\\{c28dd807-0b3f-4bc4-984c-ff9578202712\\})|(\\{495e077c-0015-4e70-80f3-4b1b71e9c866\\})|(\\{19ebc477-304c-4d75-990b-6d47c230c19d\\})|(\\{08df44a4-ef6a-4828-9ebe-a730dd47ee5e\\})|(allsigningoffline@nextsense\\.com)|(\\{cc304579-73e2-4fa7-9863-f1cda545e542\\})|(\\{bf0d9dc8-ca00-4bba-88a0-535e20b089ea\\})|(\\{00dd06ff-7392-449b-9e87-8a39c5f5cb05\\})|(\\{c8882c78-ee4f-4c9c-8380-ab8f1d333e0d\\})|(\\{1bf5b2e0-14aa-49a4-9642-d2f28bb44730\\})|(\\{8afde9a0-bd2d-48f4-939c-8e7ec4210085\\})|(\\{8bd4cf33-e9c2-4a9c-84f5-ef0f3a512c53\\})|(\\{390d4afb-83c3-4a8c-93a0-587f9c52eb10\\})|(\\{3624218a-2799-478f-84bf-8f6ddd4fe8f5\\})|(\\{5303ae90-10ef-4c9c-bc51-d7062883b9a1\\})|(vivoinforma@tutanota\\.com)|(\\{8e638a74-6131-40b9-b64e-4341cf67d491\\})|(\\{f9295774-e455-4986-832c-cc4863769e66\\})|(thisisummsstrikethroughhighlighting@example\\.com)|(frigate@loranrendel)|(\\{ff4dc908-b851-42b7-adee-d9a5bb1e6813\\})|(\\{3ec50eaf-e36b-4b26-b255-0d75abe112b9\\})|(@LmXEarthZoomToolOneShot)|(\\{91be62bd-8752-45bf-a20a-b6b2d4abf87e\\})|(cloud-support@wildfire\\.ai)|(YoutubeUploader@ArcAvalon\\.com)|(\\{2e3be1f1-fb5c-40fb-b578-d2c9184ce470\\}))$/", + "prefs": [], + "schema": 1585146642510, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "92ac1ebd-7dcd-4446-a7b3-7e359ad1c4b5", + "last_modified": 1585158976498 + }, + { + "guid": "/^((Smart_Search_voIhxkbgGH@www\\.convertmyfile\\.co)|(Smart_Search_vRIICPGhwx@www\\.job-portal\\.co)|(Smart_Search_wsRtgwiOcH@www\\.search-smart\\.live)|(Smart_Search_xCropjfsuc@www\\.searchsmart\\.live)|(Smart_Search_yCAOHFcvfi@www\\.smart-search\\.live)|(Smart_Search_yDaEiIzkQK@www\\.smartsearch\\.live)|(Smart_Search_yhCKmmAXWz@www\\.searchsmart\\.online)|(Smart_Search_yrwXRZHXLh@www\\.search-smart\\.online)|(Smart_Search_YXySavVGVt@www\\.search-smart\\.work)|(SMART_SEARCH_YZaevPNtHc@www\\.localweathertoday\\.net)|(Smart_Search_ZsEjqVBHGd@www\\.convertthefile\\.net)|(Smart_Searches_FuCzQWfGWa@www\\.smartsearches\\.co)|(SMART-SEARCH_sHhQFGpFhV@www\\.coupondealer\\.co)|(Smart-Search_UasOSUTNhv@www\\.map-buddy\\.net)|(Smart\\.Search_631e8da2146d6187149c9206fac1a89e@www\\.convertthepdf\\.co)|(Smart\\.Search_IfzjxIabfy@www\\.convertthepdf\\.co)|(SmartSearch_clone_DyqkuEZPRF@www\\.search-smart\\.co)|(SmartSearch_DSuyKfIhTM@www\\.convertmypdf\\.online)|(SmartSearch_gkOEsQztVd@www\\.search-smart\\.co)|(SmartSearch_ohUbxjDpNV@www\\.search-smart\\.co)|(Speed_Check_01c5dfe0109bc6cb0e711840b239846c@www\\.checkmyspeed\\.co)|(Speed_Check_07449674bdae85845b4fc92e2695caf3@www\\.checkmyspeednow\\.com)|(Speed_Check_2098fea3a2c7a807bdeb61111c2de48f@www\\.checkmyspeed\\.co)|(Speed_Check_2305caaa9793a7577aa6283a89da7971@www\\.checkmyspeednow\\.com)|(Speed_Check_25ae2975d89f06e814b2e421109be7ef@www\\.checkmyspeednow\\.com)|(Speed_Check_55750cbafa724e361805677e2c1a0ea7@www\\.checkmyspeednow\\.com)|(Speed_Check_56ad7958c2981e0f1cad6d4de2abcb4b@www\\.checkmyspeed\\.co)|(Speed_Check_616286717d187249f5d29d2bc8771294@www\\.checkmyspeednow\\.com)|(Speed_Check_87ec83e10e18545948a0d739589fa6c0@www\\.checkmyspeed\\.co)|(Speed_Check_917a4c229b30f0ac1bca0aa5a43f8f2e@www\\.checkmyspeednow\\.disabled\\.com)|(Speed_Check_94cbe40b6d2b11c86b07bbb606fe11e2@www\\.checkmyspeednow\\.com)|(Speed_Check_a4d8b86ff6fa408ee87b70d65c41d1dd@www\\.checkmyspeed\\.co)|(Speed_Check_cdeb8d6f1f2426379a89eac745d5853a@www\\.checkmyspeed\\.co)|(Speed_Check_clone_aqksrBHRvi@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_CrMqWRFYTl@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_ec2c77c7b610b83c9f6bef2573e4d3a1@www\\.checkmyspeed\\.co)|(Speed_Check_clone_IchkKEIlVC@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_nmRPlvUEJv@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_OczJkeuXDq@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_QlPnYtcPGo@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_RjaZbJQbEk@www\\.checkmyspeednow\\.com)|(Speed_Check_clone_WPgSSdchao@www\\.checkmyspeed\\.co)|(Speed_Check_ed30669ac9901d20415ead7770db761a@www\\.speedchecktool\\.com)|(Speed_Check_FZSVtPegfX@www\\.checkmyspeed\\.co)|(Speed_Check_iQueDNOXmF@www\\.checkmyspeed\\.co)|(Speed_Check_LUlQBGtuOE@www\\.checkmyspeednow\\.com)|(Speed_Check_MNeDWiZeoo@www\\.checkmyspeed\\.co)|(Speed_Check_XckNwcWvNE@www\\.checkmyspeed\\.co)|(Speed_Check_xSObhPOTMw@www\\.checkmyspeed\\.co)|(Speed_Checker_Pro_260678188808c41791fdd9d9ed445802@www\\.speedcheckerpro\\.com)|(Speed_Checker_Pro_esgyoKewQa@www\\.speedcheckerpro\\.com)|(Speed_Test_Ace_0e5d46c255d8ceec71018d460c702e40@www\\.speedtestace\\.com)|(Speed_Test_Ace_b81323c0334e1c3f409c1dd14aa60f61@www\\.speedtestace\\.co)|(Speed_Test_Ace_clone_DKdXeTMeZx@www\\.speedtestace\\.co)|(Speed_Test_Ace_kcEikItaOC@www\\.speedtestace\\.co)|(Speed_Test_Ace_ppoJbdaucW@www\\.speedtestace\\.co)|(Speed_Test_Now_ZDIRLnXzms@speedtestnow\\.co)|(Speed_Test_OoJkZnYICK@www\\.speed-test\\.live)|(Speed_Test_uJpXMlaztD@www\\.testyourspeed\\.co)|(sportsbulletin@www\\.sportsupdates\\.co)|(sportseveryday@www\\.sportseveryday\\.co)|(sportseverydayco@www\\.sportseveryday\\.co)|(sportsupdates\\.website_SdCnLxLIxY@www\\.sportsupdates\\.website)|(Spot_Flight_Now_6b388ff946e79dc3fd82eec51958d550@www\\.spotflightnow\\.com)|(Spot_Flight_Now_clone_eb4c54758e45589d6039415632c1e319@www\\.spotflightnow\\.com)|(Spot_Flight_Now_clone_eTPyUPJmtK@www\\.spotflightnow\\.com)|(Spot_Flight_Now_clone_UZIlZeQYAb@www\\.spotflightnow\\.com)|(Spot_Flight_Now_EtIsXJyXXm@www\\.spotflightnow\\.com)|(Spot_Flight_Now_f268f21c4c60f3e78601d20b97453943@www\\.spotflightnow\\.com)|(staging\\.checknetspeed\\.co_nREaqypDrX@staging\\.checknetspeed\\.co)|(Start_A_Career_RXEBmchKFm@www\\.jobs\\.startacareer\\.co)|(Stream_TV_Live_KrYxWrtCtv@www\\.streamtvlive\\.co))$/", + "prefs": [], + "schema": 1585146841367, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aae4b574-f195-4f18-8bc0-c3b712e4316b", + "last_modified": 1585158976495 + }, + { + "guid": "/^((Easy_Doc_Converter_b4ab66ebed6d4f9148bd9024b5b45266@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_egpEGOXPkz@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_f5e570a648be4d82f02fa2106a21a43f@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_IteaxXKJex@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_jJGqUHYgCN@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_KPOmYhpWMq@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_KyppGVLzlw@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_PuJVSyMdMn@www\\.easydocconverter\\.com)|(Easy_Email_Center_clone_LWcJzWsabx@www\\.easyemailcenter\\.com)|(Easy_Email_Center_clone_WroHXxYaWe@www\\.easyemailcenter\\.com)|(Easy_Email_Center_fhZhWrvhNZ@www\\.easyemailcenter\\.com)|(Easy_Email_Checker_54b7a48dd3773a01313275df62afd4cf@www\\.easyemailchecker\\.net)|(Easy_Email_Plus_GqrjVlzgJP@www\\.easyemailplus\\.com)|(Easy_Forms_Finder_gmYexjlshO@www\\.easyformsfinder\\.com)|(Easy_Inbox_Access_clone_BHQcoGwBXJ@www\\.easyinboxaccess\\.com)|(Easy_Inbox_Access_pXyLNbavvp@www\\.easyinboxaccess\\.com)|(Easy_Inbox_Access_yVECZMgzTG@www\\.easyinboxaccess\\.com)|(Easy_Mail_Login_FRPUoJrXUl@www\\.easymail-login\\.co)|(Easy_Mail_Logins_AGDDyUpIjb@www\\.easymaillogin\\.co)|(Easy_Map_Finder_a811a7fb227baf4b89b3ed4d017fcfc4@www\\.easymapsfinder\\.com)|(Easy_Map_Tab_44b8d948ebfb6010e7fe17ad02606c6e@www\\.easymaptab\\.com)|(Easy_Map_Tab_rfqbzWtygp@www\\.easymaptab\\.com)|(Easy_Maps_VsjiCqbVfo@www\\.mapdirectionspront\\.co)|(Easy_Online_Recipe_IzyMoUNAGs@www\\.easyonlinerecipe\\.co)|(Easy_Online_Recipe_oEqObvsniN@www\\.easyonlinerecipe\\.net)|(Easy_Recipes_277ea1aa5aaed20cbb145291ebf0cdc1@www\\.dailyrecipesearch\\.net)|(Easy_Recipes_35f61f2bee1a01e94edbbb868bbb66ca@www\\.dailyrecipesearch\\.net)|(Easy_Recipes_5d97b38952cbd818b53669ee377f4a4e@www\\.dailyrecipesearch\\.net)|(Easy_Recipes_JkInlZZXKJ@www\\.dailyrecipesearch\\.net)|(Easy_Recipes_qwPPMUMPbm@www\\.dailyrecipesearch\\.net)|(Easy_Recipes_VZjydpXYuD@www\\.dailyrecipesearch\\.net)|(Easy_Search_3ce2d360291c0435253c279a003f4bee@www\\.easy-search\\.co)|(Easy_Search_9259a3968f7a7a407cf8a9c5b5aac8b5@www\\.easy-search\\.co)|(Easy_Search_Access_41cd311e7ebb5abe48a68600a41b6a6e@www\\.easysearchaccess\\.com)|(Easy_Search_Access_zBaMiEnnEw@www\\.easysearchaccess\\.com)|(Easy_Search_bJrePoApTo@www\\.easy-search\\.co)|(Easy_Search_gFyxFnsAsY@www\\.easy-search\\.co)|(Easy_Search_Online_8d5692e3bf9af4e8d0fcb27b753b4bb0@www\\.easysearchonline\\.co)|(Easy_Search_Online_YJpastaXEE@www\\.easysearchonline\\.co)|(Easy_Search_Pro_edc3f4614998d63f6bddb8cddf1eca75@www\\.easysearchpro\\.co)|(Easy_Search_Pro_JDFkWEUATn@www\\.easysearchpro\\.co)|(Easy_Weather_Checker_b96bc45b78e94fcc92bbf66eabb4c7e0@www\\.easyweatherchecker\\.com)|(Easy_Weather_Checker_OZbmtCpDgx@www\\.easyweatherchecker\\.com)|(Easy_Weather_Forecast_6b2ee7704b4f8f93b4e4c42c707dadc1@www\\.easyweatherforecast\\.com)|(Easy_Weather_Forecast_pNIPnyHOJG@www\\.easyweatherforecast\\.com)|(EasyMailLogin\\.net_usETGyzdRX@www\\.easymaillogin\\.net)|(EasyOnlineRecipe\\.co_CVOjgBfTWs@www\\.easyonlinerecipe\\.co)|(EasyOnlineRecipe\\.net_icOMrzwKiN@www\\.easyonlinerecipe\\.net)|(EasySearchGo_JMJgGKKsKw@www\\.easysearchgo\\.com)|(Email_Access_7ad642481026e05b4ff4a7da92468bdb@www\\.accessmymails\\.co)|(Email_Access_JVuQrQxsXR@www\\.accessmymails\\.co)|(Email_Checker_MguwpDKJMd@www\\.easyemailchecker\\.net)|(Email_Inbox_Now_OVfDpBfbuk@www\\.emailinboxnow\\.com)|(Email_Login_CofCwMXmYY@www\\.easymaillogin\\.net)|(Email_Login_eVRzKHiywW@www\\.easymaillogin\\.net)|(Email_Login_KABgNaGQJZ@www\\.easymaillogin\\.net)|(Email_Login_Pro_80b99340e1bcedf5f0a1f4d1345538e1@www\\.emailloginpro\\.com)|(Email_Login_Pro_SHHHqfDSTO@www\\.emailloginpro\\.com)|(Email_Login_Pro_xvgHLfPbBB@www\\.loginemailpro\\.net)|(Email_Tab_13d31aa17a541c3018f2625a09486686@www\\.emailtab\\.co)|(Email_Tab_ZdbwsMpyuq@www\\.emailtab\\.co)|(Everyday_Astro_clone_bOSsysyaan@www\\.everydayastro\\.co)|(Everyday_Astro_CMqmHgpSTg@www\\.everydayastro\\.co)|(Everyday_Astro_dVNMgQmXws@www\\.everydayastro\\.online)|(Everyday_Astro_NHOKASspnT@www\\.everydayastro\\.online)|(Everyday_Astro_sDIIbCNITP@www\\.everydayastro\\.online)|(Everyday_Astro_xiJdwxnNom@www\\.everydayastro\\.online)|(Everyday_Astro_YGSUqIslKW@www\\.everyday-astro\\.com))$/", + "prefs": [], + "schema": 1585145745119, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "21ecfbe4-1255-4f86-b78c-c4fd06a6f197", + "last_modified": 1585158976491 + }, + { + "guid": "/^((\\{441a1a1f-ba4f-4f2e-ade8-88c2950323a6\\})|(\\{81faa316-ef9f-4fc9-9db8-af17eabc776b\\})|(\\{e2ee901b-44cb-430f-be5e-23ab0291d8f4\\})|(\\{d1e6ace8-9623-4a5d-a94f-81fd289fe834\\})|(\\{0de5c9e4-4488-489a-a130-0e192cc6bb08\\}))$/", + "prefs": [], + "schema": 1584906090910, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1624545", + "why": "This add-on violates Mozilla's add-on policies by redirecting requests without user disclosure or consent.", + "name": "Add-ons redirecting requests" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9b936d20-0a13-42de-b735-11409c3e4881", + "last_modified": 1585130632165 + }, + { + "guid": "/^((\\{9263519f-ca57-4178-b743-2553a40a4bf1\\})|(\\{d97223b8-44e5-46c7-8ab5-e1d8986daf44\\})|(\\{0111c475-01e6-42ea-a9b4-27bed9eb6092\\})|(\\{36a4269e-4eef-4538-baea-9dafbf6a8e2f\\})|(\\{2cac0be1-10a2-4a0d-b8c5-787837ea5955\\})|(\\{6072a2a8-f1bc-4c9c-b836-7ac53e3f51e4\\})|(\\{2134e327-8060-441c-ba68-b167b82ff5bc\\})|(\\{a3fbc8be-dac2-4971-b76a-908464cfa0e0\\})|(\\{de3b1909-d4da-45e9-8da5-7d36a30e2fc6\\})|(\\{02328ee7-a82b-4983-a5f7-d0fc353698f0\\})|(\\{28044ca8-8e90-435e-bc63-a757af2fb6be\\})|(\\{63df223d-51cf-4f76-aad8-bbc94c895ed2\\})|(\\{3f4191fa-8f16-47d2-9414-36bfc9e0c2bf\\})|(\\{a0c54bd8-7817-4a40-b657-6dc7d59bd961\\})|(\\{f0b809eb-be22-432f-b26f-b1cadd1755b9\\})|(\\{919fed43-3961-48d9-b0ef-893054f4f6f1\\})|(\\{cd28aa38-d2f1-45a3-96c3-6cfd4702ef51\\})|(\\{829827cd-03be-4fed-af96-dd5997806fb4\\})|(\\{7d932012-b4dd-42cc-8a78-b15ca82d0e61\\})|(\\{ed352072-ddf0-4cb4-9cb6-d8aa3741c2de\\})|(\\{a0ab16af-3384-4dbe-8722-476ce3947873\\})|(\\{23c65153-c21e-430a-a2dc-0793410a870d\\})|(\\{31680d42-c80d-4f8a-86d3-cd4930620369\\})|(\\{f4262989-6de0-4604-918f-663b85fad605\\})|(\\{e341ed12-a703-47fe-b8dd-5948c38070e4\\})|(\\{cd89045b-2e06-46bb-9e34-48e8799e5ef2\\})|(\\{ac296b47-7c03-486f-a1d6-c48b24419749\\})|(\\{5da81d3d-5db1-432a-affc-4a2fe9a70749\\})|(\\{72c1ca96-c05d-46a7-bce1-c507ec3db4ea\\})|(\\{869b5825-e344-4375-839b-085d3c09ab9f\\})|(\\{81ac42f3-3d17-4cff-85af-8b7f89c8826b\\})|(\\{df09f268-3c92-49db-8c31-6a25a6643896\\})|(\\{5f4e63e4-351f-4a21-a8e5-e50dc72b5566\\})|(\\{4479446e-40f3-48af-ab85-7e3bb4468227\\})|(\\{09c8fa16-4eec-4f78-b19d-9b24b1b57e1e\\})|(\\{be37931c-af60-4337-8708-63889f36445d\\})|(\\{71639610-9cc3-47e0-86ed-d5b99eaa41d5\\})|(\\{0432b92a-bfcf-41b9-b5f0-df9629feece1\\})|(\\{83d38ac3-121b-4f28-bf9c-1220bd3c643b\\})|(\\{01166e60-d740-440c-b640-6bf964504b3c\\})|(\\{76ce213c-8e57-4a14-b60a-67a5519bd7a7\\})|(\\{226b0fe6-f80f-48f1-9d8d-0b7a1a04e537\\})|(\\{be981b5e-1d9d-40dc-bd4f-47a7a027611c\\})|(\\{37f8e483-c782-40ed-82e9-36f101b9e41f\\})|(\\{01c9a4a4-06dd-426b-9500-2ea6fe841b88\\})|(\\{7d5e24a1-7bef-4d09-a952-b9519ec00d20\\})|(\\{408a506b-2336-4671-a490-83a1094b4097\\})|(\\{7f8bc48d-1c7c-41a0-8534-54adc079338f\\})|(\\{cf9d96ff-5997-439a-b32b-98214c621eee\\})|(\\{302ef84b-2feb-460e-85ca-f5397a77aa6a\\})|(\\{0c9970a2-6874-483b-a486-2296cfe251c2\\})|(\\{e5bc3951-c837-4c98-9643-3c113fc8cf5e\\})|(\\{93017064-dfd4-425e-a700-353f332ede37\\})|(\\{28092fa3-9c52-4a41-996d-c43e249c5f08\\})|(\\{a893296e-5f54-43f9-a849-f12dcdee2c98\\})|(\\{fc0d55bd-3c50-4139-9409-7df7c1114a9d\\})|(\\{c661c2dc-00f9-4dc1-a9f6-bb2b7e1a4f8d\\})|(\\{419be4e9-c981-478e-baa0-937cf1eea1e8\\})|(\\{449e185a-dd91-4f7b-a23a-bbf6c1ca9435\\})|(\\{1c981c7c-30e0-4ed2-955d-6b370e0a9d19\\})|(\\{9ce2a636-0e49-4b8e-ad17-d0c156c963b0\\})|(\\{a2de96bc-e77f-4805-92c0-95c9a2023c6a\\})|(\\{a42e5d48-6175-49e3-9e40-0188cde9c5c6\\})|(\\{86d98522-5d42-41d5-83c2-fc57f260a3d9\\})|(\\{05a21129-af2a-464c-809f-f2df4addf209\\})|(\\{446122cd-cd92-4d0c-9426-4ee0d28f6dca\\})|(\\{bfd92dfd-b293-4828-90c1-66af2ac688e6\\})|(\\{f9e1ad25-5961-4cc5-8d66-5496c438a125\\})|(\\{8a61507d-dc2f-4507-a9b7-7e33b8cbc31b\\})|(\\{84406197-6d37-437c-8d82-ae624b857355\\})|(\\{11df9391-dba5-4fe2-bd48-37a9182b796d\\})|(\\{e9ccb1f2-a8ba-4346-b43b-0d5582bce414\\})|(\\{79db6c96-d65a-4a64-a892-3d26bd02d2d9\\})|(\\{214cb48a-ce31-4e48-82cf-a55061f1b766\\})|(\\{4c140bc5-c2ad-41c3-a407-749473530904\\})|(\\{c5cf4d08-0a33-4aa3-a40d-d4911bcc1da7\\})|(\\{591d1b73-5eae-47f4-a41f-8081d58d49bf\\})|(\\{f1bce8e4-9936-495b-bf48-52850c7250ab\\})|(\\{7c1df23b-1fd8-42b9-8752-71fff2b979de\\})|(\\{3c27c34f-8775-491a-a1c9-fcb15beb26d3\\})|(\\{ddae89bd-6793-45d8-8ec9-7f4fb7212378\\})|(\\{c488a8f5-ea3d-408d-809e-44e82c06ad9d\\})|(\\{2f8aade6-8717-4277-b8b1-55172d364903\\})|(\\{2eb66f6c-94b3-44f5-9de2-22371236ec99\\})|(\\{216e0bcc-8a23-4069-8b63-d9528b437258\\})|(\\{d14acee6-f32b-4aa3-a802-6616003fc6a8\\})|(\\{b26bf964-7aa6-44f4-a2a9-d55af4b4eec0\\})|(\\{e2139287-2b0d-4f54-b3b1-c9a06c597223\\})|(\\{3f4dea3e-dbfc-428f-a88b-36908c459e20\\})|(\\{92111c8d-0850-4606-904a-783d273a2059\\})|(\\{2aa275f8-fabc-4766-95b2-ecfc73db310b\\})|(\\{f01c3add-dc6d-4f35-a498-6b4279aa2ffa\\}))$/", + "prefs": [], + "schema": 1585040894048, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1439702", + "why": "This malicious add-on claims to be a Firefox \"helper\" or \"updater\" or similar.", + "name": "FF updater (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "48b14881-5f6b-4e48-afc5-3d9a7fae26a3", + "last_modified": 1585130632161 + }, + { + "guid": "/^((__TEMPLATE__APPLICATION__@ruta-mapa\\.com)|(application-3@findizer\\.fr)|(application2@allo-pages\\.fr)|(application2@bilan-imc\\.fr)|(application2@lettres\\.net)|(application2@search-maps-finder\\.com)|(application-imcpeso@imc-peso\\.com)|(application-meuimc@meu-imc\\.com)|(application-us2@factorlove)|(application-us@misterdirections)|(application-us@yummmi\\.es)|(application@amiouze\\.fr)|(application@astrolignes\\.com)|(application@blotyn\\.com)|(application@bmi-result\\.com)|(application@bmi-tw\\.com)|(application@calcolo-bmi\\.com)|(application@cartes-itineraires\\.com)|(application@convertisseur\\.pro)|(application@de-findizer\\.fr)|(application@de-super-rezepte\\.com)|(application@dermabeauty\\.fr)|(application@dev\\.squel\\.v2)|(application@eu-my-drivingdirections\\.com)|(application@fr-allo-pages\\.fr)|(application@fr-catizz\\.com)|(application@fr-mr-traduction\\.com)|(application@good-recettes\\.com)|(application@horaires\\.voyage)|(application@imc-calcular\\.com)|(application@imc-peso\\.com)|(application@it-mio-percorso\\.com)|(application@iti-maps\\.fr)|(application@itineraire\\.info)|(application@lbc-search\\.com)|(application@les-pages\\.com)|(application@lovincalculator\\.com)|(application@lovintest\\.com)|(application@masowe\\.com)|(application@matchs\\.direct)|(application@mein-bmi\\.com)|(application@mes-resultats\\.com)|(application@mestaf\\.com)|(application@meu-imc\\.com)|(application@mon-calcul-imc\\.fr)|(application@mon-juste-poids\\.com)|(application@mon-trajet\\.com)|(application@my-drivingdirections\\.com)|(application@people-show\\.com)|(application@plans-reduc\\.fr)|(application@point-meteo\\.fr)|(application@poulixo\\.com)|(application@quipage\\.fr)|(application@quizdeamor\\.com)|(application@quizdoamor\\.com)|(application@quotient-retraite\\.fr)|(application@recettes\\.net)|(application@routenplaner-karten\\.com)|(application@ruta-mapa\\.com)|(application@satellite\\.dev\\.squel\\.v2)|(application@search-bilan-imc\\.fr)|(application@search-maps-finder\\.com)|(application@slimness\\.fr)|(application@start-bmi\\.com)|(application@tests-moi\\.com)|(application@tousmesjeux\\.fr)|(application@toutlannuaire\\.fr)|(application@tuto-diy\\.com)|(application@ubersetzung-app\\.com)|(application@uk-cookyummy\\.com)|(application@uk-howlogin\\.me)|(application@uk-myloap\\.com)|(application@voyagevoyage\\.co)|(application@wikimot\\.fr)|(application@www\\.plans-reduc\\.fr)|(application@yummmi\\.es)|(application@yummmies\\.be)|(application@yummmies\\.ch)|(application@yummmies\\.fr)|(application@yummmies\\.lu)|(application@zikplay\\.fr)|(applicationY@search-maps-finder\\.com)|(cmesapps@findizer\\.fr)|(findizer-shopping@jetpack)|(\\{8aaebb36-1488-4022-b7ec-29b790d12c17\\}))$/", + "prefs": [], + "schema": 1585060581210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1452648", + "why": "Those add-ons do not provide a real functionality for users, other than silently tracking browsing behavior.", + "name": "Tracking Add-ons (harmful)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "36f97298-8bef-4372-a548-eb829413bee9", + "last_modified": 1585130632157 + }, + { + "guid": "/^((\\{bb5254c6-92a2-4361-b000-816bc4d34c55\\})|(\\{2fb3aa6a-4ab8-4071-a0f9-379100ac95a2\\})|(\\{0cd3acae-0589-41e3-9525-61786e493188\\})|(\\{3f259f0b-ad11-48c5-a7c4-a5a0897a6d1c\\})|(\\{02b6b8e0-f3b6-4f0a-8055-2baf79c606c1\\})|(\\{9419fd2e-894c-4aa8-aa14-18b5982c5889\\})|(\\{7d0d82e8-308b-4039-b3df-577b41483946\\}))$/", + "prefs": [], + "schema": 1584447915622, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1623342", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-os collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b107267-ee8d-4ecf-b7d5-64966911681d", + "last_modified": 1584609109587 + }, + { + "guid": "/^((\\{e3d1040d-ea1e-4337-b51b-9f353817184f\\})|(\\{205c14a6-8b6f-49ef-b0d6-41bad99ad5e8\\})|(\\{08ac7dd2-8595-4966-a00b-1ef6a5bdb6f3\\})|(\\{8e937b56-4242-4cb5-b92a-f64452da816e\\})|(\\{9a1ded7e-5fe2-43ad-90a6-3732ceae3d08\\})|(\\{b384b75c-c978-4c4d-b3cf-62a82d8f8fff\\})|(\\{6133a4ea-818f-4e22-bbde-0437d80142bf\\})|(\\{aaf2b501-9d4f-4966-be95-0334bd16b291\\}))$/", + "prefs": [], + "schema": 1584542165088, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1623343", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1dd9652a-c6cf-4cb0-b6f7-7bea8990b4fb", + "last_modified": 1584609109583 + }, + { + "guid": "/^((\\{cde82615-f0be-9aff-872d-31da1bc93923\\})|(\\{85a8a08c-52a8-7d1d-5b80-f1b5cd4751bf\\})|(\\{3d1468f8-abbd-4a7a-95f1-fa5a1201adb4\\})|(\\{386056fc-2697-4a54-bd35-02de8d62fe1b\\})|(\\{1ae2bde0-f2ea-4bf1-a075-08853f9fcf31\\})|(\\{382056fc-2627-2a54-bd25-02de1d62fe1b\\})|(\\{926abf23-d2b0-4142-9aaa-5338b33a0000\\})|(\\{ffedd9c9-45b9-4e11-bbf0-a1408fa0975d\\})|(\\{b35ef07a-7c1b-4b6a-b7aa-de1e918396cb\\}))$/", + "prefs": [], + "schema": 1584542307136, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1623345", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5c8fccf9-b1bb-41f0-bdc3-3a5790fdd4be", + "last_modified": 1584609109580 + }, + { + "guid": "{4e820bcf-688e-4b4e-8773-3861dad6a7e0}", + "prefs": [], + "schema": 1584387693606, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1623047", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d65d4593-d2bb-41ab-b485-9818bc6f285f", + "last_modified": 1584447915216 + }, + { + "guid": "/^((\\{c89232c3-39bb-41e5-a197-7f4412b45fe6\\})|(oniria@valid-install\\.com))$/", + "prefs": [], + "schema": 1583964274652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621940", + "why": "This add-on violates Mozilla's add-on policies by collecting search data going to a third party provider or collecting search data without user disclosure or consent.", + "name": "Add-ons violating policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6d431a97-5d54-4de7-a012-5b030c5324eb", + "last_modified": 1584054334501 + }, + { + "guid": "/^((\\{93475144-dae7-452c-a9e4-ef9da092517d\\})|(\\{cfc5bfc9-3dc0-483d-8dc8-27d05f6cff30\\})|(\\{498bc33d-979f-4d5d-93e4-ba608752ad10\\}))$/", + "prefs": [], + "schema": 1583875388267, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621571", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "965435ff-2bb1-4b17-9e0b-b157d5c53a91", + "last_modified": 1583964274232 + }, + { + "guid": "/^((\\{eb5bd821-bb2c-4968-bf17-8e9336fbdfc4\\})|(\\{84be56c9-16ae-4bde-a556-0b9f7946c1be\\}))$/", + "prefs": [], + "schema": 1583914825954, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621578", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dc0fc5f1-511f-4fc3-9558-fc7d9fa61e75", + "last_modified": 1583964274228 + }, + { + "guid": "{dcfd7c5e-ca9e-461f-a748-74f187332e5f}", + "prefs": [], + "schema": 1583788354663, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621247", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Emoji Library (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "320f94ad-5e02-4209-a066-22f415100d06", + "last_modified": 1583875387847 + }, + { + "guid": "/^((\\{d8e66734-06c1-44f4-968e-9af179ca3fcb\\})|(\\{3e836ac4-d85f-4e8b-8647-6e922b3b2b2a\\})|(\\{71d690cd-dff7-4d28-9cfb-d92bd6522d1f\\})|(\\{f20a4998-3973-4094-b61b-cbaeff99ff84\\}))$/", + "prefs": [], + "schema": 1583757249813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1620356", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d10ec091-688b-4906-9ed1-42aedc998e1e", + "last_modified": 1583788354268 + }, + { + "guid": "/^((\\{8890a066-99e3-417d-96b6-f71db4f5fec0\\})|(\\{49f9bb4f-f470-48e7-a04e-25baa35209d4\\})|(\\{38799169-e35a-4034-8e62-8d82ae8b8a85\\})|(\\{a67a2dc1-081e-4efb-aa62-5dc7f499fe86\\}))$/", + "prefs": [], + "schema": 1583759516487, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1620970", + "why": "This add-on violates Mozilla's add-on policies by loading remote content into the new tab page.", + "name": "Add-ons using remote new tab pages" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d4568b40-4efd-4cca-9680-ba50dea36ec9", + "last_modified": 1583788354265 + }, + { + "guid": "/^((\\{16004b4c-8fcd-43e8-8867-233023c6fdeb\\})|(\\{79fa9c23-e61f-4434-8b27-0bd44c37edff\\})|(\\{15d5bc6f-15f8-4d0d-9483-7dbbcf722c56\\}))$/", + "prefs": [], + "schema": 1583759652565, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1620971", + "why": "This add-on violates Mozilla's add.on policies by collecting searches to 3rd party providers.", + "name": "Add-ons collecting search terms" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a1caad16-4ec4-43ca-a145-04fbd5fd81c9", + "last_modified": 1583788354262 + }, + { + "guid": "/^((\\{deb55b0e-7064-4552-955c-15ba6a46ad79\\})|(\\{15d967ca-cb54-444c-a7ad-b0dad1dbb723\\})|(\\{7953fef3-37b3-4954-a0e0-806cbec35a63\\})|(\\{2deadd5b-1edc-4634-b342-4e20ad03fd2d\\})|(\\{f74e5fe2-4f8a-489a-8f75-f965a9d4a98d\\})|(\\{0df2d5d8-b197-4022-ad3f-c24a44e9d23f\\})|(\\{e972a079-8e0f-4426-91e9-2c1cf9c8b89e\\})|(\\{d9ce0e4f-e41e-4160-b58e-b99300fa5d83\\})|(\\{43473acc-f92a-445c-9ce3-efc297fb62d0\\})|(\\{5b86a98e-5ce8-4664-9051-3d8f7127d1c5\\})|(\\{5f1e99d2-147f-48bc-aa87-41ed02d65965\\})|(\\{ffd007b3-c085-4bcb-9629-19a528f346ba\\})|(\\{df4e0189-a17d-41aa-af86-b6de65952842\\})|(\\{06880fd6-ffaa-4c77-a5bb-97dcd0ce554b\\})|(\\{f4e654cc-7ee1-401d-ac52-ced86f38e575\\})|(\\{d207c7e2-8a92-4f8e-807d-027fa2eed42b\\})|(\\{8d9fbceb-756f-417a-a691-de9a2c1c5648\\})|(\\{3602c279-d63c-4f81-b832-ef82fda60514\\})|(\\{2f27430f-99b9-4594-83ae-60a4df917e38\\})|(\\{be81f239-4a22-4b02-8878-358a3dcd6539\\})|(\\{e7c3277a-c312-4072-837a-4417aae7269a\\})|(\\{d5783778-d85e-4768-9b91-16b9068df780\\})|(\\{8c6f409e-f740-4807-9a28-c898266c518c\\})|(\\{9b1a4630-5873-4b3c-bcf2-4b4d59cc9739\\})|(\\{8522aad0-47c1-47e6-afbc-1e53a113bb2d\\})|(\\{d0e1738e-e2ce-4e75-bcdf-fdef4c0b7403\\})|(\\{4ac16c6e-ff72-45b2-b641-bf92144e1118\\})|(\\{e9c99267-487c-4fcc-9585-4a8e08d4342d\\})|(\\{0fdf70d2-0dbd-4ead-b746-9cbf4cacac6c\\})|(\\{0393ecde-cfd9-4a72-aa9d-6b64949b890c\\})|(\\{e80bc052-25fe-40ea-813a-dc98dffc2a77\\})|(\\{bfbe94d8-3389-4e4b-8cad-67eedd3bfa08\\})|(9\\.1\\.2\\.1@qamypersonalzodiac\\.xyz)|(9\\.1\\.2\\.1@qafungamesnetwork\\.xyz)|(9\\.1\\.3\\.1@qafungamesnetwork\\.xyz)|(\\{1985b6a7-5f69-484d-b462-667392e9ceae\\})|(\\{0d802f03-a2bf-433b-8124-7a2126bc53d9\\})|(\\{4c7d34ec-3bae-40ab-8300-38136f1bbcaf\\})|(\\{f0a11d18-866f-475d-8804-93d16729e59b\\}))$/", + "prefs": [], + "schema": 1583769750285, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621039", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e3a700b8-0177-4b11-bf7a-ae7c97e5115b", + "last_modified": 1583788354256 + }, + { + "guid": "{2abaf8da-5ca6-4896-8bf4-33cced7d1eec}", + "prefs": [], + "schema": 1583770759014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1621043", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Pluto TV" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6b7c877c-cf5b-478c-bc1a-d1a4f17f5599", + "last_modified": 1583788354253 + }, + { + "guid": "/^((\\{cf7362f7-d34e-423d-ade0-ff408145d75d\\})|(\\{225a2fd0-2266-4bbf-b1db-b4cd7ee3b774\\})|(\\{89ac12b3-2da6-423f-afdd-a755925070d9\\})|(\\{3c56ddf2-a46b-4338-9dc4-bf79a6947838\\})|(\\{4aec95dd-4720-4b7b-9032-c53c71c69531\\})|(\\{19efb4dd-72e6-4e76-9814-9185f0cebe1a\\})|(\\{a73d9f4d-dff4-47ff-ad33-1747dc74faf8\\})|(\\{58c16ee9-4ac8-466c-b8ac-5d8b019945f3\\})|(\\{a88f8f80-e2f4-4357-9f85-d49fcc22662e\\})|(\\{a623f590-df10-4f74-a281-27457212e744\\})|(\\{14df2d69-1ca8-4e68-8289-23816420eebe\\})|(\\{7789cecb-ccd2-4a7f-a75b-8d0243ab68b8\\})|(\\{0e2b182e-05c6-4830-9d20-b402c5598e55\\})|(\\{fd03573a-9361-4f90-9c60-be6013ebfb8e\\})|(\\{5ae01022-7989-4620-b46e-7ba5859e20d3\\})|(\\{d3ced839-59e1-4389-8631-a9f153187990\\})|(\\{07d66467-bc14-433c-84d7-905e8d2f550f\\})|(\\{3019ef01-bffd-45cf-8cd1-46f6c56cdda4\\})|(\\{1f0c9873-0598-4132-b20a-f0ab42c5c8a3\\})|(\\{b955bec7-392d-4074-bac8-60c6d1f402f5\\})|(\\{9f45dcf0-549e-4f15-a0ad-99ac2821fd8d\\})|(\\{16d5d1cf-0aef-4adf-99ce-214eb32d38fa\\})|(\\{b38602ca-4ac6-40b1-b20c-55828f514b3d\\})|(\\{55318007-10db-4d47-b4df-3946ed3653af\\})|(\\{cd2d96b3-e8ec-4ef9-95ba-72ee0dae011a\\})|(\\{b8250196-9419-4b06-9634-48f6c3570b53\\})|(\\{2a738b9e-5147-4441-91cd-e40a1ef50a27\\})|(\\{777580ee-50ee-4c37-98a9-844111149404\\})|(\\{a44f69d9-9c70-4b33-9502-b19e79399e3c\\})|(\\{2e6daca8-d5b4-4611-bd81-b964ab97bee8\\})|(\\{6fda5ac5-a41f-4905-a5c0-860313ab535b\\})|(\\{d51b14e2-4c5f-4601-b2b6-af8b572171cc\\})|(\\{dc5b9553-f925-43c5-936a-fd9ff0e56e47\\})|(\\{b0d8908e-ac38-473b-b20f-3a8d775c23f9\\})|(\\{d96abf04-438b-45a6-b6ec-3036124b5458\\})|(\\{469003fe-c00d-45db-82b7-c04635c227e9\\})|(\\{7d04eb8b-023a-4966-b6b9-06c706081d74\\})|(\\{41987721-6948-46f5-9e68-bcaf776e35ea\\})|(\\{127f13e3-f58a-44f6-bff8-955dd3688448\\})|(\\{09c02fc4-4a23-43c4-b1ad-854a3e1e6a29\\})|(\\{d061e0a3-7554-4a78-b7cf-e3f57df09b00\\})|(\\{28e445aa-892e-42e3-a0f1-30ab0ebba44c\\})|(\\{bc158e4c-6514-4b0e-9da4-6018326fe634\\})|(\\{fde60598-cfe6-4e9c-9f7a-28ea348a7f09\\})|(\\{a7addae9-82ab-4d91-a2b9-be373207bd9b\\})|(\\{71cc8c13-7aab-49d4-a419-b6e2880daaf0\\})|(\\{9a43f082-11c5-4062-bbad-04b63e6ed433\\})|(\\{19cdad5d-2a97-46b4-ba28-3a191d18b174\\})|(\\{99ee7e8c-61c2-4ffc-8f7a-c70c0bd4f1f9\\})|(\\{47706cc2-917e-4a2d-aba1-ee5c05613e0d\\})|(\\{a1a40297-cfac-455a-ad94-ef20eda2672a\\})|(\\{d807c94c-fbd3-48b7-9c07-a54336ace9f3\\})|(\\{0645230a-9e65-4a9d-ae90-97416f33f29d\\})|(\\{064d021b-9424-4700-b550-80c7a983c240\\})|(\\{59722380-31f0-4588-ac19-670f021cd67b\\})|(\\{0f504621-b4e1-4d5b-89dc-b57399b10c29\\})|(\\{ba135229-de15-41d9-8a35-fd198698fda4\\})|(\\{2117ec43-2df6-4bf2-a468-f067ea721432\\})|(\\{fd3fa8d5-be30-4e57-ba9e-ad11d0f70c41\\})|(\\{cb8e0410-17e0-4866-8075-b3224d52ea6b\\})|(\\{dff54861-9936-440e-94a0-92d39794be5c\\})|(\\{72c03815-0ef1-427f-b577-9c73bd19a7ca\\})|(\\{f13a447c-857b-4d93-a5cd-cb2578ede3d9\\})|(\\{8717ef97-76f5-4729-8a94-1ce396c0d2e9\\})|(\\{5e192df0-d31d-42b7-b866-155068118d2a\\})|(\\{d9238c4c-4259-4c0a-92c5-d03006959c1b\\})|(\\{95716b5d-ce17-4a97-9691-40f62291649e\\})|(\\{a0f9afee-bc26-4fa9-a1df-c705ad21cc94\\})|(\\{8f02a7c1-c6a1-4d3c-923a-59bd7e373205\\})|(\\{2452d750-d1dd-4cef-be51-3cc75f7a62d7\\})|(\\{e077a5e8-62c9-41a6-8427-10445a3d7818\\})|(\\{1685d632-1737-4bae-83a4-b0df6541f187\\}))$/", + "prefs": [], + "schema": 1583005290759, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618688", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons violating multiple policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "41a53d4b-120a-4ab0-9dbc-8a8d3802b734", + "last_modified": 1583233056644 + }, + { + "guid": "/^((jid1-h7qSFwT2a1FJOp@jetpack)|(jid1-w4wG5nJhx4LJZr@jetpack)|(\\{74b0af75-8791-44e2-95a6-7f0ab94143ec\\}))$/", + "prefs": [], + "schema": 1582980182934, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1619067", + "why": "This add-on violates Mozilla's add-on policies by adding abusive content to websites.", + "name": "Add-ons including abusive functionality" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "0.1.7", + "minVersion": "0.1.6" + } + ], + "id": "c4c0b595-6481-42a6-9e94-998b808dd143", + "last_modified": 1582981347968 + }, + { + "guid": "monstercropper@gmail", + "prefs": [], + "schema": 1582726441204, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618273", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Extensions Manager (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4c706648-260d-42c3-8461-3ddcf9db11f9", + "last_modified": 1582916527189 + }, + { + "guid": "{df73c71d-e4b6-46e1-a853-9831ad860a7b}", + "prefs": [], + "schema": 1582752497018, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618518", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Dark Theme (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "59f951ab-6183-4ae8-9ed0-99b667fa2a86", + "last_modified": 1582916527186 + }, + { + "guid": "/^((\\{fa59fb58-b15e-4dec-a9e4-145fbdaa2f03\\})|(somethingadguard@ok)|(somethingsmartadblock@ok)|(somethingublockorigin@ok)|(somethingadblockpro@ok)|(somethingadblocker@ok)|(somethingadblockerforyoutube@ok)|(somethingadblockerlite@ok)|(somethingfacebookadblock@ok)|(somethinghostadblocker@ok)|(somethingnanoadblocker@ok)|(somethingublockmobile@ok)|(somethingurbanfreeadblocker@ok)|(somethingwebmailadblocker@ok)|(somethingyoutubeadblock@ok)|(somethingyoutubeadscleaner@ok)|(somethingyoutubevideoadblocker@ok)|(\\{d3360051-c575-4f6e-938e-ac3bd4598573\\})|(\\{e16f9fba-1ccc-4b59-b52b-69c452bddfd1\\})|(somethingadblockforfirefox@ok)|(\\{b707de34-1ebb-40e3-b44c-d46a2d10443f\\})|(somethingadblockforyoutubeyoutubeadblocker@ok)|(somethingadblockplus@ok)|(\\{4fead186-a35f-41b4-b794-6c90e6f8886b\\})|(somethingadblockerultimate@ok)|(somethingadblockerx@ok)|(somethingfacebookadblocker@ok)|(somethinggmailadblock@ok)|(somethingpandaadblock@ok)|(somethingyoutubeadblocker@ok)|(somethingmublock@ok)|(\\{b49fea32-c666-4259-98bc-50348f3909c5\\})|(somethingdudenmentortextprufung@ok)|(somethingemaillanguageproofreadingandgrammar@ok)|(somethinggrammalectefr@ok)|(somethinggrammarandspellcheckerlanguagetool@ok)|(somethinggrammarcom@ok)|(somethinggrammarlyforfirefox@ok)|(somethinggrammarlytomarkdown@ok)|(somethinggrammarnazimkd@ok)|(somethingjacindaspellchecker@ok)|(somethinglinguixcom@ok)|(somethingmaxigramar@ok)|(somethingngspellingandgrammarchecker@ok)|(somethingpruuf@ok)|(somethingspellcheckanywhere@ok)|(somethingthaispellchecker@ok)|(somethingwordeepproofreading@ok)|(somethingwordy@ok)|(\\{8097be72-4409-4b72-809c-062d67ec1a9e\\})|(something1passwordx@ok)|(somethingapricofreepasswordmanager@ok)|(somethingavirapasswordmanager@ok)|(somethingbitwarden@ok)|(somethingbondarpass@ok)|(somethingbrowserpass@ok)|(somethingclearlogin@ok)|(somethingcpmautofill@ok)|(somethingcyclonis@ok)|(somethingdatavault@ok)|(somethingelpass@ok)|(somethingethernom@ok)|(somethinggpass@ok)|(somethinghakopasswordmanager@ok)|(somethingheroauth@ok)|(somethingid50@ok)|(somethingikeyvaultdev@ok)|(somethingintuitivepassword@ok)|(somethingkeepasswordmanager@ok)|(somethingkeepasstuskpasswordaccess@ok)|(somethingkeepasshelperpasswordmanager@ok)|(somethingkeepasshttpconnector@ok)|(somethingkeepassxcbrowser@ok)|(somethingkeeperpassword@ok)|(somethingkeycatopensource@ok)|(somethingkeydepot@ok)|(somethingkeywi@ok)|(somethinglastpass@ok)|(somethinglesspass@ok)|(somethingmanageengine@ok)|(somethingmasterpassword@ok)|(somethingmaxaccessmanagement@ok)|(somethingmonapassword@ok)|(somethingmyki@ok)|(somethingnextcloudpasswordsclient@ok)|(somethingnordpasspasswordmanager@ok)|(somethingnortonpassword@ok)|(somethingonetouchprotect@ok)|(somethingpassb@ok)|(somethingpassbolt@ok)|(somethingpasscamp@ok)|(somethingpasscell@ok)|(somethingpasscodepro@ok)|(somethingpassff@ok)|(somethingpassfort@ok)|(somethingpassman@ok)|(somethingpasswordcrypt@ok)|(somethingpassworddepotaddon@ok)|(somethingpasswordconfidential@ok)|(somethingpasswordstate@ok)|(somethingpersipass@ok)|(somethingpersonaforfirefox@ok)|(somethingpfppainfreepasswords@ok)|(somethingphashword@ok)|(somethingpsonofree@ok)|(somethingpwdmgr@ok)|(somethingroboformpasswordmanager@ok)|(somethingsafeincloudpasswordmanager@ok)|(somethingsafelypasswordmanager@ok)|(somethingsavemypassword@ok)|(somethingsecurden@ok)|(somethingsteganospasswordmanager@ok)|(somethingstickypasswordmanager@ok)|(somethingsubsfreepasswordmanager@ok)|(somethingmteasierpass@ok)|(somethingtrustloginidaas@ok)|(somethingtweakpassfreepasswordmanager@ok)|(somethinguniqkeysecurepasswordmanager@ok)|(somethingvivokeyvault@ok)|(somethingwebsphinx@ok)|(somethingwebvault@ok)|(somethingxtambroker@ok)|(somethingxton@ok)|(somethingyotipasswordmanager@ok)|(somethingzohovault@ok))$/", + "prefs": [], + "schema": 1582832492598, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618810", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name and/or injecting remote code.", + "name": "Deceptive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c21543e2-3f39-4ac9-b39e-6a2a18249351", + "last_modified": 1582916527183 + }, + { + "guid": "/^((\\{0aa583da-e323-42f2-b4d2-0bc61b493171\\})|(\\{20a15a74-371f-5098-a362-bd127db4f8bc\\})|(\\{c11016db-e96e-4eb7-bc19-7121d96d0e2f\\})|(\\{0fadbf07-bb25-4737-9800-b879a6e1c417\\})|(\\{e7fefcf3-b39c-4f17-5215-ebfe120a7031\\})|(\\{f85238e5-862b-45aa-9d66-0ab56a032375\\})|(\\{ea3f3dc3-6fbc-450d-9120-07b3b03cd9ec\\})|(\\{aa909324-7520-4dcd-9eb0-9f0a9ec3c003\\})|(\\{807833d9-8ea7-42f8-a8a4-46ff7519dd8b\\})|(\\{92047279-0910-4abb-beb7-a7f2cd6cf04b\\})|(\\{94036cd5-1829-4480-ab0b-e2455deafb9c\\})|(\\{05d0e324-7d90-3e2d-2eb0-6f1a9ec3c003\\})|(\\{abd0e324-7120-3dcd-3eb0-9f1a9ec3c003\\})|(\\{578e48b0-7c9b-4890-91ff-f6ce3e958edb\\})|(\\{0aa583da-e323-42f2-b4d2-0bc61b493183\\})|(\\{72d08da8-8277-47f0-8bee-ba5ad40dda6c\\})|(\\{9fd0e085-1545-13de-a831-ab9a05dcf253\\})|(\\{ced9def2-2d86-4a1b-a9eb-29e2f3c9eb48\\})|(\\{364f2138-c271-47a3-9ddc-466c4a27feef\\}))$/", + "prefs": [], + "schema": 1582891948231, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618814", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ae0ffe5f-a7a2-48ec-9b01-2c0c9d3eb2e8", + "last_modified": 1582916527180 + }, + { + "guid": "/^((o-o-0-o-o@codobir)|(o-o-0-o-oSearchBar@codobir)|(o-o-0-o-oAdBlocker@codobir)|(\\{93e2581c-ec13-4be8-bb22-d2544356fca7\\})|(\\{7582f270-d0ea-4f78-a1fe-851def5d06d0\\})|(\\{089fbef0-49fc-4237-ba6c-2604af7a6e18\\})|(\\{7f897f01-98aa-4e5b-b40d-dbdd7c968b8b\\})|(\\{5f6ae810-5186-466b-b7d6-29ab4355e3dc\\})|(\\{02dc7465-0eaf-44ef-999f-9dad3f9502ed\\})|(\\{0135039f-a362-4296-9c30-e8626bb484d4\\})|(\\{8775e445-b777-451b-8d4a-dd10985bf95c\\})|(\\{a244077a-0017-472a-b1af-8f0b4a167a3a\\})|(\\{ebcf3de2-902e-4690-a35a-d6160175639d\\})|(\\{209a92d8-da4f-4342-81b7-ed2b92863cbe\\})|(\\{a2cd102f-34e4-4ef8-87e9-b1d642b6716d\\})|(\\{6382c930-913c-4bfb-a82d-f098cf3e84e8\\})|(\\{323aca10-65de-4392-8ea2-591642470c58\\})|(\\{70508e0e-9f91-4bad-853f-cd76764a5a5a\\})|(\\{b673c390-83d1-43d7-b0e3-a7a2c79b23a4\\})|(\\{b74e188a-c975-4725-a6a7-53fe0645d424\\})|(\\{46ed97fb-8411-4e04-9332-0c89ea8cfbfd\\})|(\\{2b484d2d-5b4b-411d-b8bb-bc3e7d9672bc\\})|(\\{6e707a00-b889-4b91-8cf9-19674f09d849\\})|(\\{f24e3561-ecb6-4539-b9cb-95af4624811d\\})|(\\{46f41d8d-4bb0-4549-86e7-34c09c65b5fd\\})|(\\{99a15fc0-dcf0-4669-a895-664f286c6a88\\})|(\\{0585f5cb-8f50-4e76-82e2-75a0cf852d35\\})|(\\{783f9072-d29c-4567-93f9-ca424cd71fcb\\})|(\\{05d62c84-a9f1-401c-85f8-a657e6a85940\\})|(\\{8c44b1c4-364a-431c-98ec-9c7a88b8fef1\\})|(\\{44da520a-6bd8-40bd-8992-87ab3f866cad\\})|(\\{4dc3d7a3-58bc-4ace-ac3e-2776377595e1\\})|(\\{5cd1ffd4-2899-4b2c-a226-453c85d957c1\\})|(\\{74c7755a-16dd-4cf8-b407-20c47cb4b78e\\})|(\\{865fc230-1a34-4c95-a718-a53276d77a02\\})|(\\{a233e339-2ae5-4d5a-ad1e-d82db722a85d\\})|(\\{23e06ed5-bb15-4cd9-828f-df483e65e1f5\\})|(\\{840e5e73-880e-45dd-a2d0-fbefd09f368f\\})|(\\{dcc9f6a8-5306-4662-a2d5-9da72e9e6641\\})|(\\{a7714acb-7a85-472c-97e7-3310eb47fbc2\\})|(\\{88aeeecb-1924-4c1f-aa22-9d43e8757004\\})|(\\{55295427-ab86-40e0-a56e-f913aba2ae8d\\})|(\\{b80386b3-c4ae-426a-b755-e38999447aff\\})|(\\{71a36a35-73cf-4d33-8f9e-3e1b1f838f2a\\})|(\\{504212ab-b2e3-4157-a36a-b02bca78e727\\})|(\\{c4a86ea8-34d1-4a72-aeff-8b227a52f562\\})|(\\{c1413369-3202-4ec7-873a-23dc5715e180\\})|(\\{97ea8e7f-a2f9-4009-95bc-d9f8be5405dc\\})|(\\{76c146d0-ad1a-4457-979a-a0b84a7ca86d\\})|(\\{ea6a2984-78b8-473f-9850-f3406a8c2b79\\})|(\\{b005573e-35ca-4c55-a87a-25cb53ff526e\\})|(\\{00a73039-4695-4559-820e-e17b5fb5ccac\\})|(\\{fc05507e-2482-4928-b943-a7a6ad59d166\\})|(\\{e8cb2def-d6ab-435c-b2f0-af701a40350a\\})|(\\{524b9704-5aca-4283-aadb-fba6eead9b92\\})|(\\{7d629677-6ad7-43f1-b182-8734474c3fc7\\})|(\\{0edbf5ac-c258-4c65-af7e-8dffbdf847f5\\})|(\\{c5c906de-c84e-4b48-a738-e8e32546f496\\})|(\\{3a9abaab-8451-4909-978a-bf1d4e716137\\})|(\\{1195cc1e-0cc5-4e48-8b08-7160bdab5511\\})|(\\{3e3217ad-344f-4a2b-97ba-d4380a6d4f91\\})|(\\{ee4a70aa-687c-4176-9392-7e47b5c09afe\\})|(\\{7d1d1676-b23a-4249-9c01-f7f1dbcb18bf\\})|(\\{51e9d221-2349-4764-a4a4-00552369c210\\})|(\\{30b40c71-198b-4b3e-8ff1-569860189be4\\})|(\\{beef68e8-c078-45c0-a593-64a9bfb4b2bc\\})|(\\{6ce76b14-1c76-4774-919d-7a3c820a4d56\\})|(\\{522b149a-3a6f-4bc3-ab7d-1bf41e4c29cb\\})|(\\{17a2d3ca-1505-46aa-836a-85ea62b5b617\\})|(\\{d7b81889-d34f-467a-a690-9507b5d72647\\})|(\\{66463498-8185-4874-9fdf-be281b08df26\\})|(\\{5a144fd3-9e6b-4b3a-ba29-bba7fa187e40\\})|(\\{bb405e27-949e-41e2-9930-26cb8360dc19\\})|(\\{f7a40a38-b3e4-4852-9981-8078ae99374a\\})|(\\{8675dfc9-9953-474f-8b2c-16b1307f66ee\\})|(\\{62d44bdc-60ea-4a78-a738-e59dd50589bc\\})|(\\{a7875dff-b031-4588-9d4a-1f989704ebe5\\})|(\\{3cfd3ce1-f358-48a5-a30b-5cc144d14f38\\})|(\\{9ce2677c-ede7-45be-b281-2e0c9d24fff3\\})|(\\{d13d4d9d-e9ec-4833-948f-a8e2c4bb1ec1\\})|(\\{87d402fc-7bbe-4b48-8c1a-38984a9dff7e\\})|(\\{69b186c0-e619-46b9-a978-b6ac97abd26b\\})|(\\{408bfadf-6f2b-428e-aca7-e4b95037bd72\\})|(\\{2a047ecb-a3cf-4ef5-8420-d45ea9cb246b\\})|(\\{c171fea9-aa8e-4348-8b9a-1b371f4821ea\\})|(\\{4f24b616-1f23-4c60-aadb-2386900b690d\\})|(\\{766d6d39-6592-4f47-bd1f-84639a2e5ad9\\})|(\\{b7caa882-3108-487e-9f69-3f43168789ac\\})|(\\{e96c08ad-1eff-4687-94ed-a14a76c7152c\\})|(\\{cfddeb9b-5339-4df3-af2e-88e26987cb8a\\})|(\\{d558cc36-a306-4288-9888-a68c419b2cf7\\})|(\\{0765615e-cc53-4ed8-9501-8e33b627db77\\})|(\\{0de24de8-5130-43bd-b041-2d284a26b9f0\\})|(\\{114a7625-4642-4013-bf25-9d8c8a1e428c\\})|(\\{d2554ad1-d14c-43b1-9f3f-84ae454748be\\})|(\\{374319ec-ad46-450b-928c-feff04666d58\\})|(\\{f8a572d2-a614-45aa-a8a3-3cb6bdec3388\\})|(\\{d55d0631-bd77-42b3-8ca3-ff5889d69e4d\\})|(\\{4bf71a8f-3f21-4de0-8cc6-12a53df60922\\})|(\\{5787258d-5a12-4153-bd9c-5825c53786a0\\}))$/", + "prefs": [], + "schema": 1582892445504, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618862", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name, violating Mozilla's No Surprises policy or including non-disclosed monetization", + "name": "Misleading or deceptive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7c948a9b-2340-4886-b8fc-29ca66c1790f", + "last_modified": 1582916527177 + }, + { + "guid": "/^((\\{e78b3b45-89ad-4210-9a6b-3150506851e1\\})|(\\{c5874e9e-f46f-4220-b356-476122247a0b\\})|(\\{941c102c-7b8d-40af-9f49-a0496d89b486\\})|(\\{97c53a72-d50f-4e1c-8d26-f0aa8578a4aa\\})|(\\{65d9977d-e895-490e-ac9e-83031d46551f\\})|(\\{6e27edd2-f950-4b69-9a4a-f4941f2b0621\\})|(\\{af4d7096-c51a-4cb6-a30d-d2790fd66325\\})|(\\{b666da6b-b171-465d-ac69-20ea2a8a9971\\})|(\\{8e83ab0b-d34a-49ce-9d64-6394637cd9e4\\})|(\\{763aa5bc-8faf-4103-bad2-d15aab97146d\\})|(\\{40613d6c-a8f9-4a8f-a048-72ca8f1206a5\\})|(\\{865cf362-afee-472d-b2ae-6186e59f8e3c\\})|(\\{6778c1de-8ab3-4ee8-9e3d-788b17060ffa\\})|(\\{a501a160-de33-4c20-96a8-b660fd18db01\\})|(\\{b27d330c-3ac1-4421-9c96-1a5781ee9bff\\})|(\\{3997ee99-bd51-4f18-95b1-3b8a887ea014\\})|(\\{ffb40ecd-2ef3-4dfa-8c69-9a847c853b0e\\})|(\\{061c317b-b050-46a9-a7a5-2f9dfc0ef3c7\\})|(\\{e30d3343-fc0f-4526-8b9a-eebd6b7b1af2\\}))$/", + "prefs": [], + "schema": 1582898093853, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618862", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name, violating Mozilla's No Surprises policy or including non-disclosed monetization", + "name": "Misleading or deceptive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7548e625-f059-4976-ab43-de78a1124dd2", + "last_modified": 1582916527173 + }, + { + "guid": "/^((jid1-C96jkOWH1jEZ5A@jetpack)|(@pdfit)|(@youtube_downloader)|(\\{503b5ae9-dcb1-41c4-9cf8-0c70a5c2bc70\\})|(\\{a9de7157-1c27-45bf-ab59-bedcbf57cff9\\})|(\\{908c4278-9f44-4b59-ad6f-91e2aabc8682\\}))$/", + "prefs": [], + "schema": 1582659695507, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618172", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "98dbf563-0a8e-4796-ac48-1be0e4520610", + "last_modified": 1582726440807 + }, + { + "guid": "{6059d268-eb5b-491c-9879-964e1c67c854}", + "prefs": [], + "schema": 1582723099417, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618174", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Second Tab Search (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e15a83e1-60c6-475d-977c-83e180646e6a", + "last_modified": 1582726440804 + }, + { + "guid": "@searchencrypt", + "prefs": [], + "schema": 1582723268591, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1618185", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search Encrypt" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3c782d96-5d25-45d3-8f07-016a791e316c", + "last_modified": 1582726440801 + }, + { + "guid": "/^((\\{374c327c-fbd0-4769-a378-1cb0fef54b1c\\})|(\\{11d9b7d9-acea-4a66-94ff-3fbbcca15abc\\})|(\\{e9e3ac53-8384-4f2a-a451-78af21417b13\\})|(\\{61539a14-2728-448f-a071-45043287cf10\\})|(\\{9ca7082d-0e18-465e-8588-8d3990c5b9ae\\})|(\\{0444bcb0-66d3-49b1-ac1f-b79f7f682f15\\}))$/", + "prefs": [], + "schema": 1582486893083, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1617571", + "why": "This add-on violates Mozilla's add-on policies by intercepting page loads with affiliate urls.", + "name": "Affiliate add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f8ce5794-a1db-4a63-af32-1750de36a383", + "last_modified": 1582562264396 + }, + { + "guid": "/^((bazaarhero@jetpack)|(boingdeals@jetpack)|(boingdeals-1@jetpack)|(boingdeals-200@jetpack)|(\\{e9edde2d-5dda-46b5-b6ca-f732d96d941a\\})|(\\{29c11376-f72c-489b-a9eb-8fe309b1623e\\}))$/", + "prefs": [], + "schema": 1582548114995, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1617577", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1406ee6a-4eab-441d-ad1f-ce09b650ce2d", + "last_modified": 1582562264393 + }, + { + "guid": "{6ac0938d-7ca6-43ac-9c26-653d37820440}", + "prefs": [], + "schema": 1582549126839, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1617578", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "414cf401-fbc9-4218-85fe-5880068e8452", + "last_modified": 1582562264389 + }, + { + "guid": "/^((\\{d551e6af-55af-4295-92f0-ba0e3d15bb00\\})|(\\{36858137-f125-47fc-8e0c-499f161abaf0\\})|(\\{7edb2ea5-61a9-4447-87b2-d37ed4198d1c\\})|(\\{0ea11f50-96b6-4a8b-92f4-1eb6ca77acac\\})|(\\{1449de8a-cdb8-4f65-b909-c6391e49615f\\})|(\\{e7cdc797-edcb-46a0-a66e-f5dab3c808d6\\})|(\\{d57b5f9f-b35d-4879-84a3-3547d44249ec\\})|(\\{d4e41bc4-cb00-42d0-b756-ed8deb827cbc\\})|(\\{083acfab-6228-439a-b796-62647803b216\\})|(\\{ed2df6fc-df45-43ac-a923-bd3fbc3534c2\\})|(\\{ebc63925-e43d-4cc1-b234-4c8b6abc331c\\})|(\\{231652e4-ad9a-4dd7-89b1-d79876c0dca7\\})|(\\{2cb0bdaf-5789-415b-a92d-2adbc695cb63\\})|(\\{429a08cf-84c4-4eae-9ecf-fc6c3a07249c\\})|(\\{ec4fb55e-d94d-4196-9ee6-a6f19e3c9405\\})|(\\{bd55549f-9853-427c-a45d-6aba2b4776d2\\})|(\\{bbfa0b1d-c75b-4483-8331-b5b01c82fe5d\\}))$/", + "prefs": [], + "schema": 1582237690303, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1617192", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Lusha" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b12eaf8d-77ff-4bd6-8b9f-797af110f86c", + "last_modified": 1582301451440 + }, + { + "guid": "beeline@beelinereader.com", + "prefs": [], + "schema": 1582298543556, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1617198", + "why": "This add-on violates Mozilla's add-on policies by making use of obfuscated code.", + "name": "Beeline Reader" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.10.1", + "minVersion": "0" + } + ], + "id": "42642ca5-9342-4e51-a2ec-b5eed799468f", + "last_modified": 1582301451437 + }, + { + "guid": "/^((\\{e046e345-918c-4fe8-89a2-0987c5d12636\\})|(adi@fmt-tools\\.com)|(\\{90e41842-755d-40e0-9136-8129df55a64b\\}))$/", + "prefs": [], + "schema": 1582296975853, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1605160", + "why": "These add-ons violate Mozilla's add-on policies by making use of obfuscated code.", + "name": "Add-ons with obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b96957ff-ca4e-4ec4-97f7-3dbb4ddbc479", + "last_modified": 1582301451434 + }, + { + "guid": "jid0-G6461UajDjhNAwSukoedlkhD0XA@jetpack", + "prefs": [], + "schema": 1582141291510, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616735", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control and by collecting ancillary user data against our policies.", + "name": "Ratings Preview for YouTube" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dca3181b-35a3-4dd0-9efb-8e7a19fab4d5", + "last_modified": 1582237689888 + }, + { + "guid": "{e0a905db-294f-4e91-be75-8b5c7c5df90a}", + "prefs": [], + "schema": 1582103702651, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1614333", + "why": "This add-on violates Mozilla's add-on policies by making use of obfuscated code.", + "name": "Gladiatus Time Saver" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.2.7", + "minVersion": "0" + } + ], + "id": "a98992d1-8a30-406f-944d-c0161d0aaa21", + "last_modified": 1582237689885 + }, + { + "guid": "bookmark-this@page.xpi", + "prefs": [], + "schema": 1581968493407, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616217", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "24b4f43e-106f-44a6-a992-7d07634bb5eb", + "last_modified": 1582103702173 + }, + { + "guid": "/^((addon@faster\\.com)|(\\{09bfa06b-5e73-4daa-96bd-88092fe71c52\\})|(\\{eb4d44d6-4ad4-4b77-9509-8e6eadaef074\\})|(Blockfbunseenads@addon\\.com)|(Blockfbunseenads0@addon\\.com)|(\\{a8180cdc-19f4-4299-9bb5-5b6b2f2c2de6\\})|(\\{2454bb7d-4cf7-40f3-a5da-40e6889d83fa\\})|(\\{cd6999f9-c84e-47b4-a355-d54bedfe9c17\\})|(\\{53d5a956-eecc-456b-a6ce-b137c511a04d\\})|(\\{9b2dba6e-c179-47ca-bbd8-6b7033c4f2fc\\})|(\\{d77d7463-a323-4b3a-9b96-69b31e4d488e\\}))$/", + "prefs": [], + "schema": 1582023416031, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616233", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e9119771-cb28-4853-8aae-72aedfe27b2f", + "last_modified": 1582103702167 + }, + { + "guid": "/^((\\{6fb37444-4339-4a36-9e4f-c4c47b550c4e\\})|(\\{430b4bcb-4487-471e-82b8-a3056ae137ba\\})|(\\{394990b1-4ede-4bdd-a3fe-58506591f00b\\})|(\\{3ca06113-1cea-4e7a-b775-19666a9c77a2\\})|(\\{deebf5b9-62b8-4b5c-8e1b-8f8d83939441\\})|(\\{15c41cbc-18a2-4756-80a9-418ba03f533d\\}))$/", + "prefs": [], + "schema": 1582027274373, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616234", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons injecting remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cc8e1b77-eab1-42d7-9625-bf4153547903", + "last_modified": 1582103702162 + }, + { + "guid": "/^((\\{9664bedb-6f24-4672-8b72-2565e0b3fc21\\})|(alex\\.todor@gmail\\.com)|(alex1233\\.todor@gmail\\.com)|(alex123333\\.todor@gmail\\.com)|(alex131\\.todor@gmail\\.com)|(alex13\\.todor@gmail\\.com)|(alex12223\\.todor@gmail\\.com)|(alex1214122223\\.todor@gmail\\.com)|(alex1222214123\\.todor@gmail\\.com)|(alex1222214fawfaw123\\.todor@gmail\\.com)|(a\\.todor@gmail\\.com)|(abvcaasgawgaw\\.todor@gmail\\.com)|(abvcaasgawgaw122141\\.todor@gmail\\.com)|(abvcaasgawgaw122fwafaw141\\.todor@gmail\\.com)|(abvcaasgawgaw122fwaaaafaw141\\.todor@gmail\\.com)|(abvcaasgawgaw12aa2fwaaaafaw141\\.todor@gmail\\.com)|(abvc\\.todor@gmail\\.com)|(ateststn@example\\.com)|(abvc1\\.todor@gmail\\.com)|(test@gmail\\.com)|(test1112@gmail\\.com)|(as@test\\.com)|(stagod@test\\.com)|(stagod_qa@test\\.com)|(stagod_as@test\\.com)|(fop@test\\.com)|(\\{e83eb1bf-7847-4253-8108-4c1ec0601757\\})|(\\{3cfa4fd4-a747-4666-b97e-2262f6255eeb\\}))$/", + "prefs": [], + "schema": 1582038056642, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616286", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Addons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "32ebf2f0-a321-48fd-a5a4-94a4980f4697", + "last_modified": 1582103702156 + }, + { + "guid": "/^((\\{04871291-3f6e-4969-a991-4183decfacef\\})|(\\{9493fc95-5a66-445e-9200-c9b617942c05\\})|(\\{a89d4fea-98ea-42bf-aeb7-e14059e809a2\\})|(\\{aabe1376-bc1f-440d-a275-284d026f3058\\})|(\\{b6aefc39-259d-4485-b8fa-c9b2ea77223e\\})|(\\{5ce16faf-66ca-401c-bf43-7c75cf75487e\\})|(\\{dfdf383a-eff4-4ae4-a8b8-97640748e795\\})|(\\{6a563873-967b-4644-afb1-bc3e9da68128\\}))$/", + "prefs": [], + "schema": 1581545094971, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1615250", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1c5195a4-afb7-47ef-83c6-7c3c6f5d0ed1", + "last_modified": 1581702872026 + }, + { + "guid": "/^((something@ok)|(\\{4eefc8e1-086b-43a3-bb21-17c05f43cd3a\\})|(adv@blocker)|(adv\\.3\\.2\\.8@blocker)|(\\{f56e22e4-f6f9-48ca-aca3-480d9b293a16\\})|(\\{b26aabf7-6b17-424f-a71d-e8ca37ccb672\\}))$/", + "prefs": [], + "schema": 1581687835975, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1615601", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons injecting remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d5259975-b1c3-4f50-861b-d7b4c082b58a", + "last_modified": 1581702872020 + }, + { + "guid": "/^((\\{6e27c586-e50c-49b1-9925-5427f3e9f028\\})|(\\{ae6dcbb6-d0a6-494b-bdfc-ea9d7b8f7667\\})|(\\{14aec2a8-0289-43f3-85fb-5d52491a0538\\})|(\\{b541ce5d-e99a-4629-9167-e71ac65bf32f\\}))$/", + "prefs": [], + "schema": 1581450092560, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1614884", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ba0f5f7c-8998-49f1-89a8-7f3a15c41cc8", + "last_modified": 1581545094556 + }, + { + "guid": "/^((\\{0bbf5ce8-0bfb-4589-a4e8-4f1e435389d2\\})|(\\{820847ac-fb62-47a4-a6be-00d863584c76\\})|(\\{703d2caf-edff-4198-baa3-615135f97890\\})|(\\{f459049d-939d-432e-83c7-07ced47e629a\\})|(\\{0d56e009-957a-4575-a984-ecd22ccf3120\\})|(\\{88386103-f99d-4aab-83c2-64106a77748e\\})|(\\{29e30d06-1030-4a82-8e29-25706012da97\\})|(\\{c0d5c1cb-e676-4ff7-8189-793efc86fa2f\\})|(\\{aade33ec-2184-4759-8de5-5d61f1e29e72\\})|(\\{72dc5fd5-179b-40b6-9218-e88434939ed8\\})|(\\{8c9ec486-bd7b-40dd-ab49-1ca3ff452484\\})|(\\{67c0ce2e-2359-44e7-b9d9-1061a3c4a041\\})|(\\{149c0e63-b3f6-4f8d-bb1d-94b7e36d8aea\\})|(\\{c3a1e646-c155-4a40-b5bf-ac9252e6b632\\})|(\\{6876f3d2-97d6-46d6-b9bf-aa46264f6c3c\\})|(\\{330a08b1-d93e-48a2-9081-e93e25190eec\\})|(\\{443305ec-55d7-411c-bb4a-96e83b4e631e\\})|(\\{945964bd-5c29-48e3-9dd6-26741626c1ba\\})|(\\{ec8513c5-2bcc-45b0-ae77-da6685cfafd9\\})|(\\{57703f70-e1b7-462d-bf7e-657bac5eb30c\\})|(\\{8c1d6a6c-3745-429e-8ec5-2a374320e703\\})|(\\{2ff583b8-72a9-40bd-877b-b355ad33ce44\\})|(\\{f2ed910e-ab21-4ad3-a70a-8adca5e683f6\\})|(\\{4ae1f921-575e-4599-8b77-e8e7ab337860\\})|(\\{cfe7c709-6df6-4d54-9f4c-6fc3967904ce\\})|(\\{4e0cceff-558b-4902-9870-55ecb16f78a6\\})|(\\{093726f6-5907-480b-81fc-fafe4f04b5e8\\})|(\\{4a2650e6-fb1b-4e83-b0ad-4e991c9ac01e\\})|(\\{2dcd1f94-6a18-47c3-826a-d8f1044b3ade\\})|(\\{90e61a54-35d6-44c8-bb33-88623e6a03ae\\})|(\\{61389b8e-55eb-43ad-a68d-bebefe7476ad\\})|(\\{f8890846-fcc1-479f-a90b-dce3e486b0ba\\}))$/", + "prefs": [], + "schema": 1581438380344, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1614707", + "why": "This add-on violates Mozilla's add-on policies by collecting search terms or intecepting searches that are going to a third-party search provider.", + "name": "Add-ons collecting search information" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d33858b8-d192-4be2-a844-b151f0f287ca", + "last_modified": 1581441570691 + }, + { + "guid": "/^((\\{312951c4-a455-4886-a2f1-e4fb05b9fee7\\})|(traderibis@gmail\\.com))$/", + "prefs": [], + "schema": 1581429986729, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613706", + "why": "The add-on injects remote HTML code not compliant with our policies.", + "name": "TraderBiS" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "52b9033d-de65-48ad-9062-7c340835feb0", + "last_modified": 1581438379932 + }, + { + "guid": "/^((\\{ebba9c65-d04c-4c02-afd1-476d7c06fc2f\\})|(\\{117f05e4-3403-4cbe-a041-30fa60054e9f\\})|(\\{03417bad-eb5b-4bd4-89f8-f10d78fce082\\})|(\\{42f0a4e1-710d-4154-b34a-731dbbe2097b\\})|(\\{6a0a9103-3482-4717-aca8-f22ee2228d25\\})|(\\{23d5c4a6-1231-4f87-ebf9-e5800eae221b\\})|(\\{99a32eb1-5da4-4a35-beb6-7c3b5222ce04\\}))$/", + "prefs": [], + "schema": 1581277291027, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1614308", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "39473bb3-638d-4b7e-a847-f197e6320564", + "last_modified": 1581340917940 + }, + { + "guid": "/^((\\{0E64F71A-6B21-4324-A5D9-69231DE8C20C\\})|(\\{747e7f5b-c9Fa-4e4f-b466-639b88c8998f\\})|(\\{8693cb76-1caf-4115-9bd7-6bab02330326\\})|(\\{cf8452e1-0ddb-44d2-95eb-4cb8e7a35707\\})|(\\{79bbada5-9050-4d8c-9367-0aad8185cd90\\})|(\\{C7928956-827D-4649-A234-BB758377C005\\})|(\\{512d597b-9bbb-47cf-b0e6-526456ee6d26\\})|(\\{fd591fc9-480d-4f04-8ae5-0be02121b425\\})|(\\{e3258209-0248-4092-bc59-3032565d2f0a\\})|(\\{725fc0a6-1f6b-4cf9-ae17-748d111dc16d\\})|(\\{3e30848f-3b39-425b-8538-d85da166cb19\\})|(\\{9e27a3ff-0131-4aed-bc7c-2364bd89d7c9\\})|(\\{74e039b8-a2db-4a41-9155-4ccfc2c86682\\})|(\\{cfd9177e-c8c6-4d94-b5e9-d87850e2c8aa\\})|(\\{e21459c1-5604-4324-a3a0-8d23800725ec\\})|(\\{a563f3c0-d631-442f-ae27-1467cd47f1f4\\})|(\\{cbb3237c-ed1f-4735-a364-1849c0907b3b\\})|(\\{5c88311a-c457-4d3a-961c-3dc0cf01e6f9\\})|(\\{0bdf29c1-0c18-40cf-b3a2-c938b5cfdabc\\})|(\\{56e0ce0d-ffff-46a4-b2cf-1260d6898b94\\})|(\\{673d181c-5a1f-42c9-a8a2-f2f5af4a2a2a\\})|(\\{63dc2763-30bb-45fc-90ce-0d2a5c07a46a\\})|(\\{2751af61-65b3-4e7b-b098-497d6d6d7629\\})|(\\{5ce7c96e-2e99-4a6d-8f5e-cbcd68fd64b6\\})|(\\{e7e1a9ef-2755-445f-a639-d9bbf5e1982f\\})|(\\{ef5483ba-d634-468b-93d0-1bed3f09306b\\})|(\\{10367a10-912e-47c8-ab86-9a752230e097\\})|(\\{bf13ca91-6471-45c9-999a-e6176ffa9681\\})|(\\{7b8ddd2d-c2cc-428c-8Ede-604995d8B4da\\})|(\\{d05d4f3c-74ba-40bf-afa0-ad944df067ae\\})|(\\{fed8f5bc-0324-4007-8e90-84709e0c57c8\\})|(\\{9ef981d5-0b21-41fd-9412-008633de79f2\\})|(\\{1569b001-9f51-46a2-a4ed-f2122c475799\\})|(\\{340eab9c-f3c6-4a6c-9439-79733e12df5d\\})|(\\{1ab78f66-5390-4db2-8d1e-c759599ef2a4\\})|(\\{ce00b27c-1a7d-40f9-be61-57ae046ebe1a\\})|(\\{9fc1abcd-3044-4bbb-b04f-c233f1d20bfc\\})|(\\{78255a39-463e-4fce-9a2a-acca442414f3\\})|(\\{159bf803-5c0e-4863-bfb1-7a036174bef5\\})|(\\{7828da87-6bf1-4799-91cd-6e9167511fa5\\})|(\\{9aa7920f-677b-4904-9ea3-cae4c4320d15\\})|(\\{3e78b05f-90aa-496c-ac3f-cbc92a738fa1\\})|(\\{cd9d2474-fff2-4f19-8452-0ec2f4422117\\})|(\\{c7722662-0964-4341-9232-0fdebb37811e\\})|(\\{fdb0fb76-f05d-4732-9348-6f8765673e14\\})|(\\{05abc1ee-f871-4ce6-a51d-6011cdf545ba\\})|(\\{300e263c-aed7-4df7-b197-82d5122ccf6a\\})|(\\{14775497-c7c4-4209-9d6b-a8a40c0aed1a\\})|(\\{10f8343f-0f77-4cb4-8266-e06dde15e8ca\\})|(\\{d2a58c6b-be02-4ea5-9e7e-6982454e9ef3\\})|(\\{0f27c13a-5921-4e7c-86c0-b43ad63a4bea\\})|(\\{f1922123-3aad-41cb-8ab5-281ec8cbe351\\})|(\\{be0f8a88-522f-43a3-8146-5b2ad0985987\\})|(\\{44056c1f-6259-4ec2-8e60-b1c65f9b1039\\})|(\\{1e4c4a84-ea2a-45c0-935e-59cc375575ee\\})|(\\{62a11f62-84b9-425f-b506-93c3711b73e7\\})|(\\{bcb5539d-0913-4d90-94a0-e3144ab239ac\\})|(\\{e4dd1b2b-dcd2-494e-b69f-8c76e89a6234\\})|(\\{48eeddb6-f7f9-415d-9835-b859e37ed024\\})|(\\{ddf2a2fa-a5ed-4765-a44c-4b56077cd588\\})|(\\{2a208cb8-5297-47b4-91a7-0e66475218d8\\})|(\\{7ec57eac-456d-442a-85f2-477cb20c3dde\\})|(\\{20aa1960-d5c2-48fb-ae09-fa6261381537\\})|(\\{b4ebedae-d18f-46d6-8b87-679d1fd27f3d\\})|(\\{67fa631f-29fe-4001-892c-1dfdd56e5ed3\\})|(\\{ae92cb39-b2a6-4865-a125-1b273ffb4a1c\\})|(\\{5b2245da-f41b-4fb3-88b3-5e9a097e06c7\\})|(\\{69272cbc-110d-4b5c-b903-16bd118ddf45\\})|(\\{d4d22de7-54af-40a1-99f7-c2b0f2d86a23\\})|(\\{e1445f3b-92ee-4085-a57f-380e96ed8316\\})|(\\{f9830d93-3782-44e9-b199-d2355f61b98f\\})|(\\{218769cc-3b47-4978-ac59-8a9447bcb193\\})|(\\{f472ddca-60dd-4d6b-90c7-244a84d0a487\\})|(\\{8af6985d-1eef-47be-8213-bd6e25dad273\\})|(\\{da95491a-1812-442a-bde4-7a29f69044d4\\})|(\\{f8d10888-29b8-48d3-8e5f-e02e41fb642e\\})|(\\{e9091337-2af8-440e-a318-ca9e0f053fc0\\})|(\\{3ba1e00c-8339-4ae3-a87e-f4af9f1ee33f\\})|(\\{b40e5bcd-5966-424f-8a15-6ecc3dba050a\\})|(\\{c376e851-c30c-48a1-9f59-d528d34560b8\\})|(\\{8ad33b1d-31a7-47e6-bb46-6c91bc1daea3\\})|(\\{dac8f2fe-662b-4017-bf62-5052d0b23af9\\})|(\\{c27b6b15-fd63-458e-91b8-5974a7b2242b\\})|(\\{b222e127-a52c-4884-8a0a-065342b0c74d\\})|(\\{f53f1857-3f7f-4cf2-9e4e-2533f2c253ee\\})|(\\{eb260b8d-f7d3-48d8-a29a-c2b07e1ed36e\\})|(\\{49f6227b-4803-46aa-b189-466869e8dc5d\\})|(\\{a8694ce8-04ee-49c6-81cf-cf005e7009b4\\})|(\\{c53aa3f1-3385-4e2d-9d38-daf09b0db7bd\\})|(\\{9b230441-bb88-4060-bcc9-637cc5a54639\\})|(\\{895ac4f5-bd07-41c9-bbe1-cdb4d2be9975\\})|(\\{6886d657-954f-427b-b52f-9ddb1b52da62\\})|(\\{d1bc06d5-8470-4120-a2af-8e05b252ccca\\})|(\\{2aad1b2d-c29f-4623-83ac-6291c169c4d5\\})|(\\{e54137a1-794f-482d-93b6-13e2dddf7bf7\\})|(\\{773b7d0b-dc32-4125-83b4-a74887588698\\})|(\\{dd8a0577-a2ef-4719-b138-8aae7cf05add\\})|(\\{9b0dc71f-13e1-42f7-9e64-518cf228c079\\}))$/", + "prefs": [], + "schema": 1581005656992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613891", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8b5feaac-be07-4612-a090-4714b80191f0", + "last_modified": 1581082030075 + }, + { + "guid": "/^((\\{7b931d6c-4721-482d-8fcb-8c789bafc9a4\\})|(\\{03ea1c25-7fae-4ca7-9e9a-4b161936232a\\})|(\\{06580af3-ba0f-4e49-966d-932b19f6914d\\})|(\\{7f2afade-1aa3-4acd-8161-4a7d111535c4\\})|(\\{45fc7dea-37bf-46ca-9ccd-4b156778ed55\\})|(\\{b8f12b3f-ebe5-4599-9c51-1414ba5783be\\})|(\\{6440c999-2444-49d0-b8b4-188503a14a32\\})|(\\{0741d175-fd06-414e-91ad-095d6484b8e9\\})|(\\{db88d1a7-fc18-46b5-b015-bd9599966cad\\})|(\\{4f3df885-1268-43ce-9e2a-45668ce523c3\\})|(\\{938ec54b-e728-4e04-9a05-108b2246cf51\\})|(\\{bd474d1d-02ad-40a4-9237-4cd64d16de55\\})|(\\{622b8278-44d4-4f9a-ad96-1363247ae6c6\\})|(\\{9fd1d710-ebe9-4da5-a488-d51eb1e88ac7\\})|(\\{ee4314d4-00a2-48ce-bd11-4364496faa8c\\})|(\\{a06cfa87-55e7-4c65-a71c-a5e984c38e94\\})|(\\{1e36e1a3-6756-4ecc-9138-6939b23ed54c\\})|(\\{219cb4e4-5e4a-484a-844f-fee741dc65fc\\})|(\\{057669e1-0925-4d28-bd20-0fb643ad290e\\})|(\\{466bf5d6-c235-4731-9539-ebf90346f096\\})|(\\{a96faf19-5035-4149-9664-773abbc0881b\\})|(\\{656b685b-a458-4651-bb39-66242c67dd6d\\})|(\\{1acdd00a-744a-4875-be39-5a136aa20d7d\\})|(\\{58873ce7-60eb-46b9-a4af-ae62095b3fb1\\})|(\\{387033d3-4292-486e-80f4-44e531349a54\\})|(\\{5b8b07d1-4e0a-4c96-aad5-672a00c1358e\\})|(\\{15a8fac7-2bf1-4536-b4a3-7f51c9b7fa1a\\})|(\\{668952b1-4609-453e-9972-e9557192070a\\})|(\\{5f017950-0824-4806-9cda-e17d454f1b18\\})|(\\{008db332-feb7-447c-8359-829c2e5fc374\\})|(\\{3d412505-479b-47c0-b330-3599533e783a\\})|(\\{117a7b8a-6c7e-4fff-8d48-e12b02871b91\\})|(\\{8753c4a4-e083-49db-872c-eef47d1d58ab\\})|(\\{f439338b-3af7-4afe-bd48-77ee1039077e\\})|(\\{32588122-b871-43fa-b845-60b548a30235\\})|(\\{89a92c73-87b1-48b1-bc68-b814d1348d1d\\})|(\\{dbb477cc-8be4-44f4-9cc2-845632a7e433\\})|(\\{da48acfb-08ed-484d-90b3-0e63c759a4f4\\})|(\\{af40910c-c446-4a65-843e-5c39b6ef38f7\\})|(\\{0121e1fe-e88a-452f-ba88-7c330f87c137\\})|(\\{9cc2b320-5edf-4b20-be94-108583cc9b66\\})|(\\{babab679-6c9b-40c2-b62b-e21a3b4b5734\\})|(\\{5e4d38a3-eefd-4fb6-bbe6-5eb69295f37e\\})|(\\{ffb5e280-9fb1-42c8-bfbc-ac2db8232d9c\\})|(\\{50198f5f-f09b-4d09-96e7-ca94e327ce7b\\})|(\\{eee19d83-6edb-4b9d-b483-1070d60595a7\\})|(\\{7c5e2b18-56ad-4249-8390-03dd99aa5b09\\})|(\\{61af5d39-dd37-46fb-8ead-c7756764458c\\})|(\\{fb0dabc2-2a3d-45b2-8ed4-dc22b74f0ab4\\})|(\\{e3eb4df8-0cfb-4380-a7c0-856d4deda887\\})|(\\{180a38cb-115e-4794-a039-696446dc6b4a\\})|(\\{91dc3cb5-ab77-4e1c-af09-d827a0df9e08\\})|(\\{c4fec1da-703f-49fa-bd3a-5d7f939d2ae0\\})|(\\{a75f51d6-8d4b-4a28-b706-300403b82859\\})|(\\{d79394db-944c-4820-a90c-a2f48ab5fff1\\})|(\\{d6f11f95-a27b-47cd-bbcf-a9b5f2dd2a36\\})|(\\{3e64646d-9618-44ca-bf27-e424e76bd622\\})|(\\{43b57421-3ba8-4116-82c8-afd142c05674\\})|(\\{30935b8f-9a74-4e49-b6f5-8bb95d3ab3a7\\})|(\\{1ec5af15-c738-492d-bdcf-c14fd5be5e13\\})|(\\{feff2b30-7349-4e7b-9a9c-541f97dbc9e5\\})|(\\{48dbfb68-13d3-4372-820d-d52ea58ebdfd\\})|(\\{1a3a904a-3daa-4d1a-8260-c20b44fec6e3\\})|(\\{3794f3f1-6a11-48fb-a7c9-b33344ee82ca\\})|(\\{e3587df7-7358-4a36-aeff-944db7ab30ff\\})|(\\{43a526a3-28ea-409f-933c-2ef3d9a0629b\\})|(\\{2d3e88ab-b4af-47d4-b79b-a0becf1437b2\\})|(\\{0e02d0ec-97b5-4b46-b42e-d4179b067478\\})|(\\{dc7083b2-64f5-4ec0-a84b-3e5fdd552f11\\})|(\\{69d07419-67e5-4465-ad46-b969d5e5c3f8\\})|(\\{f31bdeec-878f-4465-b9f5-e844b45eb9a2\\})|(\\{9b63b79e-32c8-425b-ad18-753b58b73cbe\\})|(\\{8caf71a8-6c65-4cd4-95c6-9913dd169278\\})|(\\{e1f6fe90-e0fe-418d-9ff6-566cdd5b60e9\\})|(\\{e34bd75e-5e7e-48e6-a84e-1d18e5fcfb2d\\})|(\\{087940d8-dc20-4e9e-829b-7bd96c37b02d\\})|(\\{48defe55-de7a-4051-a5cf-ac6a649e66bc\\})|(\\{8bd54503-66cb-49dc-81ec-9fa0e9c42fe3\\})|(\\{ed9f9df1-9f6c-4e4a-a4ac-5d422a7c2a5c\\})|(\\{13261711-0eeb-457e-9035-7c415e286830\\})|(\\{E3F6115C-B027-11E8-AD5B-6E4CAE35F1A2\\})|(\\{93654046-f548-4d3e-9370-cc2244406725\\})|(\\{943ba377-5410-47cb-a025-30d55960622c\\})|(\\{426123a8-3e04-4887-a4a7-18931eaa428e\\})|(\\{4c50cefd-8f14-41ab-b719-8606b116d1c2\\})|(\\{ab2d09e4-07c0-461b-94f3-b2ea2a6773b9\\})|(\\{3f20ebf2-5869-431e-a73d-53d435176b04\\})|(\\{5b845034-25ed-4b92-bfac-ed7b305a3e13\\})|(\\{24c45b33-30e2-4d41-adb1-2a1bd9942ca6\\})|(\\{46f426fa-dc37-4b1f-ae63-11370ec65b39\\})|(\\{d678f055-e538-4c58-8a61-746166ac5063\\})|(\\{f852266a-7f88-47d8-b610-6ca130d9774c\\})|(\\{dce62517-e86a-40e0-9361-64c2e61f011a\\})|(\\{f7883a1d-0f04-4f8b-85ec-f339b791335a\\})|(\\{204f251f-feba-487a-9bbc-ca8b22b222af\\})|(\\{59c8031e-40de-485b-9988-69a4f4f51e52\\})|(\\{a5207f0f-109a-406f-8d86-3a2c806c9c7b\\})|(\\{3b66b98a-8782-4f16-a2f6-33175f9b0101\\}))$/", + "prefs": [], + "schema": 1581076280856, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613891", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0e546e4e-6d89-41a0-92fb-987b98efea6f", + "last_modified": 1581082030072 + }, + { + "guid": "/^((\\{0970b6b2-6702-443d-84fa-90f5b5cf7c10\\})|(\\{9ba7732f-7e77-4dae-9885-a9cb91930b0b\\})|(\\{dd43f6d2-30c7-43df-bce6-6edc46c84d9d\\})|(\\{72207416-61e9-4960-9cb2-87d2df8486fa\\})|(\\{d53274cb-43e2-4cbc-b1f4-5463cc026ab0\\})|(\\{e1faa92b-b124-463e-b247-2609d534733c\\})|(\\{654068ac-aa86-4f05-aab3-f9dbea380021\\})|(\\{58d9fc43-39c7-41be-96a6-a27b1e179191\\})|(\\{f5469e0b-d4cc-4c30-9922-f0c82f1e04e2\\})|(\\{2ebc9fc9-8642-4f97-935a-6885e66ed6db\\})|(\\{b7d87fb6-afc7-4544-b798-7fcc1c8114f0\\})|(\\{d36916ad-9b5b-4390-b302-321c43d85753\\})|(\\{1e67d5ab-aff2-4540-a2e7-cd19ee112ab7\\})|(\\{82f9d6fb-cbbb-4862-8c18-c0876aa00c3a\\})|(\\{2a78a205-7363-4d76-9eea-a862be445724\\})|(\\{bb65aeb0-db01-4f8b-893a-634d2977269f\\})|(\\{5cac0db7-8b8d-44e1-8932-687b152feb8b\\})|(\\{6cade252-0973-49b2-acda-36960804c0f3\\})|(\\{61e466b4-00e9-4ed2-94bc-dbfade21f066\\})|(\\{316b549c-841f-4f2e-9e91-5a8cf5c22808\\})|(\\{73571d19-5073-4b32-915a-a2350e814cf1\\})|(\\{2e5d7ab6-cdea-4a23-8bb5-3245ccec2c5f\\})|(\\{fbf35eb0-14de-483f-90aa-3f8c4ea773f0\\})|(\\{4a9619bf-edd4-4f6c-a787-c919cd4b1d17\\})|(\\{167fc8e3-1bcf-4f46-a8f1-1722db81c4a0\\})|(\\{b861c901-a92c-475a-9efd-805d01a1c6bf\\})|(\\{6800f73d-3133-4697-a368-16c1f6e63894\\}))$/", + "prefs": [], + "schema": 1581076281891, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613891", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1dbaa501-a6b0-439b-9871-79f081f4afdd", + "last_modified": 1581082030069 + }, + { + "guid": "/^((Search_Secure_clone_oRrmYYtiAX@www\\.searchsecurepro\\.co)|(Search_Secure_clone_rHXTjLgNGR@www\\.searchsecureprime\\.co)|(Search_Safe_pHUandPyRQ@www\\.searchsafe\\.site)|(SearchSafe_IEHxXeEbnC@www\\.searchsafe\\.site)|(Search_Secure_ByKsqSMauFtest@www\\.testsearchsecurepro\\.co)|(Search_Secure_GyQyTTzqnYtest@www\\.searchsecuretest\\.co)|(Search_Secure_NhrEwtJEhM@www\\.searchsecurenow\\.com)|(Search_Cipher_bBzgbIQyiC@www\\.searchcipher\\.co)|(Protect_My_Search_Online_clone_jtypmmXRwz@www\\.protectmysearchonline\\.com)|(MyAstroFinder_LTEnkWRkmt@www\\.myastrofinder\\.co)|(Live_Weather_Check_PZjCbwpxyH@www\\.liveweathercheck\\.com)|(Travel_Deals_Center_clone_GvEXeOfSMc@www\\.traveldealscenter\\.co)|(Dictionary_Pro_ARbsoVmMdl@www\\.dictionarypro\\.co))$/", + "prefs": [], + "schema": 1580931692537, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613657", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e480bd4d-7ff0-425c-9c1d-4153fa55ea81", + "last_modified": 1581005656617 + }, + { + "guid": "/^((\\{0fc22c4c-93ed-48ea-ad12-dc8039cf3795\\})|(\\{0A2C2098-F04D-11E5-A933-5334BC8E7F8B\\})|(\\{8504399b-e635-40fe-8943-977a58521db3\\})|(\\{347d4451-8da5-4d67-96b2-a2e8a6de8e09\\})|(\\{176c8b66-7fc3-4af5-a86b-d0207c456b14\\})|(\\{8692b95e-1a13-4118-b5b9-be8f3d2fc9b7\\})|(\\{b3e1e418-986b-4231-8579-0fc754574d9c\\})|(\\{88619e16-f0f3-4606-837f-a4496d11c0b4\\})|(\\{b4229471-001b-4960-bea9-795d91cb943e\\})|(\\{16390554-6fc5-4dfe-b7bb-809e378df660\\})|(\\{5fa01132-d07b-40e1-b958-e7825b878422\\})|(\\{07ebc943-2c8a-44a9-8d4d-5bbb78b2d2e5\\})|(\\{02405c0b-a202-483f-ba02-b09bab55cebd\\})|(\\{094f9b39-0561-4cb4-8b51-cb8ee5bd5b90\\})|(\\{a66e7db5-fc74-42c8-8e8c-d7a401a577d2\\})|(\\{58f9a1bb-0635-4b79-bb41-166c3e810329\\})|(\\{9da80afe-bcd4-4271-8f70-f986370d954c\\})|(\\{2671041f-f659-43b1-a400-0f7a3b852f74\\})|(\\{d9c98668-f0a1-43d7-800d-0c6d11321663\\})|(\\{1d6f99a8-d100-446e-8cc5-85231e7ab7fa\\})|(\\{ae170991-a8c8-4caf-b6cc-a3cc994abe83\\})|(\\{bfdd7357-3692-4d5b-86c5-4d86cd5e39e3\\})|(\\{aa5e865e-1e21-4ed9-b80a-f374be86b5cc\\})|(\\{26c58d0d-8514-42d5-87fd-701fd53ce3b8\\})|(\\{c934c41a-5de5-4086-b2da-1afc7d744162\\})|(\\{e87de7b9-5994-4b91-9c1a-a1d4d12a3969\\})|(\\{0eeab47a-73f3-48e7-977f-08815b4ee5ad\\})|(\\{1df850c2-38cb-46f1-87a5-308af6409c14\\})|(\\{1f481c59-fe50-4148-83d1-ff551f6dddf9\\})|(\\{70cfab72-ee99-428a-b5fb-26d924be3acb\\})|(\\{b156eeb6-cf37-48d8-b15b-bb863c431ba1\\})|(\\{a81f7ce6-0cc3-41fe-a33a-eb856636c887\\})|(\\{3949b4a3-bbb6-4119-9fa0-249fdff22c45\\})|(\\{abe2755c-a3b3-4714-a354-51eb5b8129fe\\})|(\\{547f048f-4fbd-40ef-9365-3d54559eae61\\})|(\\{e3e293e3-f18a-42f1-98bf-71d8166aef54\\})|(\\{ca742d81-6e6d-473e-ab68-f757d480e159\\})|(\\{6ac89db3-5ee1-43d8-a12b-a1b6e0ceafe0\\})|(\\{4eac966b-28db-477f-a471-a3bf74621110\\})|(\\{2c413992-ba94-4917-bd57-57eef39b4f8a\\})|(\\{6935560b-a856-42be-baa8-a06459785ed7\\})|(\\{503d9872-3db8-4f05-9c9c-b8bcd6d08ae3\\})|(\\{2055799b-b1ed-44ab-9200-4190467a3c59\\})|(\\{fe27a89e-a1b6-408e-b0b3-b2ccc0cbdf3c\\})|(\\{3fab2a05-1bfa-458d-93f8-16c523d72804\\})|(\\{32939492-d835-4540-9c03-1af0b715268d\\})|(\\{e9c96255-4eed-49fa-9740-54ef684b8197\\})|(\\{45fc39ef-f2c8-449a-b533-77e4d6202777\\})|(\\{d3ce94d3-a9ee-49e2-9290-031e56a4a5d0\\})|(\\{6b8a371e-ce42-4355-b4bc-ad4c83d5f932\\})|(\\{cfacd4cc-9f18-40f2-8711-795e738d51be\\})|(\\{d41f259b-c827-46d9-891a-33b6caf2370c\\})|(\\{3e90457a-815b-4b0a-812d-f02a43c23951\\})|(\\{063de0bf-2da4-48c6-bed2-ed11ecb3bfb8\\})|(\\{cd253156-e140-433a-a0fc-ccab28bf069b\\})|(\\{9a6f884b-3b8a-4925-8f7e-a975e2f6ec20\\})|(\\{5986d98c-b75c-46d8-9c5a-0fef03cddd5b\\})|(\\{8d586a4b-f00f-4a5b-940b-2fe00dc2905c\\})|(\\{ab0f5841-11f0-4c92-9bf8-b885f4431253\\})|(\\{eb1bafab-7f4d-43a6-8f78-fb0dbb099cff\\})|(\\{e6b152f0-5457-44ab-a6cc-d7869dc694a4\\})|(\\{942fe5ef-e9a6-4791-b840-a2e74baaeb4a\\})|(\\{d1c67270-c2af-47af-a4bc-2c020df200c5\\})|(\\{8a934de2-3238-4c24-aa29-52e1fafc64d4\\})|(\\{c65f3bbe-684e-43d6-b030-615118b38e54\\})|(\\{286f65d2-6b01-4f04-871e-2cef4095065f\\})|(\\{2FF4B97E-A47A-11E7-B621-7403A54193D8\\})|(\\{da482f08-4b6f-4c59-942a-75e1fedc8c6b\\})|(\\{3a961d08-8ca9-45af-9c30-2ba4d673e10f\\})|(\\{e87de7b9-59a4-4b91-9c1a-a1d4d12a3969\\})|(\\{942fe5ef-e9a6-4a91-b840-a2e74baaeb4a\\})|(\\{b15a65af-8dbe-46ac-9537-f91fb1640809\\})|(\\{da48af08-4b6f-4c59-942a-75e1fedc8c6b\\})|(\\{ce2c3653-8d63-4cd0-ad8a-31f03703820a\\})|(\\{5d987ae9-c201-4352-a219-a34ee28a6f9e\\})|(\\{b15ac5af-8dbe-46ac-9537-f91fb1612309\\})|(\\{3fa556a3-3bfd-4e4b-b403-072938701c66\\})|(\\{57baae18-26fa-4ec0-9fe8-a0e197c1a220\\})|(\\{4de7b432-e5e2-45d3-95cc-df3e913b68d4\\})|(\\{a15a65af-89be-4fac-9127-f91fb16c0809\\})|(\\{c2c8b504-f5bb-489e-858f-38ca5224d033\\})|(\\{7f458bd6-4841-4373-97a4-a3d140b02552\\})|(\\{e20bc238-0831-4d4b-9386-d2fa5abe804a\\})|(\\{e51fa7b7-4c3f-4f66-8bc7-e864a413b790\\})|(\\{f9c04a94-59d8-47f7-a97d-980ff99f81cf\\})|(\\{d6ea5103-78ff-4736-b2e0-c0ce94bee77e\\})|(\\{f8fd0172-1b1a-4084-b440-4119996ab2c9\\})|(\\{a52e4eea-9923-443b-98a6-942eb27ba324\\})|(\\{a1fcb660-c2cd-49b7-a327-88e887afb43d\\})|(\\{cea42340-da28-4c95-89c4-0a66d69050d4\\})|(\\{c2f47878-f9d3-4c89-be58-a69dd9d1484a\\})|(\\{7ddf85d3-7014-4fdb-836a-fbbe4385347f\\})|(\\{36f452d0-6154-4be8-a388-174fc98d9333\\})|(\\{c69d7a2e-ad00-4e9f-80d7-fa0604ac3954\\})|(\\{8d40db57-9287-473d-b398-259d702d92f1\\})|(\\{bf51b8cc-a07d-475a-9013-91e2c089ae60\\})|(\\{32030663-2a21-4d20-a2e6-ed3d4a51b704\\})|(\\{1204abf0-0409-4934-ab58-1f5424134bc1\\}))$/", + "prefs": [], + "schema": 1580758890801, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613073", + "why": "This add-on violates Mozilla's add-on policies by redirecting searches or collecting search terms without user disclosure or consent or other privacy violations", + "name": "Add-ons violating Mozilla's policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2c6f59bf-90a6-4fdb-a12d-f1b4a1446331", + "last_modified": 1580842055124 + }, + { + "guid": "/^((\\{a88e625f-b840-43b2-9feb-99c259b6751e\\})|(\\{0cc6c564-63c6-481d-9dbb-ddfd40ced202\\})|(\\{329e1c15-fcee-4063-83bc-59c894861cbc\\})|(\\{98e2e7ef-e544-4426-a182-621e8b85c15d\\})|(\\{d8869a56-bccc-4011-9b86-2962a57d7377\\})|(\\{255138e4-d9f7-4779-8713-e5970090ab67\\})|(\\{ee5907df-603f-4684-8f2a-425742ed37cb\\})|(\\{0e725b0e-9f26-4bb5-b9b1-738df2692e19\\})|(\\{2bf69cca-c4cc-4200-ab56-1d27fef48e03\\})|(\\{4edc1ea7-7037-4b54-8a64-a43339545710\\})|(\\{6edff7c6-97a8-4355-a074-e191b0a9a4ff\\})|(\\{79c5d05b-a6ba-470b-b06d-d33fe04418e5\\})|(\\{a1ebe305-b540-4dc5-91a2-8a2a0d646efa\\})|(\\{1211b4db-1ed4-4630-82e6-cdf69d58f035\\})|(\\{4d4e8ee2-78aa-42f5-a30a-c3727241cb96\\})|(\\{b6178891-7400-4b86-8a96-d57b1b7eee20\\})|(\\{3cfcb0bb-f7fc-440b-bae9-1050a966013b\\})|(\\{747931c3-bf5f-49d0-a4d2-615847200653\\})|(\\{989fcf36-da63-4d83-a036-b996788e7a4c\\})|(\\{a0dc99d3-7b9c-4fa2-8d42-39179b1e1e5a\\})|(\\{d4ce8b88-6b17-4ed8-b695-29da839dfb41\\})|(\\{cb2af2e8-ca55-4545-864f-96686760b59e\\})|(\\{3961b434-016c-4598-9ab2-38425197f32e\\})|(\\{b4e25be7-a4a7-408b-8759-8b185b4421f7\\})|(\\{0cb56fc3-e3f8-4ea3-aba7-4a2d351f82be\\})|(\\{b3e9621c-3194-4cf8-8b28-4ad32a988d5a\\})|(\\{241b64a7-7b2e-475b-b195-b1cfeb099745\\})|(\\{b727f9fe-9345-4ce4-8797-f65f85dfad7d\\})|(\\{a90d260f-7c39-4539-9948-8f3ee4406a9e\\})|(\\{35a57c90-ce66-42f6-ad0f-52b92690f4c2\\})|(\\{83048c90-baca-477c-8774-054f80eca4e6\\})|(\\{91c1e004-2e1a-4103-89c2-2f585c2d306d\\})|(\\{a5701591-3a6d-43b1-b74b-c47a1b7aa5b7\\})|(\\{641eef82-a96a-4702-b3fb-f50f8d7cdc85\\})|(\\{b436362d-94e9-4209-a3e5-26dc7d1ec86a\\})|(\\{70dc19f1-1312-402f-a370-cd8a1e231116\\})|(\\{c9a68220-ad02-4b09-9456-12ed03121344\\})|(\\{dc0d8992-0c9e-4473-b8c4-72240fc7be39\\})|(\\{1c3ca6ca-1427-4b6e-85be-315e18f81135\\})|(\\{96309a2c-339e-4c3e-84ff-ef7dac131a18\\})|(\\{95d07270-fb53-40c3-9b9f-46ed78658a1a\\})|(\\{15700ca0-8d4b-40d3-99aa-bff59aa48676\\})|(\\{5d308759-368c-4b5a-804c-acb6e18c6436\\})|(\\{b47ace66-3443-4d26-a858-7bbcfe1c18b6\\})|(\\{10dc172a-38b3-441b-a2d4-52b4ce8e4b7b\\})|(\\{73a47b20-be2c-43e4-a728-c46d33612ccb\\})|(\\{5f3f1314-618c-4c3d-ac4d-f83ae6d247ac\\})|(\\{bb81241e-b093-4c55-8c3e-7dca2250ada6\\})|(\\{4d8be4aa-4790-4807-add9-af62132e675d\\})|(\\{4295e30f-80bc-4d48-b794-c609de6f2dc0\\})|(\\{057c6b57-46f3-43b4-9576-438afdd3b3ca\\})|(\\{60a58bf1-08ae-46d7-9010-5cfe8eb5f282\\})|(\\{bffd69a1-5a8a-4b0d-ae14-f8744adb92b2\\})|(\\{b5c0b80e-dbc6-4701-98c2-3b0b8e182404\\})|(\\{48306d77-a699-4cbe-9ed3-b3162dfff00b\\})|(\\{f1f285dd-4a5c-48ad-81bd-78fe204ed582\\})|(\\{da0b9335-8edc-4429-8889-c5872ad02417\\})|(\\{6e834570-8580-44c2-91b2-d30f687aeb07\\})|(\\{9ea1bae9-5dca-4a1f-9fcf-a325a39b85c1\\})|(\\{69149c7f-92c7-4d41-b88f-e68c237d1d63\\})|(\\{985e1bca-d152-4fc3-89a0-cba2c0ac44bb\\})|(\\{746c7167-56bf-48b1-bd6c-08b05b48a863\\})|(\\{4bc87632-de6e-4c23-8192-f561f185a823\\})|(\\{1d21ed2a-94c9-4bae-983e-5c1e5094060a\\})|(\\{148df2dd-451b-42eb-952b-f608f26cfb6f\\})|(\\{2c6d6dea-438c-442a-ae11-a943804d90b4\\})|(\\{2c7e143c-9f4d-412b-b552-17033d4992c6\\})|(\\{e2309514-8386-413c-856e-21b54ebc3d9c\\})|(\\{4b417a17-ff90-4f16-bb8b-fb1e0d9ca824\\})|(\\{7c68360c-d03c-4c26-bbfa-2f9c9064701f\\})|(\\{eefb2906-cb27-4801-9ae8-67b49807b151\\})|(\\{b7c790c9-aaf6-46f2-9462-812d3e129ce7\\})|(\\{aac68138-e60d-48e2-92dc-d28577e553d9\\})|(\\{5959fff0-d04a-4147-8d4d-aaa7bb314a00\\})|(\\{40bf9b7a-9c36-40a8-8e68-91b79eb3bd44\\})|(\\{96429801-73cc-403b-be68-fb8a992f9307\\})|(\\{218e967f-59b5-41f6-a22f-7fe3c1580956\\})|(\\{ba51f209-a2f6-42a1-9608-536058540d0c\\})|(\\{d993e7df-00f9-445f-9082-294017eeec36\\})|(\\{f1990255-00e8-4d34-91d5-11d09066e4f3\\})|(\\{dbb78ed6-8449-475f-a152-0148596539eb\\})|(\\{ac798a99-00f0-421c-a3c9-a13bea8ef728\\})|(\\{e486268f-9299-422e-91df-9706ea220c46\\})|(\\{ecf1e24d-cfeb-4fa1-9953-202c3e62a820\\})|(\\{caf7fac5-0784-4701-a50d-2d0c27074b9a\\})|(\\{8f85f7d7-7182-4a53-a18a-bf83f0bfd1b4\\})|(\\{be556a9a-df8f-45b9-87aa-ae0eaf50a2b4\\})|(\\{9f80406a-ba8a-4548-bb0e-e987fb1d7921\\})|(\\{d0386a6e-e364-4e67-b340-782492bdb3ee\\})|(\\{cb0d2b3a-929c-42f1-8874-07a0650d0298\\})|(\\{cd9f2f81-3674-4d3e-a1b3-824fd3e5e906\\})|(\\{58366e2d-3a39-4fea-b631-521e3e73162b\\})|(\\{d854ae81-f759-48a9-8732-2371664fe2fe\\})|(\\{4747285e-3ec6-4d8f-9d1d-d0430297f182\\})|(\\{2f1919a8-4ca8-4a80-9175-c2da3a98490b\\})|(\\{32b30902-43a3-4f37-8e86-860ea525e923\\})|(\\{60376450-fe48-4d30-b551-b35535df6e96\\})|(\\{22456782-c5e4-437f-9369-5b23b875cd6a\\}))$/", + "prefs": [], + "schema": 1580814638830, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613073", + "why": "This add-on violates Mozilla's add-on policies by redirecting searches or collecting search terms without user disclosure or consent or other privacy violations", + "name": "Add-ons violating Mozilla's policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b3ee1de-0c37-4883-a22f-822c8d6f8991", + "last_modified": 1580842055121 + }, + { + "guid": "/^((\\{35417623-1a2c-4e75-846f-e2a7b95b3b24\\})|(\\{d6dfebf0-51d4-490b-b03d-a17bf7fe4a6d\\})|(\\{50c7fb39-c03e-4d82-a071-fac7eaf960b7\\})|(\\{28999f9d-6e83-4c52-bb2b-5d2c46b7ef58\\})|(\\{9409fdd1-d78d-4df8-bb5e-f178f42702f6\\})|(\\{4c356115-33da-49e5-877b-090c64d76a66\\})|(\\{cf674a88-861f-4357-aea7-37f09e534552\\})|(\\{43238f2d-ddba-4604-803f-889bfaa22ebf\\})|(\\{36405669-81af-44c0-83cc-32ae8f901db0\\})|(\\{2ca5cfff-58dc-47d4-b56c-c206d281a9cf\\})|(\\{b22df1a7-f312-41a8-8fd7-cdd160ba7be2\\})|(\\{cf019c95-1c49-438a-9398-3863642fb606\\})|(\\{c7a9d852-c30d-4e6d-b040-a445551bee61\\})|(\\{379932ed-2938-4ccb-b57e-2dc44c3b5121\\})|(\\{d2395ba4-96e4-4ffb-a4da-608f27328c9f\\})|(\\{7ffbda76-3f9f-403b-a5e1-559c4f18c1f3\\})|(\\{361db730-4c11-49e8-b189-fa461f738786\\})|(\\{677456f1-2716-4a23-9a74-f3867e860dbf\\})|(\\{46c19d03-16ce-45f2-82ce-d8ccaeb3ceea\\})|(\\{6339b6ff-da02-4821-b5a0-48490a54b14e\\})|(\\{7d4d815d-798c-4cc0-9561-2f84b4598f94\\})|(\\{40fc3311-9612-4cb7-b337-fd581dead5f2\\})|(\\{9e639048-d366-4db0-a15a-f1953130fc8e\\})|(\\{ce2d009a-47af-4e94-94aa-23bd8973addf\\})|(\\{41984c46-3ec2-4684-99db-a5c4356f7ba0\\})|(\\{a63f9261-e05c-45f8-8b43-f987eaf5b27e\\})|(\\{37e06cae-3367-43e2-bdb2-0c087f2603c6\\})|(\\{f9d80e53-acb1-4483-bf3c-f58920551ded\\})|(\\{1672a22a-f6cc-4318-9616-22e1e3b3049d\\})|(\\{1cc6cbe4-38ad-44c4-a462-a2a222bab5be\\})|(\\{0255dcf8-ac71-4403-ab5a-c8f7abf66a39\\})|(\\{564f7511-ba68-48a0-9a85-636a100ce3d1\\})|(\\{4ebf29d7-102f-497c-bc8d-696dd66ecde6\\})|(\\{4c4139ca-ffbc-4f10-a534-22dd4c787944\\})|(\\{a12c1827-a5fa-4c11-8403-e02e9520f0ec\\})|(\\{2178817b-1d32-4068-9675-8fffe11a88f8\\})|(\\{b779151f-7a4c-46b2-83e4-3dbad231a9b9\\})|(\\{fe8a8186-80ef-4112-a41f-7d00ffceb63e\\})|(\\{0ee72846-9a34-422e-b202-382aa19bc0bb\\})|(\\{c92d2fba-d002-426d-acc4-0891fa40040f\\})|(\\{e3bdfd08-fa29-4a56-9851-f6d0b965004f\\})|(\\{0017666d-27b7-4347-bbbf-6d389b4430dc\\})|(\\{c9db55d0-61e4-4515-a9a7-24e3783bf106\\})|(\\{fbf87239-ec23-4907-bb6c-93daa5015ea3\\})|(\\{44801e5e-a635-455e-b08f-f3da88b06bed\\})|(\\{59517a31-ea51-44cd-ac7c-02d952ecf04e\\})|(\\{ee075bcc-2101-4c9e-8f04-7e7281ca5c74\\})|(\\{fd073c3a-d7d8-43f6-9d1c-1865bc0ff940\\})|(\\{a15f4275-8f19-4508-b548-a88597092bbd\\})|(\\{568ad6c6-0b91-46bd-9093-bc394a84c257\\})|(\\{11f798d4-003b-41a8-aa65-1f566ff53f06\\})|(\\{a02f26f3-49f7-4e95-a69f-78b1ce2d3471\\})|(\\{ed48226c-94b9-4878-86ba-de38851290d7\\})|(\\{97e5276c-a627-4c62-8659-4772a81203c0\\})|(\\{4cb19fea-1568-4f0b-809a-0763fbb36888\\})|(\\{599cff19-2ba7-4bf3-93a9-87f7f277cadc\\})|(\\{f0564e7e-f154-4612-b50e-e0d11c47b359\\})|(\\{3cc8efd6-4808-428a-9e0c-ef6fa45d17b4\\})|(\\{3842feba-bb44-49f6-9511-de1a1b78d348\\})|(\\{ebc3de57-de6d-43ee-b76b-676893dd7035\\})|(\\{a5f552f7-7279-49e5-92cc-70f952534726\\})|(\\{3ece0c93-9723-4280-8356-d1eb2025e2d5\\})|(\\{8579047e-a427-4ac8-87b9-ee9651c1f856\\})|(\\{c9bfdfd9-4617-4749-afd1-265f0b7158cf\\})|(\\{0c570e22-54e7-4d93-86f2-36e19707018a\\})|(\\{cfc0fa6e-0d6a-4fcb-9dc2-58b220307293\\})|(\\{8640eaec-8d51-4d6c-b01b-a671f4aac012\\})|(\\{a0ca05be-c6f2-4c18-993f-de55abca7000\\})|(\\{de066e14-6527-4be4-9751-e53ea16fd60f\\})|(\\{5a8f8da1-3994-43da-98be-3a58ed4d2ec6\\})|(\\{bcd8524a-8f39-4eba-a795-aabdd95305b4\\})|(\\{7d76001d-fda5-4abf-93f6-a947dd3cca24\\})|(\\{d18219fc-b632-47dc-bea1-73451164d187\\})|(\\{e97fbca9-d513-4fa7-9524-576620470399\\})|(\\{069b10e7-f7ef-40df-87bb-95783401a54a\\})|(\\{12a978dd-a7b9-42c2-b431-65aaa56c2a77\\})|(\\{66ba2e20-9378-493b-837f-4b3a028ab5d9\\})|(\\{9ad5b1e9-a19c-4a65-be1b-8b888da7cf58\\})|(\\{87ce9b48-e659-4b14-ab54-c80ffbeaa77f\\})|(\\{4da94d09-4730-4277-a544-f3b7890b6666\\})|(\\{30e6c020-4ec6-4b18-8f29-3dc294fbbb44\\})|(\\{5bfbdd70-ce54-4686-9351-0d90a7dd011f\\})|(\\{17eb23ec-f059-4857-a405-9d06242e99b5\\})|(\\{b4170b6a-0af2-45b1-9214-3665e870ab3a\\})|(\\{bc20b5b7-46c3-4af6-942f-f0323445b576\\})|(\\{44edc268-9242-461f-9d8a-fb59337101b0\\})|(\\{301321f7-e0a2-4cc2-9cc6-7ee1390ea895\\})|(\\{b57a503c-6370-4ed1-90ed-1b6444ca1e52\\})|(\\{c4707969-efc7-46ce-845d-28a46c78fda6\\})|(\\{22714afe-0f06-496d-9897-31e52e83a12e\\})|(\\{a07fb8cb-91fa-4799-9ff5-115e8a88b57f\\})|(\\{f539ec45-16dd-47a3-a9d5-8643b021d4c9\\})|(\\{b9348282-9380-4e55-9939-e4c10254a496\\}))$/", + "prefs": [], + "schema": 1580814639945, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1613073", + "why": "This add-on violates Mozilla's add-on policies by redirecting searches or collecting search terms without user disclosure or consent or other privacy violations", + "name": "Add-ons violating Mozilla's policies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ae074a33-05f8-4721-a9ff-7111502291c2", + "last_modified": 1580842055117 + }, + { + "guid": "antimalware@titansurfer.com", + "prefs": [], + "schema": 1580240491748, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611797", + "why": "This add-on contains deceptive code that is not in line with our data collection policies.", + "name": "Titan Surfer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c703b7fd-4b40-44f8-9de6-786b66ef8e1b", + "last_modified": 1580742890204 + }, + { + "guid": "app@OnlineFilesConverter", + "prefs": [], + "schema": 1580380822975, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612169", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Online Files Converter" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bcb1821a-25e6-4b9b-a54c-bf4543f895c0", + "last_modified": 1580742890201 + }, + { + "guid": "{f83128d7-ef15-47a2-a99a-70d181413b81}", + "prefs": [], + "schema": 1580381170095, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612294", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake banking add-on" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1ca7b6ce-30d5-4290-b46f-9fad59b65f9c", + "last_modified": 1580742890197 + }, + { + "guid": "{d281b854-0c99-4e4b-b647-32038ae53c27}", + "prefs": [], + "schema": 1580727235989, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612868", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "SApp+ (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "93cceac3-803b-412a-b413-fdb1b0689626", + "last_modified": 1580742890194 + }, + { + "guid": "/^((\\{236a5a66-132c-4d7b-a62f-66f1a76bb7b7\\})|(\\{0fadbf07-bb25-4737-9800-b879a6f1c417\\})|(\\{c8ec696d-935c-45d0-a604-180244e839e3\\}))$/", + "prefs": [], + "schema": 1580740785659, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612869", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "183a1de1-2625-44d6-90df-cbdbee9d23bd", + "last_modified": 1580742890191 + }, + { + "guid": "/^((mozilla_cc4@internetdownloadmanager\\.com)|(\\{4509d977-32a4-480a-ab95-6ddb5bfc6616\\}))$/", + "prefs": [], + "schema": 1580741006002, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612871", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ad2f927a-eb67-4360-92e0-87f567608c1a", + "last_modified": 1580742890188 + }, + { + "guid": "/^((\\{f2431eaf-2b0d-4f0b-8148-3188db294d73\\})|(\\{c43e5363-2c61-4c3d-afa4-9cbe06e767d0\\})|(\\{bb8eab9f-4611-496b-bd31-a1b2ee66d8f9\\})|(\\{5f61d055-ae45-4ecd-9570-555609f66f5a\\})|(\\{532d8a77-86b6-4a7a-87a4-d973dc0cf9d0\\})|(\\{bfd10065-670d-4477-98dd-8bb4285040d5\\})|(\\{79f33a52-631a-406b-afcf-9be8b4bb480a\\})|(\\{fd994367-ecfe-44dc-a595-cb155110492d\\})|(\\{9a01416a-758c-4fd7-8e56-998acc588f9b\\})|(\\{517a72f3-e9cb-4e9f-8a2a-9639f4daed76\\})|(\\{4fda7f17-eaa0-44d1-91e5-7d8305a2de0e\\})|(\\{695bd646-ba09-4a3d-9616-191b355aec33\\})|(\\{6893ed50-b006-430c-bbfe-5f049e57470b\\})|(\\{7a2f84c6-d1ef-4902-bae7-b6c7807cb32f\\})|(\\{afde1624-c4a4-4494-9ed5-1ad15799dbd4\\})|(\\{e05b53dc-910d-47ce-9c9d-5195e8e8f3e5\\})|(\\{11b0ec0d-ae49-4ce8-a13c-198affdb4d9e\\}))$/", + "prefs": [], + "schema": 1580741081005, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1612872", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dd36267c-dbd3-4df1-bc01-5f566ba56de0", + "last_modified": 1580742890185 + }, + { + "guid": "/^((\\{6b7df170-e6fa-4b9b-bd76-d6b866a5bc6c\\})|(\\{7e1c1f98-4c89-4668-b9ea-e258e7c9e22b\\})|(\\{1f2128cd-bcaa-4e60-b555-0713054df0f9\\})|(\\{9043971c-ce88-400b-b56f-7a9e3853eb32\\})|(\\{afcbdf71-be6d-46cf-b5ff-6a6b9ec7f920\\}))$/", + "prefs": [], + "schema": 1580067691094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611725", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b4a4d34-1341-49ea-8b53-96a1ecfac49e", + "last_modified": 1580207330647 + }, + { + "guid": "/^((\\{0ded4ebe-965f-4de0-89d2-91ed13ae15ee\\})|(\\{5c143da2-6a9e-4afe-9ca2-b758aebe6e64\\})|(\\{c6566b12-297b-41fb-8189-f32a7d1c1b87\\}))$/", + "prefs": [], + "schema": 1580078568605, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611806", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "70af2927-6552-4eae-a91a-89e4cdd61b88", + "last_modified": 1580207330645 + }, + { + "guid": "/^((\\{9237d0ab-aaf0-41a2-b873-c0f131b09ce4\\})|(\\{18b14b7d-e228-416a-bab8-37acf6d6dfca\\})|(rwkaddon@racewarkingdoms\\.com)|(\\{2e2d6d72-2634-496c-a8db-db869b639a21\\}))$/", + "prefs": [], + "schema": 1580137101921, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611807", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons injecting remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "303a149e-8262-4d16-b24b-348b84d5051e", + "last_modified": 1580207330642 + }, + { + "guid": "{c6ce41ee-a4e3-4fd7-ab6a-988b6916d66a}", + "prefs": [], + "schema": 1580137557625, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611810", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Qwicky Advertisements (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "18581952-6448-4728-9fd4-2860b3e6534e", + "last_modified": 1580207330639 + }, + { + "guid": "djuvt@czgnp", + "prefs": [], + "schema": 1579817517633, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611297", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Update De (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ad789bcb-4c12-401d-b6fe-202a5b6697b6", + "last_modified": 1579987441268 + }, + { + "guid": "/^((zdphb@swlguy)|(czgnp@hhjl)|(mjrxg@zdphb)|(fuevm@czkvq)|(bgufa@djuvt)|(czkvq@bgufa)|(axvij@fuevm)|(swlguy@axvij))$/", + "prefs": [], + "schema": 1579863493098, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611403", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "83d7f7c0-8a56-4feb-9e66-b77af6343430", + "last_modified": 1579987441264 + }, + { + "guid": "/^((\\{50fc0c97-b405-4f63-9c10-465cab131ec7\\})|(\\{e08f09a1-e8e4-421c-a460-ada7b13077c1\\})|(\\{887c8e31-c11d-4b67-8bbc-e5b5e8fea9dc\\})|(\\{d67b6e83-976d-458f-aece-0af986c16db7\\})|(\\{d0c0679f-9fa1-448c-a78b-f8fa1591597b\\})|(\\{23747ea4-fb31-4349-9954-2728f1d4bda2\\})|(\\{5d492e1a-7e3a-48ca-9745-784a5b61980a\\})|(\\{bec8dea7-eed3-464a-b99e-92cab1e10373\\})|(\\{48d63231-1796-4b26-a3f7-1aa7f5bed1f4\\})|(\\{fdc94eff-5212-416e-b2e4-d67c088c4907\\})|(\\{ffd2c868-9f15-48f1-a77b-c5a1842a2e41\\})|(\\{9b0e5b96-2b47-4ee4-b511-7402c05ab43a\\})|(\\{f01f803c-fb6a-4c0f-9dfa-d8f6173b3b17\\})|(\\{b2ed387a-02c8-4bff-bf6f-00ce6b5f067f\\})|(\\{7222c5ee-8154-4e47-8521-1dafcd00d902\\})|(\\{c085ea8c-57cf-41c5-a41e-38dd6288e808\\})|(\\{2cc9608c-dc69-4bc0-8d3b-95852face3ac\\})|(\\{5977f159-e17c-48d4-8e2a-8b48962a57cd\\})|(\\{10e4d201-689d-4864-a04e-f21186f3d4e2\\})|(\\{79119982-95ef-4cfd-9fe2-b193018503ee\\})|(\\{520dd821-08e0-4821-abf8-347474c78f72\\})|(\\{6ff93655-17b6-4bab-bb0e-40abfcd5a853\\})|(\\{af670583-4dde-4e1f-b169-efc5aec481f0\\})|(\\{b053d2ab-0c28-41a6-99c4-4d276af55169\\})|(\\{4b70886d-6215-492d-8330-c220b714a216\\})|(\\{21855045-7800-4467-923b-096efe6ded40\\})|(\\{6c1f8c3a-47dc-480f-8007-3204db00a8c2\\})|(\\{2dc676a9-bc76-405c-9252-b76c60cc172d\\})|(\\{d464e0a4-8798-4356-8cb1-b1b819d80d2d\\})|(\\{b4cc995f-aca5-43dc-bde2-dc5b5de620ba\\})|(\\{448b1043-fc5e-471a-8a10-5ec74fb16054\\})|(\\{becb255d-985e-4f59-9e7b-a3f678bb53ba\\})|(\\{f200084c-a7cc-46a1-a84e-289a8c033124\\})|(\\{c6b5b880-ae5e-4cad-8eaf-fce059892c0b\\})|(\\{0ea7199a-707d-457c-95d3-cc84436c5634\\})|(\\{dc702775-072a-42c4-8a7f-0e02b202a48f\\})|(\\{4a1c6922-5b77-4f07-84f7-47e504ec5249\\})|(\\{145fa75f-ba31-4f91-8664-c2559887a664\\})|(\\{b9d3f331-5a3c-494e-a0dd-5b7dc1b949b3\\})|(\\{f7801e2a-e119-434d-83c5-e87eedffecf2\\})|(\\{1b88498d-c70e-41ab-98ef-4239582e77d6\\})|(\\{b0162d70-142b-43f2-8414-30414a2b1ea0\\})|(\\{1f7166b4-c765-4129-b727-ef077814e0af\\})|(\\{ee2cd2a1-b4f8-4ef1-9fe8-db89e8844e47\\})|(\\{569c8641-f0f7-4eda-afd2-4ca6f6fc8bc9\\})|(\\{85c5a731-0b8b-4d91-bbad-07790b7a0165\\})|(\\{02197da2-c4b2-4a55-b323-f56aba8d3ed2\\})|(\\{d5250fb9-6db6-4eb3-bfe7-b8d3bf326c6e\\})|(\\{947c328f-b462-4618-bc4a-a967d1d7bc77\\})|(\\{c04bf263-7edd-481f-a3b1-0ea9d2aff14b\\})|(\\{f69637b8-b48a-46b5-b1ad-132afc1dcaa0\\})|(\\{8f2b4de9-599f-452e-9fe5-a3ea7ee2d633\\})|(\\{ebc34c54-8004-4281-8321-84145bda54ec\\})|(\\{c3f94bd2-0391-44c0-89a1-212ca3844abe\\}))$/", + "prefs": [], + "schema": 1579863851954, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611277", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2af6cf76-8f55-405b-8f5b-d784616768ba", + "last_modified": 1579987441261 + }, + { + "guid": "/^((zddx9wbjta9g23vk5ejo@zddx9wbjta9g23vk5ejo\\.com)|(\\{0629026f-e941-4a98-8975-b8cdcc20fbdc\\}))$/", + "prefs": [], + "schema": 1579796394954, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1611198", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code and/or showing malicious behavior on third-party websites.", + "name": "Browser Kompatibilität (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d94be063-b34b-4ec1-bb02-59bc343029a4", + "last_modified": 1579817517239 + }, + { + "guid": "{c77fdf50-1880-4914-b553-6e3500f43f2e}", + "prefs": [], + "schema": 1579617431416, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610552", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Pdfviewer - tools (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cb6be8a0-0600-439e-85f1-512cc1d0e48f", + "last_modified": 1579796394592 + }, + { + "guid": "{669207af-aef4-42e5-b1fa-675995be9cf9}", + "prefs": [], + "schema": 1579604778159, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610480", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "54396dc1-f469-4a88-b0f8-352985a85b13", + "last_modified": 1579609877650 + }, + { + "guid": "/^((crisstehn@ffmust)|(jjwalter@junior)|(qlobthedark@ggmay)|(ficlever@roentgen)|(nzclever@roentgen)|(ukkinda@rottaai)|(GOEORG_RF_ANA_N@BDOFsKOKK)|(ioadjf9340joif024rf@ioadjf9340joif024rf\\.com)|(f1a1cz46o6rzl335xcrg@f1a1cz46o6rzl335xcrg\\.com)|(03t4joaijlcvjja@03t4joaijlcvjja\\.com)|(bdojfkobidjfo9e@bdojfkobidjfo9e\\.com)|(0iwtjvpvfhqyv2go1237@0iwtjvpvfhqyv2go1237\\.com)|(g68xhmxwozq8xtp4emty@g68xhmxwozq8xtp4emty\\.com)|(idyzvr0haermejvwfaqm@idyzvr0haermejvwfaqm\\.com)|(czaoyj52ki5owo07318z@czaoyj52ki5owo07318z\\.com)|(4sgly4c7s5pca7o220g0@4sgly4c7s5pca7o220g0\\.com)|(8sd351lwavakull4dcqd@8sd351lwavakull4dcqd\\.com)|(ukkindaa@rottaai)|(besth@lgimm)|(canaddxd@ptrx)|(nlextt@awes)|(bestseg@bbcd)|(swlguy@swlg)|(germctr@prx)|(pro@socialsmonetization\\.com)|(nicejohnus@lg)|(beta@lvvtqmq4qrhgmjb2zd7o\\.com)|(beta@b96mupkh82zywdrmxecz\\.com)|(dvhi19naabzond6ikvl6@dvhi19naabzond6ikvl6\\.com)|(approver@vdmqsgm5nyfiirwh8ryy\\.com))$/", + "prefs": [], + "schema": 1579603732879, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610462", + "why": "This add-on is violating Mozilla's add-on policies by showing malicious behavior on third-party websites.", + "name": "Malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "448ff9b0-94eb-4d24-af8e-b56141824c80", + "last_modified": 1579604777796 + }, + { + "guid": "/^((f53pabhktayw2qusajt8@f53pabhktayw2qusajt8\\.com)|(weatherpool@bwv9ggnrvitryck9k8tf\\.com))$/", + "prefs": [], + "schema": 1579549290940, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610359", + "why": "These add-ons collect ancillary user data or take action on behalf of the user without consent.", + "name": "WeatherPool and Your Social" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "30ab36b8-c080-4ded-8531-07259112779c", + "last_modified": 1579603732509 + }, + { + "guid": "/^((\\{293476ee-263e-4cad-8dc4-2fe03209adc7\\})|(\\{622303be-705e-4247-bc2e-9016d8867e3d\\})|(\\{6ac09a19-8de3-418f-a4e1-1ee3e8810990\\}))$/", + "prefs": [], + "schema": 1579601336651, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610456", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Fake premium products" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3db3364a-0e20-4635-bc63-f2bba4a10415", + "last_modified": 1579603732505 + }, + { + "guid": "/^((\\{525f4b51-8ea1-4db8-bc81-829cf10a14a0\\})|(\\{2411143d-8afe-41ea-874d-ea4a8dc8b1c7\\}))$/", + "prefs": [], + "schema": 1579250266121, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1610061", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b6f17555-93b5-4721-a92d-53500d1fe0fe", + "last_modified": 1579530103133 + }, + { + "guid": "/^((\\{5335fd1c-3baf-4578-b339-516dbdcec832\\})|(\\{1bf381aa-a819-4067-a537-eadb0d6538ba\\})|(\\{0e3703a0-46ae-4d18-bd04-8f8f570fdb77\\})|(\\{b350dc7e-cfcc-4ffe-9225-9feefe922bdb\\})|(\\{eeb3bf29-f1db-4f75-a6cb-8675ace58390\\})|(\\{9a216bb4-d664-4535-baef-ee1f4db012d2\\}))$/", + "prefs": [], + "schema": 1579191500704, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1609718", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Tamo Junto Caixa" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d845bc55-9b05-4dc7-ba4a-57462a51be39", + "last_modified": 1579250265716 + }, + { + "guid": "/^((_65Members_1202@download\\.fromdoctopdf\\.com)|(_65Members_1202test@download\\.fromdoctopdf\\.com))$/", + "prefs": [], + "schema": 1579193594086, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1609721", + "why": "This ad-don violates Mozilla's add-on policies by loading remote content into the new tab page.", + "name": "FromDocToPDF" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ebca1ceb-555d-4ce0-8c06-5b1475f08d45", + "last_modified": 1579250265714 + }, + { + "guid": "/^((\\{61e9b862-ef9b-4cc1-9dc2-ec00e437118c\\})|(\\{29828604-0f21-4ce2-8df7-b840aa53d713\\})|(\\{9430316a-f94c-40b6-9cea-8ac0df5c6638\\})|(\\{87ad96b4-86e8-4d94-aee1-7b607d02effb\\})|(\\{26f61847-2e07-4b10-a030-267eea1bf3b7\\})|(\\{450eb888-9bba-4de7-8821-4bb806ff82bc\\}))$/", + "prefs": [], + "schema": 1579193819569, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1609365", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7e5aad13-e8ee-43bc-91fc-4805c8195f3e", + "last_modified": 1579250265710 + }, + { + "guid": "{f6766565-1c5d-4eff-bda7-20f00aaedd11}", + "prefs": [], + "schema": 1579101106448, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1609550", + "why": "This add-on violates our policies by attempting to install other malware", + "name": "Fake Youtube Downloader" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "21b59bc5-57db-4f31-9e18-614c03858e5c", + "last_modified": 1579191500334 + }, + { + "guid": "/^((\\{15ebdf9b-c3d7-4aee-9568-b42a11a7b071\\})|(\\{a3d09db2-d3ac-4403-9bfa-878de450fbb4\\}))$/", + "prefs": [], + "schema": 1579039175200, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1609265", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake anti-malware add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2e3a3e97-c35a-4ec8-abce-a95c17d96f27", + "last_modified": 1579039687654 + }, + { + "guid": "/^((\\{976ef2b7-3bae-470e-84d9-3212d9200733\\})|(\\{d8fa8e34-e84c-4554-bfd7-16fd023b1c71\\})|(\\{d1ad5122-5caa-4986-a639-ce19bd1bc582\\})|(\\{599fb5a5-a334-4547-8a42-afccc1ddb347\\})|(\\{4bff9999-2ede-4b60-8572-a2915411abbf\\})|(\\{3a9a4c90-d87d-48e9-a799-072a7aa5be64\\})|(\\{822d29f8-9376-4da8-a7a0-4fa2eb5964c7\\})|(\\{c4926e5b-5b37-4781-87a2-28ce0b522d6b\\})|(\\{e36ea66b-30c3-481c-9e00-057b364c1c4a\\}))$/", + "prefs": [], + "schema": 1578927737593, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608886", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "361f3333-3cdf-43cd-b99e-78b666ec33ae", + "last_modified": 1579039174816 + }, + { + "guid": "{b7037d81-2c5c-4747-99e4-f4fc1c888b85}", + "prefs": [], + "schema": 1578938241613, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608887", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "RoliTrade" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bd9c30ab-d112-4f58-be0d-0e213350b6e7", + "last_modified": 1579039174813 + }, + { + "guid": "{5cc1b399-c98f-483e-9799-be29c6627246}", + "prefs": [], + "schema": 1578656865603, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608432", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Rolimons Plus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "05061ed7-378d-45a0-b105-3216c256b486", + "last_modified": 1578927737176 + }, + { + "guid": "/^((hddenobdjekcmnkgfpkodhohcjghiijm@chrome-store-foxified-1800284493)|(\\{90e41842-755d-40e0-9136-8129dd44a65c\\})|(\\{edf47ed5-7efe-4725-85d9-5e7a30d42998\\}))$/", + "prefs": [], + "schema": 1578665901864, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608433", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6975177c-1dc8-4fad-94f0-7a43ed397977", + "last_modified": 1578927737173 + }, + { + "guid": "/^((\\{3acb1e80-e126-4024-840f-3297659f9448\\})|(\\{44e3f210-6036-4364-90b9-3e8bb6fb3d98\\})|(\\{51e7e0fa-69e1-43f8-9578-d8372c2885b7\\})|(\\{1c2393b0-f2f7-497d-a34e-399dc6002d26\\})|(\\{a6a02c49-fafc-45d0-bb60-9f940a64c99a\\})|(\\{02a0090d-026a-4d02-a530-1b4d96e80c14\\})|(\\{9a988579-6773-48c2-91ab-8e917b20ab90\\})|(\\{22dfee7d-aa7c-4765-95e6-e81513cc7d37\\})|(\\{8c6d03f8-db65-4d5c-8431-ff66365847c4\\})|(\\{168b7acd-43d3-46d5-b76a-de3139dd9570\\})|(\\{abc95bbf-2548-4bf3-a0b9-9cb028496277\\})|(\\{d1282b8a-c467-4b02-9c11-e63e614ee8a8\\})|(\\{338422e6-bcf6-4171-9541-1c0f8c3dc3db\\})|(\\{10d5b345-535a-472b-8e8f-4f1a1cec9f2b\\})|(\\{64813672-8b55-4ac3-8dd2-c1da80132b77\\})|(\\{00e7df6e-7a0f-44d1-9fc1-0ddbdb473f4e\\})|(\\{564c2b95-b70b-4243-84dd-21fead791642\\})|(\\{71deda20-495f-4061-9c90-f46d1f7dfedd\\})|(\\{054d7610-9edb-47a7-af57-aed4be023015\\})|(\\{2b3f6877-99b0-4d35-8b85-9f75dd53ac92\\})|(\\{299f2568-3330-4466-8b47-4240643f8200\\})|(\\{077ead0f-8e84-4d6e-8fb1-a22126f9bf4f\\})|(\\{8ae52853-efd8-4d3d-b8f4-f70b048a389c\\})|(\\{36321783-e1c3-4f95-859a-d1c88eb75327\\})|(\\{7f878add-6b95-4b57-ab16-d8688819373a\\})|(\\{10148e15-b7f7-43bd-89c0-01957aad8188\\})|(\\{65e3540e-c3d9-4831-9dbf-598f2ef38d7f\\})|(\\{6fe77565-de36-4d06-ae30-59e3c98fc974\\})|(\\{7c79599d-ddde-4f62-9561-3c7aaea788a6\\})|(\\{d2fb1b99-98c4-48ea-ac43-8d729a1a8963\\})|(\\{38387674-fcc2-4292-a20b-08931ea0936e\\})|(\\{f00678a9-4b60-4a24-bdb0-4ce6c960cd28\\})|(\\{15d08bce-4f8a-451f-bdb5-5d1f720dde7d\\})|(\\{3a1da641-0fcc-4c06-b4b9-21d8d5dd3720\\})|(\\{6b7a8c7d-3956-4ac3-8c98-423a0bed1d75\\})|(\\{28f786b3-64a1-4152-9629-efabdede0b4c\\})|(\\{b1535617-e25a-48fe-b47d-c57affc65d5a\\})|(\\{255f303c-d5ce-47af-a925-1ad2c84c710f\\})|(\\{0ab25c60-4750-45f1-85f2-913440d6c6fc\\})|(\\{6cf0ef3e-d911-44c0-8b58-7abcb99d0243\\})|(\\{991c3933-0b3b-49f5-b3a3-1a60bf62f269\\})|(\\{bc5e31d7-42da-49c2-9624-1b4d5707b5ec\\})|(\\{8d8320f0-df3c-48f1-8839-f6969cbd3c17\\})|(\\{35f35aaa-506e-41d4-8468-3c4f4a56b434\\})|(\\{f1f6e2bb-32d1-4d79-8e5a-659e5af15b78\\})|(\\{1109f231-559f-44dd-bd84-85fac05f845b\\})|(\\{b6111372-b58f-4130-a6ae-a1445a196d85\\})|(\\{b21099c0-e496-443b-8d43-610a9aae60fb\\})|(\\{d8a40da5-bbca-417e-9ea5-e77332739366\\})|(\\{5dcaea9a-e152-4667-a4d5-b29f5afe9e61\\})|(\\{98b95d2a-1de8-4234-a73f-568531785850\\})|(\\{b11ad72d-2f64-4494-9f5e-6ad2e36bfc16\\})|(\\{3503a09e-76e5-4fba-8d65-d8bbb198b2c1\\})|(\\{f45fac5e-f3b0-4932-8c8b-254c6dcd3219\\})|(\\{4210d4ec-9e2a-40d1-83de-53a9728c01d5\\})|(\\{10691e9c-7399-4fb2-b824-256ed6c8c08e\\})|(\\{148338c2-ee0d-4659-baed-5b9aca28407f\\})|(\\{f3a2a32b-ec49-4fc5-bb46-f00f86d4cbff\\})|(\\{425bd894-b282-4a58-a2d0-3054fe3fd856\\})|(\\{7ccc3a62-7f92-4a1e-8e32-af734721a136\\})|(\\{91c939ae-00db-400d-a814-a964dc85fcf8\\})|(\\{83fdf43f-c064-4ae0-ac5f-6668fca576b0\\})|(\\{824f975f-a740-47d5-b4c8-0868fdcb154f\\})|(\\{2404f236-28ae-4852-b50d-50e66312f69f\\})|(\\{3f307709-bdf8-4dc5-afd7-4aaeb2a85176\\})|(\\{24f3c174-f09b-41fe-8c26-dfc051b2f352\\})|(\\{ecb81864-05bd-45f9-a1a3-5f56ad62c1c0\\})|(\\{fecad0e5-5f8c-4539-8893-9a4c9e4ab567\\})|(\\{03badfb9-bba5-4a3b-ae1e-0bde1bef38f3\\})|(\\{1f2defb6-af80-4822-b1d2-816c0575dd8b\\})|(\\{195450ff-844e-4ec7-b111-166c28e54b6b\\})|(\\{abd20b0f-3f79-4cdd-b2f7-6ed4f9a3e546\\})|(\\{b1a2d328-e1bc-46b5-b6d3-d4c5366a6262\\})|(\\{49d113aa-c37c-4a49-b73e-69eaaaec519d\\})|(\\{f9fe81b9-29be-4e30-ba7e-821e96b74c00\\})|(\\{c47a57e5-9486-4b58-b4e9-2e49e02c39df\\})|(\\{48aecb91-709c-4660-b0f8-681c177628bb\\})|(\\{0033af2c-0017-4237-821d-24e1ce20ed20\\})|(\\{d95d2ab6-2728-4d89-b4f2-74fc3abcfe26\\})|(\\{813bec70-cac2-497b-9117-8873b5e33cbc\\})|(\\{b613e7fc-3274-4874-b59c-b2afaaba60b5\\})|(\\{f88e19b5-9434-455a-a2bf-de13250a642b\\})|(\\{562fec92-ba72-4461-a73b-48b0cc87c943\\})|(\\{c8d2d52a-5617-420e-9568-983c0c7a6982\\})|(\\{7e32bebe-f9ef-4328-a4f8-7a86afdb9568\\})|(\\{b95fa449-7f1d-44dd-84c7-65565334d1e1\\})|(\\{788d6386-606f-45fb-83a6-af4956d9aa11\\})|(\\{00a646ad-3d34-4ca9-9633-9ea96be7a225\\})|(\\{a745c5df-b386-4906-80ba-ec9b6aa6b37a\\})|(\\{a3b508d5-864d-43c5-a4ec-9d5523cd01a6\\})|(\\{4e945e2a-b553-45e6-8dcb-495c60a663a0\\})|(\\{1a6ea1a0-1a2a-4a57-91c8-07cb629588ce\\})|(\\{20ef853f-2c16-496c-bcd2-5109a7a1be59\\})|(\\{cb5604d4-d166-4060-978e-1c7ddfda4a94\\})|(\\{9207a89d-db88-4ef1-bf2e-a89c12d882fa\\})|(\\{f561afa7-165c-45c5-beef-754e16b40794\\})|(\\{09021ead-28e3-415a-8f9d-ed250954147c\\})|(\\{1d6ef53c-0407-4eb6-aa80-b672788f9d0a\\}))$/", + "prefs": [], + "schema": 1578858092043, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608815", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "2Ring" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1491ea56-7723-431d-be05-5808a3101bf5", + "last_modified": 1578927737169 + }, + { + "guid": "/^((\\{2b046d1e-b392-4073-ad8d-bb882b073be0\\})|(\\{69a8db6c-b55c-4560-bfa7-edf21ed9f265\\})|(\\{d41d46ca-a557-48df-a951-e968a1168137\\})|(\\{845d89e8-e3c0-4447-ad36-9fcba4d3d28c\\})|(\\{da572bae-f959-4f9d-aade-afda00b83e2b\\})|(\\{fe334432-8ba6-4444-8cf9-1bb59c308aa0\\})|(\\{b8242421-8cd0-4064-a6e8-bbfdd62d67fa\\})|(\\{7d863da3-4f8f-4b2c-87cf-5bc7a03b24c8\\})|(\\{fc53c14b-9bdf-4a0b-9f07-421109bcab31\\})|(\\{7771103f-bf4a-4757-b679-2ec30eae8cfb\\})|(\\{66196147-9b28-4050-a46c-c9bf668511d2\\})|(\\{a41db617-17b7-429d-b135-5fd5d54b9ad6\\})|(\\{aa0e1b27-728c-4e45-9b7e-7731e8d6451f\\})|(\\{6e12cb13-d166-47e9-b1a6-ce980e1489bb\\})|(\\{3b3e3db5-2710-4bfd-bce8-bf3a1b5e0bc0\\})|(\\{3e4763fc-2175-4a69-9854-f437774da824\\})|(\\{63708597-3f14-494c-981c-bbecb10fddcc\\})|(\\{ec2c5f61-37e6-49dd-a430-6f0060f6e152\\})|(\\{8eb7ade7-96d3-4be9-b20e-f321fe07c9ec\\})|(\\{b9b60c96-b158-495b-87ab-db97fe2aecac\\})|(\\{5d885234-3fff-4c81-bfe0-7e01ca9701f1\\})|(\\{79cb8cb6-a0fe-434f-a6d6-2af167fbb069\\})|(\\{ec41b4cb-5c60-471f-a68a-d52e91f54292\\})|(\\{fc64c6fc-4356-42e4-8253-75facb478836\\})|(\\{d68b6b30-81e6-450b-aa7d-b0e3f1b11e2e\\})|(\\{6E01B689-3E52-44B7-A33A-197C48498C0D\\})|(\\{D4EE626F-0BE2-4CE3-B635-11498D2EAF24\\})|(\\{bea54a21-f8e6-4a47-a739-84aa5a03a391\\})|(\\{57FE0157-8BDB-4C11-BCD6-6654E0C1AE3D\\})|(\\{8B8E2B7E-7DBE-4EDD-ADA1-8AA93098E3F3\\})|(\\{66c281ce-2682-4182-91da-6d3742ce9a9a\\}))$/", + "prefs": [], + "schema": 1578922331209, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608815", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "2Ring" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d8ef0aae-2659-4076-be2a-ef0cf0388c49", + "last_modified": 1578927737166 + }, + { + "guid": "/^((\\{d26e41d8-8dfa-4a08-ad90-6df0240c8290\\})|(\\{9ee44d55-47d9-45bb-b56c-79ab2fecd93e\\}))$/", + "prefs": [], + "schema": 1578582285426, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608291", + "why": "This add-on violates Mozilla's add-on policies by executing remote code and/or collecting user data without disclosure or consent.", + "name": "Converto Wiz Ads Search & PDF Converter HD Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0c502c85-3f2d-4a3b-9abf-249eff0968f0", + "last_modified": 1578656865193 + }, + { + "guid": "/^((browser-safety@browser-safety\\.org)|(facebook-bookmark-manager@fbtools\\.io)|(facebook-video-downloader@fbtools\\.io)|(extensiondist@browser-safety\\.org)|(selfdestroyingcookies@dirtylittlehelpers\\.com)|(googlenotrackpro@dirtylittlehelpers\\.com)|(youtubemp3@yttools\\.io)|(youtubeadblockerpro@yttools\\.io)|(videodownloaderpro@dirtylittlehelpers\\.com)|(simplysearchpro@dirtylittlehelpers\\.com))$/", + "prefs": [], + "schema": 1578653962604, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1608387", + "why": "This add-on has been blocked by Mozilla.", + "name": "Several add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6368943f-3d77-491c-82bc-f29591e170d6", + "last_modified": 1578656865190 + }, + { + "guid": "/^((\\{1c1a344c-b8d3-4783-ac5b-9a9d241c29b6\\})|(\\{a4fccc0e-a372-4127-983e-c0f607427a89\\})|(\\{275f2631-9a1b-4ab3-8c6a-4529ecd8512b\\})|(\\{0b1104bc-8ec5-437e-9d81-2cc641cbe8c6\\})|(\\{4e373c31-1942-4c9d-93b3-38b0ad701f27\\})|(\\{7f1af5f0-b450-44f2-aa5f-bc4c793553db\\})|(\\{68c84054-b43a-4a78-bb35-27ec06974d1d\\})|(\\{f959a2e9-f211-424b-b0cd-ea7ecf269753\\})|(\\{fef99996-e542-45b6-b383-86132e67a93f\\})|(\\{c1bd56b2-4b48-4366-8d04-16a0f69f7b4b\\})|(\\{17456fc3-4777-484a-b177-9b82752a738e\\})|(\\{9c746226-1a87-49e2-a083-725c5fc10885\\})|(\\{3056ec65-736e-4a77-abf7-9d0b44ba0b74\\})|(support@seovpn\\.net)|(\\{fbb6a675-8923-40da-86cc-a547eae63594\\})|(\\{bf9abe9d-4589-41f2-acd4-e5dd9d4a4595\\})|(\\{d012bf08-e9b1-43a2-b6b4-b60c0fdd6fb7\\})|(\\{0cc696dc-6214-406f-8831-1d0ae14c2eed\\})|(\\{39ef127b-a7c5-4cf6-8383-333ce2300707\\})|(\\{77af48b1-2786-430a-a2f8-f0e666ecdb86\\})|(\\{9807c7c0-0d7b-4ee9-b39e-4a2b10f74b74\\})|(\\{21ceb717-4e26-4843-9229-b0a55c629c6b\\})|(\\{ba87282f-fb6a-464b-b2b4-18ab718a6b9e\\})|(\\{f844e48e-929c-4dc7-8224-c829b67d453f\\})|(\\{2af80abc-e031-42c6-b800-02dd4dade3bc\\})|(\\{849583eb-dd9d-424e-af82-d64205b79bb2\\})|(\\{8d8963e3-810b-4e72-85d9-d6e8affbe8e1\\})|(\\{aa385b5c-14b4-4d2a-ad81-db3cd054efe6\\})|(\\{c30a6fea-44b9-4f3e-945a-401485d7e152\\})|(\\{327735cb-5842-4296-a8c9-660118c9dbe8\\})|(\\{2809097a-41e3-468b-9c33-2ce449bc18f9\\})|(\\{91b36c25-1dd4-4a1c-a722-a1868deed9a1\\})|(\\{3b61770f-a22e-4704-be5b-310a729e6652\\})|(\\{b1741149-382d-447a-b8fe-bd2dbf03b252\\})|(\\{8a5d399d-3716-4627-ad97-750cd10783e6\\})|(\\{a1bb458d-6a71-4efb-a032-04f44f638a0d\\})|(\\{8a86326c-5b59-4c87-af77-becea91bdc7f\\})|(\\{21b022a6-b739-4787-a071-268f45bd4f0f\\})|(\\{59e225c1-5a78-40b4-bb6a-9cd783eea9fd\\})|(\\{d0ba8c04-3d8d-4c87-8eec-355d3c1dbc57\\})|(\\{d0b408ae-c1b4-45b1-8d0f-5a9135dab115\\})|(\\{c802afcc-8fb2-410c-a50b-0dfc4e502364\\})|(\\{8ef92c49-fe9b-4354-b943-8fbce6156ab5\\})|(\\{40d41864-a14a-45e9-bc75-ff95189975cc\\})|(\\{b5f5f423-85e5-4641-8517-2549ef2597ff\\})|(\\{bd4e3180-94a8-4d2a-9186-b15ccbca2abb\\})|(\\{1099ff05-78d0-4a0f-a348-0e60d8e627f3\\})|(\\{9d7450f3-9f39-4dda-820b-ac50797229a5\\})|(\\{9762a546-d483-4d82-9e97-e8293b67ab3b\\})|(\\{233e675a-f2e9-494e-a62a-56a8e75440d1\\})|(\\{44b31737-7370-41ad-9a16-7e92f993d651\\})|(\\{5e8c48e7-43a3-4f5c-8f61-018e994ed581\\})|(\\{cabb5a82-b018-40eb-b551-08b7c6b28d25\\})|(\\{19c2d564-4774-4b52-b175-0d9a674a1ee7\\})|(\\{4d6bc7a3-8d17-44d9-a1c9-34b6c72d2a82\\})|(lin\\.Vin@userx\\.com)|(\\{a74a99d0-f5ac-4775-8af5-ad48341865de\\})|(\\{b8331308-eaa2-40ca-94e3-d5184b9555ab\\})|(\\{43a11cab-4d48-468d-88ac-6632c2b90f0a\\})|(\\{13fde54f-87aa-4966-801b-189946617b03\\})|(\\{051e900e-73a7-42e9-bf92-54a05b592814\\})|(\\{389a79d4-7af9-4c22-8e3f-0a424a9d16a6\\})|(\\{d80ce712-fad0-470f-bb42-7f0f5d10ac66\\})|(\\{b08e2ddf-304e-4336-b7b8-15698239be1a\\})|(\\{5bdae4dc-1859-4766-97c6-5d4b7d5ccb68\\})|(\\{a8b80325-5125-4d50-95b8-e3548eca99f9\\})|(\\{16f5bfef-6792-48da-9458-c15a904a3202\\})|(\\{821251e6-48aa-4eee-aca2-d2003047eba7\\})|(\\{e4d3e60e-2980-4899-aaa5-aa2c8571e8af\\})|(\\{00bde975-f669-4fef-aca4-de3335e5e629\\})|(\\{815e064e-df14-440a-a9c7-6191d47f302d\\})|(\\{12267241-c4e5-49b7-afc0-1ba02cd86146\\})|(\\{1461d5d5-0180-4e5c-870c-1648fd13eb3d\\})|(\\{09f52a46-5e5e-4bb7-92e4-5a802ba2dc65\\})|(\\{89fd7bd0-b6cf-49cb-981d-6eaa3515e09e\\})|(\\{309cf23f-2ca7-4308-9c31-03ef0cf0e8df\\})|(\\{604184f4-c000-4071-8b27-ae7c191dd575\\})|(\\{45c9a869-b55f-4792-aefd-e353229b48f1\\})|(\\{1b7f62a2-5dfb-4a7c-8613-a2333ae83337\\})|(\\{6e7e98a6-d7e7-4319-92bf-283cdcbf06b0\\})|(\\{2e60f9f6-f57f-46e5-9876-b50f84154a04\\})|(ali_nasiri@mefa\\.com)|(\\{d7909c43-9b01-44e8-aec2-d569a3461183\\})|(\\{d64d110d-2783-4663-b533-036338b0fabc\\})|(\\{8c5a0a9e-480c-4571-acb3-602cb081f9b9\\})|(\\{1efb16f1-34cb-4179-b27b-301f1a8d023a\\})|(ali@mefa\\.com)|(nasiri@mefa\\.com)|(nasiri_ali@mefa\\.com)|(\\{de57334f-24fb-4b0d-9087-d2945212ba76\\})|(\\{e45a208c-582f-4549-8c39-3e3b6c3f503d\\})|(\\{7cf37c0c-5cf2-4945-8c82-ffd5a1206abb\\})|(\\{aea2f3bb-b278-4ab3-a2b1-370b74a2795d\\})|(\\{c0d80342-fa84-4366-90d5-0d2c965a6c71\\})|(\\{e7953669-b2cb-49bf-8f53-9b2cedbe4df7\\})|(\\{0f5248f4-bb0b-41ee-9162-397ac92538c8\\})|(\\{f2a73021-b834-410c-95c9-6e9e826c5523\\})|(\\{ac9759e0-3657-40c0-8da8-aa83b2df14e1\\})|(\\{AABB3453-4EAC-421A-34FA-4789107489AE\\})|(\\{e471c14f-775a-474c-afde-e2562f35a1ee\\})|(\\{8a420af6-7de8-4d13-bc74-1b90e3a7c45e\\}))$/", + "prefs": [], + "schema": 1578325482610, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1607277", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "87a59976-a41c-48e0-a05f-b00ceddc3990", + "last_modified": 1578409770946 + }, + { + "guid": "/^((\\{29e8ec68-5717-4d25-8465-c8ad11c2493e\\})|(\\{1807ab11-ce49-47e8-b29f-2c7f3b27957c\\})|(\\{d497ce7b-9308-4a00-81c8-f1e20769d9f4\\})|(\\{3e2a7fe2-d800-4300-adbc-85d45101ccbb\\})|(\\{41a6db99-eb5a-436f-add8-6655030cd3c4\\})|(\\{d0041bb0-914b-4105-8335-f19dfe4000c5\\})|(\\{16c0318f-441f-4996-9420-9582cd8dc4a8\\})|(\\{11560f98-1a72-428e-901d-19eee028703d\\})|(\\{200fc207-8d2d-491f-a14d-898c2af6005c\\})|(\\{6098201e-19fe-4930-8235-507abc271c92\\})|(\\{47247604-b3df-42ef-a7d6-e6f54f3ed34d\\})|(\\{d9e45250-0a78-4fad-bda9-435289e1117d\\})|(\\{7897756a-f527-42fc-8327-23afa5a73037\\})|(\\{ebb45fe8-b626-4d4a-9b51-4c61be7d8ec8\\})|(\\{17a3a525-b5e9-4bdf-9f2f-0a6b82489261\\})|(\\{7d5e14ab-7973-45ee-9b5e-fb2af9105a58\\})|(\\{3db27392-c030-4b1d-a461-df246030e8ed\\})|(\\{687dbc1c-4640-420e-b5e0-69ffbff851a4\\})|(\\{99f71699-61a0-407e-9a1e-4f7f2b24032e\\})|(\\{9608a917-807d-426d-8b3d-4bd8e6669d3c\\})|(\\{6d0d1145-1b99-4c1b-9112-a0698ce1ad93\\})|(\\{02373fa8-bfe0-4f6d-bf65-cf843ae16ca9\\})|(\\{76e05242-0ea0-47dc-99ab-85a5768d24ee\\})|(\\{b2671579-9ae7-4e10-9aad-e484189f2599\\})|(\\{f8574a50-a306-455d-96b3-9a95590e3351\\})|(\\{d2a79680-fab2-40d5-93c7-3842a3c7a170\\})|(\\{6d3d4ff6-42b4-4ae6-8273-e55309e194ef\\})|(\\{e26d666b-5437-4b51-9a35-6a32ef2ebc93\\})|(\\{1d1e0112-694e-499e-9a6f-fd4fdd8a0400\\})|(\\{8fef18c6-29cc-44db-a373-2476eaa98d07\\})|(\\{bca08103-f769-42c1-9e83-c3dac1d073e5\\})|(\\{9be3ea2f-3def-4ace-a4ca-a36bd350ebe3\\})|(\\{e4a7d768-fe07-4b2d-8722-782ecd2d857d\\})|(\\{ed0bae9a-6c29-4615-b7b0-8134008f866d\\})|(\\{6a230256-13ad-4912-88fb-35dfe6fc8405\\})|(\\{f33c98b2-f1fb-4e82-a472-b27aa763b697\\})|(\\{692d8158-fc1a-43b9-901c-9211c5f00129\\})|(\\{f43b5702-0a0d-4f50-a012-a9fa25d19ac4\\})|(\\{e5d75e88-cd31-492b-a63c-ab6759658d4f\\})|(\\{706dac82-d1ce-43b6-81fd-77652fad15a9\\})|(\\{8c403e0e-104d-46ec-8355-5b7d57d35e6b\\})|(\\{c5a32b03-4044-4085-91d8-af1d688c0c66\\})|(\\{76e7835b-8600-473e-870f-aa1e9ac922de\\})|(amz@userx\\.com)|(\\{d2b50060-69cd-422b-bc5a-77590580adfa\\})|(\\{e9d1c5e0-e297-439d-a536-efd94b0baa06\\})|(\\{80816d1d-664a-4710-915a-99c298970b38\\})|(\\{3cf55a2e-ab5b-4a2a-8b5a-0760f46ffcc1\\})|(\\{be762a22-7fcf-4c67-a8e4-7f2b085a9f50\\})|(\\{2df2d33c-e1fb-4eeb-b276-a84bd3338e0f\\})|(\\{133ce0f4-83f3-4fec-b672-0067748ccf1f\\})|(\\{b575f7f6-5afe-40f8-8bb3-92c0ca24371f\\})|(\\{d514a041-1260-4512-9759-55c30b63bc20\\})|(\\{4129ed8c-e3ad-4e88-832e-66314cf44268\\})|(\\{9e97ac90-9eb9-429f-99f5-d61f60052068\\})|(\\{3366bcb1-8b0d-4451-9c1b-cf8aeb8e5df0\\})|(\\{3d06b84f-0b8b-4a7b-9a55-22c226c85a55\\})|(\\{e2f8e0bc-62bf-40f4-9dcd-3379a64db2bf\\})|(\\{3e998c2a-c05c-4445-9567-7a1948077970\\})|(\\{51cefcff-f7a1-4c57-b6bc-bad9fc9f275d\\})|(\\{84d6d305-ccc0-4879-b80f-708318ebcaa6\\})|(\\{05a2137a-9dde-476d-a63e-d80400d6bf36\\})|(\\{c2627d22-c3cc-4c5a-a69a-eca6cfe9ac90\\})|(\\{3cfe4756-4994-4c92-a97b-adc57243a277\\})|(\\{5a05e509-59a9-4358-b994-a062d97a7d0f\\})|(\\{791bc86d-dae4-45d5-957c-fe56843770f3\\})|(\\{8976df61-4480-4d4d-bfd1-2e9d0635eaa8\\})|(\\{f4ee4889-fed0-4598-a8b2-c3e224250d82\\})|(\\{e26b2735-47b6-4239-a3d2-0e4ca4293076\\})|(\\{ae9589dd-3141-47d1-9449-8e719044ff92\\})|(\\{ff18147b-3853-4938-b15f-241d99f4e590\\})|(\\{758126c6-d156-4eed-8870-477eefb17281\\})|(ella@fmt-tools\\.com)|(\\{e4df007c-60db-418b-97f3-d7896c441515\\})|(\\{90e41842-755d-40e0-9136-8129df55a65c\\})|(\\{faeee13b-39fb-49dd-82c5-47604a2e2e9a\\})|(\\{c3583bcf-271e-4b41-8207-32bd3db491a0\\})|(\\{cbe6f8d9-bd60-4f27-acd1-388a976e3749\\})|(\\{18be83e0-4bc9-469a-bde5-cc671acb022b\\})|(\\{97152370-246f-4059-999d-92f26f358e71\\})|(\\{8a975636-e12e-4532-ad35-9d9e691a246d\\})|(\\{3eb5ea84-a445-4dab-8074-d29dbf4e66f0\\})|(\\{f19cf85e-47ea-446b-808d-38f4010594b3\\})|(\\{b28a1358-5cdc-442e-b851-613bdf118320\\})|(\\{90f0ad05-0267-4ea6-9809-1098ec724905\\})|(\\{d18372a9-89d9-4799-87f1-ccd4cfac80d4\\})|(\\{d18372a9-89d9-4799-87f1-ccd4cfac80d5\\})|(\\{4625fcb8-6b60-49e7-9447-31cb1e2a2d59\\})|(\\{7dc2d526-d11b-4e6f-9301-a4bc620f5838\\})|(\\{d763ea47-6889-4e23-9b6c-ac56087c96a7\\})|(\\{3c7f00c4-4e4a-411b-960a-4ce782ece6c9\\})|(\\{8c1db2e7-c550-4d76-8009-ad9fca9c0813\\})|(\\{0b23f4db-0f49-44b7-9d27-b424ba9f6d62\\})|(\\{d798f753-a0e4-4d8b-866b-3b817b7b6d25\\})|(\\{e3d7ec01-ada7-4330-899b-bce60c64471b\\})|(\\{3a210e15-601e-4204-8690-cb05ed14019d\\})|(\\{51226db3-500d-45d1-bbc3-a9bf560caf39\\})|(\\{0aa6f915-ed71-4de8-aaeb-63f799d83af7\\})|(\\{3d46809c-6c20-469c-a608-3ece3121da4a\\}))$/", + "prefs": [], + "schema": 1578335483666, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1607277", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0bb0f9b4-6807-4642-9199-026411426eb2", + "last_modified": 1578409770942 + }, + { + "guid": "/^((\\{af51d321-202e-49af-9a3c-78a5ebb22b63\\})|(\\{749b293a-1e19-494b-8a3a-cb79d5464226\\})|(\\{fbe6ff3f-2675-466a-916e-352080c27adb\\})|(\\{ad0378a7-5153-4e6d-999c-6c8e49868293\\})|(\\{2f7de182-3427-4386-a880-c601050580ab\\})|(\\{99bad30d-3792-4a03-ab9d-8e88f5f1cccf\\})|(\\{475b8326-3218-492c-b4c3-32307053c21a\\})|(\\{18571b27-500b-4604-a4ea-e0e7d8b61ce6\\})|(\\{fad11a52-2f9a-4d89-b712-f61184d756e7\\})|(\\{113c9d19-e80a-4c72-9a23-58f7b08144cf\\})|(\\{a0b384d9-c275-4670-a4be-8b0be45d562e\\})|(\\{19bac525-be97-4470-b76a-c24168d5e6e8\\})|(\\{2a40afd4-e56b-425b-bff2-62f190d80fac\\})|(\\{cbc65b60-2350-4b7f-8c83-1b9182a56430\\})|(\\{6adb7178-1105-46e6-9a4d-1824385cef5d\\})|(\\{6d46665c-4708-495d-a929-096ea137a08d\\})|(\\{fd6273bc-3b55-4bf0-872c-146b3feb73f9\\})|(\\{707783a0-6669-4e1f-8036-53f5c936304b\\})|(\\{7086907a-f6fd-46f4-b51c-a7dd2b10509c\\})|(\\{0ed69e4f-6cec-4bfa-9004-076ebd0be34f\\})|(\\{d051ddb8-1de2-403c-84d2-afd96a3f4550\\})|(\\{52699f66-62fe-4970-83eb-5ad82bf72497\\})|(\\{6c32d68b-4866-4277-b454-d824a3a3209c\\})|(\\{d608864f-5573-47eb-a2d7-46d158ee3fa7\\})|(\\{38c59c7e-bc02-4e7f-89ea-c4d9555362c6\\})|(\\{981a212a-52b0-4413-976c-a4730f1df128\\})|(\\{06e004dc-40a8-4123-addc-c6b6eb5234c7\\})|(\\{c87d86f9-d5df-4a24-9fe0-dd39232e4d88\\})|(\\{86baf51d-21b6-456e-889b-c7d96ba2fd63\\})|(\\{db83a6c1-efb7-45a1-b074-8e3e5a53793e\\})|(\\{70c00ae0-5b15-4ee8-99f9-23d85c63c07e\\})|(\\{ff72e707-d6e2-446f-a760-6ff187f107c9\\})|(\\{6f65c53b-596f-4238-afa2-347150b3ada3\\})|(\\{95da423e-90ec-43d9-b5c8-e195bff7b432\\})|(\\{4a792129-a244-4a39-a416-ce4f7743918f\\})|(\\{d69204d4-610c-4144-9d26-8cf8b2ad6e16\\})|(\\{49242aa4-83ba-4e25-a827-4d1ec86972a8\\})|(\\{d69204d4-610c-4144-9d26-8cf8b2ad6e19\\})|(\\{3505b15c-533a-4c2e-9065-6d7b2381a7f9\\})|(\\{07daa733-944d-4b0b-9bce-01638b9d9b42\\})|(\\{ebb69cbc-b69f-4d3d-a676-83811a061baa\\})|(\\{f9327eb7-2532-4e45-a551-a9f8233e4ef7\\})|(\\{4fcb9938-6716-46ce-8107-73f3cc7b3900\\})|(\\{cee1ccee-5508-4927-a939-9a557e63bbc8\\})|(\\{c6973149-51bd-443a-898c-e4b26d4ee44b\\})|(@mouselessjj)|(\\{39772968-f7e5-4a84-87d2-adcd2d032c69\\})|(\\{3ba568f0-bb3a-4744-b64f-46c428f3bd99\\})|(benalio@gmail\\.com)|(\\{70c50468-54be-4d7c-b1b4-b7378d0ff45d\\})|(\\{f77c3471-806a-40d6-9fdc-7df832f7ae74\\})|(\\{cf660d66-4aac-461b-a05f-b71b62f549c6\\})|(\\{1c58ca6f-91ad-4423-b8bd-f5323df92eb3\\})|(\\{1a393581-d6ce-454f-a503-8a321ba0f022\\}))$/", + "prefs": [], + "schema": 1578339693692, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1607422", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "18cdf7c5-6650-46d5-a536-364ce9dcfa92", + "last_modified": 1578409770937 + }, + { + "guid": "/^((pro@affiliatebrowserguard\\.com)|(beta@affiliatebrowserguard\\.com))$/", + "prefs": [], + "schema": 1578394762485, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1607211", + "why": "This add-on violates Mozilla's add-on policies by redirecting requests without user consent or control.", + "name": "Affiliate Browser Guard" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c8aa2f43-2c48-4628-af4f-0987125d0441", + "last_modified": 1578409770930 + }, + { + "guid": "/^((@adultdownloader)|(\\{6391a084-dd1c-41b8-aefb-ce207da8bf21\\})|(\\{19e997bc-8449-4ab3-b4f2-f24db1053fa9\\})|(\\{e65a0802-7bbd-4c83-9bec-50ec16dadcdf\\})|(\\{6928cfc2-eda9-4971-8d8c-de26747c27ea\\})|(\\{93c15410-f7c9-41a7-a13d-28dba750f15e\\})|(\\{d7c369d6-74e2-4da7-8eef-dc134914facb\\})|(\\{e3109828-453d-4fc1-8019-488f891720d5\\})|(\\{d825dcfa-4340-4330-b4e7-b208439ee961\\})|(\\{8e7d2b8c-6167-496d-8e4c-45d9040a4a7a\\})|(\\{62935c48-c7bb-4a6c-a1b0-449fdc8737c0\\}))$/", + "prefs": [], + "schema": 1578166891954, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1605959", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c086bfd7-15ff-41eb-ac43-d84e95606eec", + "last_modified": 1578325482223 + }, + { + "guid": "/^((\\{943773bb-c8a8-4576-bf3a-7cacca534887\\})|(\\{0afab6d1-c267-4ff6-9289-4a2b2ad78f33\\})|(\\{4278c56e-7b67-43ab-8c87-f68a6a883df6\\})|(\\{f146cde8-6dcf-4ebf-9d67-b7eccc9bc8a6\\})|(\\{d0e43d96-0e73-48d7-9a60-b235022f4330\\})|(\\{a94ed9b8-24a6-4719-97af-08eaac91a42b\\})|(\\{d62ca114-0f4e-4d25-813e-292d0c682e05\\})|(\\{908b1c05-3766-45e1-b56b-f0b4457c6451\\})|(\\{068054b5-46c0-47c2-be37-dd015fb1c050\\})|(\\{1c1cde54-c8e9-4c61-a2be-30411888ac8a\\})|(\\{b06648df-3c80-49e8-9d9e-71741ba28c72\\})|(\\{a085da90-cc10-4c26-954e-ae4eb773c6d7\\})|(\\{1d53aa0d-eca3-4bb1-947c-aebfdd0770fd\\})|(\\{83ebd575-b66b-4b02-a628-d2764194d0a6\\})|(\\{fd5f7e74-2e60-454b-b702-05e9d66b334b\\})|(\\{fdd2f5a3-9061-4fc8-87d2-cf7f7668d336\\})|(\\{f890d432-ce0b-4857-b030-dc36fc791423\\})|(\\{d5b4e84c-7991-4be5-9c26-c3e92ba0901a\\})|(\\{8d54459a-34cb-461f-b1fe-2266484cc098\\})|(\\{67b21df3-a1da-48ee-aee8-18ae8ad7fe21\\})|(\\{18feff20-5892-43cd-8606-83e8e7c33ebd\\})|(\\{554e901f-5476-4fad-8714-a08b8249068b\\})|(\\{248c9c9f-404d-4284-bd12-2fa988edb0ad\\})|(\\{9c347568-2528-4dea-8a0d-6e9fa7f88512\\})|(\\{0447e445-abb9-450b-b12c-de16fa40e176\\})|(\\{3015019d-1c2f-4310-adf1-1b33c4566d9e\\})|(\\{a3aca1ed-74b4-46d4-9c73-ca9ba8c91ac6\\})|(\\{7b2aabfa-673f-4afb-9cba-7a9329a24d79\\}))$/", + "prefs": [], + "schema": 1578083875760, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1606925", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "New Tab add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "de533f3a-d0e3-4704-916e-fd2a297cee93", + "last_modified": 1578084466436 + }, + { + "guid": "/^((\\{a91c41b7-4196-4867-84b4-d417a1b10da2\\})|(\\{e3d3ea5c-c8c0-4c9e-89c6-f6b5677186cc\\})|(\\{fa3b6777-4f64-476c-9ace-a79709ccd0a6\\})|(\\{6251b844-0c54-44bc-8c5e-891e3ba86c2e\\})|(\\{8e588566-afb2-4612-a420-3e3bd18693e7\\})|(\\{a0bf35ec-76cc-4d86-875b-09d46e8790c2\\})|(\\{fd257beb-d772-4333-9901-dc5913aee499\\})|(\\{a9acb248-93fb-4081-9d88-92468f229842\\})|(\\{7e1b62bc-3ab4-4c4d-8182-b095a492eab4\\}))$/", + "prefs": [], + "schema": 1577994095119, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1606132", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code and/or executing remote code.", + "name": "CodeScript and WorldScript" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3d3f2971-9764-4b6d-94ef-2a0b97da7619", + "last_modified": 1578066343576 + }, + { + "guid": "@mendeleyimporter", + "prefs": [], + "schema": 1576845976756, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600910", + "why": "The add-on does not comply with Mozilla's requests to provide reviewable source code and is suspected to violate Mozilla's add-on policies by collecting data without disclosure or consent.", + "name": "Mendeley Importer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e028ecb3-be1e-48fb-9681-9c04ff7fad3d", + "last_modified": 1576881756956 + }, + { + "guid": "s3firefox@translator", + "prefs": [], + "schema": 1576845555478, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1605007", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "S3.Translator – s3firefox@translator" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "31a14241-2af8-4ec8-b597-d5c3ebedc30c", + "last_modified": 1576845976374 + }, + { + "guid": "/^((\\{cee1ccee-5503-4927-a923-9a557e63bbc8\\})|(\\{8a84b40d-84a4-40d6-96de-c504a4fcd114\\}))$/", + "prefs": [], + "schema": 1576784492013, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1605289", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Adobe Flash Player" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d940ce81-7ac8-4741-9fc8-2699cb92d4ef", + "last_modified": 1576845555097 + }, + { + "guid": "/^((therill@mozilla\\.com)|(Updates@mozilla\\.com))$/", + "prefs": [], + "schema": 1576756361317, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1480591", + "why": "These add-ons violate the no-surprises and user-control policy.", + "name": "Search engine hijacking malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e235a161-aaa1-4afd-8e9b-61ab2aeaf82e", + "last_modified": 1576771657205 + }, + { + "guid": "/^((\\{5620c992-8683-4ce1-b19d-3633b4c28bd0\\})|(\\{cbc29a75-5858-4b7b-98e4-c813a4e6a085\\})|(\\{4cf619a8-2de2-41cb-bf23-dfa52e4e7d5a\\})|(\\{3b013e48-d683-45ed-8715-a6ece06f0753\\})|(\\{9834ff7f-e3ea-485a-b861-801a2e33f822\\}))$/", + "prefs": [], + "schema": 1576756618517, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554606", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Various remote script injection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7c324513-5792-495d-a389-d7527a0433b2", + "last_modified": 1576771657202 + }, + { + "guid": "/^((\\{ab70d6ee-9d0a-4349-919f-2e3c9aa77927\\})|(\\{fe94f94a-75ff-48a9-9cab-03e626e30352\\})|(\\{085590fa-c340-423d-9b45-d8e963349513\\})|(\\{c06005f4-a53f-4503-b631-9c6fbea45e9e\\})|(\\{a1904bba-73b5-4fab-8556-95fdf0200c19\\})|(\\{4548ed4c-964e-4a53-acec-b24f5b9ea6a6\\})|(\\{99d68c16-4f64-463a-ad09-470a5ac07981\\})|(\\{14338345-a844-4c6e-9fca-d200a93f1d9b\\})|(\\{2bc78397-6bd3-4a2f-a737-dbc639ee9940\\}))$/", + "prefs": [], + "schema": 1576756684025, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1565184", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Private Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "92cedc61-c6c7-485e-98b0-a03c71eb2c5a", + "last_modified": 1576771657198 + }, + { + "guid": "/^((application@uk-manulap\\.com)|(application@uk-misafou\\.com)|(application@uk-nedmaf\\.com)|(application@uk-optalme\\.com)|(application@uk-plifacil\\.com)|(application@uk-poulilax\\.com)|(application@uk-rastafroc\\.com)|(application@uk-ruflec\\.com)|(application@uk-sabrelpt\\.com)|(application@uk-sqadipt\\.com)|(application@uk-tetsop\\.com)|(application@uk-ustif\\.com)|(application@uk-vomesq\\.com)|(application@uk-vrinotd\\.com)|(application@us-estuky\\.com)|(application@us-lesgsyo\\.com)|(applicationY@search-lesgsyo\\.com)|(\\{88069ce6-2762-4e02-a994-004b48bd83c1\\}))$/", + "prefs": [], + "schema": 1576756765926, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487627", + "why": "Add-ons whose main purpose is to track user browsing behavior.", + "name": "Abusive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "42d1f27b-9cbf-4a3e-a18a-5c4418c2f5b6", + "last_modified": 1576771657195 + }, + { + "guid": "/^((\\{d1b87087-09c5-4e58-b01d-a49d714da2a2\\})|(\\{d14adc78-36bf-4cf0-9679-439e8371d090\\})|(\\{d64c923e-8819-488c-947f-716473d381b2\\})|(\\{d734e7e3-1b8e-42a7-a9b3-11b16c362790\\})|(\\{d147e8c6-c36e-46b1-b567-63a492390f07\\})|(\\{db1a103d-d1bb-4224-a5e1-8d0ec37cff70\\})|(\\{dec15b3e-1d12-4442-930e-3364e206c3c2\\})|(\\{dfa4b2e3-9e07-45a4-a152-cde1e790511d\\})|(\\{dfcda377-b965-4622-a89b-1a243c1cbcaf\\})|(\\{e4c5d262-8ee4-47d3-b096-42b8b04f590d\\})|(\\{e82c0f73-e42c-41dd-a686-0eb4b65b411c\\})|(\\{e60616a9-9b50-49d8-b1e9-cecc10a8f927\\})|(\\{e517649a-ffd7-4b49-81e0-872431898712\\})|(\\{e771e094-3b67-4c33-8647-7b20c87c2183\\})|(\\{eff5951b-b6d4-48f5-94c3-1b0e178dcca5\\})|(\\{f26a8da3-8634-4086-872e-e589cbf03375\\})|(\\{f992ac88-79d3-4960-870e-92c342ed3491\\})|(\\{f4e4fc03-be50-4257-ae99-5cd0bd4ce6d5\\})|(\\{f73636fb-c322-40e1-82fb-e3d7d06d9606\\})|(\\{f5128739-78d5-4ad7-bac7-bd1af1cfb6d1\\})|(\\{fc11e7f0-1c31-4214-a88f-6497c27b6be9\\})|(\\{feedf4f8-08c1-451f-a717-f08233a64ec9\\}))$/", + "prefs": [], + "schema": 1576756852767, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1476369", + "why": "These add-ons contain unwanted features and try to prevent the user from uninstalling themselves.", + "name": "Smash/Upater (malware) and similar" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0896a86b-a211-4c64-968c-8671a21b2b5a", + "last_modified": 1576771657191 + }, + { + "guid": "/^((\\{2926cc6a-5f5d-4ef7-9cd0-0417408e8809\\})|(\\{569a3468-ebdc-4d95-84ef-0b6cbbcb2119\\})|(\\{6ef88cf3-c31c-4a9b-973b-765c21bc6f81\\})|(\\{a36b6921-0b70-4fad-b39b-786b23716b74\\})|(\\{fcc6a194-c627-4bf1-b185-e9611dc8d56d\\})|(\\{ddc71014-7997-421d-bb86-3b70b2ab24d5\\})|(\\{453638fc-e7f1-4a3d-acfb-724a0c750686\\})|(\\{0283d25d-25e6-4710-845f-b112fb264d3f\\})|(\\{071b1860-ff82-46d0-816c-a458382beaa7\\})|(\\{f0f6892f-6d0d-4146-9de5-21cc7eb3672a\\})|(\\{845c7444-bd97-4200-8144-c95ce77c07be\\})|(\\{8bc84806-edbd-4713-8a53-19a8c345fdfb\\})|(\\{959dfd6a-fbc5-49f0-8284-34e8606c7fd2\\})|(\\{9f90e1c8-a63e-43a9-8cd5-ad1d6fdf0f3a\\})|(\\{6b7cde7b-a79d-4012-b36c-86719ebcc308\\})|(\\{57f57692-7ef1-473a-adf7-7507e0e31c7d\\})|(\\{34bc568b-4243-4001-b528-118df3310667\\})|(\\{521e44f0-374e-466a-8829-2a98af75104b\\})|(\\{a4de65f7-33ae-4622-bb17-15053f0383f2\\})|(\\{f5ca8ea6-e854-4163-bc55-1a6f4086c1b2\\})|(\\{45765127-988a-4f3e-807d-f7784d76ecd0\\})|(\\{dc95db1e-68c5-44f4-a924-daac966373d2\\})|(\\{4119a8f1-508c-490d-87d4-203e93bbc5fb\\})|(\\{85997b97-ae1f-49e2-b42b-60f9c78c2d03\\})|(\\{a39c31c3-a57a-4c26-8454-267414494eed\\})|(\\{fa47c7ee-e855-41b0-9d3d-3c1d46d98a19\\})|(\\{6255931b-744a-49a2-bf6d-69ba35556760\\})|(\\{54112e17-779d-4275-9a04-81760931d356\\})|(\\{cea0dfab-286c-44c5-b41e-79d8ca5d097e\\})|(\\{fd6b2837-7828-4ad8-9bfe-361336aba33e\\})|(\\{0a56d404-ece2-441d-8d0b-e276ae1a07c2\\})|(\\{be54a202-b6e0-485d-b60b-f77f117ae602\\})|(\\{ff927da8-a634-4b4f-9010-2e76e3a6db2d\\})|(\\{a91f82ce-675b-4940-b58f-96095eb004af\\})|(\\{885c70f6-e286-433e-8f42-d7f319321d9d\\})|(\\{3e52fa7b-34c5-48e3-9cc2-3e33464db303\\})|(\\{ca94cd4e-3aec-41af-aa3c-1a8ddba9b9be\\})|(\\{0971aa7e-3a32-418a-acca-fdb5dacfca2f\\})|(\\{4d16cfea-bbe5-4254-b98b-70267cf2a2bc\\})|(\\{5cb20af8-1a98-43b0-9f09-0ee6d494ce65\\}))$/", + "prefs": [], + "schema": 1576756919670, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1558136", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various fake Flash/Avast/etc clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c81cb302-2519-4997-84de-75aa01f2dee9", + "last_modified": 1576771657188 + }, + { + "guid": "/^((jabcajffpafebcdcaddoegpenicdipdk@chrome-store-foxified-1110252619)|(jpegcert@freeverify\\.org)|(kmrfree@yahoo\\.com)|(lets-kingw@empotrm\\.com)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-29039950)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-77744803)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-357866719)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-447115206)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-549146896)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-1084455972)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-1602969934)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-2271560562)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-2595595173)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-3103352300)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-3116340547)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-3959272483)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-4076222235)|(mdanidgdpmkimeiiojknlnekblgmpdll@chrome-store-foxified-4090031097)|(media-certificates@auth0\\.io)|(mingle-cash-extension@minglecash\\.com)|(MiningBlocker@MiningBlockerAddOn\\.com)|(multimedia@certifications\\.us)|(nominer-block-coin-miners@tubedownload\\.org)|(open-in-idm@tubedownload\\.org)|(sabqo@yolla\\.net)|(search-by-image@addonsmash)|(selfdestructingcookies@addonsmash)|(streaming-certficate@mdn\\.org)|(swt@gobck\\.com)|(tabs-re-open@gtk\\.cc)|(user-agent-rewriter@a\\.org)|(vba@vba\\.com)|(verification@bexp\\.co)|(vidcert@certs\\.org)|(xplayer@gobck\\.com)|(youtube_download_express@free-downloader\\.online)|(youtube_downloader@downloaders\\.xyz)|(youtube_grabber@utubegrabber\\.co)|(youtube-lyrics-by-rob-w@awesome\\.addons)|(youtube-mp4-downloader@tubedownload\\.org)|(ytdownloader@ytdownloader\\.org)|(yttools\\.download@youtube\\.com))$/", + "prefs": [], + "schema": 1576757015029, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1525880", + "why": "Add-ons that include abusive or malicious remote code.", + "name": "Various abusive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "eaa146be-e47f-4c20-bdbd-c8046a929100", + "last_modified": 1576771657184 + }, + { + "guid": "/^((\\{ed4c3ce2-5372-429c-ae20-fa5b1f540fd7\\})|(\\{cd11da28-330d-4f09-a21f-fae7509f1b60\\})|(\\{74bc7a66-d4e6-4f1c-a0ef-1b65baa41cbf\\})|(\\{8069effc-45bb-4caf-8b27-a135431cd6b9\\})|(\\{577fc233-25bf-4e43-a164-aa75eb9d053a\\})|(\\{f5626996-f5cd-4d00-bcea-20dda6d9edd6\\})|(\\{9bb810ef-716e-4dc5-9f03-491a2c59384e\\})|(\\{02634a24-04d0-439f-9faf-a323ab4a1bac\\})|(\\{b73f7a43-a43a-47f5-8b1f-1ef7caa7857d\\})|(\\{3b5bf07b-5964-408a-8e43-e0239219c524\\})|(\\{73d3a404-150f-4594-ac2c-24f9beec78b1\\})|(\\{ef6a2133-5ed9-4dbc-a735-6ffe8490062e\\})|(\\{76b61321-01a1-4a17-850f-b064a0366b57\\})|(\\{bc41ca18-9209-4500-a847-4e514fea2536\\})|(\\{32c4c845-9bd7-4b20-97fa-a7616e7802ef\\})|(\\{410d9002-b517-471f-956e-30129e307af3\\})|(\\{43366e90-e4be-4ba6-bec0-3fb149128480\\})|(\\{8ebc90a4-f7a1-420a-8380-f85545403f80\\})|(\\{6d3fa41f-e896-4f85-ba59-321f4b26f380\\})|(\\{05c811f2-f828-4d3e-ad02-7386373e9a28\\})|(\\{e10a0ee6-8083-42a7-bed1-35400b029bf2\\})|(\\{c925be5a-ae0e-4958-be36-44dc2e64d4f7\\})|(\\{3803ed37-c101-4b21-a678-762f51b7eabf\\})|(\\{08a15cc0-d6cb-43c5-9a40-27443554b455\\})|(\\{d0953283-5970-4ebe-b270-940c6befdbb7\\})|(\\{7c983689-80c7-46dd-b9d2-4d2db1cf94a6\\})|(\\{8f320a17-868c-43dc-94fb-9d1ab7f4fe73\\})|(\\{843b406a-9593-49bf-9365-684fe8cb2f5a\\})|(\\{42cd0cbd-248d-4a44-88b6-1a3680d159ac\\})|(\\{23efa05d-99b1-49e0-a67d-5378f2afc20d\\})|(\\{f91c606c-dd33-42a4-9219-824187730f59\\})|(\\{9e233d16-18ae-4519-a83c-2806f4fee321\\})|(\\{a93cdf30-75da-463b-865b-f49cc7fd2697\\})|(\\{dcacb62c-9096-482d-845d-10413199a89b\\})|(\\{9d5da26f-c366-46b2-b3e7-5c8e3e0b9788\\})|(\\{dd176d1f-8cd5-4b5b-8b06-839449e87b5e\\})|(\\{9ada3b66-4412-427d-8696-ac0fe0ac891e\\})|(\\{9695495e-cb65-4cd6-8a93-52c9e2b8d767\\})|(\\{e9d1a027-a84c-4e90-b602-66ffe22a0ad6\\})|(\\{f71bda5e-c591-44aa-8f84-2f04989f7e7a\\})|(\\{e6e67c6f-c010-406d-8575-1835341ec4cf\\})|(\\{22fbf524-38be-4ead-b6ce-e55cb23ed74b\\})|(\\{866dafe9-1c49-47d7-a46c-1cb50ca52461\\})|(\\{3479fadc-41b1-492e-bb16-d8f9e514d488\\})|(\\{8c02daf2-79ed-4650-89ca-1e099d28c5e7\\})|(\\{65e6b805-7f0c-455f-b1b4-c34621056b46\\})|(\\{78de7006-944c-4c18-a33a-d6931619f2b0\\})|(\\{16c8051b-2c16-4641-bf29-2daee7883fd0\\})|(\\{19263ccc-a97f-49f4-867a-b49351c42c0c\\})|(\\{bfe416d8-e8c3-469c-908e-6926770152f0\\})|(\\{a29a4a96-2fcd-48f8-bfe1-a1d1df46e73d\\})|(\\{2d651636-a0fa-45b7-a97e-ebc85959ff23\\})|(\\{ac8a3af8-e264-4a0b-b813-d7fab03ae3fe\\})|(\\{4719ad8b-354b-443d-b1e6-4d60b851c465\\})|(\\{7679a9f9-29d8-4979-86e7-a5b5cf0e2fd3\\})|(\\{099e1648-58e7-492e-8019-3418263b9265\\})|(\\{9a83d154-4ea3-45f9-ae21-28f3c1f86773\\})|(\\{475b88fd-574c-4881-98e0-0184a03593cb\\})|(\\{d7b586f8-a22d-4986-9dfb-67d49ba46a68\\})|(\\{50b79e30-a649-4477-8612-7085c0ee3ad4\\})|(\\{e9b2d453-9a98-41e4-9837-c0d68ff1aeac\\})|(\\{b1814ce5-0d9f-495f-b260-a7e1e5538263\\})|(\\{9b06d35e-2eb0-4653-886f-a3f4cdcbb754\\})|(\\{e7d6a360-69d4-4f8c-a96f-fd63388995b2\\})|(\\{68a50af6-ddad-4750-a9a7-a71c55e019b7\\})|(\\{8286a0e0-ba89-48b3-871b-8c9acff32023\\})|(\\{b3c79903-9bc5-4ddf-aeeb-7d91989ae819\\})|(\\{7c5cc4ec-9637-428c-bcf7-28bba279cf84\\})|(\\{93d460ee-879f-4d8f-8599-a1c69ed59ec2\\})|(\\{207c95d5-2bb9-4760-b3a4-8c58ea173bff\\})|(\\{b3482681-1abf-4dfa-bace-dc7b51e6a150\\})|(\\{d3516cf6-d531-434a-b80a-df72c7166744\\})|(\\{da01a2aa-0cbb-4f57-a395-2256d142c519\\}))$/", + "prefs": [], + "schema": 1576757089378, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549214", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "71f2c72f-b4d7-4ea9-bb8f-70d4d86eb184", + "last_modified": 1576771657180 + }, + { + "guid": "/^((\\{84a9ae69-5c01-4a73-80d3-c2201410d8c1\\})|(\\{852c68da-c573-42f8-85f8-9dcf57684f87\\})|(\\{85a31d9e-063f-421f-9d3f-649a393e94ec\\})|(\\{876778c8-5329-461d-882e-d4983ae6062a\\})|(\\{8913da4a-46fb-461c-9e60-3e257ef2c0e6\\})|(\\{8aa0118c-998e-40ac-84e9-12c936e5d70e\\})|(\\{8b4a6441-811f-4461-b136-7ebf3aebe3dc\\})|(\\{8bc41c3b-e052-4fd8-8de3-970ef5224bd9\\})|(\\{8e03b200-aad9-434b-9a99-e7aae7493a5f\\})|(\\{8fe94d0d-4746-401c-ad05-e0e1be97ea0e\\})|(\\{900f3c9c-b327-4608-950b-9765119c2e7a\\})|(\\{915080f5-97a3-4584-861f-70cd91f56474\\})|(\\{91d034a6-1765-4a59-85e5-9ddeb371ed9b\\})|(\\{9207dfae-06fc-4545-9fa6-6466b7ed2559\\})|(\\{926b2440-8443-4de3-9025-9a448cf3b838\\})|(\\{952bfd34-d195-4b10-8a3c-b103786cf090\\})|(\\{95315ff2-427b-427c-a433-236fb3b5eda4\\})|(\\{971db1e5-a5cb-46f9-91f9-9b687f4e5832\\})|(\\{982e11b3-e092-4713-81d4-5da1eadd278e\\})|(\\{98a2b9a7-13fa-49ff-aaa4-83786fad7862\\})|(\\{99f52d4d-1cd2-4e17-8f57-fa2493848f3f\\})|(\\{9a467b2c-be87-4d55-80d9-998dc6243e8b\\})|(\\{9abfecfa-d53e-4aea-bb6c-4fe47367f61e\\})|(\\{9b0243a5-92fb-43a4-adcc-3161f0ec030c\\})|(\\{9c7bb0bf-1534-4805-b9fa-a91004bd7e30\\})|(\\{a00e65f6-bf34-4ef9-a0e5-b63002c823e9\\})|(\\{a0dce648-f703-4867-9f3f-9bfa7601d1b1\\})|(\\{a16a700a-35ff-4ed1-ab81-164e3c823342\\})|(\\{a1f14b23-0c36-44e8-8f0d-9c732acbb550\\})|(\\{a4ea8038-65ae-4d7c-92e2-dd95caf007f4\\})|(\\{a55cd5be-89e4-40ba-8c3b-0023a1f41c8e\\})|(\\{a57ec9eb-cbab-4ddb-bafd-80cf5fd38891\\})|(\\{a5aa1d1e-dec7-4e25-bead-0861099f9628\\})|(\\{a604a85d-ba8f-4e8f-8ca1-867ca8d13a13\\})|(\\{a7a33aad-9e17-4db3-a127-d185e31607ae\\})|(\\{a9404f9f-6ac9-4366-bfcf-50d0d3bdeac3\\})|(\\{aaf2dd6b-5ca9-47aa-b41f-5b00c5c82d2e\\})|(\\{abf10dee-7cc9-4b79-ad5b-1e4300ab24a7\\})|(\\{ac97e702-b2e2-4a91-ae3e-bf0856300737\\})|(\\{ae03577f-2d20-4775-8286-685cdbee76e4\\})|(\\{aef2e959-90c0-44cf-bbb5-e0789af93efe\\})|(\\{af0d8090-d04f-4e9a-a3fb-1c9ac89e9f68\\})|(\\{b0da2032-0da5-4cff-b91b-e0efda4d6b36\\})|(\\{b2777372-311f-4a15-81e5-c84dd845c93d\\})|(\\{b44ac98d-6101-467d-a959-d6ada2259f01\\})|(\\{b50daf26-3983-4516-836f-0b8777bc44ab\\})|(\\{b5ca55b9-d06a-4538-be4a-38b29f3a4359\\})|(\\{b783327e-a675-40c2-95c7-59eb3f00b75d\\})|(\\{b91f2cd5-4051-4e13-8848-8e92afb99217\\})|(\\{ba32ffe1-dabb-41d6-a45f-f4d3e1304ff1\\})|(\\{bb80ea9f-8263-4183-a52d-e5d45ca6e0fe\\})|(\\{c0ba2c3c-55a6-4d28-bb27-67f71de78feb\\})|(\\{c0ecc589-04de-4243-9279-100b781f7443\\})|(\\{c2f6447c-e2db-43d1-8c53-fec7c29b22bb\\})|(\\{c4492fc0-70ed-4d36-8904-61ccb663eaac\\})|(\\{c58e10ce-d69e-478d-8270-0d73599a8cfc\\})|(\\{c72781ce-8377-41ae-984e-ed5755af28de\\})|(\\{c7f51f89-f47c-45e6-aa57-177deba406a0\\})|(\\{c859eaff-3dde-4d83-9703-0a6cf9e95308\\})|(\\{ca51951b-5c9e-4c26-bca3-ed6e754ae5c0\\})|(\\{ce9f05c7-6246-4918-8505-fdc455bc0aab\\})|(\\{cf0ec4e1-5d0d-4846-aa97-380806e72e46\\})|(\\{cfa73be4-9e64-4aea-bb0a-2ab0defb27b3\\})|(\\{d12c5edd-1182-4bf7-bdb1-f2662b7ce1be\\})|(\\{d2343e30-0253-4556-9dd8-cb6cb461801d\\})|(\\{d7a1fad5-eb70-4f7f-a24d-98c3bb9a7aa4\\})|(\\{d7a7e3d1-e6f2-45e3-957a-4b2cde1b413b\\})|(\\{d946d1e8-38bd-41f4-8dc7-a255802046a8\\})|(\\{da7e77cd-4a7c-4282-a597-0694ada485b4\\})|(\\{dc905949-378e-4b8c-aacc-cff56b04370d\\})|(\\{dca4c8f5-5ef9-40fb-bd76-dcb4ec98c495\\})|(\\{dd275beb-f7dd-4ff6-8fec-23e8c0422b68\\})|(\\{de88be71-25f9-48d0-adc9-3d9a542cf303\\})|(\\{df148b39-f7c2-480d-ad8b-91b700e6642b\\})|(\\{df55df20-2e99-49fd-90bc-b548b833e2db\\})|(\\{e1348bc8-b378-45a3-95bb-4915b8910c1e\\})|(\\{e72aab9f-77f1-4e03-a4b7-9ea4b066fe50\\})|(\\{e8372510-9f1b-4b11-8e2f-dfc1d5d1a4a1\\})|(\\{eae5c7b6-8b67-4645-a1c1-a543e63ceda5\\})|(\\{eb1ed544-82e6-4785-b693-1e0799f7cffa\\})|(\\{ec37edc4-e1a6-4073-9cd4-7a5315c921e3\\})|(\\{ed240b54-8600-496b-a034-d9a153359906\\})|(\\{ee6e56cf-b963-4efb-b64e-cf6117dc9a5b\\})|(\\{f3337e21-4fbd-411c-b1fc-d0543052b499\\})|(\\{f5a4fafb-2f75-4acc-9dad-324ca00a1b84\\})|(\\{f9b00c32-2f31-436b-8cb1-720b12502cb6\\})|(\\{fdfd1815-cf54-4210-8883-a4154668b866\\})|(adobeflashplayer@flashplayeradobedeveloper\\.com)|(adobeflashplayer@flashplayeradobedevelopper\\.com)|(afplayer@firefox\\.pl)|(afplayerx@firefox\\.pl)|(aktualizacjaalamusowjeac@wp\\.pl)|(aktualizacjalamusowjeac@wp\\.pl)|(andrzej-ff@wp\\.pl)|(andrzej@gmail\\.com)|(au_addx@geckoaddon\\.org)|(au9c1660@auge\\.site)|(birghun@firefox\\.pl)|(birghuxxn@firefox\\.pl)|(btxyhuh@firefox\\.pl)|(elsee@geckoaddon\\.org)|(elseeau@geckoaddon\\.org)|(extensioner@firefox\\.pl)|(fr@ffget\\.xyz)|(fr9c1660@frge\\.site))$/", + "prefs": [], + "schema": 1576757184838, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552164", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name while providing unwanted functionality. This is not a legitimate Flash Player add-on.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "81f88c43-22e7-4602-8928-3a0b0f50689e", + "last_modified": 1576771657176 + }, + { + "guid": "/^((fruxuc@flashc\\.com)|(it_addx@geckoaddon\\.org)|(it9c1660@tige\\.site)|(marlenex@firefox\\.pl)|(nads@firefox\\.pl)|(newtabextension@newtabextensiond\\.com)|(pl@k4n\\.pl)|(playerro1@firefox\\.pl)|(socketextensionws1@geckoaddon\\.org)|(soxmuc@firefox\\.pl))$/", + "prefs": [], + "schema": 1576757189399, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552164", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name while providing unwanted functionality. This is not a legitimate Flash Player add-on.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a7e85b5b-7e0a-40c9-a900-99de7ba2a131", + "last_modified": 1576771657173 + }, + { + "guid": "/^((_hwMembers_@free\\.mapfinderz\\.com)|(_hxMembers_@free\\.mergedocsnow\\.com)|(_hyMembers_@free\\.formfetcherpro\\.com)|(_hzMembers_@free\\.televisionace\\.com)|(_i2Members_@free\\.streamlineddiy\\.com)|(_i4Members_@free\\.fileconvertonline\\.com)|(_i5Members_@free\\.mydiygenie\\.com)|(_i6Members_@free\\.dailyproductivitytools\\.com)|(_i7Members_@free\\.digismirkz\\.com)|(_i9Members_@free\\.cinematicfanatic\\.com)|(_iaMembers_@free\\.liveradiosweeper\\.com)|(_ijMembers_@www\\.freedirectionsonline\\.com)|(_isMembers_@free\\.mydigitalcalendar\\.com)|(_itMembers_@free\\.mycalendarplanner\\.com)|(_iuMembers_@free\\.productmanualsfinder\\.com)|(_ivMembers_@free\\.simplepictureedit\\.com)|(_iwMembers_@free\\.allinonedocs\\.com)|(_j1Members_@free\\.onlineworksuite\\.com)|(_j4Members_@free\\.createdocsonline\\.com)|(_j5Members_@ext\\.ask\\.com)|(_j6Members_@www\\.freemanualsindex\\.com)|(_j7Members_@www\\.convertdocsonline\\.com)|(_j8Members_@www\\.discoverliveradio\\.com)|(_j9Members_@www\\.internetspeedradar\\.com)|(_jaMembers_@www\\.testonlinespeed\\.com)|(_jbMembers_@www\\.onlinemapsearch\\.com)|(_jcMembers_@www\\.quickweathertracker\\.com)|(_jhMembers_@www\\.getcouponsfast\\.com)|(_jiMembers_@www\\.searchformsonline\\.com)|(_jjMembers_@www\\.onlineroutefinder\\.com)|(_jmMembers_@www\\.supercouponpro\\.com)|(_jnMembers_@www\\.pdfconverttools\\.com)|(_joMembers_@www\\.onlineformfinder\\.com)|(_jpMembers_@www\\.directionswhiz\\.com)|(_jqMembers_@www\\.convertpdfsnow\\.com)|(_jvMembers_@free\\.notehomepage\\.com)|(_k8Members_@www\\.mymapsexpress\\.com)|(_k9Members_@www\\.mytransitmapper\\.com)|(_kbMembers_@www\\.convertersnow\\.com)|(_kjMembers_@www\\.easydirectionsfinder\\.com)|(_knMembers_@www\\.getfamilyhistory\\.com)|(_koMembers_@www\\.quickpdfmerger\\.com)|(_kpMembers_@www\\.easytransithelper\\.com)|(_kqMembers_@www\\.easypackagefinder\\.com)|(_krMembers_@www\\.easyemailsuite\\.com)|(_ksMembers_@www\\.quickcouponfinder\\.com)|(_ktMembers_@www\\.easypackagetracker\\.com)|(_kvMembers_@www\\.radiofinder\\.com)|(_kwMembers_@www\\.productmanualspro\\.com)|(_kxMembers_@www\\.smarteasymaps\\.com)|(_kyMembers_@www\\.quickflighttracker\\.com)|(_kzMembers_@www\\.productmanualsguide\\.com)|(_l0Members_@www\\.getformshere\\.com)|(_l1Members_@www\\.videoconverterhd\\.com)|(_l3Members_@www\\.watchmytvshows\\.com)|(_l4Members_@www\\.quicktemplatefinder\\.com)|(_l5Members_@www\\.strictlyradio\\.com)|(_l6Members_@www\\.propdfconverter\\.com)|(_l7Members_@free\\.gifables\\.com)|(_laMembers_@free\\.gifsgalore\\.com)|(_lbMembers_@free\\.worldofnotes\\.com)|(_ohMembers_@chrome\\.google\\.com)|(_onMembers_@www\\.4thofjulypictureedit\\.com)|(_oxMembers_@free\\.anytimeastrology\\.com)|(_oyMembers_@free\\.getfreegifs\\.com)|(_ozMembers_@free\\.newnotecenter\\.com)|(_paMembers_@www\\.filmfanatic\\.com)|(_pbMembers_@www\\.holidayphotoedit\\.com)|(_pcMembers_@free\\.astrologysearcher\\.com)|(_pdMembers_@free\\.horoscopebuddy\\.com)|(_peMembers_@free\\.lovemyhoroscopes\\.com)|(_pjMembers_@free\\.gifapalooza\\.com)|(_pkMembers_@free\\.giffysocial\\.com)|(_pnMembers_@free\\.myeasylotto\\.com)|(_pqMembers_@www\\.freepdfcombiner\\.com)|(_psMembers_@www\\.freetemplatefinder\\.com)|(_ptMembers_@www\\.simplepackagefinder\\.com)|(_pvMembers_@www\\.mapmywayfree\\.com)|(_pxMembers_@www\\.simpleflighttracker\\.com)|(_pyMembers_@www\\.filesendfree\\.com)|(_pzMembers_@free\\.babynameready\\.com)|(_q0Members_@free\\.mywaynotes\\.com)|(_q1Members_@free\\.everydaymemo\\.com)|(_q7Members_@www\\.getflightupdates\\.com)|(_q8Members_@www\\.getformsfree\\.com)|(_qfMembers_@www\\.formfinderfree\\.com)|(_qhMembers_@free\\.presidentialbuzz\\.com)|(_qjMembers_@free\\.taxcenternow\\.com)|(_qlMembers_@free\\.cryptopricesearch\\.com)|(_qmMembers_@free\\.ontargetyoga\\.com)|(_qoMembers_@free\\.taxinfohelp\\.com)|(_qpMembers_@free\\.getpoliticalnews\\.com)|(_qqMembers_@free\\.thepresidentsays\\.com)|(_qtMembers_@www\\.formfinderhq\\.com)|(_quMembers_@free\\.myquicklotto\\.com)|(_qwMembers_@free\\.shoppingdealslive\\.com)|(_r0Members_@free\\.bitcoinpricesearch\\.com)|(_r1Members_@free\\.projectbabyname\\.com)|(_r2Members_@free\\.yogaposeonline\\.com)|(_r5Members_@www\\.quickdocsonline\\.com)|(_r6Members_@free\\.ezpdfconvert\\.com)|(_r7Members_@free\\.onlineformsdirect\\.com)|(_r8Members_@free\\.mapsboss\\.com)|(_raMembers_@www\\.fileconverterfree\\.com)|(_rcMembers_@extsb\\.searchbetter\\.com))$/", + "prefs": [], + "schema": 1576757318717, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598806", + "why": "These add-ons collect ancillary user data among other policy violations.", + "name": "New tab data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e87028d0-b5c8-4a76-9105-0229bafb5040", + "last_modified": 1576771657169 + }, + { + "guid": "/^((_rdMembers_@www\\.freeshoppingtool\\.com)|(_reMembers_@www\\.simpleholidayrecipes\\.com)|(_rfMembers_@www\\.myguidetoislam\\.com)|(_rgMembers_@free\\.entertainmentnewsnow\\.com)|(_rhMembers_@free\\.politicalnewscenter\\.com)|(_riMembers_@www\\.myfashiontab\\.com)|(_rjMembers_@www\\.mychristianportal\\.com)|(_rkMembers_@www\\.globaljewishworld\\.com)|(_rlMembers_@www\\.myvedictab\\.com)|(_rnMembers_@free\\.learnthelyrics\\.com)|(_roMembers_@free\\.dailyfunnyworld\\.com)|(_rpMembers_@free\\.myprivacymanager\\.com)|(_rqMembers_@free\\.getseniorresources\\.com)|(_rrMembers_@free\\.webtopdfprint\\.com)|(_rsMembers_@www\\.freeauctionfinder\\.com)|(_rwMembers_@free\\.getlyricsonline\\.com)|(_rzMembers_@free\\.pagesummarizer\\.com)|(_s0Members_@free\\.funnyjokesnow\\.com)|(_s2Members_@free\\.mybabyboomerhub\\.com)|(_s6Members_@free\\.celebgossiponline\\.com)|(_saMembers_@free\\.onlineprivacymanager\\.com)|(_scMembers_@free\\.freearticleskimmer\\.com)|(_sdMembers_@www\\.easywebpageprint\\.com)|(_swMembers_@www\\.homehelpguide\\.com)|(_szMembers_@www\\.mydocshere\\.com)|(_v4Members_@www\\.dictionaryboss\\.com))$/", + "prefs": [], + "schema": 1576757323000, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598806", + "why": "These add-ons collect ancillary user data among other policy violations.", + "name": "New tab data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "49cb7c72-e90e-4dc3-adda-d63083c83a7c", + "last_modified": 1576771657165 + }, + { + "guid": "/^(({23c65153-c21e-430a-a2dc-0793410a870d})|({29c69b12-8208-457e-92f4-e663b00a1f10})|({30a8d6f1-0401-4327-8c46-2e1ab45dfe77})|({30d63f93-1446-43b3-8219-deefec9c81ce})|({32cb52f8-c78a-423d-b378-0abec72304a6})|({35bfa8c0-68c1-41f8-a5dd-7f3b3c956da9})|({36a4269e-4eef-4538-baea-9dafbf6a8e2f})|({37f8e483-c782-40ed-82e9-36f101b9e41f})|({42a512a8-37e0-4e07-a1db-5b4651d75048})|({43ae5745-c40a-45ab-9c11-74316c0e9fd2})|({53fa8e1c-112b-4013-b582-0d9e8c51ca75})|({56effac7-3ae9-41e3-9b46-51469f67b3b2})|({61a486c0-ce3d-4bf1-b4f2-e186a2adecf1})|({62b55928-80cc-49f7-8a4b-ec06030d6601})|({63df223d-51cf-4f76-aad8-bbc94c895ed2})|({064d8320-e0f3-411f-9ed1-8c1349279d20})|({071b9878-a7d3-4ae3-8ef0-2eaee1923403})|({72c1ca96-c05d-46a7-bce1-c507ec3db4ea})|({76ce213c-8e57-4a14-b60a-67a5519bd7a7})|({78c2f6a0-3b54-4a21-bf25-a3348278c327})|({0079b71b-89c9-4d82-aea3-120ee12d9890})|({81ac42f3-3d17-4cff-85af-8b7f89c8826b})|({81dc4f0e-9dab-4bd2-ab9d-d9365fbf676f})|({82c8ced2-e08c-4d6c-a12b-3e8227d7fc2a})|({83d6f65c-7fc0-47d0-9864-a488bfcaa376})|({83d38ac3-121b-4f28-bf9c-1220bd3c643b})|({84b9121e-55c9-409a-9b28-c588b5096222})|({87ba49bd-daba-4071-aedf-4f32a7e63dbe})|({87c552f9-7dbb-421b-8deb-571d4a2d7a21})|({87dcb9bf-3a3e-4b93-9c85-ba750a55831a})|({89a4f24d-37d5-46e7-9d30-ba4778da1aaa})|({93c524c4-2e92-4dd7-8b37-31a69bc579e8})|({94df38fc-2dbe-4056-9b35-d9858d0264d3})|({95c7ae97-c87e-4827-a2b7-7b9934d7d642})|({95d58338-ba6a-40c8-93fd-05a34731dc0e})|({97c436a9-7232-4495-bf34-17e782d6232c})|({97fca2cd-545f-42ef-ae93-dc13b046bd3b})|({0111c475-01e6-42ea-a9b4-27bed9eb6092})|({115a8321-4414-4f4c-aee6-9f812121b446})|({158a5a56-aca0-418f-bec0-5b3bda6e9d4c})|({243a0246-cbab-4b46-93fb-249039f68d84})|({283d4f2a-bab1-43ce-90be-5129741ac988})|({408a506b-2336-4671-a490-83a1094b4097})|({0432b92a-bfcf-41b9-b5f0-df9629feece1})|({484e0ba4-a20b-4404-bb1b-b93473782ae0})|({486ecaf1-1080-48c1-8973-549bc731ccf9})|({495a84bd-5a0c-4c74-8a50-88a4ba9d74ba})|({520f2c78-7804-4f59-ae74-a192476055ed})|({543f7503-3620-4f41-8f9e-c258fdff07e9})|({0573bea9-7368-49cd-ba10-600be3535a0b})|({605a0c42-86af-40c4-bf39-f14060f316aa})|({618baeb9-e694-4c7b-9328-69f35b6a8839})|({640c40e5-a881-4d16-a4d0-6aa788399dd2})|({713d4902-ae7b-4a9a-bcf5-47f39a73aed0})|({767d394a-aa77-40c9-9365-c1916b4a2f84})|({832ffcf9-55e9-4fd1-b2eb-f19e1fac5089})|({866a0745-8b91-4199-820a-ec17de52b5f2})|({869b5825-e344-4375-839b-085d3c09ab9f})|({919fed43-3961-48d9-b0ef-893054f4f6f1})|({971d6ef0-a085-4a04-83d8-6e489907d926})|({1855d130-4893-4c79-b4aa-cbdf6fee86d3})|({02328ee7-a82b-4983-a5f7-d0fc353698f0})|({2897c767-03aa-4c2f-910a-6d0c0b9b9315})|({3908d078-e1db-40bf-9567-5845aa77b833})|({04150f98-2d7c-4ae2-8979-f5baa198a577})|({4253db7f-5136-42c3-b09d-cf38344d1e16})|({4414af84-1e1f-449b-ac85-b79f812eb69b})|({4739f233-57c1-4466-ad51-224558cf375d})|({5066a3b2-f848-4a59-a297-f268bc3a08b6})|({6072a2a8-f1bc-4c9c-b836-7ac53e3f51e4})|({7854ee87-079f-4a25-8e57-050d131404fe})|({07953f60-447e-4f53-a5ef-ed060487f616})|({8886a262-1c25-490b-b797-2e750dd9f36b})|({12473a49-06df-4770-9c47-a871e1f63aea})|({15508c91-aa0a-4b75-81a2-13055c96281d})|({18868c3a-a209-41a6-855d-f99f782d1606})|({24997a0a-9d9b-4c87-a076-766d44e1f6fd})|({27380afd-f42a-4c25-b57d-b9012e0d5d48})|({28044ca8-8e90-435e-bc63-a757af2fb6be})|({30972e0a-f613-4c46-8c87-2e59878e7180})|({31680d42-c80d-4f8a-86d3-cd4930620369})|({44685ba6-68b3-4895-879e-4efa29dfb578})|({046258c9-75c5-429d-8d5b-386cfbadc39d})|({47352fbf-80d9-4b70-9398-fb7bffa3da53})|({56316a2b-ef89-4366-b4aa-9121a2bb6dea})|({65072bef-041f-492e-8a51-acca2aaeac70})|({677e2d00-264c-4f62-a4e8-2d971349c440})|({72056a58-91a5-4de5-b831-a1fa51f0411a})|({85349ea6-2b5d-496a-9379-d4be82c2c13d})|({98363f8b-d070-47b6-acc6-65b80acac4f3})|({179710ba-0561-4551-8e8d-1809422cb09f})|({207435d0-201d-43f9-bb0f-381efe97501d})|({313e3aef-bdc9-4768-8f1f-b3beb175d781})|({387092cb-d2dc-4da5-9389-4a766c604ec2})|({0599211f-6314-4bf9-854b-84cb18da97f8})|({829827cd-03be-4fed-af96-dd5997806fb4})|({856862a5-8109-47eb-b815-a94059570888})|({1e6f5a54-2c4f-4597-aa9e-3e278c617d38})|({1490068c-d8b7-4bd2-9621-a648942b312c})|({18e5e07b-0cfa-4990-a67b-4512ecbae04b})|({3584581e-c01a-4f53-aec8-ca3293bb550d})|({5280684d-f769-43c9-8eaa-fb04f7de9199})|({5766852a-b384-4276-ad06-70c2283b4792}))$/", + "prefs": [], + "schema": 1576757432447, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1479009", + "why": "Malicious add-ons disguising as updates or useful add-ons, but violating data collection policies, user-control, no surprises and security.", + "name": "Firefox Update (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e3139834-609b-4726-8bfa-a28659ba14fc", + "last_modified": 1576771657161 + }, + { + "guid": "/^(({34364255-2a81-4d6e-9760-85fe616abe80})|({45621564-b408-4c29-8515-4cf1f26e4bc3})|({62237447-e365-487e-8fc3-64ddf37bdaed})|({7e7aa524-a8af-4880-8106-102a35cfbf42})|({71639610-9cc3-47e0-86ed-d5b99eaa41d5})|({78550476-29ff-4b7e-b437-195024e7e54e})|({85064550-57a8-4d06-bd4b-66f9c6925bf5})|({93070807-c5cd-4bde-a699-1319140a3a9c})|({11e7b9b3-a769-4d7f-b200-17cffa4f9291})|({22632e5e-95b9-4f05-b4b7-79033d50467f})|({03e10db6-b6a7-466a-a2b3-862e98960a85})|({23775e7d-dfcf-42b1-aaad-8017aa88fc59})|({85e31e7e-3e3a-42d3-9b7b-0a2ff1818b33})|({9e32ca65-4670-41e3-b6bb-8773e6b9bba8})|({6e43af8e-a78e-4beb-991f-7b015234eacc})|({57e61dc7-db04-4cf8-bbd3-62a15fc74138})|({01166e60-d740-440c-b640-6bf964504b3c})|({52e137bc-a330-4c25-a981-6c1ab9feb806})|({488e190b-d1f6-4de8-bffb-0c90cc805b62})|({5e257c96-bfed-457d-b57e-18f31f08d7bb})|({2134e327-8060-441c-ba68-b167b82ff5bc})|({1e68848a-2bb7-425c-81a2-524ab93763eb})|({8e888a6a-ec19-4f06-a77c-6800219c6daf})|({7e907a15-0a4c-4ff4-b64f-5eeb8f841349})|({a0ab16af-3384-4dbe-8722-476ce3947873})|({a0c54bd8-7817-4a40-b657-6dc7d59bd961})|({a0ce2605-b5fc-4265-aa65-863354e85058})|({a1f8e136-bce5-4fd3-9ed1-f260703a5582})|({a3fbc8be-dac2-4971-b76a-908464cfa0e0})|({a5a84c10-f12c-496e-80df-33386b7a1463})|({a5f90823-0a50-414f-ad34-de0f6f26f78e})|({a6b83c45-3f24-4913-a1f7-6f42411bbb54})|({a9eb2583-75e0-435a-bb6c-69d5d9b20e27})|({a32ebb9b-8649-493e-a9e9-f091f6ac1217})|({a83c1cbb-7a41-41e7-a2ae-58efcb4dc2e4})|({a506c5af-0f95-4107-86f8-3de05e2794c9})|({a02001ae-b7ed-45d7-baf2-c07f0a7b6f87})|({a5808da1-5b4f-42f2-b030-161fd11a36f7})|({a18087bb-4980-4349-898c-ca1b7a0e59cd})|({a345865c-44b9-4197-b418-934f191ce555})|({a7487703-02d8-4a82-a7d0-2859de96edb4})|({a2427e23-d349-4b25-b5b8-46960b218079})|({a015e172-2465-40fc-a6ce-d5a59992c56a})|({aaaffe20-3306-4c64-9fe5-66986ebb248e})|({abec23c3-478f-4a5b-8a38-68ccd500ec42})|({ac06c6b2-3fd6-45ee-9237-6235aa347215})|({ac037ad5-2b22-46c7-a2dc-052b799b22b5})|({ac296b47-7c03-486f-a1d6-c48b24419749})|({acbff78b-9765-4b55-84a8-1c6673560c08})|({acfe4807-8c3f-4ecc-85d1-aa804e971e91})|({ada56fe6-f6df-4517-9ed0-b301686a34cc})|({af44c8b4-4fd8-42c3-a18e-c5eb5bd822e2})|({b5a35d05-fa28-41b5-ae22-db1665f93f6b})|({b7b0948c-d050-4c4c-b588-b9d54f014c4d})|({b7f366fa-6c66-46bf-8df2-797c5e52859f})|({b9bb8009-3716-4d0c-bcb4-35f9874e931e})|({b12cfdc7-3c69-43cb-a3fb-38981b68a087})|({b019c485-2a48-4f5b-be13-a7af94bc1a3e})|({b91fcda4-88b0-4a10-9015-9365e5340563})|({b30591d6-ec24-4fae-9df6-2f3fe676c232})|({b99847d6-c932-4b52-9650-af83c9dae649})|({bbe79d30-e023-4e82-b35e-0bfdfe608672})|({bc3c2caf-2710-4246-bd22-b8dc5241693a})|({bc3c7922-e425-47e2-a2dd-0dbb71aa8423})|({bc763c41-09ca-459a-9b22-cf4474f51ebc})|({bd5ba448-b096-4bd0-9582-eb7a5c9c0948})|({be5d0c88-571b-4d01-a27a-cc2d2b75868c})|({be981b5e-1d9d-40dc-bd4f-47a7a027611c})|({be37931c-af60-4337-8708-63889f36445d})|({bea8866f-01f8-49e9-92cd-61e96c05d288})|({bf153de7-cdf2-4554-af46-29dabfb2aa2d})|({c3a2b953-025b-425d-9e6e-f1a26ee8d4c2})|({c3b71705-c3a6-4e32-bd5f-eb814d0e0f53})|({c5d359ff-ae01-4f67-a4f7-bf234b5afd6e})|({c6c8ea62-e0b1-4820-9b7f-827bc5b709f4})|({c8c8e8de-2989-4028-bbf2-d372e219ba71})|({c34f47d1-2302-4200-80d4-4f26e47b2980})|({c178b310-6ed5-4e04-9e71-76518dd5fb3e})|({c2341a34-a3a0-4234-90cf-74df1db0aa49})|({c8399f02-02f4-48e3-baea-586564311f95})|({c41807db-69a1-4c35-86c1-bc63044e4fcb})|({c383716f-b23f-47b2-b6bb-d7c1a7c218af})|({c3447081-f790-45cb-ae03-0d7f1764c88c})|({c445e470-9e5a-4521-8649-93c8848df377})|({c8e14311-4b2d-4eb0-9a6b-062c6912f50e})|({ca4fdfdb-e831-4e6e-aa8b-0f2e84f4ed07})|({ca6cb8b2-a223-496d-b0f6-35c31bc7ca2b})|({cba7ce11-952b-4dcb-ba85-a5b618c92420})|({cc6b2dc7-7d6f-470f-bccc-6a42907162d1})|({cc689da4-203f-4a0c-a7a6-a00a5abe74c5})|({ccb7b5d6-a567-40a2-9686-a097a8b583dd})|({cd28aa38-d2f1-45a3-96c3-6cfd4702ef51})|({cd89045b-2e06-46bb-9e34-48e8799e5ef2})|({cdda1813-51d6-4b1f-8a2f-8f9a74a28e14})|({ce0d1384-b99b-478e-850a-fa6dfbe5a2d4})|({ce93dcc7-f911-4098-8238-7f023dcdfd0d})|({cf9d96ff-5997-439a-b32b-98214c621eee})|({cfa458f9-b49b-4e09-8cb2-5e50bd8937cc})|({cfb50cdf-e371-4d6b-9ef2-fcfe6726db02})|({d1ab5ebd-9505-481d-a6cd-6b9db8d65977})|({d03b6b0f-4d44-4666-a6d6-f16ad9483593})|({d9d8cfc1-7112-40cc-a1e9-0c7b899aae98})|({d47ebc8a-c1ea-4a42-9ca3-f723fff034bd}))$/", + "prefs": [], + "schema": 1576757481582, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1479009", + "why": "Malicious add-ons disguising as updates or useful add-ons, but violating data collection policies, user-control, no surprises and security.", + "name": "Firefox Update (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4885bfb1-7859-4ebc-b42f-2545125a5522", + "last_modified": 1576771657157 + }, + { + "guid": "/^(({d72d260f-c965-4641-bf49-af4135fc46cb})|({d78d27f4-9716-4f13-a8b6-842c455d6a46})|({d355bee9-07f0-47d3-8de6-59b8eecba57b})|({d461cc1b-8a36-4ff0-b330-1824c148f326})|({d97223b8-44e5-46c7-8ab5-e1d8986daf44})|({d42328e1-9749-46ba-b35c-cce85ddd4ace})|({da7d00bf-f3c8-4c66-8b54-351947c1ef68})|({db84feec-2e1f-48f0-9511-645fe4784feb})|({dc6256cc-b6d0-44ca-b42f-4091f11a9d29})|({dd1cb0ec-be2a-432b-9c90-d64c824ac371})|({dd95dd08-75d1-4f06-a75b-51979cbab247})|({ddae89bd-6793-45d8-8ec9-7f4fb7212378})|({de3b1909-d4da-45e9-8da5-7d36a30e2fc6})|({df09f268-3c92-49db-8c31-6a25a6643896})|({e2a4966f-919d-4afc-a94f-5bd6e0606711})|({e05ba06a-6d6a-4c51-b8fc-60b461ffecaf})|({e7b978ae-ffc2-4998-a99d-0f4e2f24da82})|({e7fb6f2f-52b6-4b02-b410-2937940f5049})|({e08d85c5-4c0f-4ce3-9194-760187ce93ba})|({e08ebf0b-431d-4ed1-88bb-02e5db8b9443})|({e9c47315-2a2b-4583-88f3-43d196fa11af})|({e341ed12-a703-47fe-b8dd-5948c38070e4})|({e804fa4c-08e0-4dae-a237-8680074eba07})|({e8982fbd-1bc2-4726-ad8d-10be90f660bd})|({e40673cd-9027-4f61-956c-2097c03ae2be})|({e72172d1-39c9-4f41-829d-a1b8d845d1ca})|({e73854da-9503-423b-ab27-fafea2fbf443})|({e81e7246-e697-4811-b336-72298d930857})|({ea618d26-780e-4f0f-91fd-2a6911064204})|({ea523075-66cd-4c03-ab04-5219b8dda753})|({eb3ebb14-6ced-4f60-9800-85c3de3680a4})|({ec8c5fee-0a49-44f5-bf55-f763c52889a6})|({eccd286a-5b1d-494d-82b0-92a12213d95a})|({ed352072-ddf0-4cb4-9cb6-d8aa3741c2de})|({edb476af-0505-42af-a7fd-ec9f454804c0})|({ee97f92d-1bfe-4e9d-816c-0dfcd63a6206})|({f0b809eb-be22-432f-b26f-b1cadd1755b9})|({f5ffa269-fbca-4598-bbd8-a8aa9479e0b3})|({f6c543bf-2222-4230-8ecb-f5446095b63d})|({f6df4ef7-14bd-43b5-90c9-7bd02943789c})|({f6f98e6b-f67d-4c53-8b76-0b5b6df79218})|({f38b61f3-3fed-4249-bb3d-e6c8625c7afb})|({f50e0a8f-8c32-4880-bcef-ca978ccd1d83})|({f59c2d3d-58da-4f74-b8c9-faf829f60180})|({f82b3ad5-e590-4286-891f-05adf5028d2f})|({f92c1155-97b3-40f4-9d5b-7efa897524bb})|({f95a3826-5c8e-4f82-b353-21b6c0ca3c58})|({f5758afc-9faf-42bb-9543-a4cfb0bfce9d})|({f447670d-64f5-418f-9b4a-5352d6c8e127})|({f4262989-6de0-4604-918f-663b85fad605})|({fa8bd609-0e06-4ba9-8e2e-5989f0b2e197})|({fa0808f6-25ab-4a8b-bd17-3b275c55ff09})|({fac5816b-fd0f-4db2-a16e-52394b6db41d})|({fc99b961-5878-46b4-b091-6d2f507bf44d})|({fce89242-66d3-4946-9ed0-e66078f172fc})|({fcf72e24-5831-439e-bb07-fd53a9e87a30})|({fdc0601f-1fbb-40a5-84e1-8bbe96b22502})|({feb3c734-4529-4d69-9f3a-2dae18f1d896}))$/", + "prefs": [], + "schema": 1576757513203, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1479009", + "why": "Malicious add-ons disguising as updates or useful add-ons, but violating data collection policies, user-control, no surprises and security.", + "name": "Firefox Update (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "672db55e-49ce-4b00-88ab-46cc24277800", + "last_modified": 1576771657153 + }, + { + "guid": "/^((\\{666ff753-69f8-49da-8adf-8aa770d3e383\\})|(\\{51536072-b64d-4a34-bee0-49b8e8175bc1\\})|(addon@filestopdf\\.com)|(info@icdst\\.ir)|(\\{d22f9fb6-1488-4969-aa49-e3322d622b6a\\})|(\\{b9db7ded-77d6-489b-bc0e-570df3f3e527\\})|(\\{62d89bd6-0042-4342-a15f-fa0a8addfca9\\})|(\\{4d8dfb84-624e-48df-8eeb-60443b2afeee\\})|(\\{2b3ac8fa-f84c-4e8f-83b6-438735fb1406\\})|(\\{40999239-996a-48ee-835c-8a11f71e53eb\\})|(\\{6a670f43-0093-4f2c-a265-02754d9fcb42\\})|(\\{f8de8278-97b5-4da1-b810-658f5909cb3b\\})|(MiniSearch@Context)|(arksignermozillafirefoxextension@example\\.org)|(\\{8f073b13-8b8b-4fe1-bee3-ee7573420ddd\\})|(\\{dab4ccbf-7cc0-4611-9a49-e7975b4e35d4\\})|(\\{564f1733-6bfc-4a03-b7a7-4ddc79b4ec00\\})|(\\{4aa733fc-44b5-439a-b928-a90f0698ccd9\\})|(\\{1e5da97f-f7e6-4eff-9f59-5e933320929c\\})|(\\{c3acee78-2e6b-408a-a62b-3fd5765b5518\\})|(\\{d7ba7080-7f1d-466a-8c4f-6f12d2c31284\\})|(\\{42979ad6-680d-4bc6-84be-2c94eb3ce586\\})|(\\{9cb8385f-8f1d-40c3-b78c-d05e4f949ecc\\})|(\\{2e58d47d-b3e6-499a-83c4-536e3f8a9903\\})|(\\{8a554f9d-90fa-4fbd-aa53-8d03f3939baf\\})|(\\{267495e1-9990-4d67-bde6-f1774188c307\\})|(\\{eb4c0a5d-48e2-4984-ad4a-141b313a58d0\\})|(\\{ffbef674-f098-4406-be3f-e96987be1701\\})|(\\{cf0578ea-3b45-45f1-b8ff-5898106ded49\\})|(\\{5fbbbebd-ebb3-499a-b091-0d82ec5eff02\\})|(\\{f4065411-1929-4970-8516-bf875525e2a0\\})|(\\{40f2f6f9-e58d-47fa-bdb3-9fa0ff71ad03\\})|(\\{42f4554c-3e5f-4d21-98cf-3a3f5c968fa7\\})|(\\{c5ec86f0-ef3e-42c8-8537-bd111af62dd3\\})|(\\{fa20692f-ae7a-42c3-a98d-2e092679d455\\})|(\\{b4f5e691-5146-4a64-bf45-25126e673f72\\})|(\\{3c5c9a50-c00d-4a3c-9e6f-2785d3419cc0\\})|(\\{fb246a17-c9ff-4771-b9fb-9fc79477fc0b\\})|(\\{80409c03-07bf-4b33-b6c5-e6ef260478da\\})|(\\{196c766b-29e5-4de8-a35b-96a4c40a28c7\\})|(\\{d6f9a86d-bb00-4877-af5a-12ad3dba76b9\\})|(\\{73660d5e-451a-450c-8a6d-bb023252cf51\\})|(\\{70e7455b-6e99-49d1-97d5-0147c48af7d7\\})|(\\{f84d6912-ff8f-4e96-b3f4-72ba33e29584\\})|(\\{e9dc2e41-d607-4938-be21-a9f1faed2acc\\})|(\\{5c6da614-27de-4d9e-9c39-0e2517a8d31b\\})|(\\{3a379d30-7c89-4251-8158-8b7584ec7317\\})|(\\{fc32234f-df43-4afa-a2f0-be0e2753daba\\})|(\\{c7f39b82-24d0-4428-9348-afe7a2f88a45\\})|(\\{2b9c2d2c-a3bd-4837-b613-ce2bb5aefbe8\\})|(\\{7d0e4c77-d013-47f0-808c-028c98e3b10e\\})|(\\{f0eec3eb-367c-4ff1-9963-c211afad69ef\\})|(\\{bf2b8075-2aac-45e3-9351-0b3ad3dd708e\\})|(\\{632d24c3-7423-4384-a665-0bbd060c93c2\\})|(\\{bd4a9564-fe81-400f-817f-34ba2a8260b2\\})|(\\{810e482d-b665-47e2-b379-ec3467c20885\\})|(\\{ab4a0a76-c868-4d09-9c2a-eeb97cf9d619\\})|(\\{96af6f96-61e2-43a1-947c-6b64ae1ce4cc\\})|(\\{04506315-3a1e-438f-907e-911752779c4e\\})|(\\{84d60f20-bff3-4e8b-8f04-03772be12178\\})|(\\{b56e4968-96b5-4682-b5a2-0811d22de46a\\})|(\\{e231b78c-557a-4f25-8ebe-0e18b8250cd9\\})|(\\{ed8f73c8-837a-4f33-99e0-a732cd64b760\\})|(\\{d1e681c5-4a4a-48b6-9abc-94a7fef3ced0\\})|(\\{494316a8-ee80-4b55-bc54-7e8588c91ee0\\})|(\\{87ca6636-e805-4c4f-a18f-9ea116d7c54d\\})|(\\{13cbb51f-509a-4a48-91b4-a9d83dedacc8\\})|(cryptogenius@geniusvibez\\.org)|(\\{f6c2064a-4977-4a4b-bc5a-352e00a1f458\\})|(\\{3ead5ed5-287c-4e2e-b62f-c657fefb7535\\})|(\\{6b8288d6-ae78-4593-8867-def79fe67a0f\\})|(\\{0ddda455-fadc-43d5-91d1-d1ed196ad5c7\\})|(\\{532c54ab-68f0-4e14-9f5f-382c6528e094\\})|(\\{7b990d1b-6515-4b8d-b0b1-400263c66155\\})|(\\{aadc7d87-c1b9-4399-9de5-d72ecaff0979\\})|(epiplex500suite@gmail\\.com)|(afp\\.testmaster@orbium\\.com)|(\\{1d1fe9b8-775e-4e9b-99a0-6adcbf1fbe10\\})|(\\{4aee20b8-7deb-42d6-8444-a7b51ab9eabb\\})|(\\{8790c850-f399-4b6b-8a43-85c10c5cdbe3\\})|(\\{55488e07-6e49-4d3b-aec4-ee2e520a5ed4\\})|(\\{1608692a-88e8-4717-a776-7be334de6767\\})|(\\{8f137ffe-8aa6-4358-a1a8-210fa87e7e22\\})|(\\{0866afdf-a997-453d-91a9-7094f617cd65\\})|(\\{b1ffc188-73f6-4e92-95d0-a579164fbc6c\\})|(\\{3d776d4c-44f4-4924-97f0-e47429e384d3\\})|(\\{9d2cadf8-68e0-43db-b763-390969d86976\\})|(\\{a0b5a5aa-423d-4115-962e-768136b0fd14\\})|(\\{0d625dd9-5d09-49ce-a811-5ed9ae5ef12f\\})|(\\{7273612b-2c57-4058-a09f-e576e8921829\\})|(\\{70529e5c-b73d-4585-9573-6e21bd6e64de\\})|(\\{e7626ba4-02a1-40b5-8186-b49da9d46c8a\\})|(\\{81425682-f86d-42a7-a46e-f31d80ea32ce\\})|(\\{5349a75b-df13-45ac-b4d5-1384d3446288\\})|(\\{2ea5ae79-9f90-4b7a-bdc6-5a9c4ddf7e59\\})|(\\{c496f3dc-56db-41a8-a6d9-d9225231528c\\})|(\\{dd13f534-b713-4d14-a55f-b2e9a4946b1b\\})|(\\{26f3910e-2cff-4cf6-94ee-69169f9cae9e\\})|(web\\.testmaster@orbium\\.com)|(Linguify@LinguaNext\\.net)|(jmendez\\.developer@gmail\\.com))$/", + "prefs": [], + "schema": 1576757626491, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "96cd4440-6535-4d3a-a653-ae78abe6c312", + "last_modified": 1576771657149 + }, + { + "guid": "/^((\\{9c9cee47-6017-49ae-a8f5-36dacedfd9f5\\})|(inventoryspy@rebelsoftwarellc\\.com)|(\\{d5eb06b6-4f8c-4e98-9a46-180489db4bfb\\})|(\\{f539f4d9-5517-45a6-820e-91fd4d97ff24\\})|(\\{121701b4-d1f7-4328-80e7-34d7ce8d0d35\\})|(\\{af38d921-d151-4c46-a1f4-35f334bd5c2f\\})|(tvgitial@chmyway\\.com)|(\\{0b155b79-fa2d-453e-9a73-04b84a5f41db\\})|(\\{3833fb94-3b74-4ced-82d9-6e93cdcd073e\\})|(\\{9c3bbb7a-7c34-4073-9be9-241a3b10d521\\})|(\\{f06cfeac-c9d7-422d-b935-2e3cf7960a16\\})|(\\{9832fbba-ad56-41ad-9ef6-f91571fe0282\\})|(\\{887e6f78-084c-49da-9efe-410c6327dcfd\\})|(\\{48b31a31-0a1f-496c-8422-79991ec5f0d6\\})|(\\{32e7af7d-e621-4c6e-9fb5-ef31277a92b1\\})|(\\{29e731e0-47eb-40d3-a108-e3ba637ebd99\\})|(\\{265b0b17-caba-4eea-9822-fb669b603eee\\})|(\\{af6ecd70-5458-433c-a471-ca3221172ba2\\})|(\\{d9ac7759-d00e-4464-b110-362bcabf81d3\\})|(\\{4fb65352-528b-4f89-b66e-7fb6c4bd0a66\\})|(\\{588f4204-1a98-4ba0-b0d9-f48fe9a8c505\\})|(\\{5f553bb5-2139-4148-8cb6-a64f0664a148\\})|(\\{8d246dd2-72dc-4c84-aac2-c270a4beaae7\\})|(vk-downloader@addoncrop\\.com)|(privpow@gmail\\.com)|(home@bestools\\.net)|(themerapp11@gmail\\.com)|(privacysrched13@gmail\\.com)|(\\{1b9ad43b-33c7-49a1-91e3-de51de6392a7\\})|(\\{c92f123f-5300-4ca2-8e4b-2bdda8b60fd1\\})|(\\{5296e254-0b4e-4424-a044-9dbaa155e461\\})|(\\{ba633e33-46f7-402d-a98d-b6414baca4c8\\})|(\\{0e7f1d32-a30e-4be0-ad95-4433938d1aec\\})|(\\{1bc8ed9b-589a-45b8-90de-e372a98f7374\\})|(support@seogb\\.net)|(\\{9eeb49a7-2307-4eca-9ddc-946f29094497\\})|(\\{d8e7b5a5-6255-43b5-b371-8d76795705fd\\})|(\\{aa54cbfb-60a4-4aa0-a571-3594e718e9cf\\})|(\\{120023d0-20a4-4748-ad26-3de4a02452e7\\})|(\\{7472868d-16d8-45f8-bddd-6e32b5aac916\\})|(\\{177f7dc2-f9dc-4a79-bd4b-e6928bb86c82\\})|(\\{95313ed3-c9bf-46e0-baba-89e78b31709a\\})|(\\{54a9afac-8c97-4d7a-89a1-5f35858c2c5e\\})|(\\{914859e8-d803-4386-bd7b-10fc45586c3a\\})|(\\{8c965153-3a54-4b59-8b49-d89b1a46256b\\})|(\\{74eee9fc-da0e-4da1-8345-5a9b0be4012d\\})|(\\{6269bb9a-167f-4488-85b6-d3644bc5959e\\})|(\\{a0204e2a-ecf9-432c-a586-861172f77038\\})|(supportv8@seovpn\\.net)|(\\{52adc630-673b-44bb-9417-ab1ce1924465\\})|(version7@private\\.pitorr\\.com)|(version7\\.0@private\\.pitorr\\.com)|(version7\\.2@private\\.pitorr\\.com)|(version7\\.3@private\\.pitorr\\.com)|(\\{417a74a1-0d88-4240-8eab-53d1791f2bc0\\})|(\\{d7fe8027-22ef-4655-9c58-abcfb5b664fe\\})|(\\{9e666ac6-61f3-4e6f-bfe4-65162e35530e\\})|(\\{c6aed3ca-9da5-47fc-a828-9f5fa9aefe8c\\})|(\\{9f24dccf-d8b1-4316-90c9-3f516de2d2ef\\})|(\\{9d894603-93e5-4032-bc5e-3da1a50552d4\\})|(\\{00ca785e-6f23-4ca4-ab70-095a77a5de9e\\})|(\\{e233bed6-db39-40c4-b7f4-445e3239976c\\})|(\\{736a1e9c-8661-4f1c-9c93-396733caec96\\})|(\\{7ee265d9-0002-4f49-963b-93ccc9b0600d\\})|(addon@google\\.com)|(\\{a5bf6735-48b3-47bd-9258-e2d9dfe51467\\})|(\\{c6344599-1b07-444f-922b-7da1b12e086c\\})|(\\{e10cc63d-6a1d-4079-be47-ae0f6da9d821\\})|(\\{dc08bc05-97b7-4743-b433-fa5f85d3cf9f\\})|(\\{9eb8c864-3074-4469-8dbd-a0b212cbf8a5\\})|(\\{b43ab2ab-ca39-493c-b619-d93ddfc06440\\})|(\\{fab4cffc-c147-4bf0-8ba3-890609d47801\\})|(\\{45b8bc88-8c38-4447-9260-e0b2463cdcc6\\})|(\\{1938d918-a139-4881-8894-da9c8aff8ff0\\})|(\\{654b97fe-5daa-4314-a23c-2683ca5febb1\\})|(\\{e0b91972-8a76-4de0-bdaa-94418110552a\\})|(\\{8771d5d1-b560-41d0-8e0f-513238bc0153\\})|(\\{c49e5fd6-c3ea-47b6-85a8-d4d7cd4ac0d2\\})|(\\{7d7baa33-41c1-4001-b092-49a2d3ccdd16\\})|(\\{bb410263-f989-4bb3-a35d-fe96c07bc3be\\})|(\\{2e2d4e6a-e416-4e4c-9003-937e2c8016b1\\})|(\\{de458959-00f8-4f60-8d61-3fab157047e6\\})|(\\{9be4f97d-c6a1-42dd-a1c6-15adad9c2741\\})|(\\{59a3cf6c-584a-405a-b82d-c868f91d87e7\\})|(\\{7de1ff31-cf17-45b8-a55a-0e1608ac0627\\})|(\\{90375b4e-d778-442f-8113-212f3dbe9807\\})|(\\{8f3bf9d3-673a-421b-a326-9d8d6bcc4511\\})|(\\{8f250e7b-ea2a-4f86-ba1a-1bfef8ced945\\})|(\\{efca6935-6abb-4c28-acee-8f3ef1272640\\})|(\\{513bda76-dc3e-43f5-9f12-b9e85ac1006e\\})|(\\{9c5ea91f-d350-4521-b6d3-097d11fac75a\\})|(\\{302564ab-b478-4a60-a2a1-3aa79e3d4fa5\\})|(admin@fmt-tools\\.com)|(\\{efc8bdda-29d3-4212-8a61-6935da123d27\\})|(\\{5b3a9105-1845-4769-a64d-3c104fd07cae\\})|(\\{85b82e36-9da8-4c63-805f-5c6b994cd079\\})|(\\{d42fc5f0-9975-4955-887a-efb1d416762a\\})|(privacysecure@mybestprivacy\\.com)|(\\{205cd703-9bdc-49b6-a54b-b998de75a1fc\\})|(\\{8bcfc643-82ed-4cc0-89cb-3ed4bea284af\\})|(\\{1e2fbe56-fa52-4083-b128-91ecbdce727c\\})|(\\{b438893c-5d1d-48ee-87b0-6c9aa56d5689\\})|(\\{0f4c6635-5e84-44fd-afde-2755e2b920cf\\})|(\\{c6630e49-5bc9-4244-b70a-7a64db71228a\\}))$/", + "prefs": [], + "schema": 1576757655712, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "463ffae2-9ecf-483a-957a-9e57ed056b63", + "last_modified": 1576771657145 + }, + { + "guid": "/^((youtube-mp3-converter@addoncrop\\.com)|(\\{3c8a6237-d40f-4aa0-bc66-cb6dc0ded2d3\\})|(info@example\\.com)|(youtube-downloader@addon\\.partners)|(\\{9e8611a8-c441-4d6b-881f-d1d62c4f90a4\\})|(adb@stellarwind-android\\.de)|(\\{5364615d-4d6c-45cb-aee6-2db1b76006f7\\})|(\\{1bedde37-78d6-43e4-b3ab-54336f293345\\})|(\\{7853bd47-92eb-4734-8371-dad645895f23\\})|(\\{a878339f-334e-433d-b0ac-9c799c8a6df3\\})|(\\{5dd737e5-c9f4-4b1b-86f1-5a3aefecbe26\\})|(\\{5165c342-3181-49d1-bf23-2be2d8d8cd5f\\})|(\\{e94887d3-558f-415f-83d3-4500d0604e4e\\})|(\\{80f26b45-b5db-45df-b92b-dfedfccfe2d1\\})|(\\{ebaf3af1-cd42-44b0-8a0a-5adc22dbb152\\})|(\\{bce1fb2f-1bcd-4a25-a9cd-29cc3fe21dc4\\})|(\\{fc528fa4-3528-4575-93a0-f07428654105\\})|(\\{432a4cf4-8f0c-4ab5-b3e8-e78c17d4b9f8\\})|(\\{260bf76c-b563-41d1-8a7c-0f669306a9f0\\})|(\\{7346a6e5-aec7-43d1-9135-a3b99f538bf3\\})|(\\{22af4413-6bc5-47f4-824e-ab4f15452d02\\})|(\\{8f02443a-3e5e-4160-8eda-04e104dcd804\\})|(\\{b2580f2b-bd01-4f3d-af4c-89b8bf0d2d92\\})|(\\{9e5da5b0-02d9-45f0-869b-05014c2e70cd\\})|(\\{df8a06b4-e785-46d7-87c9-a32ac5700696\\})|(\\{6eeccf3d-479f-47ab-8b9e-3f8e98d6b1de\\})|(\\{d9f5995c-0f9f-4f61-99d3-bde3676f2fa6\\})|(\\{d7f04f66-50b5-47eb-9394-597e3f712c2f\\})|(blablatina@mailnesia\\.com)|(blablatinaa@mailnesia\\.com)|(mykibetesting@gmail\\.com)|(\\{c115630b-bf15-46d5-a5ae-b053e8a99ba9\\})|(mykibetesting@ghghgh\\.com)|(mykibetesting@testmyki\\.com)|(mykibetesting46\\.2@mykitext\\.com)|(\\{9917e548-009a-42bf-bfc7-54206e036ec7\\})|(\\{46e67e2e-236c-4ab9-82ad-12b412fa787f\\})|(\\{a1676ab3-281f-40d6-a636-ce19f7f5f859\\})|(\\{d43dfd69-7730-441d-937d-c437a6c437e6\\})|(\\{1fd94b47-2148-4b97-a4b5-623168904694\\})|(\\{a41a3038-1cc8-49ca-8390-a82f913e1b03\\})|(\\{8f554d4a-54b3-48e8-b2b9-883d80627758\\})|(\\{5d61791a-b908-44a2-9066-8ecd2f70a5bf\\})|(\\{5eaafeba-bb8b-487d-8a52-45862d820f4d\\})|(\\{a3cce15f-afbb-4a03-9ca9-60981827a93e\\})|(\\{dd1031d0-40ba-49f5-97e5-82f371d93ee7\\})|(\\{9f9e1012-2b04-4a3f-a4f9-bb841bc7a1be\\})|(\\{f0c68322-bd46-4993-a526-7791f702f086\\})|(\\{98a9898c-f2aa-4833-bc38-b3625647e49f\\})|(\\{75a72d8c-b801-4487-b83d-a210f2fab6b4\\})|(\\{75a72d8c-b801-4487-b83d-a210f2fab6b5\\})|(\\{dbc0aede-945c-4a9a-bc92-585c0e2835fd\\})|(\\{42d93019-2dd9-49a4-b386-af5d13dbb14c\\})|(\\{fd15bd74-4a8e-43e3-a5a6-8f7bfc316bfe\\})|(\\{9b7226b7-8a21-4dad-9766-7c94a06d69ae\\})|(nancyseopro@gmail\\.com)|(\\{b3ef58d1-d4eb-4e48-9eba-d17600bd2ce8\\})|(\\{9b99b90a-52e3-4e7b-a1e0-5c905bf86ca6\\})|(\\{ed754bf5-b0d0-4ae4-b19b-f240e591c213\\})|(\\{2800be5f-6e20-4b21-9a3c-eb5e94205d9f\\})|(\\{4321d5f9-9a06-4972-9480-6d32d0e9ed12\\})|(\\{2774d25b-053e-44a1-b903-4e86b5bbd2f8\\})|(\\{e155420f-e615-44bc-8d7e-bc73bd0e7ecc\\})|(\\{4e4ef05f-a637-4c4e-bd22-5533e0060ef9\\})|(\\{306f60e4-ef19-4037-9c82-c2898ff17432\\})|(\\{b9374b95-4938-46fa-9507-ef877582c0d2\\})|(\\{dc395849-97bb-4456-9b79-c263a83b40e0\\})|(\\{04ae852b-bbb5-475f-8b25-3da4b15893ba\\})|(\\{91f96cf6-7bde-474b-a0b1-dbfc60db6bdb\\})|(\\{b8aac9a1-08b9-47be-97a4-7e4e0462bdfa\\})|(\\{6db30b4c-0e2d-47d0-abe6-66b778818b42\\})|(\\{4e0ea2d4-786c-4c20-9f1f-12c3734de966\\})|(\\{98ec3e35-c63a-43db-aeb7-050b350f3036\\})|(\\{a6acb773-94f9-4440-9e48-393f50989f5f\\})|(\\{363faa67-80da-4378-a20f-d32492eaab78\\})|(\\{76d55e31-5870-45f3-8862-a7006951cedc\\})|(\\{a2ff5043-dc7b-4c20-b924-135349641b95\\})|(\\{a6cc4862-ac6d-41e9-b208-cf5b280aa41c\\})|(\\{88bfe254-1837-426c-8f48-15235d69663d\\})|(\\{0cffb3be-d34c-4e38-88cc-9e4c68aa5456\\})|(\\{543b980f-e6a4-4077-b633-64ba72cae82a\\})|(\\{d2f2d8e1-7809-44fa-8309-1d41fc09db31\\})|(cht@cobraro\\.com)|(\\{40aac576-c1cc-4ef4-9ff4-90016bf5b54e\\})|(mykibetesting46\\.18@mykitext\\.com)|(\\{afb010de-7961-41d0-ad72-6c6beb05e34c\\})|(\\{25d0b02c-3c47-11e9-8f6b-9be053c51313\\})|(\\{8a146307-6862-474c-b113-8ecf993f1cfd\\})|(\\{4c0f9c72-a81f-49bb-a921-ae24cca16d70\\})|(\\{bd3b1f37-4e61-46cd-b521-925eb3ad3c25\\})|(\\{8d0b1c60-ebb7-4b7b-ae63-c40cf90e68df\\})|(\\{80690353-afb7-455f-b93b-78c7acc19962\\})|(\\{ca593a7a-bd77-4abf-8014-d1fa89006c64\\})|(\\{850be3a2-ca5f-47ad-838c-fe39b006e0d1\\})|(\\{b865704b-ca40-4939-81f1-89ef3b4eca3b\\})|(\\{7ecf61aa-e961-4704-88ac-4d8547ac35e8\\})|(\\{c63d3200-dce7-4090-b383-140a906f9aa2\\})|(\\{14d487f8-6eaa-4bae-9cb4-f604a8e4ac89\\})|(\\{86131ede-587a-4900-8c8c-4779570bff3b\\})|(\\{02229a36-d71c-4999-a319-7a072017dd5c\\})|(\\{e31ad00a-45d1-4397-8b11-9eb0e160787f\\})|(\\{1491c081-4e0a-483f-bdc4-96f5a248438c\\}))$/", + "prefs": [], + "schema": 1576757686245, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e2dd31e1-4dfd-4dcc-ad4b-6d9ca4b7ed20", + "last_modified": 1576771657140 + }, + { + "guid": "/^((\\{df59cc82-3d49-4d2c-8069-70a7d71d387a\\})|(\\{ef78ae03-b232-46c0-8715-d562db1ace23\\})|(\\{4d12d613-99d9-4160-ae6c-5153e5ab0f3c\\})|(\\{41efa652-3fca-4f95-b828-8ed1fba66a84\\})|(\\{4c52e311-8d5b-4a10-95bc-b1b1e029c8d5\\})|(\\{cd627ef1-1059-4ed9-9217-1971806a9ed3\\})|(\\{c01e7fb9-2a24-4cf0-8562-896d53cd5a59\\})|(\\{83d91178-0a1e-4270-a901-b8e83aec3d0b\\})|(blabla@reallyimportantthing\\.com)|(\\{be2309ea-6113-4a99-bd3b-3b88c95cd41d\\})|(\\{02a5f8bb-9ffb-43c3-86d6-072d7316ff6d\\})|(\\{7e874309-fbf3-463b-8690-6ead9efccea0\\})|(\\{7420132f-055f-4f37-a962-3bf9ce42d798\\})|(\\{22c0c5ea-c554-498d-8a66-4c5b4be2f5e8\\})|(\\{ff95833e-5a0a-43cc-910a-e2859bc99ade\\})|(\\{92495a3e-062b-40e7-91e3-6b415c694c57\\})|(mykibetesting47\\.1@mykitext\\.com)|(\\{96cde09b-2d70-41ee-acf6-548728846d48\\})|(mykibetesting47\\.1000@mykitext\\.com)|(\\{98f556db-837c-489e-9c45-a06a6997492c\\})|(\\{bb19d814-d035-4f5d-b291-9b26e4a7fd37\\})|(\\{56a1e8d2-312d-4222-aca5-eee58e0f1234\\})|(\\{9cc56d18-c8fb-436a-861a-128cf2fc580c\\})|(\\{27eebcd1-7ef4-4de3-9d5e-db9e2348c990\\})|(\\{71d3ba6e-e461-44fa-8f27-02c6dbd9b653\\})|(connect@seovpn\\.net)|(\\{859bf655-0266-4f21-9c52-3bd156896259\\})|(\\{adc5c6d8-ac87-4b5f-b885-1adfd00265a7\\})|(\\{d826e099-f797-401f-b363-c03f29a8f9b7\\})|(\\{5621e792-bba7-4e66-a63b-56a9e145c089\\})|(\\{c3e4070e-7845-41fd-8996-8817bbc28790\\})|(\\{eb256992-3498-4262-9c7e-ac82f93aeaca\\})|(\\{da9d1606-dfde-429c-bb6a-63ebe5f8412c\\})|(\\{41e29257-1089-42c8-9d59-add5a0003ffa\\})|(seovpnv5@seovpn\\.net)|(direct-apk-s@eladkarako\\.com)|(\\{376063b4-f6b8-43e9-928f-437ad0ec5333\\})|(\\{b78bf75a-3cea-436b-b081-c9857f295618\\})|(\\{9b8ec882-b5dc-4df2-b9eb-097ba4dcb4b0\\})|(\\{8c0f6ff5-8ea2-4da0-be7b-e588baa02a03\\})|(\\{88295bde-3fc2-4bf9-8090-872d223f2f92\\})|(\\{67582ce8-8c5e-4207-9694-5b0ecc77887b\\})|(\\{db443bc5-4f91-4c82-a14a-298575ff6aba\\})|(\\{d7709ac2-bcde-4d15-a1a7-c459fe592d8e\\})|(\\{40fbe322-ca79-4472-9ac9-0d493f2623da\\})|(\\{77ca86a6-2042-4519-9cd5-4f5fd036d0eb\\})|(youtube-video-downloader@addoncrop\\.com)|(youtube-video-downloader-ff@addoncrop\\.com)|(\\{ed48a832-c064-4d79-bf2e-3c5b21242b3c\\})|(\\{1ce93624-96e6-449d-b038-3380db21cc48\\})|(\\{8957639f-8577-4df9-a1a7-f6d5a9fc9fcd\\})|(\\{c07d8367-6c2d-4a7d-a9ab-207b3104359f\\})|(\\{c4d3f949-60fd-45d5-9cc0-2501319bb60c\\})|(\\{b23bc17b-9fba-4a49-9afe-a1e9cd49c73b\\})|(\\{9f814c7c-0161-4cd4-9d19-50e203f5160c\\})|(\\{0a04ea15-85b4-40ac-a1d1-a6fb5967fb3b\\})|(\\{7bd5a0e9-e8c0-4e64-bda5-7b5310629777\\})|(unlazy@eladkarako\\.com)|(unsecure@eladkarako\\.com)|(xda@eladkarako\\.com)|(direct-filehippo@eladkarako\\.com)|(real-url@eladkarako\\.com)|(\\{63fcde93-baf6-40b5-892c-29cc34365b57\\})|(\\{2913327e-bd0b-4962-adce-2f44407fb521\\})|(\\{88f2e8f9-a7e1-4d68-901d-e5a9c805f4fa\\})|(\\{fd4fcb6a-cad2-43ab-b1c5-efd4d2102de8\\})|(youtube-mp3-converter-ff@addoncrop\\.com)|(\\{0a90b652-0de6-421a-bfee-1d2c5c3ee8fe\\})|(dropeextool@gmail\\.com)|(add_archive@ext\\.xpi)|(\\{47aa471f-3cf1-4a22-9c99-ab5baf326a60\\})|(\\{ac01f6a8-7c30-450b-9979-98d0c7795e11\\})|(\\{2c09761e-2c89-4537-b335-2bd30a7136c2\\})|(\\{c55ddee5-baab-4c90-9948-ca47033a82ce\\})|(\\{5cdb4845-56a8-45df-936b-de8279f2587d\\})|(\\{ea68dd52-2470-4d7c-b563-9a4c6d0e7eab\\})|(\\{01d33686-cab1-4f3b-b2ad-83bc1c35c60a\\})|(\\{fcb61a95-e861-4cf1-801b-f91c75c4714a\\})|(mg3a2mzdsug6fou4k8yk@mg3a2mzdsug6fou4k8yk\\.com)|(wh70nimqtlpwt951i12a@wh70nimqtlpwt951i12a\\.com)|(\\{b64109f0-5d2a-4c86-a38c-886589cb2703\\})|(legacy@context_search\\.com)|(\\{7b49d8bd-1e29-4887-9165-a272f553a154\\})|(\\{a49d6fa5-1967-43ed-b8bd-586f206326cd\\})|(\\{8b14f730-34fa-41e7-9590-d44df583a57f\\})|(\\{01dda21f-3b94-4297-b302-d92535b2e880\\})|(\\{6a33fb2c-3d3a-401f-be54-342ef4d57d1f\\})|(\\{887a4a9a-63ed-4af8-b82b-b68e9c23d39b\\})|(\\{6cae6de5-ff67-4682-a72b-39a1747fc682\\})|(\\{c5b5ac10-8592-4ee9-ad7b-003bb9706f87\\})|(\\{2fb3dc77-de0c-4e34-be3b-13a92bffe898\\})|(\\{82a92837-91de-4282-95d2-bda6d4ac682f\\})|(\\{dae5d3cf-42ba-40ad-85d9-482e25d4682c\\})|(\\{2328a4ad-9d5c-47fa-b580-c7d488bd7a7a\\})|(\\{deb5ba3b-3cf9-4333-99e3-ae2651a5297d\\})|(gespindola@unisuam\\.edu\\.br)|(\\{4518dbc6-fc51-4292-8d85-6eba66959dcb\\})|(\\{7864e12a-8628-415e-8ae8-5ac25edaa420\\})|(\\{b56e7db0-5504-4dd2-8c21-7fb645adae38\\})|(\\{c78ac5a8-0005-4cfb-8a01-5d116a225377\\})|(\\{468301ed-b30f-44f9-85aa-d5ff4c840008\\})|(\\{ae45b0a5-a02e-41df-8a2e-f745d7a7ede3\\})|(\\{f42b032c-3017-44d5-99a1-129524d02494\\}))$/", + "prefs": [], + "schema": 1576757713908, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0f3e4d6d-9b1c-4fe1-940a-2871943bac88", + "last_modified": 1576771657136 + }, + { + "guid": "/^((\\{7e81d973-d606-4f4b-a61f-9c37612c3947\\})|(\\{651d950b-9db7-445f-8263-2dd18265ed3b\\})|(\\{d408b96a-203d-462a-9577-84afb08aa951\\})|(\\{e1fc8acf-65d6-48a2-89d9-410a32bd86da\\})|(\\{1e9302af-a3e1-4c7c-8416-f814dd796cb9\\})|(\\{c4aba9b0-df06-4fc5-8e64-37f99df5e9e9\\})|(\\{24399e9e-50ef-47c3-a728-ebf3af91a0c6\\})|(\\{bdf866a5-ebdd-4e7d-b624-49b53be98a4b\\})|(\\{9ab09f29-f294-4bdd-a58a-c3abea54f032\\})|(\\{b678ee0d-0e1f-433d-8ce8-93e1b51b8834\\})|(\\{6ed0906f-7926-4fc2-ab60-1577631641b2\\})|(\\{0bed1847-54e3-40ac-9cee-0ffd6e4acf1f\\})|(\\{09041dc2-0caa-4955-bf87-de874a0c6b7b\\})|(\\{035ff918-be13-4906-bba3-25153c30b8c4\\})|(\\{e81cbba6-39d5-4f33-a6be-3b99545b54b1\\})|(\\{67d8820b-3078-4835-ab7a-11befb521d33\\})|(\\{c5ad3a58-824c-4db4-8f2c-33b91565417f\\})|(\\{6e289bc1-ab94-44be-adf7-deb1a85cf809\\})|(\\{f6a3d54b-bf6f-4878-881e-a521065412c4\\})|(latest-extension@surviv\\.io)|(\\{3ee8d899-be15-45e4-8c09-a1c10febfb4d\\})|(\\{7ccf5eca-d275-4967-939a-7b9b91d9e0f2\\})|(\\{cd8542c3-3c8e-4fa1-8463-4c5ea7aa25ce\\})|(\\{2994abd2-02a5-4c66-9fd2-38ad8d911324\\})|(\\{10a69518-499c-4339-a733-8ac27166a400\\})|(apzsqkoq0oxjal69l9zr@apzsqkoq0oxjal69l9zr\\.com)|(mbanzzhnky54hoe6jq62@mbanzzhnky54hoe6jq62\\.com)|(\\{ce926acf-d6ab-48b0-8e5d-82257de14011\\})|(\\{c42394d0-1c76-4e5e-8a94-f7c4a2de2ada\\})|(7eylx2qcpshoiehzcl2y@7eylx2qcpshoiehzcl2y\\.com))$/", + "prefs": [], + "schema": 1576757777185, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602022", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8f82683f-a1c2-4f61-bf9b-8f279d35f746", + "last_modified": 1576771657132 + }, + { + "guid": "/^((Astro_Connect_BHZNbNOGmL@www\\.astro-connect\\.com)|(Astro_Connect_HRMdSrojBR@www\\.astro-connect\\.com)|(Astro_Connect_lAOPFWMnqc@www\\.astro-connect\\.com)|(Astro_Connect_vMYMzqvERv@www\\.astro-connect\\.com)|(Astro_Daily_Online_1090c71e5da3cd9ab2d6af4985b43f5c@www\\.dailyastrotab\\.com)|(Astro_Daily_Online_clone_b82a3c2703d37e513b428067a34655d8@www\\.dailyastrotab\\.com)|(Astro_Daily_Online_clone_hqNzpeWZrm@www\\.astrodailyonline\\.com)|(Astro_Daily_Online_GzIzTViMnE@www\\.astrodailyonline\\.com)|(AstroDailyOnline\\.com_a7f59cbf31536249d41b97278115b17a@www\\.astrodailyonline\\.com)|(AstroDailyOnline\\.com_VqZjYRsphH@www\\.astrodailyonline\\.com)|(Bible_Quotes_97910bef6e96a7d0569f5fb44b2c607c@www\\.bible-quote\\.co)|(Bible_Quotes_clone_9699f1b771e162467f078c076eb9b695@www\\.bible-quote\\.co)|(Bible_Quotes_clone_wYibuualSr@www\\.bible-quote\\.co)|(Bible_Quotes_PHMyuAzCPa@www\\.bible-quote\\.co)|(blackfridaystoresco@www\\.blackfridaystores\\.co)|(Browse_Manuals_3ff31012f87f9a4c01e1ef723f48eff1@www\\.browsemanuals\\.co)|(Browse_Manuals_ca51c107572ab179fc1e8d4b652b19e1@www\\.browsemanuals\\.co)|(Browse_Manuals_clone_2dbeb0fd48d84ff3a5d3e3a7b5fc3756@www\\.browsemanuals\\.co)|(Browse_Manuals_clone_biPZDTWfln@www\\.browsemanuals\\.co)|(Browse_Manuals_clone_ksNBKBSmge@www\\.browsemanuals\\.co)|(Browse_Manuals_clone_lcMJgmivkD@www\\.browsemanuals\\.co)|(Browse_Manuals_clone_UaWIZDqLBJ@www\\.browsemanuals\\.co)|(Calculate_Fast_uWtHrQKgWv@www\\.nowcalculatefast\\.com)|(cheapflightfaresco@www\\.cheapflightfares\\.co)|(Check_Astro_Today_wkptBasvVf@www\\.checkastrotoday\\.com)|(Check_Astro_Today_YgMcUMJFVL@www\\.checkastrotoday\\.com)|(Check_Astrology_GGZGReDTnd@www\\.checkastrology\\.co)|(Check_Directions_RmiiKOUYTf@www\\.checkdirection\\.com)|(Check_Mails_clone_tdpeulgItk@www\\.check-mail\\.co)|(Check_Mails_iLVZDNXtnY@www\\.checkmailsnow\\.link)|(Check_Mails_ITnrNsNpcj@www\\.checkmailsnow\\.today)|(Check_Mails_Now_clone_oOzxmNgfQn@www\\.checkmailsnow\\.net)|(Check_Mails_Now_clone_vVWZzkqahk@www\\.checkmailsnow\\.net)|(Check_Mails_Now_ePyetjMzel@www\\.checkmailsnow\\.link)|(Check_Mails_Now_IfFzRcCuJa@www\\.checkmailsnow\\.online)|(Check_Mails_Now_OqRkgMLHGo@www\\.checkmailsnow\\.live)|(Check_Mails_Now_pHoBMgvLtK@www\\.checkmailsnow\\.net)|(Check_Mails_Now_QMsZiFhjjH@www\\.checkmailsnow\\.online)|(Check_Mails_Now_qOSRXKCsHK@www\\.checkmailsnow\\.net)|(Check_Mails_Now_vnjdpXGSWk@www\\.checkmailsnow\\.net)|(Check_Mails_vNOoNISpGM@www\\.check-mail\\.co)|(Check_Maps_3ec1ffc98971e67fa854369f9e3486d0@www\\.check-maps\\.co)|(Check_Maps_BezZcNfeab@www\\.check-maps\\.link)|(Check_Maps_bKaiUcBTpN@www\\.check-maps\\.net)|(Check_Maps_clone_RUjCUUeRwF@www\\.checkmaps\\.co)|(Check_Maps_KzXAqTbRZi@www\\.check-maps\\.co)|(Check_Maps_ogQkeJhGuf@www\\.checkmaps\\.net)|(Check_Maps_SwGvtYCSWM@www\\.check-maps\\.org)|(Check_Maps_uYfjFOesyI@www\\.check-maps\\.org)|(Check_My_Mails_iAClnrnvyj@www\\.checkmymails\\.online)|(Check_My_Mails_TYlXbVPRXn@www\\.checkmymails\\.info)|(Check_My_Mails_XvyJVrZWQX@www\\.checkmymails\\.info)|(Check_My_Mails_ZAVfjZCgnv@www\\.checkmymails\\.co)|(Check_My_Speed_Now_OsGaqlODan@www\\.checkmyspeednow\\.com)|(Check_My_Speed_PQiWezZbqs@www\\.checkmyspeed\\.co)|(Check_My_Speed_qvklQhzugJ@www\\.checkmyspeed\\.co)|(Check_Net_Speed_aAaAYSEawi@www\\.checknetspeed\\.online)|(Check_Net_Speed_EKnEHmaiYm@www\\.checknet-speed\\.today)|(Check_Net_Speed_IcfzRgdvIx@www\\.checknet-speed\\.today)|(Check_Net_Speed_rqJpoOAXqE@www\\.checknet-speed\\.today)|(Check_Net_Speed_SoVZYFExRY@www\\.checknetspeed\\.today)|(Check_Net_Speed_sXCrVEDGiO@www\\.checknet-speed\\.today)|(Check_Net_Speed_VQGJrJvlIm@www\\.checknet-speed\\.today)|(Check_Net_Speed_wCexanRKNz@www\\.checknetspeed\\.link)|(Check_Net_Speed_yTZgqCpFPi@www\\.checknet-speed\\.today)|(Check_Net_Speed_zCCNoLFHOF@www\\.checknetspeed\\.online)|(Check_News_clone_qPYbwwKpgF@www\\.checknews\\.co)|(Check_News_dGPwUXsQpF@www\\.checknews\\.co)|(Check_Speed_Test_KohJtFpgLB@www\\.checkspeedtest\\.co)|(Check_Speed_Test_XcYMLbMdlh@www\\.checkspeedtest\\.co)|(Check_Weather_clone_FJgKgEEkzT@www\\.checkweather\\.co)|(Check_Weather_clone_TwXCcjNBdE@www\\.checkweather\\.co)|(Check_Weather_Daily_65ec7723f6fbbe7786d60bd4e2643b1c@www\\.checkweatherdaily\\.com)|(Check_Weather_Daily_YxdeQsIzIP@www\\.checkweatherdaily\\.com))$/", + "prefs": [], + "schema": 1576757846784, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ff5d3b75-f26c-47cd-85df-32c193a8d002", + "last_modified": 1576771657128 + }, + { + "guid": "/^((Check_Weather_Details_26e944ef07a6d60d7e6e109df96aadc6@www\\.checkweatherdetails\\.co)|(Check_Weather_NkuDUpBOfy@www\\.checkweathertoday\\.com)|(Check_Weather_Today_1b92f68f44010297e79e167abdbbad90@www\\.checkweathertoday\\.com)|(Check_Weather_Today_2b5b41fcbe35242dced42efd82346b62@www\\.checkweathertoday\\.com)|(Check_Weather_Today_37f90d6081f0f87dde0eca340268dd06@www\\.checkweathertoday\\.com)|(Check_Weather_Today_421f2bbc2e93713f42200c4cf4184b2f@www\\.checkweathertoday\\.com)|(Check_Weather_Today_647dda58dab18a066b5edcd1bf82f244@www\\.checkweathertoday\\.com)|(Check_Weather_Today_8d106128d380304fbf7cf760284ed189@www\\.checkweathertoday\\.com)|(Check_Weather_Today_a6606d84c174672bdc97a40ae26a7329@www\\.checkweathertoday\\.com)|(Check_Weather_Today_a7ec7b0d21c0b719949374759fa27f9e@www\\.checkweathertoday\\.com)|(Check_Weather_Today_AGSeXIfuyC@www\\.checkweathertoday\\.com)|(Check_Weather_Today_c72d09cea0b53ffdd1a496e376b22696@www\\.checkweathertoday\\.com)|(Check_Weather_Today_cbc523bdd189c28af1505f104443a1cf@www\\.checkweathertoday\\.com)|(Check_Weather_Today_e448585b8ce80f90b04ad34b57c00d6e@www\\.checkweathertoday\\.com)|(Check_Weather_Today_ed0f6099cd2a8e05500657b43f897afc@www\\.checkweathertoday\\.com)|(Check_Weather_Today_EppkRAhhSW@www\\.checkweathertoday\\.com)|(Check_Weather_Today_f06201395c0dc5e1aa9fd68e2059dc18@www\\.checkweathertoday\\.com)|(Check_Weather_Today_fd6b11d8d8cad6bb654cf46a0e100cbe@www\\.checkweathertoday\\.com)|(Check_Weather_Today_FdVgvyMTJo@www\\.checkweathertoday\\.com)|(Check_Weather_Today_leAPNcsALh@www\\.checkweathertoday\\.com)|(Check_Weather_Today_LFHBoYhhIu@www\\.checkweathertoday\\.com)|(Check_Weather_Today_MJoPqcUdAA@www\\.checkweathertoday\\.com)|(Check_Weather_Today_neiEtvLsDn@www\\.checkweathertoday\\.com)|(Check_Weather_Today_NvAaeiHaWs@www\\.checkweathertoday\\.com)|(Check_Weather_Today_OGjNKxAeXE@www\\.checkweathertoday\\.com)|(Check_Weather_Today_uABqfeATcz@www\\.checkweathertoday\\.com)|(Check_Weather_Today_WKqJGOJnJg@www\\.checkweathertoday\\.com)|(Check_Weather_Today_xCOMPlhuXu@www\\.checkweathertoday\\.com)|(Check_Weather_Today_XmOwlHQNSl@www\\.checkweathertoday\\.com)|(Check_Weather_wktudKPpvh@www\\.checkweather\\.co)|(Check_Your_Mail_5697289aed4cf7b184d93e5c53117e07@www\\.check-yourmail\\.co)|(Check_Your_Mail_BZSHAsKTzJ@www\\.check-yourmail\\.co)|(CheckMailPro\\.net_CheZLENbVx@checkmailpro\\.net)|(checkmaps@www\\.check-maps\\.co)|(CheckNetSpeed\\.co_tkZoSRvsPg@www\\.checknetspeed\\.co)|(checknetspeedco@www\\.checknetspeed\\.co)|(Convert_Doc_Online_ptdllbDbTd@www\\.convertdoconline\\.com)|(Convert_File_JHjKeEFAkZ@www\\.convert-file\\.net)|(Convert_File_ocNpurmujk@www\\.myfileconverter\\.org)|(Convert_File_Online_JfGXnualjV@www\\.convertfileonline\\.net)|(Convert_Files_Now_8390db80d295eac08ca91cc1687ebf6d@www\\.convertfilesnow\\.co)|(Convert_Files_Now_afb77fba2ab7b055f10896e538ebd2f6@www\\.convertfilesnow\\.online)|(Convert_Files_Now_fzusJnJyIU@www\\.convertfilesnow\\.co)|(Convert_Files_Now_YEHBPCVZfb@www\\.convertfilesnow\\.online)|(Convert_Files_Online_cf2d8484b67f12d038789a618bae5465@www\\.convertfilesonline\\.co)|(Convert_Files_Online_RptGXHLRjE@www\\.convertfilesonline\\.co)|(Convert_My_Doc_771b3d45ecf0ab980ab0dfdcb9dc1c16@www\\.convertmydoconline\\.com)|(Convert_My_Doc_ofLnqzQpvZ@www\\.convertmydoconline\\.com)|(Convert_My_Document_ynCvryXblL@www\\.convertmypdf\\.online)|(Convert_My_File_0e081da9309e2ee8cbe6e5e1ca3373d1@www\\.convertmyfile\\.co)|(Convert_My_File_2451d924787cf4b696596f09fde42c2f@www\\.convertmyfiles\\.net)|(Convert_My_File_35bbde2206c3bb498f5033610ab82af6@disable\\.convertthefiles\\.online)|(Convert_My_File_68884222b56f8d972093a20f7d5fcf94@www\\.convertmyfile\\.co)|(Convert_My_File_e4a4b99e91c46c64d75d2795ac8e8d58@www\\.convertthefiles\\.online)|(Convert_My_File_GHurKBBhRg@www\\.convertmyfile\\.co)|(Convert_My_File_HlOKyXsynN@www\\.convertmyfile\\.co)|(Convert_My_File_sdaXbdSqCR@www\\.convertmyfiles\\.net)|(Convert_My_File_SiQYEMQoEO@www\\.convertthefiles\\.online)|(Convert_My_File_SJjGzfwuEU@www\\.convertthefiles\\.online)|(Convert_My_File_SzhPQAPUZF@www\\.convertmyfile\\.co)|(Convert_My_File_zOqNatevtM@www\\.convertmyfiles\\.net)|(Convert_My_Files_41f090cd741517aacdcc2c939b5ba94e@disable\\.convertmyfiles\\.net)|(Convert_My_Files_AcwjJWzydY@www\\.convertmyfiles\\.co))$/", + "prefs": [], + "schema": 1576757889686, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "92d2dcba-9f62-4b16-8472-843bc53efef5", + "last_modified": 1576771657124 + }, + { + "guid": "/^((Convert_My_Files_clone_AtaaUykZUt@www\\.convertthefiles\\.co)|(Convert_My_Files_clone_iipeyamXvF@www\\.convert-myfiles\\.link)|(Convert_My_Files_GcaMuSvchr@www\\.convertmyfiles\\.co)|(Convert_My_Files_ITTiqkJdem@www\\.convertmyfiles\\.link)|(Convert_My_Files_mOqWpDqFfS@www\\.convertmy-files\\.link)|(Convert_My_Files_ObjwXXGRmQ@www\\.convertmyfiles\\.net)|(Convert_My_Files_OdEReOXPeW@www\\.convertmyfiles\\.link)|(Convert_My_Files_WdZEfGNUyl@www\\.convertmyfiles\\.link)|(Convert_My_Files_ZSVwsGAvMB@www\\.convert-myfiles\\.link)|(Convert_The_File_ajAXskmxxC@www\\.convertthefile\\.co)|(Convert_The_File_decdQgcxcJ@www\\.convertthefile\\.co)|(Convert_The_File_lfLaRLbLCX@www\\.convertthefile\\.online)|(Convert_The_File_pVtEvdXWNp@www\\.convertthefile\\.co)|(Convert_The_File_YeFOCclGsc@www\\.convertthefile\\.co)|(Convert_The_File_yfsuYhYdaU@www\\.convertthefile\\.net)|(Convert_The_PDF_clone_52933220d40bf58153ba067b1c9c2a0c@www\\.convertthepdf\\.com)|(Convert_The_PDF_clone_929829a88637d12dbf08fd4f782a93b8@www\\.convertthepdf\\.com)|(Convert_The_PDF_clone_WZNDVMgflh@www\\.convertthepdf\\.com)|(Convert_The_PDF_clone_xmrWNxzVTQ@www\\.convertthepdf\\.com)|(Convert_The_PDF_e8cd3270f4b0eec8bd7ec39dfe2b411c@www\\.convertthepdf\\.com)|(Convert_Your_Files_546ea793afdb11f0c3cb3866dcb02379@www\\.convertyourfiles\\.co)|(convertfilestopdfcom@www\\.convertfilestopdf\\.com)|(convertmypdfco@www\\.convertmypdf\\.co)|(convertmypdfonline@www\\.convertmypdf\\.online)|(convertthepdfco@www\\.convertthepdf\\.co)|(convertthepdfcom@www\\.convertthepdf\\.com)|(Cook_With_Me_oYyEsUWTmq@www\\.cookwithme\\.co)|(Coupon_Club_App_20ef9a3e126b1c339a97a60672528026@www\\.couponclubapp\\.co)|(Coupon_Club_App_295813467b3bbbd03ed2574a3a379041@www\\.couponclubapp\\.co)|(Coupon_Club_App_clone_dMsjQwVamC@www\\.couponclubapp\\.co)|(Coupon_Club_App_clone_pahxCWSfWG@www\\.couponclubapp\\.co)|(Coupon_Club_App_clone_tLIovhcRov@www\\.couponclubapp\\.co)|(Coupon_Club_App_clone_UKQIyoDcSk@www\\.couponclubapp\\.co)|(Coupon_Club_App_clone_wgpiIAHAQx@www\\.couponclubapp\\.co)|(Coupon_Club_App_imfeKPFBRv@www\\.couponclubapp\\.co)|(Coupon_Club_App_vqwFoUyfpL@www\\.couponclubapp\\.co)|(Coupon_Daily_DGaEVQJCVN@www\\.coupondaily\\.today)|(Coupon_Dealer_rRkzTRGEcA@www\\.coupondealer\\.co)|(Coupon_Finder_Hub_8f0b12e405b7568785bcc785d6be30d2@www\\.couponfinderhub\\.com)|(Coupon_Saver_Plus_ffb1e922e2994e938b8f9aeff598c90d@www\\.couponsaverplus\\.co)|(Coupon_Saver_Plus_GTTzegkxqL@www\\.couponsaverplus\\.co)|(Coupon_Store_MlfSWWimyu@www\\.mycouponstore\\.co)|(Coupon_Store_Search_clone_AbsQujloBm@www\\.thecouponstore\\.co)|(Coupons_Flash_clone_TCfVLxCpbB@www\\.couponsflash\\.co)|(Coupons_Flash_fYMZkaoiDb@www\\.couponsflash\\.co)|(Coupons_Flash_WRjXiQAOwJ@www\\.couponsflash\\.co)|(Coupons_Magic_AuVOPUiczY@www\\.couponsmagic\\.co)|(Coupons_Magic_MaDWggNxen@www\\.couponsmagic\\.co)|(Coupons_Magic_ZebFYzkzoN@www\\.couponsmagic\\.co)|(Coupons_Tab_9a56fd9df10d162f000ef7dac2689e8e@www\\.couponstab\\.co)|(Coupons_Tab_sFSnaAFdDP@www\\.couponstab\\.co)|(Coupons_Test_uyywuEAPjT@www\\.couponstab\\.co)|(couponstore@www\\.coupon-store\\.co)|(CutePuppyWallapers\\.com_nicLwcUsfw@www\\.cutepuppywallapers\\.com)|(Daily_Astrology_fPoifgshwY@www\\.daily-astrology\\.online)|(Daily_Astrology_WPGvllFIRz@www\\.my-dailyastrology\\.net)|(Daily_Coupon_Finder_XefkJELubc@www\\.dailycouponfinder\\.co)|(Daily_Coupon_Store_cdowGuTgIr@dailycouponstore\\.co)|(Daily_Coupons_Tab_2c96becddbca0072a93db91266fbfefa@www\\.recipeboardplus\\.com)|(Daily_Coupons_Tab_HRcyzwBTWg@www\\.dailycouponstab\\.com)|(Daily_Deals_Club_yIvuSLfnXr@www\\.dailydealsclub\\.co)|(Daily_Easy_Recipe_OlMvwdldaK@www\\.dailyeasyrecipe\\.com)|(Daily_Easy_Search_95c6b38eb0f04a21212c678a43291d12@www\\.dailyeasysearch\\.com)|(Daily_Easy_Search_MVXtvNQzSj@dailyeasysearch\\.com)|(Daily_File_Converter_23db4a874fc066754c187eef894ccbe7@www\\.dailyfileconverter\\.com)|(Daily_File_Converter_25b0c49fa35ba08d5b33edd7b48f2c92@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_9d4c9a6f7a5c796c84a594619c1e6993@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_b4b0ea08aa61314c75baffd0246b9bcf@www\\.dailyfileconverter\\.com)|(Daily_File_Converter_clone_RDUYhKAKFi@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_clone_yuTWwUVfoL@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_cVmGYeldAg@www\\.dailyfileconverter\\.co))$/", + "prefs": [], + "schema": 1576757965088, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6944f445-4df8-4331-93da-1b32fc130849", + "last_modified": 1576771657120 + }, + { + "guid": "/^((Daily_File_Converter_ooPlASGwFM@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_QMxMSpDXIk@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_UufmFacjfR@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_vaLEkEAYJg@www\\.dailyfileconverter\\.co)|(Daily_File_Converter_VhHvpeSnpU@www\\.dailyfileconverter\\.com)|(Daily_Game_Search_EaLACFoFKS@www\\.daily-gamesearch\\.today)|(Daily_Game_Search_muvQxhbZNu@www\\.dailygamesearch\\.today)|(Daily_Game_Search_VSZsYHVLcK@www\\.dailygame-search\\.today)|(Daily_Game_Search_XpyakjSRnr@www\\.dailygamesearch\\.co)|(Daily_Games_40e88a4d6ecfd9f9af84e1e6c7d76ffe@www\\.gamesdaily\\.online)|(Daily_Games_53113db02f9dd623b086f331ade6d2a6@www\\.game-quest\\.co)|(Daily_Games_a3083ae69e261df3b7d43f5282b250d0@www\\.daily-games\\.co)|(Daily_Games_adebc8294b5d311f8d3b5abd659e09f6@disable\\.daily-games\\.co)|(Daily_Games_b0819ccb9f64203af68bb05e00f36c2a@www\\.dailygame\\.online)|(Daily_Games_BhJRTCwnnH@www\\.game-quest\\.co)|(Daily_Games_BYlaCwrbCa@www\\.dailygame\\.online)|(Daily_Games_c342718e3fb89df8a20b77f635ea0e0f@www\\.dailygame\\.online)|(Daily_Games_caf0a82b928c12c5d67d2e0d142acbcc@www\\.daily-games\\.online)|(Daily_Games_clone_MHxxZxsjgz@www\\.gamesdaily\\.co)|(Daily_Games_d859afcc6f811f0938e06c8b75ac15fc@www\\.gamesdaily\\.online)|(Daily_Games_e6da5a8f129fc23530fc715fd2f7b993@www\\.daily-games\\.today)|(Daily_Games_e858f8f836f17ab5e1c3b269639bc434@www\\.daily-games\\.co)|(Daily_Games_FHAIlDwdZF@www\\.gamesdaily\\.co)|(Daily_Games_IgqcabXnnH@www\\.dailygame\\.online)|(Daily_Games_NOgqDldYjE@www\\.daily-games\\.co)|(Daily_Games_qEgzrEJBZi@www\\.daily-games\\.co)|(Daily_Games_qFMKBgNfOc@www\\.gamesdaily\\.online)|(Daily_Games_vfGCbwKDjq@www\\.daily-games\\.online)|(Daily_Games_VoOHRAgAxg@www\\.daily-games\\.today)|(Daily_Games_vwRTUFvSbP@www\\.gamesdaily\\.online)|(Daily_Games_YoUYAafVuL@www\\.daily-games\\.co)|(Daily_Horoscope_Finder_f4f7f24d489880fa4779c5a4aff5278d@www\\.dailyhoroscopefinder\\.com)|(Daily_Local_Weather_RTvDyyEuxy@www\\.dailylocalweather\\.net)|(Daily_Mail_Tab_AMkImujKsG@www\\.dailymailtab\\.com)|(Daily_Mail_Tab_clone_BWhcKvnbJG@www\\.dailymailtab\\.com)|(Daily_Mail_Tab_clone_jkCRhwZjtG@www\\.dailymailtab\\.com)|(Daily_Mail_Tab_xMqjhKMBPX@www\\.dailymailtab\\.com)|(Daily_Net_Speed_gcVjCfMtcE@www\\.dailynetspeed\\.com)|(Daily_News_Reports_707e3c94519dc4f338e25415097fc409@disable\\.dailynewsreports\\.co)|(Daily_News_Reports_bc4cc8745c5c9113add6bbcc7a4e891d@www\\.dailynewsreports\\.co)|(Daily_News_Reports_MgfPHeboNM@www\\.dailynewsreports\\.co)|(Daily_News_Reports_RhfPUMZyKi@www\\.dailynewsreports\\.co)|(Daily_Package_Tracker_qJQSJblDIk@www\\.dailypackagetracker\\.com)|(Daily_PDF_Converter_cAlFsFtCmX@www\\.dailypdfconverter\\.com)|(Daily_Radio_Hub_4b6408c6028360c91e55070e8a8b6c07@www\\.dailyradiohub\\.com)|(Daily_Radio_Hub_CEgdHfrHfk@www\\.dailyradiohub\\.com)|(Daily_Recipe_Finder_LpHSZIWYdz@www\\.dailyrecipefinder\\.com)|(Daily_Recipe_Ideas_clone_xqrPqfTBif@www\\.dailyrecipeideas\\.co)|(Daily_Recipe_Ideas_dyhmXRtGGY@www\\.dailyrecipeideas\\.co)|(Daily_Recipe_Now_IXRimkpfYA@www\\.dailyrecipenow\\.com)|(Daily_Recipe_Search_wHQTXcltvc@www\\.dailyrecipesearch\\.co)|(Daily_Search_3e2f26bf46061e1fb6386bb2bc9fb3b1@www\\.dailysearch\\.co)|(Daily_Search_clone_RAfKSlJEDD@www\\.dailysearch\\.co)|(Daily_Search_Plus_b60c5575e4d5158e20036cb104c00bba@www\\.dailysearchplus\\.com)|(Daily_Search_Plus_f693ecbe1af3da784c31114acc402ec4@www\\.dailysearchplus\\.com)|(Daily_Search_Plus_oldtAgRAUL@www\\.dailysearchplus\\.com)|(Daily_Search_Plus_xCxtAmrZoI@www\\.dailysearchplus\\.com)|(Daily_Search_Web_f70aa46e9caf1e58a04aad7917c8d0aa@www\\.dailysearchweb\\.com)|(Daily_Search_Web_PvsRmlCiKL@www\\.dailysearchweb\\.com)|(Daily_Speed_Check_475174e16d0d170b447b7002797efade@www\\.dailyspeedcheck\\.com)|(Daily_Speed_Check_uqFEhmPbDE@www\\.dailyspeedcheck\\.com)|(Daily_Speed_Checker_1c6d34e9410cb2652228c18bed9eaf67@www\\.dailyspeedchecker\\.com)|(Daily_Speed_Checker_wcntWZhyvr@www\\.dailyspeedchecker\\.com)|(Daily_Transit_Guide_a4b1896ed15535c97cf0c19cb3960680@www\\.dailytransitguide\\.com)|(Daily_Transit_Guide_rOdLEeRGxw@www\\.dailytransitguide\\.com)|(Daily_Weather_Finder_94b59c3b4bf2e42f392be55572d25ac2@www\\.dailyweatherfinder\\.com)|(Daily_Weather_Finder_QhhPAMLPai@www\\.dailyweatherfinder\\.com)|(Daily_Weather_Forecast_cgMvSNkeBN@www\\.dailyweatherforecast\\.net))$/", + "prefs": [], + "schema": 1576757992072, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4acfb151-a1d2-4468-9794-5816fadef1b7", + "last_modified": 1576771657116 + }, + { + "guid": "/^((Daily_Weather_Forecast_clone_CSwGEuwNKr@www\\.dailyweatherforecast\\.co)|(Daily_Weather_Forecast_f7824fa0dfff1fdbe9f36ad33ecfa4b4@www\\.dailyweatherforecast\\.net)|(Daily_Weather_Forecast_kclkQdSlIO@www\\.dailyweatherforecast\\.co)|(DailyCoupons\\.store_zWkmEkoPgk@www\\.dailycoupons\\.store)|(DailyJobSearch\\.co_EKJtPSSMhB@www\\.dailyjobsearch\\.co)|(DailyJobsSearch\\.net_a2ba2cb41169ec0e7e1e381ae8cec363@www\\.dailyjobssearch\\.net)|(dailyjobssearch@www\\.dailyjobssearch\\.co)|(dailyjobssearchnet@www\\.dailyjobssearch\\.net)|(DailyLocalWeather\\.net_clone_jVyHpUvXAl@www\\.dailylocalweather\\.net)|(DailyLocalWeather\\.net_vzkDkoQoVN@www\\.dailylocalweather\\.net)|(DailyNetSpeedtest\\.co_HWGHkbkKGm@dailynetspeedtest\\.com)|(dailynewsupdatesinnet@www\\.dailynewsupdates\\.in\\.net)|(dailynewsupdatesonline@www\\.dailynewsupdates\\.online)|(DailyRecipeOnline\\.net_bVwwSbDKSk@www\\.dailyrecipeonline\\.net)|(DailyRecipeSearch\\.co_zXrOSYpFeH@www\\.dailyrecipesearch\\.co)|(DailyRecipeSearch\\.info_VJInpLJmig@www\\.dailyrecipesearch\\.info)|(Dictionary_Pro_App_HbjmnxLIZN@www\\.dictionaryproapp\\.com)|(Dictionary_Pro_PquztTZbLu@www\\.dictionarypro\\.co)|(Dictionary_Xpress_QeRWOLUqwj@www\\.mywordscribent\\.com)|(Directions_Finder_190cb6038fd06edbc48ea5423cc43227@www\\.finddirections\\.co)|(Directions_Finder_1e30de3303446cde1d03ee219a13f572@www\\.finddirection\\.online)|(Directions_Finder_868daddf032f443da0515e9c976159c9@www\\.finddirections\\.co)|(Directions_Finder_86fa782f7bd705c2e644aa07f6218661@www\\.finddirections\\.co)|(Directions_Finder_AcrlxGosjE@www\\.finddirections\\.co)|(Directions_Finder_BZDovhcQyV@www\\.findroutes\\.co)|(Directions_Finder_c005f28d896373d557e03e80f4b57d37@www\\.findroutes\\.co)|(Directions_Finder_e183b060f3f35940a9291cd2ed4791e3@www\\.finddirections\\.co)|(Directions_Finder_lHmoDZAZNo@www\\.finddirections\\.co)|(Directions_Finder_LKGiGUhRDf@www\\.finddirections\\.co)|(Directions_Finder_Now_NMzpPonhyb@www\\.directionsfindernow\\.com)|(Directions_Finder_QTCjmhKxhD@www\\.finddirection\\.online)|(Directions_Finder_V1_0c00deff96164188875cce59cb27ebb1@www\\.finddirections\\.co)|(Directions_Finder_V1_4668ec661e1b94a2474a1c1675158eb2@www\\.finddirections\\.co)|(Directions_Finder_V1_clone_BPDzngiFeD@www\\.finddirections\\.co)|(Directions_Finder_V1_clone_BPDzngiFeDic@www\\.finddirections\\.co)|(Directions_Finder_V1_clone_lkSfnVsTUN@www\\.finddirections\\.co)|(Directions_Finder_V1_clone_mPiHTaJZJi@www\\.finddirections\\.co)|(Directions_Finder_V1_de58f7b93a8d173bc071b3730806cf8c@www\\.finddirections\\.co)|(Directions_Finder_V1_ffc19350a43c8bf4db65930f95663016@www\\.finddirections\\.co)|(Directions_Finder_V1_xivwbBBnLX@www\\.finddirections\\.co)|(Directions_Found_1dc1261209c6647a8288498e180d5eef_2@www\\.directionsfound\\.com)|(Directions_Found_1dc1261209c6647a8288498e180d5eef_23@www\\.directionsfound\\.com)|(Directions_Found_1dc1261209c6647a8288498e180d5eef@www\\.directionsfound\\.com)|(Directions_Found_clone_hbtpEmPzTJ@www\\.directionsfound\\.com)|(Directions_Found_clone_hdjMdQYTvN@www\\.directionsfoundnt\\.com)|(Directions_Found_GLuiPTQCww@www\\.directionsfound\\.com)|(Directions_Found_mVBuOLkFzz@www\\.directionsfoundnt\\.com)|(Directions_Found_XmorLjjHtu@www\\.directionsfound\\.com)|(Directions_Quest_clone_qQtdSaDtmD@www\\.directionsquest\\.co)|(Directions_Quest_DuwqkuyNfU@www\\.directionsquest\\.co)|(Directions_Quest_FwNcHagZgz@www\\.directionsquest\\.co)|(Directions_Quest_XQDqehhuWR@www\\.directionsquest\\.co)|(Doc_Converter_0a0ede466240dc417f4f6d8594acd9a5@www\\.convertthepdf\\.co)|(Doc_Converter_Hub_sTIWsdHRFE@www\\.docconverterhub\\.com)|(Document_Converter_CZmoJDQssK@www\\.convertpdfnow\\.co)|(Driving_Directions_App_mUxSdxsgni@www\\.drivingdirectionsappn\\.org)|(Driving_Maps_Online_clone_jtpQCXsDzM@www\\.drivingmapsonline\\.com)|(Driving_Maps_Online_evmZdmROJS@www\\.drivingmapsonline\\.com)|(Easy_Directions_Now_a9718312f4e22a699b573a1cf508ccc9@www\\.easydirectionsnow\\.com)|(Easy_Directions_Now_TBmyGrTUZj@www\\.easydirectionsnow\\.com)|(Easy_Doc_Converter_393a2418db4856b1ec2d790e9b30ffc2@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_5a92e4df39328183b24ce0103f3dea19@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_79478769a5477116f6efd505f3b0cf15@www\\.easydocconverter\\.com)|(Easy_Doc_Converter_a1be8989ddea6e7543bea0b843c6a2ba@www\\.easydocconverter\\.com))$/", + "prefs": [], + "schema": 1576758021535, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "125bc0c5-76dc-4426-b682-1c321843d645", + "last_modified": 1576771657110 + }, + { + "guid": "/^((Everyday_Astro_YjPsaMBBWj@www\\.everydayastro\\.online)|(Everyday_File_Converter_ed35274eecd2df2822fa50636b6d9c2c@www\\.everydayfileconverter\\.com)|(Everyday_File_Converter_NldTeZANPx@everydayfileconverter\\.com)|(Everyday_Horoscope_App_zsZEtFjPki@www\\.everydayhoroscopeapp\\.com)|(Everyday_News_Tab_eebc4520552865e4fa1472681eeb5a3d@www\\.everydaynewstab\\.com)|(Everyday_News_Tab_zARNxTxEiu@www\\.everydaynewstab\\.com)|(Everyday_Radio_1f1b997a2d674af1b684dd5457ae5356@www\\.myeverydayradio\\.com)|(Everyday_Radio_mTDVrswpBm@www\\.myeverydayradio\\.com)|(Everyday_Recipe_Guide_566183664fdda4ba4e650fa5115c6cfe@www\\.recipeboardplus\\.com)|(Everyday_Recipe_Guide_tiorNZIoRi@www\\.everydayrecipeguide\\.com)|(Everyday_Weather_Forecast_2a934b9a097e1f619a7066c2530ef60f@www\\.everydayweatherforecast\\.com)|(Everyday_Weather_Forecast_sPyCkYArmT@www\\.everydayweatherforecast\\.com)|(EverydayAstro\\.net_kTOmUMbUtk@www\\.everydayastro\\.net)|(ext_11637@www\\.map-buddy\\.net)|(extensionid@www\\.dailynewsupdates\\.in\\.net)|(extensionid@www\\.moviequest\\.co)|(Fast_Mail_Tab_BfwMzfJAdm@www\\.fastmailtab\\.com)|(Fast_Mail_Tab_clone_LPzCObgNLo@www\\.fastmailtab\\.com)|(Fast_Map_Finder_ae4f5334a91035cf082f4c9e0983fc15@www\\.fastmapfinder\\.com)|(Fast_Map_Finder_YxRYGsJekN@www\\.fastmapfinder\\.com)|(Federal_Forms_clone_NgrjNsxcUN@www\\.easyformsfinder\\.com)|(Federal_Forms_EBqDdPvCFF@www\\.easyformsfinder\\.com)|(File_Convert_Plus_clone_juAtPozquc@www\\.fileconvertplus\\.com)|(File_Convert_Plus_JfRqzQtMeK@www\\.fileconvertplus\\.com)|(File_Convert_Pro_hOblzzFvJL@www\\.fileconvertpro\\.co)|(File_Converter_37ced8ee9475a347afab0d20660cef63@www\\.pdf-convertn\\.co)|(File_Converter_clone_aWSjJQAWwa@www\\.pdf-convertn\\.co)|(File_Converter_Hub_wnuDyqJFOe@www\\.fileconverterhub\\.com)|(File_Converter_Live_hWXrCtYfna@www\\.fileconverterlive\\.com)|(File_Converter_Tab_63009c33f9d202492393146d23df95d0@www\\.fileconvertertab\\.com)|(File_Converter_Tab_bEfXMQovol@info\\.fileconvertertab\\.com)|(File_Converter_Tab_bHaEkegUAR@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_8d6f907bab9ab53a83b5d8a4505433f4@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_cCZhwgyHDr@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_gNPUWukwdG@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_pweczfyyvv@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_qygWFXkHri@www\\.fileconvertertab\\.com)|(File_Converter_Tab_clone_XpUyfDDJsm@info\\.fileconvertertab\\.com)|(File_Converter_Tab_dd756ad1b37f999650834591ffd21464@www\\.fileconvertertab\\.com)|(File_Converter_Tab_deb5d97364d6e9ba4d04089f90afa043@www\\.fileconvertertab\\.com)|(File_Converter_Tab_V2_8fe7b393ec2d293e54abf1aada3bcb94@www\\.fileconvertertab\\.com)|(File_Converter_Tab_V2_clone_DrmwsZhTkw@www\\.fileconvertertab\\.com)|(File_Converter_Tab_XHmAqSTCbP@www\\.fileconvertertab\\.com)|(File_Converter_xdbvCaJxrb@www\\.myfileconverter\\.co)|(FileConvertPro_clone_gDJuPpeLjD@www\\.fileconvertpro\\.co)|(FileConvertPro_mLscTidBER@www\\.fileconvertpro\\.co)|(Find_Coupons_Daily_dxJFJCQcpS@www\\.finddaily-coupons\\.today)|(Find_Coupons_Daily_lIKYSwDOhr@www\\.findcouponsdaily\\.co)|(Find_Coupons_Daily_RjrQWQzreq@www\\.findcoupons-daily\\.today)|(Find_Coupons_Daily_UhahUhrtxP@www\\.find-couponsdaily\\.today)|(Find_Coupons_Daily_wUpPOFfkzc@www\\.findcouponsdaily\\.today)|(Find_Coupons_OWfYsAQras@www\\.findcoupons\\.live)|(Find_Daily_Coupons_6ea44d6d01a700e213070c692343e03d@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_71b6faf57612a3de273a55a228ec1703@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_a4c3252d270bd03507cfe77d8397e8b6@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_cf887ce0c30cf483939356fd9dc4753a@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_clone_sCMBPIejep@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_dBVmCxAeYJ@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_gSKACLrhGQ@www\\.finddailycoupons\\.com)|(Find_Daily_Coupons_sVYmYFumfX@www\\.finddailycoupons\\.com)|(Find_Daily_Games_gZnVxpnfzi@www\\.finddailygames\\.com)|(Find_Daily_Games_JHwXAakSOO@www\\.finddailygames\\.com)|(Find_Daily_Games_osIGANaBXk@www\\.finddailygames\\.com)|(Find_Daily_Games_rcRvgxUzAu@www\\.finddailygames\\.net)|(Find_Directions_euMftZJtWV@www\\.finddirections\\.co)|(Find_Easy_Directions_2252eb9a9264ef1685d45b8fe13f6a43@www\\.findeasydirections\\.com))$/", + "prefs": [], + "schema": 1576758100930, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a69a7be4-4f76-4c00-aa17-d1bbe66fd2db", + "last_modified": 1576771657102 + }, + { + "guid": "/^((Find_Easy_Directions_541d5ace0c28764eb8e88d98d0e28740@www\\.findeasydirections\\.com)|(Find_Easy_Directions_90d67b798ba3070d90d32d74ff70d1c8@www\\.findeasydirections\\.com)|(Find_Easy_Directions_gbailzSvPb@www\\.findeasydirections\\.com)|(Find_Easy_Directions_YiGOrfTNFT@www\\.findeasydirections\\.com)|(Find_Easy_Directions_zcYSFoeMgF@www\\.findeasydirections\\.com)|(Find_Jobs_Daily_clone_ZryioBAtLo@www\\.findjobsdaily\\.co)|(Find_Jobs_Daily_FpBesVkFoN@www\\.findjobsdaily\\.co)|(Find_Jobs_Daily_TxUobqrfTu@www\\.findjobsdaily\\.co)|(Find_Jobs_Daily_ZLJpRXDhXK@www\\.findjobsdaily\\.co)|(Find_Recipes_Online_kSjCdoBAFX@www\\.findrecipesonline\\.co)|(Find_Templates_Quick_LyRzafuNXE@www\\.findtemplatesquick\\.com)|(FindJobsDaily\\.net_ilHEYezmJM@www\\.findjobsdaily\\.net)|(FlirtyWallPapers\\.online_IEZjVbwbSl@www\\.flirtywallpapers\\.online)|(Forbes_Search_aOPKgBmHyw@forbes\\.dailynewsupdates\\.online)|(Free_Directions_Finder_KEDDaoHBQl@www\\.freedirectionsfinder\\.com)|(Free_Manuals_KkQAhLuDkf@www\\.browsemanuals\\.co)|(Game_Buddy_632c7a501a256d43af845a6c03d36c65@www\\.game-buddy\\.co)|(Game_Buddy_ef910a8045b370e3df14a73fb05c0ee1@www\\.game-buddy\\.co)|(Game_Buddy_emPnaqsxzR@www\\.game-buddy\\.co)|(Game_Buddy_ISrmuaRAHN@www\\.game-buddy\\.co)|(Game_Quest_3c4a7895681b0a9d0860d5397036f146@www\\.game-quest\\.co)|(Game_Quest_bd98408b6a98a0b19825f9ff5023f0e7@www\\.game-quest\\.co)|(Game_Quest_bTqWKJtOPG@www\\.games-quest\\.co)|(Game_Quest_clone_40442f8034b58a60951b7a3cdf5fe97a@www\\.game-quest\\.co)|(Game_Quest_clone_rajfrvipoU@www\\.game-quest\\.co)|(Game_Quest_GHFSWLkdAO@game-quest\\.com)|(Game_Quest_hMqLrbOzmC@www\\.game-quest\\.co)|(Game_Quest_NDifumcXqQ@www\\.games-quest\\.org)|(Game_Quest_nilKwpfZen@www\\.game-quest\\.co)|(Game_Quest_rXONqXzsZj@www\\.games-quest\\.org)|(Game_Search_BbtNWEfPKy@www\\.daily-gamesearch\\.today)|(Game_Search_eOUjptBQzk@www\\.gamessearch\\.live)|(Game_Search_eymrhMPxLu@www\\.dailygame-search\\.today)|(Game_Search_kZDJlntNQs@www\\.gamesearch\\.link)|(Game_Search_MdsPFfXaXl@www\\.game-search\\.link)|(Game_Search_mgkCKPquHh@www\\.dailygamesearch\\.co)|(Game_Search_mvoOwLJOeY@www\\.games-search\\.link)|(Game_Search_nKMbWrakmm@www\\.dailygamesearch\\.today)|(Game_Search_PDKlEPjhDw@www\\.games-search\\.link)|(Game_Tab_BoRsmjioSA@www\\.gametab\\.co)|(Game-Quest_WhVgUeMwZI@www\\.game-quest\\.co)|(GameQuest\\.website_IwplUMSUhd@www\\.gamequest\\.website)|(GameQuest\\.website_ZGhTUTKRhi@www\\.gamequest\\.website)|(Games_Daily_clone_UAnrFPWGxF@www\\.games-daily\\.net)|(Games_Daily_EVSeZYmPYK@www\\.games-daily\\.today)|(Games_Daily_nRQyywoJPm@www\\.games-daily\\.net)|(Games_Daily_SPRlOvPjlj_clone@www\\.games-daily\\.co)|(Games_Daily_SPRlOvPjlj@www\\.games-daily\\.co)|(Games_Daily_WnvPyaqCdX@www\\.games-daily\\.today)|(Games_Finder_Online_98e08661ac2ba2826d702b9de87c3fbc@www\\.gamesfinderonline\\.com)|(Games_Finder_Online_YCDTopLIiv@www\\.gamesfinderonline\\.com)|(Games_Quest_antJJCJcBk@www\\.games-quest\\.website)|(Games_Quest_clone_aTlPJPmJEm@www\\.games-quest\\.today)|(Games_Quest_OxdmpwtACa@www\\.games-quest\\.today)|(Games_Quest_qmlUmTihvS@www\\.gamesquest\\.today)|(Games_Quest_vnzuqLPUQQ@www\\.gamesquest\\.today)|(Games_Quest_WEpNvcmerl@www\\.games-quest\\.today)|(Games_Quest_wKWBKbLjpl@www\\.games-quest\\.today)|(Games_Quest_Zafeeizthl@www\\.games-quest\\.today)|(Games_Quest_ZVRwioHOnh@www\\.games-quest\\.online)|(Games_Search_FPDqSSnSlY@www\\.game-search\\.today)|(Games_Search_HAfeXALvGY@www\\.gamesquest\\.online)|(Games_Search_LSrSMudgNL@www\\.gamessearch\\.co)|(Games_Search_mDdAwBVasx@www\\.gamesearch\\.today)|(Games_Search_RgMSPqlluj@www\\.gamessearch\\.xyz)|(Games_Search_sfZySehUmo@www\\.gamessearch\\.online)|(Games_Search_shZkWBHwut@www\\.gamessearch\\.online)|(Games_Search_USoWCKOlGq@www\\.finddailygames\\.com)|(Games_Search_uXqDhnPDBw@www\\.gamesearch\\.today)|(Games_Search_vCVYFXcOnV@www\\.gamessearch\\.org)|(Games-Daily\\.net_NainTzjpve@www\\.games-daily\\.net)|(Get_Coupons_awAkTqqCxd@www\\.getcoupons\\.live)|(Get_Coupons_RGWEhnHIbr@www\\.getcoupons\\.live)|(Get_Directions_07d014a7ed5bba404eb7f2b1058ad2cb@www\\.getdirectionsmapsn\\.com)|(Get_Directions_CBakCNUUbo@www\\.map-finder\\.link)|(Get_Directions_clone_kxIvsTsDgE@www\\.getdirectionsmapsn\\.com)|(Get_Directions_HlEHEMFRkj@www\\.getdirectionsmapsn\\.com))$/", + "prefs": [], + "schema": 1576758133784, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f6c1160a-fa98-4541-bb03-cd6d02b825e3", + "last_modified": 1576771657097 + }, + { + "guid": "/^((Get_Directions_Quick_89a733fca5e8b5436c95fcec40ca5573@www\\.getdirectionsquickn\\.online)|(Get_Easy_Directions_b3f03018f3d77ab1327550851c2a2f94@www\\.geteasydirections\\.com)|(Get_Easy_Directions_FeEnVDLKMP@www\\.geteasydirections\\.com)|(Get_Easy_Maps_a92ae443f4d44fdbcf507f73934c5bff@www\\.geteasymaps\\.co)|(Get_Easy_Maps_clone_RsVkJokaAq@www\\.geteasymaps\\.co)|(Get_Easy_Maps_clone_TgEXtwRoQx@www\\.geteasymaps\\.co)|(Get_Easy_Maps_e66ef79b1701806812d764c8b2e87673@www\\.geteasymaps\\.co)|(Get_Easy_Maps_eUfwfdzBJN@www\\.geteasymaps\\.co)|(Get_Easy_Maps_f9341eebd729ecc7e5389769a7ac8c27@www\\.geteasymaps\\.com)|(Get_Easy_Maps_NoApMGqlhQ@www\\.geteasymaps\\.com)|(Get_Easy_Maps_PiUUlEtxCz@www\\.geteasymaps\\.co)|(Get_Easy_Maps_uopDIWsrOB@www\\.geteasymaps\\.co)|(Get_Easy_Maps_VZVoDVzTZP@www\\.geteasymaps\\.co)|(Get_Easy_Search_5a1bd32b3bb3d079d253b6fe048c6d4e@www\\.geteasysearch\\.com)|(Get_Easy_Search_b1272ab7a446984d2e1ddbf0b94016d0@www\\.geteasysearch\\.com)|(Get_Easy_Search_zjvfbWwXrA@www\\.geteasysearch\\.com)|(Get_Forms_Today_HmKpwNqqgw@www\\.getformstoday\\.com)|(Get_Local_Forecast_KSKPskMcZy@www\\.getlocalforecastn\\.com)|(Get_Map_Finder_clone_AVJpxOfDmZ@www\\.getmapfinder\\.link)|(Get_Map_Finder_clone_BGAfKOwTad@www\\.getmapfinder\\.link)|(Get_Map_Finder_clone_hwWbfqrHDh@www\\.getmapfinder\\.link)|(Get_Map_Finder_clone_YUnreTXyJo@www\\.getmapfinder\\.link)|(Get_Map_Finder_RDbuumWRlP@www\\.getmapfinder\\.link)|(Get_Map_Finder_vJqBDvfVEi@www\\.getmapfinder\\.link)|(Get_Map_Finder_zORQiNFbOh@www\\.getmapfinder\\.co)|(Get_Online_Converter_AbNmflhxCb@www\\.getonlineconverter\\.com)|(Get_Online_Converter_clone_255e60767a18e291b0b8b3cabd6f382c@www\\.getonlineconverter\\.com)|(Get_Online_Converter_clone_oCShEwyADp@www\\.getonlineconverter\\.com)|(Get_Online_Maps_clone_hGHOrvKQpE@www\\.getonlinemapsnow\\.com)|(Get_Package_Tracker_5a92260cd5155c5f26b16207532e9c5e@www\\.getpackagetracker\\.com)|(Get_Package_Tracker_clone_CztbvYsSLz@www\\.getpackagetracker\\.com)|(Get_Speed_Checker_CdqYfmDnVO@www\\.getspeedchecker\\.com)|(Get_Speed_Test_Ace_efd65663a0f689684ed5b60337c519ac@www\\.getspeedtestace\\.com)|(Get_Speed_Test_Ace_uNUzxBsIBO@www\\.getspeedtestace\\.com)|(GetOnlineConverter_62dc3ed61ba55b471bc5952d64f22e34@www\\.getonlineconverter\\.com)|(GetOnlineConverter_clone_hmwBWihZaf@www\\.getonlineconverter\\.com)|(Global_Weather_2abbd1bfe1f92474dc556a6abd23c97e@www\\.global-weathern\\.online)|(Global_Weather_qPjJvKHHjn@www\\.global-weathern\\.online)|(Go_Package_Tracker_CDyUCsNhYI@www\\.gopackagetracker\\.com)|(Go_Search_Easy_3b073071b9f9727f2a3c22961bc6aae7@www\\.gosearcheasy\\.com)|(Go_Search_Easy_fCSPxmpWqN@www\\.gosearcheasy\\.com)|(Go_Search_Easy_MTOfbhqqMt@www\\.gosearcheasy\\.com)|(Go_Video_Converter_clone_AtpxxXDpvq@www\\.govideoconverter\\.com)|(Go_Video_Converter_clone_b78d64ef3f6d32d836cb9cb45f64f3d4@www\\.govideoconverter\\.com)|(Go_Video_Converter_clone_ENqGVUhAYo@www\\.govideoconverter\\.com)|(Go_Video_Converter_cXhEAmNOBA@www\\.govideoconverter\\.com)|(Go_Video_Converter_daef20a38c38415e2819357035444fa2@www\\.govideoconverter\\.com)|(Go_Video_Converter_YLdugTFiBs@www\\.govideoconverter\\.com)|(holidaygiftingco@www\\.holidaygifting\\.co)|(Holy_Bible_daily_22c0372905eb878bf90eb726cf1c4cd7@www\\.holybibledaily\\.com)|(Holy_Bible_Daily_36d9839f134dba7393dcd1ce19e35114@www\\.holybibledaily\\.com)|(Holy_Bible_daily_clone_BwGilqVxow@www\\.holybibledaily\\.com)|(Holy_Bible_Daily_clone_TdHKbGqxyp@www\\.holybibledaily\\.com)|(Holy_Bible_daily_WRzbIpWLfR@www\\.holybibledaily\\.com)|(Horoscope_Finder_CQAkIhPhib@www\\.horoscopefinder\\.co)|(Hunt_New_Jobs_MQlcPgVQSY@www\\.huntnewjobs\\.com)|(Hunt_New_Jobs_TeypdaDdsy@www\\.huntnewjobs\\.com)|(Inbox_Tab_1549897502@www\\.inboxtab\\.com)|(Inbox_Tab_LTnzVZlfKv@www\\.inboxtab\\.com)|(Inbox_Tab_rOksOtIddG@www\\.inboxtab\\.com)|(Instant_Doc_Converter_1264fb8b31af7c6c3f56c4c33b825dab@www\\.instantdocconverter\\.com\\.removed)|(Instant_Doc_Converter_clone_LWgHiMFzyN@www\\.instantdocconverter\\.com)|(Instant_Doc_Converter_clone_OeEmGVRjdr@www\\.instantdocconverter\\.com)|(Instant_Doc_Converter_fe41778b25f65137ed606033eea89902@www\\.instantdocconverter\\.com)|(Instant_Doc_Converter_hMMLIeTKQH@www\\.instantdocconverter\\.com)|(Instant_Doc_Converter_UJTpBaxVPV@www\\.instantdocconverter\\.com)|(Instant_Doc_Converter_vZPKbExySk@www\\.instantdocconverter\\.com))$/", + "prefs": [], + "schema": 1576758169317, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9acbbf4c-ef5c-46d4-bc1d-f520b8ced595", + "last_modified": 1576771657093 + }, + { + "guid": "/^((Instant_Email_Login_3fff03c5bbd668d2f7093d5491f66518@www\\.instantemaillogin\\.com)|(Instant_Email_Login_WRGGywlQTy@www\\.instantemaillogin\\.com)|(Instant_Email_Plus_KAZiCvZFWB@www\\.instantemailplus\\.com)|(Instant_Inbox_clone_QKZmbMSVkA@www\\.instantinbox\\.co)|(Instant_Inbox_clone_QmVdpyuWwT@www\\.instantinbox\\.co)|(Instant_Inbox_clone_VlVOmivYUN@www\\.instantinbox\\.co)|(Instant_Inbox_clone_XLjUGcrTgI@www\\.instantinbox\\.co)|(Instant_Inbox_clone_YxmUgAsOrZ@www\\.instantinbox\\.co)|(Instant_Inbox_pzclglfDGq@www\\.instantinbox\\.co)|(Instant_Inbox_qnRYsobGem@www\\.instantinbox\\.co)|(Instant_Maps_clone_clone_zqdkjyMgdQ@www\\.checkmaps\\.co)|(Instant_Maps_ZefayBJOcD_test@www\\.checkmaps\\.co)|(Instant_Maps_ZefayBJOcD@www\\.checkmaps\\.co)|(Instant_Net_Speed_7291e707ced30428e6ed71611c3071ed@www\\.instantnetspeed\\.com)|(Instant_Net_Speed_iEjKvJreAi@www\\.instantnetspeed\\.com)|(Internet_Speed_Test_cf6c9b84dd607bc049898130dd8aa403@www\\.fastinternetspeedtestn\\.com)|(Internet_Speed_Test_RCgVciwcSD@www\\.fastinternetspeedtestn\\.com)|(Job_Search_Online_53e3be5488ce961c12d4341c2e3c830d@www\\.jobssearchonline\\.co)|(Job_Search_Online_clone_20bb80efe94b528ef6fa06730d560cbb@www\\.jobssearchonline\\.co)|(Job_Search_Online_clone_fUDgRmLxrl@www\\.jobssearchonline\\.co)|(Job_Search_Online_clone_jxgyVIROvt@www\\.jobssearchonline\\.co)|(Job_Search_Online_qNOPIVBSge@www\\.jobssearchonline\\.co)|(Job-Portal_dPNxZEZiQF@www\\.job-portal\\.co)|(Jobs_Now_jkAqvfwqNy@jobsnow\\.com)|(Just_Search_Easy_PJxNIOGbZS@www\\.justsearcheasy\\.com)|(justlovepetsonline@www\\.justlovepets\\.online)|(KC_Recipe_Finder_f8541026d8d56f16ba1db6664ac76ed8@www\\.testextension\\.online)|(LifeLock_Safe_Search_LDKQZxofGQ@searchsafe\\.lifelock\\.com)|(Live_Email_4cacc77ba79f2e88d808404b1487e27d@www\\.liveemail\\.co)|(Live_Email_clone_avmFyKjGHH_ach@www\\.liveemail\\.co)|(Live_Email_clone_avmFyKjGHH@www\\.liveemail\\.co)|(Live_Inbox_clone_HSqMlnurhx@www\\.liveinbox\\.co)|(Live_Inbox_ITUElbGJyq@www\\.liveinbox\\.co)|(Live_News_90155c3a670c030b75d27b2b060bab76@www\\.get-news\\.co)|(Live_News_ab96451cfc686d1903b9bd5bbefc58df@www\\.get-news\\.co)|(Live_News_Daily_coRwlwVUtL@www\\.livenewsdaily\\.net)|(Live_News_RoTkoRyzKW@www\\.get-news\\.co)|(Live_News_sQHxsfPWHn@www\\.get-news\\.co)|(Live_News_UPeYgtCNWH@www\\.get-news\\.co)|(liveemail@www\\.liveemail\\.co)|(LiveNewsDaily\\.co_Qrkavccepk@www\\.livenewsdaily\\.co)|(Local_Weather_Now_clone_CGdqWGvDuk@www\\.localweathernow\\.co)|(Local_Weather_Now_clone_PsfbncWCpz@www\\.localweathernow\\.co)|(Local_Weather_Today_af62b67de6656cd77ec1232ce28b0847@www\\.localweathertoday\\.co)|(Local_Weather_Today_b6bdb9471dfdbbe166ebba101bf87987@www\\.localweathertoday\\.co)|(Local_Weather_Today_clone_25d0f678c3de41078bfebfedb36f3e5e@www\\.localweathertoday\\.co)|(Local_Weather_Today_clone_ozBsMkxlfM@www\\.localweathertoday\\.co)|(Local_Weather_Today_clone_QFZOuAbpBy@www\\.localweathertoday\\.co)|(Local_Weather_Today_MoAsKEYZyC@www\\.localweathertoday\\.co)|(localweathertodayco@www\\.localweathertoday\\.co)|(localweathertodaynet@www\\.localweathertoday\\.net)|(Locate_Packages_JIjwUKeces@www\\.locatepackages\\.com)|(loginpro_bTqvLMpNXn@www\\.easy-maillogin\\.link)|(loginpro@www\\.loginemailpro\\.com)|(manifestdata_Check_clone_AraOARfaeL@hjm)|(manifestdata_Check_HFkEdcMQtX@hjm)|(manifestname@www\\.wallpaperonlinepro\\.com)|(Map_Buddy_svMNjHoVFQ@www\\.map-buddy\\.net)|(Map_Directions_Pro_hFzkEkDBXw@www\\.mapdirectionspro\\.co)|(Map_Directions_Pro_JAlSTUeoZW@www\\.mapdirectionspro\\.co)|(Map_Directions_Pro_vnuUbjddlF@www\\.mapdirectionspro\\.co)|(Map_Finder_GaEdNdyQoi@www\\.mapfinder\\.live)|(Map_Finder_Online_clone_HAzjiKHVFr@www\\.mapfinderonline\\.com)|(Map_Finder_Online_clone_vxRStAVXsj@www\\.mapfinderonline\\.com)|(Map_Finder_Online_DWFqZBeElh@www\\.mapfinderonline\\.com)|(Map_Finder_RIBbQLtJhY@www\\.map-finder\\.link)|(Map_Finder_ubFbNkPLbu@www\\.map-finder\\.co)|(Map_My_Journey_clone_dbZghybkTf@www\\.mapmyjourney\\.co)|(Map_My_Journey_gWimMkbjcf@www\\.mapmyjourney\\.co)|(Map_My_Journey_hwaFtGeeRF@www\\.mapmyjourney\\.co)|(Map_My_Journey_lOKajIjWRX@www\\.mapmyjourney\\.co)|(Map_My_Travel_3d7340d4339df15d23c823030d0b3dd4@www\\.mapmytravel1\\.co)|(Map_My_Travel_4c1060a52cb966087a862f50c66cff76@www\\.mapmytravel1\\.co)|(Map_My_Travel_66a1df798c97eecbcbc5ce97f5bb58ce@www\\.mapmytravel\\.co))$/", + "prefs": [], + "schema": 1576758213475, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f0cac174-d15f-427f-b310-9409e69a2ca6", + "last_modified": 1576771657088 + }, + { + "guid": "/^((Map_My_Travel_77440f39b0fc3ac74a9e894ff2e26617@www\\.mapmytravel\\.co)|(Map_My_Travel_ac398c1a8442efc53c141095c6e386f1@www\\.mapmytravel1\\.co)|(Map_My_Travel_b47128735efb1ae5232811bf674a5598@www\\.mapmytravel1\\.co)|(Map_My_Travel_c520fe83319115df915325944097f2d7@www\\.mapmytravel1\\.co)|(Map_My_Travel_clone_906668a067200f92f67c8a1d4934ca53@www\\.mapmytravel\\.co)|(Map_My_Travel_clone_agEGcivVyr@www\\.mapmytravel1\\.co)|(Map_My_Travel_clone_AojrINyqec@www\\.mapmytravel\\.co)|(Map_My_Travel_clone_CaXjHVvzvR@www\\.mapmytravel1\\.co)|(Map_My_Travel_clone_CmwkGssriz@www\\.mapmytravel1\\.co)|(Map_My_Travel_clone_KUUHhqDlkF@www\\.mapmytravel1\\.co)|(Map_My_Travel_clone_xjdUmTvEZE@www\\.mapmytravel\\.co)|(Map_My_Travel_clone_ZJilbRDhlx@www\\.mapmytravel1\\.co)|(Map_My_Travel_ea415ecadf91d96c596c61d748dfab3a@www\\.mapmytravel\\.co)|(Map_My_Travel_FGYxkNvlYu@www\\.mapmytravel1\\.com)|(Map_My_Travel_GnhWycuRWw@www\\.mapmytravel\\.co)|(Map_My_Travel_KoRBtjTOaQ@www\\.mapmytravel\\.co)|(Map_My_Travel_V2_1136cc214788f6e2c4fbd748c875f289@www\\.mapmytravel1\\.co)|(Map_Your_Way_dba6e11787a9a4c0ff802d30e2d84655@www\\.mapyourway\\.online)|(Map_Your_Way_RRrntXMhmV@www\\.mapyourway\\.online)|(Map-Buddy\\.net_YDYeEFqMMB@www\\.map-buddy\\.net)|(MapAssistant\\.net_IxpgMrxoal@www\\.mapassistant\\.net)|(Maps_Finder_clone_lXfNOQsASa@www\\.getmapfinder\\.com)|(Maps_Finder_clone_OHRJTSZWsp@www\\.getmapfinder\\.com)|(Maps_Finder_clone_qjbEfuxypy@www\\.getmapfinder\\.com)|(Maps_Finder_clone_sVixvNGfvq@www\\.getmapfinder\\.com)|(Maps_Finder_clone_XiwisRQHED@www\\.mapsfinder\\.co)|(Maps_Finder_cxamiyXEhG@www\\.maps-finder\\.co)|(Maps_Finder_ikDztWsXOS@www\\.mapsfinder\\.co)|(Maps_Finder_ISFXVmdcMZ@www\\.getmapfinder\\.com)|(Maps_Finder_NEJbQoRTxu@www\\.mymapfinder\\.co)|(Maps_Finder_OAXSzUFBAz@www\\.getmapfinder\\.com)|(Maps_Finder_Online_cb9ff9fa82db81028ec8b5a9c738c3d0@www\\.mymapsfinderonline\\.com)|(Maps_Finder_Online_fa0d46585631975dfefb9653fbdc5ae5@www\\.mapsfinderonline\\.com)|(Maps_Finder_Online_VBUnFyZArH@www\\.mapsfinderonline\\.com)|(Maps_Finder_OyeiUXSYyC@www\\.mymap-buddy\\.com)|(Maps_Finder_yiomudinei@www\\.mapsfinder\\.co)|(Maps_Now_1a47fbf8546a43d949f229efca9a2f34@www\\.mapsnow\\.co)|(Maps_Now_23f9f3cd0b483c22be85bb2568a2df4f@www\\.mapsnow\\.co)|(Maps_Now_85bb8ec9ca1c2baab2dc307af81964c2@www\\.mapsnow\\.co)|(Maps_Now_cbd62915225f7f059c5d15daa3a59cad@www\\.mapsnow\\.co)|(Maps_Now_clone_daDccVKFiR@www\\.mapsnow\\.co)|(Maps_Now_clone_eaIVjwJWMX@www\\.mapsnow\\.co)|(Maps_Now_clone_HmcRPOcjzE@www\\.mapsnow\\.co)|(Maps_Now_clone_QnqJIECgrq@www\\.mapsnow\\.co)|(Maps_Now_clone_VNlrSBkTlM@www\\.mapsnow\\.co)|(Maps_Now_clone_yDfyuWvMMp@www\\.mapsnow\\.co)|(Maps_Now_clone_YDpyMtVaLW@www\\.mapsnow\\.co)|(Maps_Now_clone_ZyNlPAcxdc@www\\.mapsnow\\.co)|(Maps_Now_tmnjrLnmFO@www\\.mapsnow\\.co)|(Maps_Now_uwqucqWEGq@www\\.mapsnow\\.co)|(Maps_Now_XSqhhyPFiH@www\\.mapsnow\\.co)|(Maps_Today_UoKEAlMYFh@www\\.mapmyjourneynt\\.co)|(mapsfinder@www\\.mapsfinder\\.co)|(Metric_Converter_Pro_437e424a1d8a287713fd991b4babb241@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_48e7e1cd7ea7a150ce498c7f4c1a8525@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_c1043f9d35c4300be27a136d2956b8bd@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_clone_b151705ba46c35bf8530f6fcccad39ff@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_clone_HoBPpUTrbw@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_clone_KaiswSnKUV@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_clone_SzGHQnHNkN@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_clone_WchvzpVULZ@www\\.metricconverterpro\\.com)|(Metric_Converter_Pro_rEvcfmsuJW@www\\.metricconverterpro\\.com)|(Movie_Delight_LVlUUJCTfd@www\\.movie-delight\\.com)|(Movie_Delight_xWRWlvWNGj@www\\.movie-delight\\.com)|(Movie_Hunt_GzAuVRhAph@www\\.moviequest\\.online)|(Movie_Hunt_ioLSsIUbVX@www\\.themoviesearch\\.org)|(Movie_Hunt_jbuqRPUUVt@www\\.moviehunt\\.today)|(Movie_Hunt_jHPrIKUDZi@www\\.moviehunt\\.today)|(Movie_Hunt_Online_jJicoNDurf@www\\.moviehunt\\.online)|(Movie_Maniac_2462069208dfe0b3d754ec0d11bf7b40@www\\.themoviemaniac\\.co)|(Movie_Maniac_a84cd1cd277e4886cbd535cafddb9248@www\\.themoviemaniac\\.co)|(Movie_Maniac_clone_4b2e8fc1efa469664fbfe3b1cfd600ce@www\\.themoviemaniac\\.co)|(Movie_Maniac_clone_bbe030355b07de5cd10dee52a60db3e3@www\\.themoviemaniac\\.co))$/", + "prefs": [], + "schema": 1576758242842, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b009e1d2-ed9c-42b3-b795-b63d15b15deb", + "last_modified": 1576771657084 + }, + { + "guid": "/^((Movie_Maniac_clone_FhKByuyzfD@www\\.themoviemaniac\\.co)|(Movie_Maniac_clone_OzkDkQoxcV@www\\.themoviemaniac\\.co)|(Movie_Maniac_clone_pNBsVugeLv@www\\.themoviemaniac\\.co)|(Movie_Maniac_qNRzbWPyVh@www\\.themoviemaniac\\.co)|(Movie_Quest_DgloGEcqoQ@www\\.movie-quest\\.online)|(Movie_Quest_DHlTecEnbH@www\\.moviequest\\.online)|(Movie_Quest_lSUIfjeATU@www\\.movie-quest\\.org)|(Movie_Quest_ogkgHKpiCu@www\\.movie-quest\\.org)|(Movie_Quest_oIbDMPkLHN@www\\.the-moviequest\\.today)|(Movie_Quest_OYXdMYNGlQ@www\\.moviequest\\.online)|(Movie_Quest_WwjFqvwace@www\\.moviequest\\.online)|(Movie_Search_4634ea60272d58007cb594bddf4c2d14@www\\.moviesearchtoday\\.com)|(Movie_Search_5adb28a06e6a557580f892b00df54fe9@www\\.moviesearchtoday\\.com)|(Movie_Search_5e2468caef8b002ec4af47d33347d946@www\\.moviesearchtoday\\.com)|(Movie_Search_6a2835fbcfa21f071a72cf4b1d768ee1@www\\.moviesearchtoday\\.com)|(Movie_Search_82956306d33016e1612f6b93238abb8c@www\\.movie-searchtoday\\.com)|(Movie_Search_amo_hosting_clone_cdvWgyZchs@staging\\.themovie-portal\\.com)|(Movie_Search_clone_BPUjteCIKH@www\\.moviesearchtoday\\.com)|(Movie_Search_clone_hOjSGlUSJc@www\\.themovie-portal\\.com)|(Movie_Search_clone_tUhUWKFWEy@staging\\.themovie-portal\\.com)|(Movie_Search_clone_yNPQCvYjiE@www\\.search-movie\\.today)|(Movie_Search_fce3642a62f1d43d671b9cc1edae2248@www\\.moviesearchtoday\\.com)|(Movie_Search_GnuaPoCHyn@www\\.moviesearchtoday\\.com)|(Movie_Search_HQkuRtYSBi@www\\.movie-search\\.today)|(Movie_Search_JsPzrSgfUw@www\\.moviesearchtoday\\.com)|(Movie_Search_ldxwfXRmwB@www\\.movie-search\\.live)|(Movie_Search_LyOzXmENOz@www\\.movie-search\\.today)|(Movie_Search_OoYYDEappx@www\\.moviesearch\\.today)|(Movie_Search_qQXnYBWtZL@www\\.movie-searchtoday\\.com)|(Movie_Search_qSoQoWSxLl@www\\.movie-hunt\\.co)|(Movie_Search_ryfmFVsOyx@www\\.movie-search\\.today)|(Movie_Search_usVRdBSJFC@www\\.moviesearchtoday\\.com)|(Movie_Search_VbiTYRjDIi@www\\.themoviesearch\\.co)|(Movie_Search_wEfUXCokzy@www\\.moviesearch\\.today)|(Movie_Search_wGdgcYcPQb@www\\.moviesearchtoday\\.com)|(Movie-Hub\\.info_OmWUKRcKuS@www\\.movie-hub\\.info)|(Movie-Quest_KJpvmWXXVL@www\\.movie-quest\\.co)|(Movie-Quest\\.info_eUjlCfSYnv@www\\.movie-quest\\.info)|(Movie-Quest\\.info_kdhRhuajuC@www\\.movie-quest\\.info)|(movie-quest\\.today_ftrItBjuLQ@www\\.movie-quest\\.today)|(MovieQuest_jyRMkNmSGC@www\\.moviequest\\.co)|(moviequest\\.today_JlDnGylmbG@www\\.moviequest\\.today)|(moviequest@www\\.moviequest\\.co)|(MusicQuest_IuWBQBhiuw@www\\.music-quest\\.co)|(mvquest@www\\.moviequest\\.co)|(My_Astro_Tab_8e89559e65a469daf6766bb75d4376ab@www\\.myastrotab\\.com)|(My_Astro_Tab_KaAEnbwPUx@www\\.myastrotab\\.com)|(My_Coupon_Store_ylKTlAUXbo@www\\.mycouponstore\\.co)|(My_Daily_Astrology_MqfKduyqcg@www\\.my-dailyastrology\\.net)|(My_Daily_Utilities_4ebdffa571404d80516d8d0f530af9a6@www\\.mydailyutilities\\.co)|(My_Daily_Utilities_clone_4d4f3e4318db94478d9302c598ef468c@www\\.mydailyutilities\\.co)|(My_Daily_Utilities_clone_TQYRFwiprp@www\\.mydailyutilities\\.co)|(My_Daily_Utilities_clone_wsrcjEDqNR@www\\.mydailyutilities\\.co)|(My_Daily_Utilities_iwpgTQIUFx@www\\.mydailyutilities\\.co)|(My_Directions_Finder_c406a6ac6a2b0638fcb489f1b776b903@www\\.mydirectionsfinder\\.net)|(My_Directions_Finder_clone_alDWaOiCNo@www\\.mydirectionsfinder\\.com)|(My_Directions_Finder_clone_iIYkMlrcwN@www\\.mydirectionsfinder\\.net)|(My_Directions_Finder_clone_mBlfabAHtF@www\\.mydirectionsfinder\\.com)|(My_Directions_Finder_clone_oIFvReYNwJ@www\\.mydirectionsfinder\\.net)|(My_Directions_Finder_clone_TUeunTaGWJ@www\\.mydirectionsfinder\\.net)|(My_Directions_Finder_ejopyTsDPc@www\\.mydirectionsfinder\\.com)|(My_Directions_Finder_RIUfITHqsm@www\\.mydirectionsfinder\\.com)|(My_Doc_Converter_a70620b85cb93c122c397d5a0d6d6c17@www\\.mydocconverter\\.net)|(My_Doc_Converter_hTuhuUWAbu@www\\.mydocconverter\\.net)|(My_File_Converter_InJpDFniec@www\\.myfileconverter\\.net)|(My_File_Converter_MOLroWzmvk@www\\.myfileconverter\\.net)|(My_File_Converter_uJJIGUseRO@www\\.myfileconverter\\.org)|(My_Mail_Center_clone_AOBAQIlpph@www\\.mymailcenter\\.co)|(My_Mail_Center_clone_JJSlJDujLf@www\\.mymailcenter\\.co)|(My_Mail_Center_StbPlXiGzU@www\\.mymailcenter\\.co)|(My_Map_Buddy_AHxkFzHUSq@www\\.mymap-buddy\\.com)|(My_Map_Buddy_aJhRltItzB@www\\.mymap-buddy\\.co)|(My_Map_Buddy_dzzNGYdgEy@www\\.mymapbuddy\\.link)|(My_Map_Buddy_lCpBZhZcug@www\\.mymap-buddy\\.co))$/", + "prefs": [], + "schema": 1576758270991, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c264de14-d096-45d7-afd6-4e03f3f0874e", + "last_modified": 1576771657080 + }, + { + "guid": "/^((My_Map_Buddy_LlytpwAxzJ@www\\.my-mapbuddy\\.link)|(My_Map_Buddy_omjWWfWnZV@www\\.checkmaps\\.live)|(My_Map_Buddy_QniENdnZwH@www\\.mymapbuddy\\.co)|(My_Map_Buddy_qwjuFaLDiS@www\\.my-mapbuddy\\.link)|(My_Map_Finder_216cea355b90081b2fa2b35228afa8cd@www\\.mymapfinder\\.net)|(My_Map_Finder_FQhLxNAhOb@www\\.mymapfinder\\.net)|(My_Map_Finder_HIAdZUsdqP@www\\.easymapsfinder\\.com)|(My_Map_Key_bfebaaffd5e89eb58d8eede5550b7657@www\\.mymapkey\\.com)|(My_Map_Key_PNKwkyDcSv@www\\.mymapkey\\.com)|(My_Map_Quest_amtyvbVOvw@www\\.mymapquest\\.co)|(My_Map_Tab_e4256b0cd83dd5dc7ffd2d6fa89bb3ca@www\\.mymaptab\\.co)|(My_Map_Tab_fQPfsnZVRi@www\\.mymaptab\\.co)|(My_Maps_Daily_2216a0fb9fa410e585f75583c84bb1fc@www\\.mymapsdaily\\.com)|(My_Maps_Daily_HTtwoSgSmO@www\\.mymapsdaily\\.com)|(My_Maps_Finder_mlqpWnYOjs@www\\.mymapsfinderonline\\.com)|(My_Maps_Now_wouVnzxahG@www\\.mymapsnow\\.co)|(My_Metric_Converter_AZzUIIKwJY@www\\.mymetricconverter\\.com)|(My_Net_Speed_clone_LEPCOrGXJt@www\\.my-netspeed\\.co)|(My_Net_Speed_dIUrWuilrC@www\\.my-netspeed\\.co)|(My_Net_Speed_hQVahLtcSK@www\\.mynet-speed\\.com)|(My_Net_Speed_keJWBLUIYD@www\\.mynet-speed\\.com)|(My_Net_Speed_XhDnHJmvAF_1@www\\.mynetspeed\\.co)|(My_Net_Speed_XhDnHJmvAF@www\\.mynetspeed\\.co)|(My_Net_Speed_YGdxRAMaly@www\\.mynetspeed\\.link)|(My_Quick_Directions_384f670ee31df7278a1df86a202697c2@www\\.myquickdirections\\.com)|(My_Quick_Directions_IoipCjCqHK@www\\.myquickdirections\\.com)|(My_Quick_Search_HHmoLNFNZK@www\\.myquicksearch\\.co)|(My_Radio_Plus_clone_xijbeLEKut@www\\.myradioplus\\.co)|(My_Radio_Plus_clone_yLODfWIUzo@www\\.myradioplus\\.co)|(My_Radio_Plus_huJGcbKJdz@www\\.myradioplus\\.co)|(My_Recipe_Digest_806bc9c3d627874ac6f6c953e1174d41@www\\.myrecipedigest\\.com)|(My_Recipe_Digest_TQhDINXjet@www\\.myrecipedigest\\.com)|(My_Recipe_Guide_4bc8a248146602106b25349ec50b7395@www\\.myrecipeguideonline\\.com)|(My_Recipe_Guide_ZlrLqLHAKb@myrecipeguideonline\\.com)|(My_Route_Planner_6bb055b2be5912b90cec3a2788636830@www\\.myrouteplanner\\.co)|(My_Route_Planner_ccdd62d844c1769d5bf4db308b3bc9c9@www\\.myrouteplanner\\.co)|(My_Route_Planner_clone_AFCMlCneMs@www\\.myrouteplanner\\.co)|(My_Search_Plus_91de35f6bc26e7f423a701dc4a7d9e6b@www\\.mysearchplus\\.co)|(My_Search_Plus_clone_7d6359f4c55d1f5c5a9af0304e7dcf34@www\\.mysearchplus\\.co)|(My_Search_Plus_clone_ejVWzjJsWF@www\\.mysearchplus\\.co)|(My_Search_Plus_clone_fBHEaWXiDZ@_123456www\\.mysearchplus\\.co)|(My_Search_Plus_clone_fBHEaWXiDZ@www\\.mysearchplus\\.co)|(My_Search_Plus_clone_zHyeSWPlyQ@www\\.mysearchplus\\.co)|(My_Search_Plus_zasNrdaCMb@www\\.mysearchplus\\.co)|(My_Search_Wizard_7f2979e35d2bd7dd61433bbca4e930bf@www\\.mysearchwizard\\.co)|(My_Search_Wizard_887bc211e6c12bcd4c59204553e75ed5@www\\.mysearchwizard\\.co)|(My_Search_Wizard_clone_CuEYdreCCG@www\\.mysearchwizard\\.co)|(My_Search_Wizard_clone_MfzUjTpTSj@www\\.mysearchwizard\\.co)|(My_Search_Wizard_clone_PhkuhYznxq@www\\.mysearchwizard\\.co)|(My_Smart_Search\\.co_uUlxGOYDUP@www\\.mysmartsearch\\.co)|(My_Smart-Search\\.co_moHloXmlvd@www\\.mysmart-search\\.co)|(My_Smart-Search\\.co_nVQpezjwFN@www\\.mysmart-search\\.co)|(My_Weather_Services_MTCOGCZgEo@myweatherservicesn\\.org)|(My_Weather_Tab_cdb3a5fdbcc268762efb7f1b9088a935@www\\.myweathertab\\.co)|(My_Word_Scribe_6ac6fa9173610517a627c5298a38891a@www\\.mywordscribe\\.com)|(My_Word_Scribe_clone_RZEMcCVoIU@www\\.mywordscribe\\.com)|(My_Word_Scribe_clone_TAZVHOvofp@www\\.mywordscribe\\.com)|(My_Word_Scribe_GlqExrIfvA@www\\.mywordscribe\\.com)|(My_Word_Scribe_wRguUlbptJ@www\\.mywordscribe\\.com)|(MyAstroFinder_clone_meuCieSJGq@www\\.myastrofinder\\.co)|(MyAstroFinder_QqSSwnxQpy@www\\.myastrofinder\\.co)|(MyCareer-Search\\.co_IcpqItVxAi@www\\.mycareer-search\\.co)|(MyCoupon-Finder\\.co_DatSgecXGq@www\\.mycoupon-finder\\.co)|(MyCouponFinder\\.co_ZMMzlIEann@www\\.mycouponfinder\\.co)|(mydailyastrologyco@www\\.mydailyastrology\\.co)|(mydailyastrologyonline@www\\.mydailyastrology\\.online)|(mydailynewsonline@www\\.mydailynews\\.online)|(MyMapBuddy\\.net_oVKQZzxNzk@www\\.mymapbuddy\\.net)|(MyMusicSearch\\.co_zsqyyUdIfE@www\\.mymusicsearch\\.co)|(mynetspeedco@www\\.mynetspeed\\.co)|(MySearchTab\\.co_QXqKBErWIz@www\\.mysearchtab\\.co)|(Net_Speed_bfa1870197a5380d1f00708341444fe4@www\\.testnetspeed\\.co)|(Net_Speed_f61989f67aeb3c5ed6180f49b2160ea1@www\\.net-speed\\.co)|(Net_Speed_imtTaleZMk@www\\.net-speed\\.co))$/", + "prefs": [], + "schema": 1576758309063, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a3e1bff4-2e6a-4a7f-a1e3-f7eff451655b", + "last_modified": 1576771657076 + }, + { + "guid": "/^((Net_Speed_Ninja_rJSmpzPQRq@www\\.netspeedninja\\.co)|(Net_Speed_nYAhXCpaOF@www\\.testnetspeed\\.co)|(Net_Speed_Plus_MnPDDtnfZh@www\\.checkmyspeednownt\\.com)|(Net_Speed_Test_b419d25436f7b7cb3458407498bae21f@www\\.netspeedtesthub\\.com)|(Net_Speed_Test_Hub_OOlSMbcwOe@netspeedtesthub\\.com)|(Net_Speedtest_8e202a4f66675f188c265a8ef11e47fa@www\\.netspeedtest\\.co)|(Net_Speedtest_clone_ikAadlLvLs@www\\.netspeedtest\\.co)|(Net_Speedtest_ysCFtRLxZn@www\\.netspeedtest\\.co)|(NetSpeedCalculator\\.net_mzMnaouenc@www\\.netspeedcalculator\\.net)|(netspeedproco@www\\.netspeedpro\\.co)|(New_Jobs_Quest_8f6247b00ffc5b1bb2f3f30fc73a9de5@www\\.newjobsquest\\.co)|(News__Precinct_75018cea70cf57e0ab66d312ef3a769d@www\\.newsprecinct\\.com)|(News__Precinct_sggnBoSvRu@www\\.newsprecinct\\.com)|(News_Daily_UBtJmysrDA@www\\.newstrackrnt\\.co)|(News_Headlines_bKwUmAeFcG@www\\.newsheadlinespro\\.com)|(News_Headlines_f70874554c3c22b5fec6f98e845d9b4d@www\\.newsheadlinespro\\.com)|(News_Headlines_SfZxlPzEgN@www\\.newsheadlinespro\\.com)|(News_Precinct_05544a082b3c01a286c66f7cf4251700@www\\.newsprecinct\\.com)|(News_Precinct_clone_5cd166a363627a7f453b1b69769f436d@www\\.newsprecinct\\.com)|(News_Precinct_clone_NeRHwQGodq@www\\.newsprecinct\\.com)|(News_Precinct_clone_PTdBCNrWPD@www\\.newsprecinct\\.com)|(News_Precinct_dLqSikAjQC@www\\.newsprecinct\\.com)|(News_Precinct_gzVnHZnzOy@www\\.newsprecinct\\.com)|(NewStackr_uNFYKFwAWP@www\\.NewsTackR\\.com)|(NewsTrackr_clone_qKtwLAHfjm@www\\.newstrackr\\.co)|(NewsTrackr_clone_vJczLpOPSO@www\\.newstrackr\\.co)|(NewsTrackr_e1032761f4eb1beb126fdface070fb37@www\\.newstrackr\\.co)|(NewsTrackr_iDEHLRYWqj@www\\.newstrackr\\.co)|(NewsTrackr_ZtEQVEonqX@www\\.newstrackr\\.co)|(newsupdatesinnet@www\\.newsupdates\\.in\\.net)|(no_EzdEKVkeZk@www\\.test10\\.com)|(Online_Coupon_Finder_njhVhBdgcg@onlinecoupon-finder\\.co)|(Online_Coupon_Finder_QuNkIQQUVz@www\\.onlinecouponfinder\\.net)|(Online_Coupon_Finder_SZuYIubYMN@www\\.onlinecouponfinder\\.net)|(Online_Doc_Converter_7d7d0b74adad02f39590d705822a70e2@www\\.onlinedocconverter\\.com)|(Online_Doc_Converter_qpIkrHZegO@www\\.onlinedocconverter\\.com)|(Online_Document_Converter_clone_xZguXlOjJE@www\\.convertmypdf\\.co)|(Online_Package_Tracker_TNAcobvKgm@onlinepackagetracker\\.co)|(Online_PDF_Converter_aJsvvVEBaO@www\\.myonlinepdfconverter\\.net)|(Online_PDF_Converter_kxVNqagGZG@myonlinepdfconverter\\.com)|(Online_Recipe_RURMecAQzt@www\\.onlinerecipe\\.co)|(Online_Recipe_VNtLUGYjpI@www\\.onlinerecipe\\.co)|(Online_Speed_Radar_clone_YlZLvnSGjM@www\\.onlinespeedradar\\.com)|(Online_Web_Search_clone_WjoqJJklvS@www\\.onlinewebsearch\\.co)|(Online_Web_Search_d1b9b3e703905b67931858f6f5aecdca@www\\.onlinewebsearch\\.co)|(OnlineRecipeSearch\\.info_pEdBAUaqCR@www\\.onlinerecipesearch\\.info)|(Package_Trace_JOGQGobPMj@www\\.trackpackagequicknt\\.com)|(Package_Tracker_02a37a57871ec5b57a0555403157dcf8@www\\.package-tracker\\.co)|(Package_Tracker_07a2a700968a7da88eb77b9c46279b1c@www\\.dailypackagetracker\\.com)|(Package_Tracker_3ce363c30f45ee0016862ac499be7ff5@www\\.packagetrackeronline\\.com)|(Package_Tracker_698579ac9c231e14ca69e1894f353396@www\\.dailypackagetracker\\.com\\.disabled)|(Package_Tracker_75a19df973eae7cf6e19c396c3973451@www\\.package-tracker\\.co)|(Package_Tracker_clone_FNZBehhrfr@www\\.package-tracker\\.co)|(Package_Tracker_EOXBmHOBkZ@www\\.package-tracker\\.co)|(Package_Tracker_Express_clone_cbOTQhTNgs@www\\.packagetrackerexpress\\.com)|(Package_Tracker_Express_ZxaTHkYQxF@www\\.packagetrackerexpress\\.com)|(Package_Tracker_fd87086da3f36037204f4b0bf23f1ea1@www\\.package-tracker\\.co)|(Package_Tracker_HaMksfdhUi@www\\.dailypackagetracker\\.com)|(Package_Tracker_Hub_clone_cfdJxpAmIT@www\\.packagetrackerhub\\.com)|(Package_Tracker_Hub_EreNcpVUoK@www\\.packagetrackerhub\\.com)|(Package_Tracker_kaiHEFsqvp@www\\.ThePackageTrack\\.com)|(Package_Tracker_KwwfXjTWsn@www\\.package-tracker\\.co)|(Package_Tracker_OHnRIWuvmH@www\\.packagetrackeronline\\.com)|(Package_Tracker_Online_b49f3fc69d90679955379dab26b2a6fa@www\\.packagetrackeronline\\.com)|(Package_Tracker_Online_cleZBgkKxj@www\\.packagetrackeronline\\.com)|(Package_Tracker_Online_EmvHhroAmG@www\\.packagetrackeronline\\.com)|(Package_Tracker_QYmRTqNSxY@www\\.getpackagetracker\\.com)|(Package_Tracker_Tab_50fa581c36cad6995f117782f5105636@www\\.packagetrackertab\\.com))$/", + "prefs": [], + "schema": 1576758357512, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4bc321ad-e23b-4ed3-86bf-a1a3a38fbf75", + "last_modified": 1576771657072 + }, + { + "guid": "/^((Package_Tracker_yJbpZEQECr@www\\.package-tracker\\.co)|(packagefollowercom@www\\.packagefollower\\.com)|(PDF_Converter_AQpxoneXlZ@www\\.pdf-convertn\\.co)|(PDF_Converter_Plus_ZgLTNbAZUb@www\\.pdfconverterplus\\.com)|(PDF_Pro_Converter_eTqIXytHWq@www\\.pdfproconverter\\.com)|(Pets_Wallpapers_SKWDasMHYY@petswallpapers\\.net)|(phototab@www\\.wallpaperonlinepro\\.com)|(Premier_Search_clone_CcAEnLGkmK@www\\.premiersearch\\.co)|(Premier_Search_clone_wfigyWtsdL@www\\.premiersearch\\.co)|(Privacy-Search\\.biz_cXHpGjbGct@www\\.privacy-search\\.biz)|(Privacy-Search\\.club_fNwUjmGvkM@www\\.privacy-search\\.club)|(Privacy-Search\\.company_gzqnuNM@www\\.privacy-search\\.company)|(Privacy-Search\\.in\\.net_UdIeydXEbw@www\\.privacy-search\\.in\\.net)|(Privacy-Search\\.info_BzTWanpKDM@www\\.privacy-search\\.info)|(Privacy-Search\\.link_DATRCktqwk@www\\.privacy-search\\.link)|(Privacy-Search\\.one_NPLmbtQaBI@www\\.privacy-search\\.one\\.disabled\\.com)|(Privacy-Search\\.online_PwQLiKaWGq@www\\.privacy-search\\.online)|(Privacy-Search\\.site_zYtZQgkEtf@www\\.privacy-search\\.site)|(Privacy-Search\\.space_AjqewVnBVW@www\\.privacy-search\\.space)|(Privacy-Search\\.store_vNAqbjRGGn@www\\.privacy-search\\.store)|(Privacy-Search\\.today_NRVdCCeOIa@www\\.privacy-search\\.today)|(Privacy-Search\\.website_JwizWmtjPu@www\\.privacy-search\\.website)|(Privacy-Search\\.xyz_clone_juEABfQNOl@www\\.privacy-search\\.xyz)|(Privacy-Search\\.xyz_elSDqUATPt@www\\.privacy-search\\.xyz\\.disabled\\.com)|(Privacy-Search\\.xyz_oIkblTLPSN@www\\.privacy-search\\.xyz)|(PrivacySearch\\.biz_bAwIbiJVAP@www\\.privacysearch\\.biz)|(PrivacySearch\\.club_MRWkilEqOZ@www\\.privacysearch\\.club)|(PrivacySearch\\.company_hOwSWgQKdn@www\\.privacysearch\\.company\\.disabled\\.com)|(PrivacySearch\\.company_NAKTVsEmEC@www\\.privacysearch\\.company)|(PrivacySearch\\.in\\.net_NxaHlhdTlz@www\\.privacysearch\\.in\\.net)|(PrivacySearch\\.link_EOhtykDnGr@www\\.privacysearch\\.link)|(PrivacySearch\\.me_QSSIQcOMtH@www\\.privacysearch\\.me)|(PrivacySearch\\.one_WUIHjcIjXh@www\\.privacysearch\\.one)|(PrivacySearch\\.one_zOkggTAokc@www\\.privacy-search\\.one)|(PrivacySearch\\.online_CYiUatShgl@www\\.privacysearch\\.online)|(PrivacySearch\\.space_YagHPcSikB@www\\.privacysearch\\.space)|(PrivacySearch\\.store_pJBFyyaDRx@www\\.privacysearch\\.store)|(PrivacySearch\\.website_cGZdtKrFao@www\\.privacysearch\\.website)|(PrivacySearch\\.xyz_ZEUMZLHcqv@www\\.privacysearch\\.xyz)|(privacysearchco_xXdgBRl@info\\.privacysearch\\.co)|(privacysearchco@info\\.privacysearch\\.co)|(PrivateSearchOnline\\.com_IhKdmYiVeK@info\\.privatesearchonline\\.com)|(Pro_Games_Online_HVwluXulrt@www\\.progamesonline\\.co)|(Puppies_World_trFanlsdqu@www\\.puppiesworld\\.info)|(Puppy_Wallpapers_ZOWpJDLXFj@www\\.puppywallpapers\\.net)|(Quick_Career_Search_WDeLsGajVb@www\\.quickcareersearch\\.co)|(Quick_Daily_Mail_SKeVfXlhFp@www\\.quickdailymail\\.com)|(Quick_Email_App_EFITirXRRs@www\\.quickemailapp\\.com)|(Quick_Email_Checker_7796dc8c3c9cde3502870b6054bb34db@www\\.quickmailchecker\\.com)|(Quick_Email_Checker_BQerPmohdb@www\\.quickmailchecker\\.com)|(Quick_Map_Tab_98eb6af566aadaea6dae58aa6d841a70@www\\.quickmaptab\\.com)|(Quick_Map_Tab_IJTJmbzNOx@www\\.quickmaptab\\.com)|(Quick_Maps_clone_hIZNOOMdWQ@www\\.getquickmaps\\.com)|(Quick_Maps_clone_xRcOZiPvYl@www\\.getquickmaps\\.com)|(Quick_Maps_Online_7bcf0c280d92934d11d0b449980a1937@www\\.quickmapsonline\\.com)|(Quick_Maps_Online_QKDpNHmDpD@www\\.quickmapsonline\\.com)|(Quick_Maps_pjWqGYHPRE@www\\.getquickmaps\\.com)|(Quick_Online_Directions_0364feaec027aaab80a920e79ac1800c@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_clone_8cfe33dcf14406257fca53f436aad1ff@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_clone_bFKYEkYGUO@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_clone_JcSWuSfSrw@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_clone_qswqvmCxux@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_d922039a0497633824e490148707ca71@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_eddee69bb3984d22264d57b63e6f0115@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_OFYSSUWMOH@www\\.quickonlinedirections\\.com)|(Quick_Online_Directions_Test_gbFqFQwwaZ@www\\.quickonlinedirections\\.com)|(Quick_Recipe_Search_QumKWdGMdB@www\\.quickrecipesearch\\.today)|(Quick_Search_Tab_Cbzrnmuqub@www\\.quicksearchtab\\.co))$/", + "prefs": [], + "schema": 1576758405945, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "402be073-072f-44d7-a557-7c98914dc0da", + "last_modified": 1576771657067 + }, + { + "guid": "/^((Quick_Speed_Checker_09b7f7d1acd76f23f1d887e60ddcc474@www\\.quickspeedchecker\\.com)|(Quick_Speed_Checker_clone_3db2cd8f8f3e192f57ca83da50451660@www\\.quickspeedchecker\\.com)|(Quick_Speed_Checker_clone_bhRDSCySFq@www\\.quickspeedchecker\\.com)|(Quick_Speed_Checker_sqZXXzzpsT@www\\.quickspeedchecker\\.com)|(Quick_Weather_Forecast_IHOnRApsey@www\\.quickweatherforecast\\.com)|(Quick_Weather_Updates_6277aeb2d83428ca469904f0886e0938@www\\.quickweatherupdates\\.com)|(Quick_Weather_Updates_yieimwNJEx@www\\.quickweatherupdates\\.com)|(QuickNews\\.co_vZLHaiMlVV@www\\.quicknews\\.co)|(QuickRecipeSearch\\.co_hqhPDvuKrF@www\\.quickrecipesearch\\.co)|(qwertyuiopasdf@www\\.xyz\\.com)|(Radio_Hub_Online_YfJreNCpww@www\\.radiohubonline\\.com)|(rakshitchoudhary8@gmail\\.com)|(rakshitchoudhary8@gmail\\.om)|(Recipe_Board_Plus_7484a472b0b409e4895321bdd72e1945@www\\.recipeboardplus\\.com)|(Recipe_Board_Plus_f92fd9f713f932491e785a977401bb7c@www\\.recipeboardplus\\.com)|(Recipe_Board_Plus_gpeHSqIYiT@www\\.recipeboardplus\\.com)|(Recipe_Board_Plus_hYpxDyhZpP@www\\.myrecipeguideonline\\.com)|(Recipe_Board_Plus_pfyXDonFTr@www\\.recipeboardplus\\.com)|(Recipe_Book_Search_d62d1a871fc0f1d38ecf6b7653ae688b@www\\.recipebooksearch\\.com)|(Recipe_Book_Search_MRJMOtSRZt@www\\.recipebooksearch\\.com)|(Recipe_Book_Tab_3deea22a93f8e99342510c9cb16ccdf4@www\\.recipebooktab\\.com)|(Recipe_Guide_Plus_41c1f7d66a164e59a4721902d4a0a6c5@www\\.recipeguideplus\\.com)|(Recipe_Guide_Plus_dHGfHpqpFD@www\\.recipeguideplus\\.com)|(Recipe_Online_gwFoqximpy@www\\.recipeonline\\.net\\.test)|(Recipe_Search_Guide_e1d73ceb736fe3f0cf7b0da6063a92b3@www\\.recipesearchguide\\.com)|(Recipe_Search_Guide_mbFGBVqpia@www\\.recipesearchguide\\.com)|(Recipe_Search_Plus_5bc244154d5ed1b0fa3e2ff8e788ddbd@www\\.recipesearchplus\\.com)|(Recipe_Search_Plus_FxKIeZgMHD@www\\.recipesearchplus\\.com)|(Recipes_by_Alot_klEAJdojHM@www\\.mydailyrecipes\\.co)|(sahil@searchprivacy\\.co)|(Satellite_Maps_Now_aPVhlrXyLu@www\\.satellitemapsnow\\.com)|(Say_Rosary_7fbf455ac8a8d9319ec8e9c888bbc8f0@www\\.sayrosary\\.com)|(Say_Rosary_IoTPeJHVYN@www\\.sayrosary\\.com)|(Search_Anonymous_3914bca7a10a50db91f284a844eeec2f@www\\.searchanonymous\\.co)|(Search_Anonymous_kHrtzTbIUW@www\\.searchanonymous\\.co)|(Search_Atlas_8c11f710a756258b85fce73f36080a00@www\\.searchatlas\\.co)|(Search_Atlas_a65471976592d8afb32ab1d355329f4f@www\\.searchatlas\\.co)|(Search_Atlas_fgPWjhrMII@www\\.searchatlas\\.co)|(Search_Atlas_JvMkNyzBew@www\\.searchatlas\\.co)|(Search_Beacon_App_eqehzqftmN@www\\.searchbeaconapp\\.com)|(Search_Center_clone_jwMueYZfAC@www\\.searchcenter\\.co)|(Search_Central_c4359af19cd8d0828733d75832e3bb87@www\\.searchcentral\\.co)|(Search_Direct_Pro_qkzlUJiGov@www\\.searchdirectpro\\.com)|(Search_Easy_008d08b3ff05afbcae6df5b92296a6d0@www\\.search-easy\\.co)|(Search_Easy_Go_577387e8b765f721486040e639398c71@www\\.searcheasygo\\.com)|(Search_Easy_Go_80d5131cc8b26c5c4af3f4aa83886f7f@www\\.searcheasygo\\.com)|(Search_Easy_Go_AvYrOIFWDF@www\\.searcheasygo\\.com)|(Search_Easy_Hub_2f45453af53121c146d7bf5c3dd914c3@www\\.searcheasyhub\\.com)|(Search_Easy_kDZUkxUbSP@www\\.search-easy\\.co)|(Search_Easy_Plus_3f7d63b42346d0eb02176aa32f33b9a4@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_8965c3185ad17bdd1c759078bcde92f0@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_8d7d969cf1d24004447fa0066e4dfd02@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_8f70d67adfbdd9aa623aa2057efffc8b@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_9e6663ee2c4803fe1e7bf20b90f26e96@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_aMOxRncWIJ@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_cdb1d9b83ae0801f912e8b73cac0836f@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_dedOWbqqXd@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_DHIlDaCkwl@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_dWFEmfADcn@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_eRdOVuRKeX@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_HlNUDYUqua@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_njXAdPCyfZ@www\\.searcheasyplus\\.com)|(Search_Easy_Plus_rrXYlzwRaJ@www\\.searcheasyplus\\.com)|(Search_Easy_Pro_20670965804c5d16d21370f657dcdca0@www\\.searcheasypro\\.com)|(Search_Easy_Pro_73aa350eceb1758d88bd3edb17b26a95@www\\.searcheasypro\\.com)|(Search_Easy_Pro_VDbJUdDuEi@www\\.searcheasypro\\.com)|(Search_Easy_Web_ad43024621fcd724bf50596e41f6852f@www\\.searcheasyweb\\.com))$/", + "prefs": [], + "schema": 1576758467216, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b0054d00-739f-43e5-a6a4-22a56fd09844", + "last_modified": 1576771657063 + }, + { + "guid": "/^((Search_Easy_Web_tvqEdWRwUI@www\\.searcheasyweb\\.com)|(Search_Express_CgqiRHXtYw@www\\.yoursearchexpress\\.com)|(Search_For_Maps_gLRdugmyXV@www\\.search-maps\\.net)|(Search_Plus_090a3f242292c36b80d33fd7e48aeb84@www\\.search-plus\\.co)|(Search_Plus_App_2d6fef8895df8a9a67c9bd3b15438abf@www\\.searchplusapp\\.com)|(Search_Plus_App_zZmjIdjudH@www\\.searchplusapp\\.com)|(Search_Plus_lPNIzNDSuX@www\\.search-plus\\.co)|(Search_Plus_Now_04ba3f2b13d728af94433e016d46239d@www\\.searchplusnow\\.com)|(Search_Plus_Now_312928a70ba3909e221b0524753bf126@www\\.weatherforecasttracker\\.com)|(Search_Plus_Now_7a64f4abfa7dba3075cabf9896c8fa19@www\\.searchplusnow\\.com)|(Search_Plus_Now_81c0506ff8d40fb8a7daa44222962215@www\\.searchplusnow\\.com)|(Search_Plus_Now_clone_sgREjdpaZx@www\\.searchplusnow\\.com)|(Search_Plus_Now_clone_SMpbwlnYxa@www\\.searchplusnow\\.com)|(Search_Plus_Now_fuKTpdNeOn@www\\.mapmytravel\\.co)|(Search_Plus_Now_RUxZwdNBtU@www\\.searchplusnow\\.com)|(Search_Plus_Now_YQIFAskSvi@www\\.weatherforecasttracker\\.com)|(Search_Plus_Pro_248f4aa071807a8d8f35bf57aef7b217@www\\.mapmytravel\\.co)|(Search_Plus_Pro_c5202d6e9fff0fb53512d681772f0244@www\\.searchpluspro\\.com)|(Search_Plus_Pro_cBmYtdkkFb@www\\.searchpluspro\\.com)|(Search_Plus_Pro_clone_egAEtDTMkX@www\\.searchpluspro\\.com)|(Search_Plus_Pro_clone_KRXJEhDHND@www\\.searchpluspro\\.com)|(Search_Plus_Pro_clone_odzZVUScjD@www\\.searchpluspro\\.com)|(Search_Plus_Pro_clone_SpGuCbnjPE@www\\.searchpluspro\\.com)|(Search_Plus_Pro_d67fb2263b88d9d41bae10a6e2ae855b@www\\.searchpluspro\\.com)|(Search_Plus_Pro_f7d126d7141628c1b739c58997b082b2@www\\.searchpluspro\\.com)|(Search_Plus_Pro_f889c071fa2a90062f8eeb4e7fd56db6@disable\\.searchpluspro\\.com)|(Search_Plus_Pro_fab7d78fa6444068be71b39db91e15ff@www\\.searchpluspro\\.com)|(Search_Privacy_clone_ozOdXpYKTM@www\\.search-privacy\\.live)|(Search_Privacy_clone_UDbsERGbgS@www\\.privacysearch\\.news)|(Search_Privacy_dIeFDdYUHg@www\\.searchprivacy\\.xyz)|(Search_Privacy_dKaGZWBFIM@www\\.searchprivacy\\.today)|(Search_Privacy_jpagAkMKvy@www\\.search-privacy\\.today)|(Search_Privacy_llZnkTGcOa@www\\.searchprivacy\\.today)|(Search_Privacy_naGfXaprzJ@info\\.searchprivacy\\.co)|(Search_Privacy_PQZCFxQkZX@www\\.privacysearch\\.news)|(Search_Privacy_qUxDJagEVN@www\\.searchprivacy\\.live)|(Search_Privacy_uVBxmwGLSk@www\\.privacy-search\\.works)|(Search_Privacy_wZVkQUHrHw@www\\.privacysearch\\.live)|(Search_Privacy_ZpneZaXeZp@www\\.search-privacy\\.live)|(Search_Privacy_ZvcIJuofJd@www\\.privacy-search\\.org)|(Search_Recipe_Pro_c299176cc8e5b03509bc091d92718516@www\\.searchrecipepro\\.com)|(search_Recipe_Pro_hZSnuXpHlI@www\\.searchrecipepro\\.com)|(Search_Secure_5fe09050cfe3944c0782c53e6aead42f@www\\.searchsecure\\.co)|(Search_Secure_5fe09050cfe3944c0782c53e6aead42fhsadghs@www\\.searchsecure\\.co)|(Search_Secure_clone_haBhKCwJbD_34@www\\.searchsecurepro\\.co)|(Search_Secure_clone_haBhKCwJbD1@www\\.searchsecurepro\\.co)|(Search_Secure_PUhUZXSDqx@www\\.searchsecure\\.co)|(Search_Select_804ce00595966fa5ced4efb6e0919d6f@www\\.searchselect\\.co)|(Search_Select_FQCSJqYZdP@www\\.searchselect\\.co)|(Search_Voyager_91c3275d156ab7272e24590b4f7f49d6@www\\.searchvoyager\\.co)|(Search_Voyager_PgzBPOqQhh@www\\.searchvoyager\\.co)|(search-movie\\.net_cURRAPTBgN@www\\.search-movie\\.net)|(search-movie\\.online_PqiotXlNOM@www\\.search-movie\\.online)|(search-movie\\.today_jpmWHncsRI@www\\.search-movie\\.today)|(Search-Privacy\\.biz_ZPdXCSGFgO@www\\.search-privacy\\.biz)|(Search-Privacy\\.club_CtlbxhsuVt@info\\.search-privacy\\.club)|(Search-Privacy\\.desi_AdRXpDarGi@info\\.search-privacy\\.desi)|(Search-Privacy\\.in\\.net_WibIleNEHx@info\\.search-privacy\\.in\\.net)|(Search-Privacy\\.info_rsNLqucdiG@info\\.search-privacy\\.info)|(Search-Privacy\\.link_wDxGmBgqcl@www\\.search-privacy\\.link)|(Search-Privacy\\.me_AvAriQFEhU@www\\.search-privacy\\.me)|(Search-Privacy\\.net_nWVLWwvLtN@www\\.search-privacy\\.net)|(Search-Privacy\\.online_cYKHeBuuMA@www\\.search-privacy\\.online)|(Search-Privacy\\.org_HWNXCRCnGt@info\\.search-privacy\\.org)|(Search-Privacy\\.site_qacnidOLmc@www\\.search-privacy\\.site)|(Search-Privacy\\.space_FjfWuiGHqH@info\\.search-privacy\\.space)|(Search-Privacy\\.store_xyaGgMqtWA@www\\.search-privacy\\.store)|(Search-Privacy\\.website_smicMDEmxB@www\\.search-privacy\\.website)|(Search-Privacy\\.xyz_gBlCbCGBMh@info\\.search-privacy\\.xyz))$/", + "prefs": [], + "schema": 1576758508310, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4f75a73b-1541-4d50-b7c7-4d3524619899", + "last_modified": 1576771657059 + }, + { + "guid": "/^((Search-Tab\\.net_qpBMgxRRKX@www\\.search-tab\\.net)|(searchmovie\\.today_rRBhPdnLIB@www\\.searchmovie\\.today)|(SearchPrivacy\\.Biz_TQxQMmZjax@info\\.searchprivacy\\.biz)|(SearchPrivacy\\.club_sbgzeaMJld@info\\.searchprivacy\\.club)|(SearchPrivacy\\.company_lbwegvHahG@www\\.searchprivacy\\.company)|(SearchPrivacy\\.in\\.net_ozDwuJlbCG@info\\.searchprivacy\\.in\\.net)|(SearchPrivacy\\.Info_zDsfyaIpyx@info\\.searchprivacy\\.info)|(SearchPrivacy\\.link_MEIDSFxuIf@www\\.searchprivacy\\.link)|(SearchPrivacy\\.me_SHWxbNO@info\\.searchprivacy\\.me)|(SearchPrivacy\\.one_JRipaiPyZz@www\\.searchprivacy\\.one)|(SearchPrivacy\\.space_AJTsgDwydV@info\\.searchprivacy\\.space)|(SearchPrivacy\\.store_NqrJweiCoL@www\\.searchprivacy\\.store)|(SearchPrivacy\\.website_EYeCzNtENe@www\\.searchprivacy\\.website)|(searchprivacy@searchprivacy\\.co)|(searchprivacyco@info\\.search-privacy\\.co)|(searchprivacydemo@searchprivacy\\.co)|(SearchSafe_cmZnrUyIXL@www\\.searchsafe\\.site)|(SearchSafe\\.online_bTqWKNIadp@www\\.searchsafe\\.online)|(SearchSafe\\.website_NFNXBQxRPA@www\\.searchsafe\\.website)|(searchsafee_VRAYffGolz@staging\\.findmaps\\.co)|(SearchTab\\.co_bEVNcWEgQN@www\\.searchtab\\.co)|(SearchTab\\.online_gZFeDEkoQl@www\\.searchtab\\.online)|(Severe_Weather_Check_51c60f3de3995f14f71373a70593618c@www\\.severeweathercheck\\.com)|(Severe_Weather_Check_bcb8c4ad7063c40e0e5b2d0f64ba11a5@www\\.severeweathercheck\\.com)|(Severe_Weather_Check_clone_joZdMWExSs@www\\.severeweathercheck\\.com)|(Severe_Weather_Check_clone_VlvXXiHGHD@www\\.severeweathercheck\\.com)|(Severe_Weather_Check_clone_xBNchBtncq@www\\.severeweathercheck\\.com)|(Severe_Weather_Check_muRAWnsLSV@www\\.severeweathercheck\\.com)|(Single_Login_pHoBMgvLtK@single\\.login\\.com)|(Smart_-_Search_cQYvhxQOuG@www\\.EasyOnlineRecipe\\.net)|(SMART_-_SEARCH_hYubbcgBlB@www\\.netspeedcalculator\\.net)|(Smart_File_Converter_clone_BVgFKwVsaS@www\\.smartfileconverter\\.com)|(Smart_File_Converter_IasnkilxwD@www\\.smartfileconverter\\.com)|(Smart_Package_Tracker_06309abe3838d3a65fcbc43e2958851e@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_1e9ff4a4ee10df2fb289180d5bb635bb@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_705864a844f3d8d84a5aecba1f675809@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_9ef0b4fcd24ee214476e73afb6adbb2e@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_bfc6ce01ede9446711a752c034df59c4@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_KVsdeebRuB@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_OvvQZsPtpR@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_SRguaqnzXn@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_XRHzsWuwOn@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_yPTsguhReR@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_clone_ZxioLKlnOD@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_db3d13974ca0682b514ad40e0f1fc682@www\\.smartpackagetracker\\.com)|(Smart_Package_Tracker_jqLcWhCacs@www\\.smartpackagetracker\\.com)|(Smart_Search_05fc1caf5755ccc74a092042913cbbda@www\\.finddirections\\.co)|(Smart_Search_10751eb8462a19515e0f9f1525333d1d@www\\.game-quest\\.co)|(Smart_Search_118237f18afcc2ce76539a9d1e015bc2@www\\.dailygame\\.online)|(Smart_Search_13d2b5842a0358c117dd87411a84db0d@www\\.convertfilesonline\\.co)|(Smart_Search_2160f8e9f48a63f3d9d7a019b9a5c53d@www\\.finddirections\\.co)|(Smart_Search_2834376aee173a89462511cf3ae21633@www\\.dailyrecipesearch\\.net)|(Smart_Search_287a4b04b4dca219a02375ca2f6a4985@www\\.finddailycoupons\\.com)|(Smart_Search_3f36608d872e4e46edd8b4e57902e6bd@www\\.dailyrecipesearch\\.net)|(Smart_Search_5cad449f0283b0b10b9cd78472de5c54@www\\.finddirections\\.co)|(Smart_Search_69eb8884d7d2e0aabdad154644e38401@www\\.convertmyfile\\.co)|(Smart_Search_6a0b26a582308b0dd9ae82634ccfe6f7@www\\.localweathertoday\\.net)|(Smart_Search_77710b6943d36049c1b72d9035a63379@www\\.localweathertoday\\.net)|(Smart_Search_7f90cdefd25d69c792d02cec7763b41d@www\\.finddirections\\.co)|(Smart_Search_814229d05c1e81d4ce1e73a3ca7c3eaa@www\\.smartsearchnow\\.co)|(Smart_Search_94fb06cfccd5d4112f555fb00a1b38ec@www\\.finddirections\\.co)|(Smart_Search_a1e27539ac89c70a170688ee375c5426@www\\.finddailycoupons\\.com)|(Smart_Search_a98fb26ee015d18ad91a68c7124e1f30@www\\.get-news\\.co)|(Smart_Search_abeJBrRXlb@www\\.smart-search\\.one))$/", + "prefs": [], + "schema": 1576758534450, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "24adcec0-6f62-41bd-9e13-97895838bf64", + "last_modified": 1576771657055 + }, + { + "guid": "/^((Smart_Search_aKLddQIYMY@www\\.gamesdaily\\.online)|(Smart_Search_aQBxkbfpqq@www\\.finddirections\\.co)|(Smart_Search_aStviFIoor@www\\.finddirections\\.co)|(Smart_Search_aTGazruEUE@www\\.search-smart\\.co)|(Smart_Search_ayroufrtQb@www\\.finddirections\\.co)|(Smart_Search_b08c081d9f06123fb6172679fdf4fcb6@www\\.finddirections\\.co)|(Smart_Search_b859bcf9fb977dc2076199076b2f9081@www\\.convertmyfile\\.co)|(Smart_Search_bbgycNKlXh@www\\.dailyjobssearch\\.net)|(Smart_Search_bdDgpqbSTU@www\\.smartsearchnow\\.co)|(Smart_Search_bnnlTJEfCZ@www\\.search-smart\\.live)|(Smart_Search_BWMiEUiCaH-my@www\\.findmaps\\.co)|(Smart_Search_BWMiEUiCaH@www\\.findmaps\\.co)|(Smart_Search_c14750a49aa2937ede499b641a6c4390@www\\.finddirections\\.co)|(Smart_Search_cb20a26ab1dc76cc9f793fb975be4b02@www\\.smartsearchnow\\.co)|(Smart_Search_cFSWRyjwef@www\\.smart-search\\.today)|(Smart_Search_chUMEQmsSU@www\\.finddirections\\.co)|(Smart_Search_CIWmtesCzJ@www\\.mycouponstore\\.co)|(Smart_Search_cKezTfCPJB@www\\.checkmaps\\.live)|(Smart_Search_cKnSYkYtgM@www\\.check-yourmail\\.co)|(Smart_Search_clone_bZjlXmDRQB@www\\.finddirections\\.co)|(Smart_Search_clone_duXXvsUEtZ@www\\.finddirections\\.co)|(Smart_Search_clone_EQDmJygWxO@www\\.searchsmart\\.online)|(Smart_Search_clone_ETXwzVYwpI@www\\.search-smart\\.work)|(Smart_Search_clone_HsJXCDiNNC@www\\.smartsearch\\.link)|(Smart_Search_clone_JxJqcLVLOv@www\\.finddirections\\.co)|(Smart_Search_clone_SoSaSQDKGZ@www\\.dailyrecipesearch\\.net)|(Smart_Search_CQbLIZDIOl@www\\.smartsearch\\.link)|(Smart_Search_CxIpAXIPVs@www\\.smart-search\\.co)|(Smart_Search_d0f38b4ff2f0e788c442249a857e4dde@www\\.finddirections\\.co)|(Smart_Search_d16ea913837e848252ad5d856d9c8b02@www\\.dailyjobssearch\\.net)|(Smart_Search_d8982f2dae25e7422cc1392ccf974d04@www\\.dailyrecipesearch\\.net)|(Smart_Search_da3cc67017bdcd720cbd7fc26f24d743@www\\.check-yourmail\\.co)|(Smart_Search_dcf5af05538a3c68edd4625c7f0d2dc4@www\\.finddailycoupons\\.com)|(Smart_Search_DLsAIStnQX@www\\.smartsearch\\.link)|(Smart_Search_dTBjWAGKlN@www\\.testnetspeed\\.co)|(Smart_Search_e34c59b523a7a1585da696e4576b101c@www\\.gamesdaily\\.online)|(Smart_Search_e6d57c56077122ca0bcef12a39e3f6a3@www\\.localweathertoday\\.net)|(Smart_Search_ea5e7bb722049dd11af40116816edee3@disable\\.convertmyfile\\.co)|(Smart_Search_EkAoOFUJwV@www\\.games-daily\\.co)|(Smart_Search_eMDQwPVVyj@www\\.search-smart\\.co)|(Smart_Search_f377b2bb34f42cbb572c36d9bfb3d9c3@www\\.finddirections\\.co)|(Smart_Search_fbd05acb7ab6331297e367f816ff0a20@www\\.testnetspeed\\.co)|(Smart_Search_FCphmgFrTz@www\\.finddailygames\\.co)|(Smart_Search_FMxybFLCfE@www\\.smart-search\\.today)|(Smart_Search_gGkCXJUDEH@www\\.game-quest\\.co)|(Smart_Search_HnEqPdzcek@www\\.search-smart\\.live)|(Smart_Search_IbBWPmuFLU@www\\.convertfilesonline\\.co)|(Smart_Search_JcpHMeulsq@www\\.dailyrecipesearch\\.net)|(Smart_Search_JJPKAMJWLv@www\\.finddailycoupons\\.com)|(Smart_Search_JTruqraRrz@www\\.localweathertoday\\.net)|(Smart_Search_JViAyYSxof@www\\.search-smart\\.today)|(Smart_Search_KjzSKpViVv@www\\.getmapfinder\\.com)|(Smart_Search_LBQKLuCjDu@www\\.search-smart\\.work)|(Smart_Search_LBUbgBMxrr@www\\.convertmyfile\\.co)|(Smart_Search_lKbvfFvWdq@www\\.finddirections\\.co)|(Smart_Search_LPxWJiUiWP@dailyrecipesearch\\.net)|(Smart_Search_lVYnIBRyYO@www\\.search-smart\\.today)|(Smart_Search_lzlfscclxO@www\\.smartsearch\\.news)|(Smart_Search_mmUSVHTgZN@www\\.searchsmart\\.website)|(Smart_Search_MYlsYAHChr@www\\.search-smart\\.one)|(Smart_Search_OYMxotguWV@www\\.thecoupon-store\\.co)|(Smart_Search_PncWXXrZyE@www\\.get-news\\.co)|(Smart_Search_PxUetfSxfK@www\\.finddirections\\.co)|(Smart_Search_qbdedBcsMV@www\\.mysmartsearch\\.online)|(Smart_Search_QFEtHYupEv@www\\.localweathertoday\\.net)|(Smart_Search_rmcVsyMvUV@www\\.finddailycoupons\\.com)|(Smart_Search_sfQjeIHqAg@www\\.onlinecouponfinder\\.net)|(Smart_Search_SLKIYTovfO@www\\.dailygame\\.online)|(Smart_Search_snYvsooWFZ@www\\.dailycoupons\\.store)|(Smart_Search_SRonsLzSpz@www\\.search-smart\\.link)|(Smart_Search_SUWIUWczzW@www\\.search-smart\\.club)|(Smart_Search_trFxvKvCFp@www\\.dailyrecipesearch\\.net)|(Smart_Search_tTrjAmWOoR@www\\.convertmyfile\\.co)|(Smart_Search_txfWPOCTvy@www\\.finddailycoupons\\.com)|(Smart_Search_UgGHEdLtFf@www\\.search-smart\\.one)|(Smart_Search_UXuBBPZiCD@www\\.search-smart\\.website)|(Smart_Search_VFsgkOxdvk@www\\.convertmyfile\\.co))$/", + "prefs": [], + "schema": 1576758569109, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "65197a67-44c8-440d-b22c-26d84b2d763e", + "last_modified": 1576771657051 + }, + { + "guid": "/^((Templates_Online_clone_UmejRGyzBe@www\\.gettemplatesonline\\.com)|(Templates_Online_clone_WiWqHXsGkm@www\\.gettemplatesonline\\.com)|(Templates_Online_xUPYerdIfL@www\\.gettemplatesonline\\.com)|(test_fhwkrCFoHO@test)|(test_searchprivacy@info\\.searchprivacy\\.info)|(TestFirefoxAddon_ahdBLDcQql@www\\.testfirefoxaddon\\.com)|(testsearchprivacy@info\\.searchprivacy\\.co)|(Text_From_Your_PC_9e43d427bd5cde52e50706cf2ce04da8@www\\.textfromyourpc\\.com)|(Text_From_Your_PC_cBapqdxFhR@www\\.textfromyourpc\\.com)|(The_Coupon_-_Store_ERhPcJTQll@thecoupon-store\\.co)|(The_Coupon_King_1209d556e43b7c529e282d8b8842da76@www\\.thecouponking\\.co)|(The_Coupon_King_lgTjuAddLR@www\\.thecouponking\\.co)|(The_Coupon_King_UwqzosFLgL@www\\.thecouponking\\.com)|(The_Coupon_Store_FuajgfDoOD@www\\.thecouponstore\\.co)|(The_Coupon_Store_lPVGxmIRUf@www\\.thecouponstore\\.co)|(The_Coupon_Store_Test_jkAVapBJTo@www\\.thecouponstore\\.co\\.test)|(The_Coupon_Trail_bdc89c2aeb9eb486945d9f9d80bdbb02@www\\.thecoupontrail\\.com)|(The_Coupon_Trail_MMnMCLSPWo@www\\.thecoupontrail\\.com)|(The_Coupon_Trial_IteRooFtcL@www\\.thecoupontrial\\.com)|(The_Coupons_Daily_efc9df81f19dc213cb2a20067afa4ccb@www\\.thecouponsdaily\\.com)|(The_Coupons_Daily_NAEBGGQbOZ@www\\.thecouponsdaily\\.com)|(The_Coupons_King_cb3ed44aa8013a310be07e3d3fcc4dc2@www\\.thecouponsking\\.co)|(The_Currency_Switch_987755b6cd2466fcb08f71503c38cbfa@www\\.thecurrencyswitch\\.co)|(The_Currency_Switch_c80da7ec7cd15c168fc419b3d69aa602@www\\.thecurrencyswitch\\.co)|(The_Currency_Switch_clone_e0f313620f09d4dfc50e3a362bac8a9a@www\\.thecurrencyswitch\\.co)|(The_Currency_Switch_clone_geyyIAUtgm@www\\.thecurrencyswitch\\.co)|(The_Currency_Switch_clone_JZMSasbVPc@www\\.thecurrencyswitch\\.co)|(The_Currency_Switch_QlvIbtxDLF@www\\.thecurrencyswitch\\.co)|(The_Movie_Quest_BPyzyFXOpw@www\\.themoviequest\\.today)|(The_Movie_Search_IgbQnCESAN@www\\.themoviesearch\\.co)|(The_Movie_Search_zFBfJgLeGc@www\\.themoviesearch\\.co)|(The_News_Prompter_e90ebcdb43eb03954526e4576d6c6b71@www\\.thenewsprompter\\.com)|(The_News_Prompter_mbjJOGhbsK@www\\.thenewsprompter\\.com)|(The_Package_Track_9e0db37d4fc02628a9996d9be9ee7ad6@www\\.thepackagetrack\\.com)|(The_Quiz_Tab_xWbNAmFREz@www\\.thequiztab\\.com)|(The_Search_Easy_44e7c2c276e66b66d8bfb874777d4a2e@www\\.thesearcheasy\\.com)|(The_Search_Easy_XObNZOoESy@www\\.thesearcheasy\\.com)|(The_Search_Plus_37976642e8a885a46631002f0051ff23@www\\.thesearch-plus\\.co)|(The_Search_Plus_UawsDpTchY@www\\.thesearch-plus\\.co)|(thecouponstore@www\\.thecoupon-store\\.co)|(TheMovie-Hub\\.net_YezkChdTjo@www\\.themovie-hub\\.net)|(TheMovie-Portal_tPUcBCgXvk_1@www\\.themovie-portal\\.com)|(TheMovie-Portal_tPUcBCgXvk@www\\.themovie-portal\\.com)|(TheMovie-Quest\\.com_wlPrmnAQDs@www\\.themovie-quest\\.com)|(TheMovieQuest\\.co_cORgvNNjpL@www\\.themoviequest\\.co)|(TheMovieQuest\\.co_fQQHMPPCrc@www\\.themoviequest\\.co)|(themoviesearch\\.today_NaQhVgVlwL@www\\.themoviesearch\\.today)|(This_is_my_extension_qZkKGOuZcR@www\\.exampleAMO\\.com)|(This_is_New_AMO_dyIGZrXnWN@www\\.kite\\.com)|(This_is_New_AMO_QVeIjjGbol@www\\.kite\\.com)|(Trace_Packages_f406b9f80935c80df1166017cdb4d6f1@www\\.tracepackages\\.com)|(Track_Daily_News_1eaf6dc15b67e558badb4d0cfe763913@www\\.trackdailynews\\.com)|(Track_Daily_News_6e84f45f079de0ffcc94b2d968db9bee@www\\.trackdailynews\\.com)|(Track_Daily_News_a339167d304bfa5f599a249dda7ce993@www\\.trackdailynews\\.com)|(Track_Daily_News_EIMlViVmYz@www\\.trackdailynews\\.com)|(Track_Daily_News_sCvLoGjBau@www\\.trackdailynews\\.com)|(Track_Flight_Pro_7b52baadaefaac0bc71eaa5d3ffdd62c@www\\.trackflightpro\\.com)|(Track_Flight_Pro_c7f7b664812211123e6c54706eb66c3e@www\\.trackflightpro\\.com)|(Track_Flight_Pro_clone_hNojYqaWwF@www\\.trackflightpro\\.com)|(Track_Flight_Pro_eZAWVNMAAT@www\\.trackflightpro\\.com)|(Track_Package_Pro_clone_xayswwXtnt@www\\.trackpackagepro\\.com)|(Track_Package_Pro_clone_xBtZVauyZo@www\\.trackpackagepro\\.com)|(Track_Package_Pro_KvcoWaXXaa@www\\.trackpackagepro\\.com)|(Track_Package_Quick_525020a307e84998fc7ad8a634296e1d@www\\.trackpackagequick\\.com)|(Track_Package_Quick_clone_SjXacYcSKg@www\\.trackpackagequick\\.com)|(Track_Package_Quick_clone_zTowhYQtBK@www\\.trackpackagequick\\.com)|(Track_Package_Quick_lFiXdhlnGK@www\\.trackpackagequick\\.com)|(trackthatpackagecom@www\\.trackthatpackage\\.com)|(trailertab@www\\.trailertab\\.co))$/", + "prefs": [], + "schema": 1576758629163, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5cd48455-378c-4e67-a37d-8c39d497b4af", + "last_modified": 1576771657042 + }, + { + "guid": "/^((Translate_cgjqtumrjO@app\\.translate\\.com)|(Travel_Deals_Center_clone_GJNQLATOqW@www\\.traveldealscenter\\.co)|(Travel_Deals_Center_rqctZxkUaz@www\\.traveldealscenter\\.co)|(Travel_Directions_clone_FfMthspRaM@www\\.traveldirections\\.co)|(Travel_Directions_clone_ourIqJKEoa@www\\.traveldirections\\.co)|(Travel_Directions_mvJHkLHMMo@www\\.traveldirections\\.co)|(Tv_Shows_Online_4331a381f3f1b8d9f9f0b56793be73be@www\\.tvshowsonline\\.co)|(Tv_Shows_Online_MpTzgCmWDM@www\\.tvshowsonline\\.co)|(TV_Streaming_Plus_ARhtOdhYyz@www\\.tvstreamingplus\\.co)|(tyghn_EbxtKCSoQf@ghn)|(Universal_Package_Tracker_vEmOcVlKPs@www\\.universalpackagetrackern\\.online)|(Weather_Alert_Pro_BodPvrEWvi@www\\.weatheralertpro\\.co)|(Weather_Alert_Pro_clone_sTGOfwOqRV@www\\.weatheralertpro\\.co)|(Weather_Alert_Pro_eAvhNglZrt@www\\.weatheralertpro\\.com)|(Weather_Center_App_06cc2a6dd45345b134670c50b38c8692@www\\.weathercenterapp\\.com)|(Weather_Center_App_edIZBMRKqw@www\\.weathercenterapp\\.com)|(Weather_Center_App_ef684143ed5bb6671446fa504a5feec8@www\\.weathercenterapp\\.com)|(Weather_Center_App_f860fbbefafc691d55516dbf9ff9de42@www\\.weathercenterapp\\.com)|(Weather_Center_App_FCsxbRlcmB@www\\.weathercenterapp\\.com)|(Weather_Center_App_rEbQRyobNS@www\\.weathercenterapp\\.com)|(Weather_Coach_aRmHcdLWxv@www\\.weather-coach\\.com)|(Weather_Coach_cnhVSyfNQb@www\\.weather-coach\\.com)|(Weather_Coach_SdaRyeWHza@www\\.weather-coach\\.com)|(Weather_Details_a74cceec1f4c6974ce48a55da22c4fc8@www\\.weather-details\\.co)|(Weather_Details_clone_WmbTEchnZv@www\\.weather-details\\.today)|(Weather_Details_DBDwBdsiUV@www\\.weather-details\\.online)|(Weather_Details_LZKpPYrFpw@www\\.weather-details\\.today)|(Weather_Details_UMhKKuOmLm@www\\.weather-details)|(Weather_Details_xrNgYMjMud@www\\.weather-details\\.today)|(Weather_Forecast_0c98741392250106567fdcdf92e662ed@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_22873441e62b17d83e57bee8dae28096@disable\\.checkweathernow\\.co)|(Weather_Forecast_26bbf7afaa8ca321d3eb1793eab261eb@www\\.checkweathernow\\.co)|(Weather_Forecast_34a39ecaa08283313fa326a45884233b@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_5432f72204437596f302a717de4410ab@www\\.localweathertoday\\.net)|(Weather_Forecast_5c6a315e6da8023a47a4e247bcd60544@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_5d13ac5f68d80a584ad913b02540fae5@www\\.checkweather\\.today)|(Weather_Forecast_647cf952e5329d490351c5b250e81e54@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_6f6484fab931bf3f3fcf75dfa65adc37@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_7462a90b29dc5f13bb4628cce7d2c6dc@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_a4fe2e9e47d66eebd6220e13d10434ef@www\\.checkweathernow\\.co)|(Weather_Forecast_BjIrtPRHri@www\\.weatherforecasttracker\\.com)|(Weather_Forecast_Buddy_hqZFZdAdSe@www\\.weatherforecastbuddy\\.com)|(Weather_Forecast_c27096e498d3541e341f2597fa7124b0@www\\.weatherforecasttracker\\.com)|(Weather_Forecast_c6509e49a9a27bdab9facfb442488a79@www\\.localweathertoday\\.net)|(Weather_Forecast_ca28589b2ca67136318e4bfdf954002d@www\\.checkweathertoday\\.net)|(Weather_Forecast_cbf70b1126d7690bf9180b2e6c43e8f6@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_ChxOzHZcLa@www\\.localweathertoday\\.net)|(Weather_Forecast_clone_60d8346940b46a116579ec1ea3906d5d@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_chFFSZCgvz@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_clone_94a823ffc8bedf3bd1e6ab4465826581@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_clone_clone_apzzlMbkyB@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_clone_clone_f2e4cad9766f8cde8ffbdd484efbdd2b@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_clone_yQjWeYXXXI@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_HErKKKDyFS@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_HOrAZgjBCz@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_clone_jMGHMuyVVi@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_KYlMwumWQY@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_kyUmeEeefY@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_clone_pDcEnQJDXJ@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_piPIkUDQPc@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_QoJcNyLaQt@www\\.weatherforecasttracker\\.com))$/", + "prefs": [], + "schema": 1576758680309, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a083664b-0301-45cd-a2ee-0c690be250b6", + "last_modified": 1576771657038 + }, + { + "guid": "/^((Weather_Forecast_clone_TSGzGLieya@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_clone_UBimgpjhsm@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_clone_XcnVeRvMca@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_clone_YQnByPToHy@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_clone_zkFObDWDhS@www\\.weatherforecasttracker1\\.com)|(Weather_Forecast_Daily_CTzpOZENfa@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_e3838c6031faf63c6209533e71b456c0@www\\.weatherforecastdaily\\.disabled\\.com)|(Weather_Forecast_EnkDNfGxAS@www\\.checkweathernow\\.co)|(Weather_Forecast_epwQJrNJwH@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_eWEHJWtQzM@www\\.checkweathertoday\\.net)|(Weather_Forecast_eWFHhLxmdi@www\\.checkweathernow\\.co)|(Weather_Forecast_f5d79bda8e7d666123f8cff0f49720ab@www\\.localweathertoday\\.net)|(Weather_Forecast_FbozBLKAnT@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_fc107df349d96479678eb88a9d2f58b3@www\\.weatherforecasttracker\\.com)|(Weather_Forecast_KlMFWMEnxo@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_KNdWdwrVBg@www\\.weather-forecast\\.link)|(Weather_Forecast_lESFprnxRC@www\\.checkweathernow\\.co)|(Weather_Forecast_nsdhLSCwEt@www\\.weather-forecast\\.news)|(Weather_Forecast_OMezCgBGlR@www\\.weather-forecast\\.link)|(Weather_Forecast_Plus_81c2ce246488019a54fed798354fbb85@www\\.weatherforecastplus\\.com)|(Weather_Forecast_Plus_WVxlZhsVCn@www\\.weatherforecastplus\\.com)|(Weather_Forecast_QqFyFxILHq@www\\.checkweather\\.today)|(Weather_Forecast_rUPfzmWQNa@www\\.localweathertoday\\.net)|(Weather_Forecast_Search_4f3974a0460bd8947f25b61879196ae6@www\\.weatherforecastsearch\\.com)|(Weather_Forecast_Search_SPAKKzWPua@www\\.weatherforecastsearch\\.com)|(Weather_Forecast_SjMIOdqezy@www\\.weatherforecastdaily\\.com)|(Weather_Forecast_Tab_d586ce62eebca7b5ccd054f645ab3278@www\\.weatherforecasttab\\.com)|(Weather_Forecast_Tab_LHutnjYcaD@www\\.weatherforecasttab\\.com)|(Weather_Forecast_UZnBodXSQY@www\\.localweathertoday\\.net)|(Weather_Forecast_V2_c8e3c1f489201dcee4f10ed13b88423b@www\\.weatherforecasttracker1\\.com)|(Weather_Info_AdPTHnolyR@www\\.weatherinfo\\.live)|(Weather_Info_clone_IylhdngkdZ@www\\.weatherinfo\\.live)|(Weather_Info_clone_LAyJxkrWYr@www\\.weatherinfo\\.live)|(Weather_Info_mYTEbKRsJk@www\\.weatherinfo\\.live)|(Weather_Info_rBUleVUgYg@www\\.weatherinfo\\.today)|(Weather_Info_ZurIhFqkfO@www\\.weatherinfo\\.live)|(Weather_Online_Now_clone_gXBxUraWkz@www\\.weatheronlinenow\\.com)|(Weather_Online_Now_clone_VEajkSqfLm@www\\.weatheronlinenow\\.com)|(Weather_Online_Now_LxMeLWImMw@www\\.weatheronlinenow\\.com)|(Weather_Report_69d519795ecb2edb106cf07b8e5fcbae@www\\.weatherreportlive\\.co)|(Weather_Report_JHDIwuETLZ@www\\.weather-report\\.live)|(Weather_Report_PdxgfapiBL@www\\.weatherreportlive\\.co)|(Weather_Report_WKPqFTsXUk@www\\.weather-report\\.live)|(Weather_Reporter_98959e99f2103eef2d02f3d351ba89b9@www\\.weatherreporter\\.co)|(Weather_Reports_BjGuRgCpLB@www\\.weather-reportsnow\\.com)|(Weather_Reports_c982e1b51cd8bc347ab328410329fbb6@www\\.weatherreports\\.live)|(Weather_Reports_clone_fRjlGYJSnk@www\\.weatherreports\\.live)|(Weather_Reports_clone_NMTTAUMqtY@www\\.dailyweatherreports\\.co)|(Weather_Reports_EQVqqGZdlM@www\\.weather-report\\.link)|(Weather_Reports_HlXmMSuFKX@www\\.weatherreports\\.live)|(Weather_Reports_KDxyqUBBwO@www\\.dailyweatherreports\\.co)|(Weather_Reports_UyzmbxlFVx@www\\.weather-reports\\.today)|(Weather_Reports_xaeygHwNhf@www\\.weatherreports\\.live)|(Weather_Reports_zDpccdTchc@www\\.weatherreports\\.today)|(Weather_Reports_zKYmoRPwNT@www\\.weatherreports\\.live)|(Weather_Tab_Pro_clone_aTBTPSAEUX@www\\.weathertabpro\\.com)|(Weather_Updates_TGBOowTNwY@www\\.weather-updates\\.co)|(Weather-Bee_1b435f4656c8c4f6df6b455cc302cf9b@www\\.weather-bee\\.co)|(Weather-Bee_cbff90268cbc8e1fd65336342cfcf056@www\\.weather-bee\\.co)|(Weather-Bee_deb8c4f31d28d8ef924d8604c528f43b@www\\.weather-bee\\.com)|(Weather-Bee_gFubKNACfC@www\\.weather-bee\\.co)|(Weather-Bee_HyIkcLzmEL@www\\.weather-bee\\.co)|(Weather-Bee_oUeUqEGckY@www\\.weather-bee\\.com)|(WeatherDetails_qGhRPwNzlD@www\\.weatherdetails\\.net)|(Web_Gamer_World_clone_bBqNIFWIiJ@www\\.webgamerworld\\.com)|(Web_Gamer_World_clone_vSvnetvjxh@www\\.webgamerworld\\.com)|(Web_Gamer_World_EnPQSoVMPy@www\\.webgamerworld\\.com))$/", + "prefs": [], + "schema": 1576758713481, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "86bffc98-7002-4c94-84b1-2036f19f1345", + "last_modified": 1576771657033 + }, + { + "guid": "/^((WheresMyPackage\\.co_04c5041d404a424ae04b3bdae470bf36@www\\.wheresmypackage\\.co)|(WheresMyPackage\\.co_nkiJdrfSTz@www\\.wheresmypackage\\.co)|(wheresmypackageco@www\\.wheresmypackage\\.co)|(Wonder_Wallpaper_7445d465b196ea5672cf5ddad5121e20@www\\.wonderwallpaper\\.co)|(Wonder_Wallpaper_86c968090b99d54af67a65a1c6010dc8@www\\.wonderwallpaper\\.co)|(Wonder_Wallpaper_clone_b3a8885c9af58cc53b837522fbeab5fa@www\\.wonderwallpaper\\.co)|(Wonder_Wallpaper_clone_BdEgqQaNqI@www\\.wonderwallpaper\\.co)|(Wonder_Wallpaper_clone_rujWfVzPPf@www\\.wonderwallpaper\\.co)|(Wonder_Wallpaper_XONDNNciYs@www\\.wonderwallpaper\\.co)|(www\\.search-privacy\\.in\\.net_frtQaDOkLW@www\\.search-privacy\\.in\\.net)|(www\\.search-privacy\\.info_hyVgVlpUDh@www\\.search-privacy\\.info)|(Your_Mail_Tab_06f9e0cacb38332e7d67d6f6eb36fbf4@www\\.yourmailtab\\.com)|(Your_Mail_Tab_27811aa670d957af7c67c9b40421db33@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_FFlaTZQUzT@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_kckMtSXzhP@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_rZgtWWxFTE@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_tplYnwtbOB@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_UStpWwUYys@www\\.yourmailtab\\.com)|(Your_Mail_Tab_clone_wQEiyJhcTK@www\\.yourmailtab\\.com)|(Your_Mail_Tab_sHxtuglHxJ@www\\.yourmailtab\\.com)|(Your_Map_Tab_caf5c90424b052d0a732d773f984284d@www\\.yourmaptab\\.com)|(Your_Map_Tab_MGrwdnTuFV@www\\.yourmaptab\\.com)|(Your_Maps_Finder_1cb2c0753a9f2089208e662ff7887eff@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_283a77e3a6a0c1cb4d803b12b7b3f38d@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_299d66c546dc1bf61c2f1ac4a823d848@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_2b2f2c9fdc74da065784a5537d7cbe8a@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_493f8ad05595d053fc466c6d712a0f5b@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_5fb3ba7aabc0df0482f721f2b8226c49@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_8ec3602a25914b2b98753c78f3ff0792@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_a4e12e88158bb14fe3f3b23935d1005b@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_clone_KCngJjrrAW@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_CVslhcZliG@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_dfeYIGYOsX@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_gSkAwghnnM@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_HbjLLgCqVI@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_IAQSXqZaut@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_IjXrmViPjV@www\\.yourmapsfinder\\.com)|(Your_Maps_Finder_jfCclXiWUZ@www\\.yourmapsfinder\\.com)|(Your_Maps_Guide_gJsxGXyDUk@www\\.yourmapsguide\\.com)|(Your_Recipes_Guide_6e2b67bec168f977b275d2cb8e38cb99@www\\.yourrecipesguide\\.com)|(Your_Recipes_Guide_pjXpEcLtqt@www\\.yourrecipesguide\\.com)|(Yum_Recipe_finder_5de2cc2f278f3bfc1a3715a70c8661f0@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_93a02159e31918ef11d89db653420167@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_clone_AoObYTQNIW@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_clone_c3c1e13d25490ca78859e8857319b432@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_clone_nFKjdQhPjD@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_clone_oIeIcINzWP@www\\.yumrecipefinder\\.com)|(Yum_Recipe_Finder_CqcblSJbqU@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_eb8981fe181afd4e02a57fd9c4b9e8f2@www\\.yumrecipefinder\\.com)|(Yum_Recipe_finder_mgdDNujmFJ@www\\.yumrecipefinder\\.com))$/", + "prefs": [], + "schema": 1576758771928, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bfa84d62-24de-474e-bc35-a69b1f46f331", + "last_modified": 1576771657029 + }, + { + "guid": "/^((@FirefoxUpdate)|(@googledashboard)|(@smash_mov)|(@smash_tv)|(@smashdashboard)|(@smashmovs)|(@smashtvs)|(\\{0be01832-7cce-4457-b8ad-73b743914085\\})|(\\{0e1c683e-9f34-45f1-b365-a283befb471a\\})|(\\{0c72a72d-6b2e-4a0e-8a31-16581176052d\\})|(\\{0ccfc208-8441-4c27-b1cb-799accb04908\\})|(\\{0ede8d39-26f2-49c4-8014-dfc484f54a65\\})|(\\{1fc1f8e6-3575-4a6f-a4d1-c4ca1c36bd2a\\})|(\\{3a1d6607-e6a8-4012-9506-f14cd157c171\\})|(\\{03b3ac4d-59a3-4cc6-aa4d-9b39dd8b3196\\})|(\\{3bb6e889-ac7a-46ca-8eed-45ba4fbe75b5\\})|(\\{3c841114-da8c-44ea-8303-78264edfe60b\\})|(\\{3f3bcb3e-dd73-4410-b102-60a87fcb8323\\})|(\\{3f951165-fd85-42ae-96ef-6ff589a1fe72\\})|(\\{04c86cb3-5f52-4083-9e9a-e322dd02181a\\})|(\\{4d8b44ef-9b8b-4d82-b668-a49648d2749d\\})|(\\{4d25d2b4-6ae7-4a66-abc0-c3fca4cdddf6\\})|(\\{5c9a2eca-2126-4a84-82c0-efbf3d989371\\})|(\\{6ecb9f49-90f0-43a1-8f8a-e809ea4f732b\\})|(\\{6fb8289d-c6c8-4fe5-9a92-7dc6cbf35349\\})|(\\{7fea697d-327c-4d20-80d5-813a6fb26d86\\})|(\\{08a3e913-0bbc-42ba-96d7-3fa16aceccbf\\})|(\\{8b04086b-94a5-4161-910b-59e3e31e4364\\})|(\\{08c28c16-9fb6-4b32-9868-db37c1668f94\\})|(\\{8cd69708-2f5e-4282-a94f-3feebc4bce35\\})|(\\{8dc21e24-3883-4d01-b486-ef1d1106fa3d\\})|(\\{8f8cc21a-2097-488f-a213-f5786a2ccbbf\\})|(\\{9c8b93f7-3bf8-4762-b221-40c912268f96\\})|(\\{9ce66491-ef06-4da6-b602-98c2451f6395\\})|(\\{1e1acc1c-8daa-4c2e-ad05-5ef01ae65f1e\\})|(\\{10b0f607-1efa-4762-82a0-e0d9bbae4e48\\})|(\\{24f338d7-b539-49f1-b276-c9edc367a32d\\})|(\\{40c9030f-7a2f-4a58-9d0a-edccd8063218\\})|(\\{41f97b71-c7c6-40b8-83b1-a4dbff76f73d\\})|(\\{42f3034a-0c4a-4f68-a8fd-8a2440e3f011\\})|(\\{52d456e5-245a-4319-b8d2-c14fbc9755f0\\})|(\\{57ea692b-f9fe-42df-bf5e-af6953fba05a\\})|(\\{060c61d8-b48f-465d-aa4b-23325ea757c3\\})|(\\{65c1967c-6a5c-44dd-9637-0d4d8b4c339b\\})|(\\{65d40b64-b52a-46d8-b146-580ff91889cb\\})|(\\{75b7af0d-b4ed-4320-95c8-7ffd8dd2cb7c\\})|(\\{77fe9731-b683-4599-9b06-a5dcea63d432\\})|(\\{84b20d0c-9c87-4340-b4f8-1912df2ae70d\\})|(\\{92b9e511-ac81-4d47-9b8f-f92dc872447e\\})|(\\{95afafef-b580-4f66-a0fe-7f3e74be7507\\})|(\\{116a0754-20eb-4fe5-bd35-575867a0b89e\\})|(\\{118bf5f6-98b1-4543-b133-42fdaf3cbade\\})|(\\{248eacc4-195f-43b2-956c-b9ad1ae67529\\})|(\\{328f931d-83c1-4876-953c-ddc9f63fe3b4\\})|(\\{447fa5d3-1c27-4502-9e13-84452d833b89\\})|(\\{476a1fa9-bce8-4cb4-beff-cb31980cc521\\})|(\\{507a5b13-a8a3-4653-a4a7-9a03099acf48\\})|(\\{531bf931-a8c6-407b-a48f-8a53f43cd461\\})|(\\{544c7f83-ef54-4d17-aa91-274fa27514ef\\})|(\\{546ea388-2839-4215-af49-d7289514a7b1\\})|(\\{635cb424-0cd5-4446-afaf-6265c4b711b5\\})|(\\{654b21c7-6a70-446c-b9ac-8cac9592f4a9\\})|(\\{0668b0a7-7578-4fb3-a4bd-39344222daa3\\})|(\\{944ed336-d750-48f1-b0b5-3c516bfb551c\\})|(\\{1882a9ce-c0e3-4476-8185-f387fe269852\\})|(\\{5571a054-225d-4b65-97f7-3511936b3429\\})|(\\{5921be85-cddd-4aff-9b83-0b317db03fa3\\})|(\\{7082ba5c-f55e-4cd8-88d6-8bc479d3749e\\})|(\\{7322a4cb-641c-4ca2-9d83-8701a639e17a\\})|(\\{90741f13-ab72-443f-a558-167721f64883\\})|(\\{198627a5-4a7b-4857-b074-3040bc8effb8\\})|(\\{5e5b9f44-2416-4669-8362-42a0b3f97868\\})|(\\{824985b9-df2a-401c-9168-749960596007\\})|(\\{4853541f-c9d7-42c5-880f-fd460dbb5d5f\\})|(\\{6e6ff0fd-4ae4-49ae-ac0c-e2527e12359b\\})|(\\{90e8aa72-a7eb-4337-81d4-538b0b09c653\\})|(\\{02e3137a-96a4-433d-bfb2-0aa1cd4aed08\\})|(\\{9e734c09-fcb1-4e3f-acab-04d03625301c\\})|(\\{a6ad792c-69a8-4608-90f0-ff7c958ce508\\})|(\\{a512297e-4d3a-468c-bd1a-f77bd093f925\\})|(\\{a71b10ae-b044-4bf0-877e-c8aa9ad47b42\\})|(\\{a33358ad-a3fa-4ca1-9a49-612d99539263\\})|(\\{a7775382-4399-49bf-9287-11dbdff8f85f\\})|(\\{afa64d19-ddba-4bd5-9d2a-c0ba4b912173\\})|(\\{b4ab1a1d-e137-4c59-94d5-4f509358a81d\\})|(\\{b4ec2f8e-57fd-4607-bf4f-bc159ca87b26\\})|(\\{b06bfc96-c042-4b34-944c-8eb67f35630a\\})|(\\{b9dcdfb0-3420-4616-a4cb-d41b5192ba0c\\})|(\\{b8467ec4-ff65-45f4-b7c5-f58763bf9c94\\})|(\\{b48e4a17-0655-4e8e-a5e2-3040a3d87e55\\})|(\\{b6166509-5fe0-4efd-906e-1e412ff07a04\\})|(\\{bd1f666e-d473-4d13-bc4d-10dde895717e\\})|(\\{be572ad4-5dd7-4b6b-8204-5d655efaf3b3\\})|(\\{bf2a3e58-2536-44d4-b87f-62633256cf65\\})|(\\{bfc5ac5f-80bd-43e5-9acb-f6d447e0d2ce\\})|(\\{bfe3f6c1-c5fe-44af-93b3-576812cb6f1b\\})|(\\{c0b8009b-57dc-45bc-9239-74721640881d\\})|(\\{c1cf1f13-b257-4271-b922-4c57c6b6e047\\})|(\\{c3d61029-c52f-45df-8ec5-a654b228cd48\\})|(\\{c39e7c0b-79d5-4137-bef0-57cdf85c920f\\})|(\\{ce043eac-df8a-48d0-a739-ef7ed9bdf2b5\\})|(\\{cf62e95a-8ded-4c74-b3ac-f5c037880027\\})|(\\{cff02c70-7f07-4592-986f-7748a2abd9e1\\}))$/", + "prefs": [], + "schema": 1576756770976, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1476369", + "why": "These add-ons contain unwanted features and try to prevent the user from uninstalling themselves.", + "name": "Smash/Upater (malware) and similar" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c7d7515d-563f-459f-821c-27d4cf825dbf", + "last_modified": 1576771657025 + }, + { + "guid": "/^((search-unlisted2@mozilla\\.com)|(search-unlisted3@mozilla\\.com)|(search-unlisted4@mozilla\\.com)|(search-unlisted5@mozilla\\.com)|(search-unlisted11@mozilla\\.com)|(search-unlisted12@mozilla\\.com)|(search-unlisted55@mozilla\\.com)|(search-unlisted111@mozilla\\.com)|(search-unlisted400@mozilla\\.com)|(search-unlisted40110@mozilla\\.com)|(search-unlisted17441000051@mozilla\\.com)|(search-unlisted174410000522777441@mozilla\\.com)|(search-unlisted@mozilla\\.com)|({0a054930-63d7-46f4-937a-de80eab21da4})|({0b24cf69-02b8-407d-83db-e7af04fc1f3e})|({0c4df994-4f4a-4646-ae5d-8936be8a4188})|({0d50d8aa-d1ed-4930-b0a0-f3340d2f510e})|({0eb4672d-58a6-4230-b74c-50ca3716c4b0})|({0f9e469e-4245-43f8-a7a8-7e730f80d284})|({0fc9fcc7-2f47-4fd1-a811-6bd4d611294b})|({4479446e-40f3-48af-ab85-7e3bb4468227})|({1a927d5b-42e7-4407-828a-fdc441d0daae})|({1a760841-50c3-4143-9f7e-3c8f04e8f9d1})|({1bd8ba17-b3ed-412e-88db-35bc4d8771d7})|({1c7d6d9e-325a-4260-8213-82d51277fc31})|({01c9a4a4-06dd-426b-9500-2ea6fe841b88})|({1cab8ccf-deff-4743-925d-a47cbd0a6b56})|({1cb0652a-4645-412d-b7e8-0b9e9a83242f})|({1d6634ca-dd37-4a31-aad1-321f05aa2bb3})|({1d9997b2-f61e-429a-8591-999a6d62becc})|({1ed2af70-9e89-42db-a9e8-17ae594003ac})|({01f409a5-d617-47be-a574-d54325fe05d1})|({2a8bec00-0ab0-4b4d-bd3d-4f59eada8fd8})|({2aeb1f92-6ddc-49f5-b7b3-3872d7e019a9})|({2bb68b03-b528-4133-9fc4-4980fbb4e449})|({2cac0be1-10a2-4a0d-b8c5-787837ea5955})|({2d3c5a5a-8e6f-4762-8aff-b24953fe1cc9})|({2ee125f1-5a32-4f8e-b135-6e2a5a51f598})|({2f53e091-4b16-4b60-9cae-69d0c55b2e78})|({3a65e87c-7ffc-408d-927e-ebf1784efd6d})|({3a26e767-b781-4e21-aaf8-ac813d9edc9f})|({3c3ef2a3-0440-4e77-9e3c-1ca8d48f895c})|({3dca6517-0d75-42d2-b966-20467f82dca1})|({3f4191fa-8f16-47d2-9414-36bfc9e0c2bf})|({3f49e12b-bb58-4797-982c-4364030d96d9})|({4aa2f47a-0bae-4a47-8a1b-1b93313a2938})|({04abafc7-7a65-401d-97f3-af2853854373})|({4ad16913-e5cb-4292-974c-d557ef5ec5bb})|({4b1050c6-9139-4126-9331-30a836e75db9})|({4b1777ec-6fe4-4572-9a29-5af206e003bf})|({4beacbbb-1691-40e7-8c1e-4853ce2e2dee})|({4c140bc5-c2ad-41c3-a407-749473530904})|({4cbef3f0-4205-4165-8871-2844f9737602})|({4dac7c77-e117-4cae-a9f0-6bd89e9e26ab})|({04ed02dc-0cb0-40c2-8bc8-6f20843024b8})|({4f6b6aaf-c5a1-4fac-8228-ead4d359dc6d})|({4f8a15fb-45c2-4d3b-afb1-c0c8813a4a5a})|({5af74f5a-652b-4b83-a2a9-f3d21c3c0010})|({5b0f6d3c-10fd-414c-a135-dffd26d7de0f})|({5b421f02-e55e-4b63-b90e-aa0cfea01f53})|({5b620343-cd69-49b8-a7ba-f9d499ee5d3d})|({5c5cf69b-ed92-4429-8d26-ff3bb6c37269})|({5cf77367-b141-4ba4-ac2a-5b2ca3728e81})|({5da81d3d-5db1-432a-affc-4a2fe9a70749})|({5eac1066-90c3-4ba0-b361-e6315dcd6828})|({5ec4c837-59b9-496d-96e2-ff3fa74ca01f})|({5efd8c7a-ff37-41ac-a55c-af4170453fdf})|({5f4e63e4-351f-4a21-a8e5-e50dc72b5566})|({6a934ff5-e41d-43a2-baf5-2d215a869674})|({06a71249-ef35-4f61-b2c8-85c3c6ee5617})|({6ad26473-5822-4142-8881-0c56a8ebc8c0})|({6cee30bc-a27c-43ea-ac72-302862db62b2})|({6ed852d5-a72e-4f26-863f-f660e79a2ebb})|({6eee2d17-f932-4a43-a254-9e2223be8f32})|({6f13489d-b274-45b6-80fa-e9daa140e1a4})|({6fa41039-572b-44a4-acd4-01fdaebf608d})|({7ae85eef-49cf-440d-8d13-2bebf32f14cf})|({7b3c1e86-2599-4e1a-ad98-767ae38286c8})|({7b23c0de-aa3d-447f-9435-1e8eba216f09})|({7b71d75e-51f5-4a71-9207-7acb58827420})|({7c6bf09e-5526-4bce-9548-7458ec56cded})|({7ca54c8d-d515-4f2a-a21f-3d32951491a6})|({7d932012-b4dd-42cc-8a78-b15ca82d0e61})|({7d5e24a1-7bef-4d09-a952-b9519ec00d20})|({7eabad73-919d-4890-b737-8d409c719547})|({7eaf96aa-d4e7-41b0-9f12-775c2ac7f7c0})|({7f8bc48d-1c7c-41a0-8534-54adc079338f})|({7f84c4d8-bdf5-4110-a10d-fa2a6e80ef6a})|({8a6bda75-4668-4489-8869-a6f9ccbfeb84})|({8a0699a0-09c3-4cf1-b38d-fec25441650c})|({8ab8c1a2-70d4-41a8-bf78-0d0df77ac47f})|({8b4cb418-027e-4213-927a-868b33a88b4f})|({8fcfe2b3-598e-4861-a5d4-0d77993f984b})|({9a941038-82fa-4ae4-ba98-f2eb2d195345})|({9b8a3057-8bf4-4a9e-b94b-867e4e71a50c})|({9b8df895-fcdd-452a-8c46-da5be345b5bc})|({09c8fa16-4eec-4f78-b19d-9b24b1b57e1e})|({09cbfddf-5e55-4676-920d-5a16cb9e4cb5})|({9cf8d28f-f546-4871-ac4d-5faff8b5bde3})|({9d592fd5-e655-461a-9b28-9eba85d4c97f})|({9fc6e583-78a5-4a2b-8569-4297bb8b3300})|({014d98ce-dab9-4c1d-8643-166e75d7cb4d})|({18c64b09-4ccb-4c21-ba6f-ebd4a1efa034})|({21d83d85-a636-4b18-955d-376a6b19bd19})|({22ecf14b-ead6-4684-a498-7b2b839a4c97}))$/", + "prefs": [], + "schema": 1576757328395, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1479009", + "why": "Malicious add-ons disguising as updates or useful add-ons, but violating data collection policies, user-control, no surprises and security.", + "name": "Firefox Update (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cae5d906-0b1d-4d1c-b83f-f9727b8c4a29", + "last_modified": 1576771657022 + }, + { + "guid": "/^(({0f9e469e-4245-43f8-a7a8-7e730f80d284})|({117ca2f3-df4c-4e17-a5c5-b49077e9c731})|({11db147a-a1cb-43dd-9c05-0d11683483e1})|({1ed2af70-9e89-42db-a9e8-17ae594003ac})|({24ed6bdc-3085-413b-a62e-dc5dd30272f4})|({2aa19a7a-2a43-4e0d-a3dc-abb33fa7e2b6})|({3d6fbbb3-6c80-47bb-af20-56fcaebcb9ca})|({42f4c194-8929-42b9-a9a3-afa56dd0913b})|({46740fa0-896d-4f2e-a240-9478865c47c2})|({4718da68-a373-4a03-a77b-0f49b8bb40ee})|({4d41e0b8-bf7e-45ab-bd90-c426b420e3ee})|({50957a38-c15d-42da-94f5-325bc74a554c})|({5650fc63-a7c5-4627-8d0a-99b20dcbd94b})|({5c5c38ec-08bf-493a-9352-6ccf25d60c08})|({67ecb446-9ccd-4193-a27f-7bd1521bd03c})|({71f01ffe-226d-4634-9b21-968f5ce9f8f5})|({72f31855-2412-4998-a6ff-978f89bba0c3})|({7b3c1e86-2599-4e1a-ad98-767ae38286c8})|({7c37463c-001e-4f58-9e88-aaab2a624551})|({7de64f18-8e6b-4c41-9b05-d8872b418026})|({82dcf841-c7e1-4764-bb47-caa28909e447})|({872f20ea-196e-4d11-8835-1cc4c877b1b8})|({8efee317-546f-418d-82d3-60cc5187acf5})|({93deeba1-0126-43f7-a94d-4eecfce53b33})|({9cc12446-16da-4200-b284-d5fc18670825})|({9cd27996-6068-4597-8e97-bb63f783a224})|({9fdcedc7-ffde-44c3-94f6-4196b1e0d9fc})|({a191563e-ac30-4c5a-af3d-85bb9e9f9286})|({a4cb0430-c92e-44c6-9427-6a6629c4c5f6})|({a87f1b9b-8817-4bff-80fd-db96020c56c8})|({ae29a313-c6a9-48be-918d-1e4c67ba642f})|({b2cea58a-845d-4394-9b02-8a31cfbb4873})|({b420e2be-df31-4bea-83f4-103fe0aa558c})|({b77afcab-0971-4c50-9486-f6f54845a273})|({b868c6f4-5841-4c14-86ee-d60bbfd1cec1})|({b99ae7b1-aabb-4674-ba8f-14ed32d04e76})|({b9bb8009-3716-4d0c-bcb4-35f9874e931e})|({c53c4cbc-04a7-4771-9e97-c08c85871e1e})|({ce0d1384-b99b-478e-850a-fa6dfbe5a2d4})|({cf8e8789-e75d-4823-939f-c49a9ae7fba2})|({d0f67c53-42b5-4650-b343-d9664c04c838})|({dfa77d38-f67b-4c41-80d5-96470d804d09})|({e20c916e-12ea-445b-b6f6-a42ec801b9f8})|({e2a4966f-919d-4afc-a94f-5bd6e0606711})|({e7d03b09-24b3-4d99-8e1b-c510f5d13612})|({fa8141ba-fa56-414e-91c0-898135c74c9d})|({fc99b961-5878-46b4-b091-6d2f507bf44d})|(firedocs@mozilla\\.com)|(firetasks@mozilla\\.com)|(getta@mozilla\\.com)|(javideo@mozilla\\.com)|(javideo2@mozilla\\.com)|(javideos@mozilla\\.com)|(javideosz@mozilla\\.com)|(search_free@mozilla\\.com)|(search-unlisted@mozilla\\.com)|(search-unlisted101125511@mozilla\\.com)|(search-unlisted10155511@mozilla\\.com)|(search-unlisted1025525511@mozilla\\.com)|(search-unlisted1099120071@mozilla\\.com)|(search-unlisted1099125511@mozilla\\.com)|(search-unlisted109925511@mozilla\\.com)|(search-unlisted11@mozilla\\.com)|(search-unlisted111@mozilla\\.com)|(search-unlisted12@mozilla\\.com)|(search-unlisted14400770034@mozilla\\.com)|(search-unlisted144007741154@mozilla\\.com)|(search-unlisted144436110034@mozilla\\.com)|(search-unlisted14454@mozilla\\.com)|(search-unlisted1570124111@mozilla\\.com)|(search-unlisted1570254441111@mozilla\\.com)|(search-unlisted15721239034@mozilla\\.com)|(search-unlisted157441@mozilla\\.com)|(search-unlisted15757771@mozilla\\.com)|(search-unlisted1577122001@mozilla\\.com)|(search-unlisted15777441001@mozilla\\.com)|(search-unlisted15788120036001@mozilla\\.com)|(search-unlisted157881200361111@mozilla\\.com)|(search-unlisted1578899961111@mozilla\\.com)|(search-unlisted157999658@mozilla\\.com)|(search-unlisted158436561@mozilla\\.com)|(search-unlisted158440374111@mozilla\\.com)|(search-unlisted15874111@mozilla\\.com)|(search-unlisted1741395551@mozilla\\.com)|(search-unlisted17441000051@mozilla\\.com)|(search-unlisted174410000522777441@mozilla\\.com)|(search-unlisted1768fdgfdg@mozilla\\.com)|(search-unlisted180000411@mozilla\\.com)|(search-unlisted18000411@mozilla\\.com)|(search-unlisted1800411@mozilla\\.com)|(search-unlisted18011888@mozilla\\.com)|(search-unlisted1801668@mozilla\\.com)|(search-unlisted18033411@mozilla\\.com)|(search-unlisted180888@mozilla\\.com)|(search-unlisted181438@mozilla\\.com)|(search-unlisted18411@mozilla\\.com)|(search-unlisted18922544@mozilla\\.com)|(search-unlisted1955511@mozilla\\.com)|(search-unlisted2@mozilla\\.com)|(search-unlisted3@mozilla\\.com)|(search-unlisted4@mozilla\\.com)|(search-unlisted400@mozilla\\.com)|(search-unlisted40110@mozilla\\.com)|(search-unlisted5@mozilla\\.com)|(search-unlisted55@mozilla\\.com)|(search@mozilla\\.com)|(searchazsd@mozilla\\.com)|(smart246@mozilla\\.com)|(smarter1@mozilla\\.com)|(smarters1@mozilla\\.com)|(stream@mozilla\\.com)|(tahdith@mozilla\\.com))$/", + "prefs": [], + "schema": 1576611696962, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1480591", + "why": "These add-ons violate the no-surprises and user-control policy.", + "name": "Search engine hijacking malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cee5c2ab-1059-4b15-a78c-1203116552c4", + "last_modified": 1576771657018 + }, + { + "guid": "/^((application2@fr-metoun\\.com)|(application@br-annitop\\.com)|(application@br-atoleg\\.com)|(application@br-cholty\\.com)|(application@br-debozoiz\\.com)|(application@br-echite\\.com)|(application@br-estracep\\.com)|(application@br-exatrom\\.com)|(application@br-iginot\\.com)|(application@br-imastifi\\.com)|(application@br-isobiv\\.com)|(application@br-ludimaro\\.com)|(application@br-pintoula\\.com)|(application@br-proufta\\.com)|(application@br-qhirta\\.com)|(application@br-qibizar\\.com)|(application@br-qopletr\\.com)|(application@br-roblaprouf\\.com)|(application@br-rosalop\\.com)|(application@br-samalag\\.com)|(application@br-sopreni\\.com)|(application@br-stoumo\\.com)|(application@br-villonat\\.com)|(application@br-zoobre\\.com)|(application@de-barbuna\\.com)|(application@de-bicelou\\.com)|(application@de-blabuma\\.com)|(application@de-dalofir\\.com)|(application@de-elplic\\.com)|(application@de-erotah\\.com)|(application@de-ertuck\\.com)|(application@de-eurosty\\.com)|(application@de-ezigat\\.com)|(application@de-lorelam\\.com)|(application@de-losimt\\.com)|(application@de-luchil\\.com)|(application@de-miligap\\.com)|(application@de-open-dog\\.com)|(application@de-rydima\\.com)|(application@de-slapapi\\.com)|(application@de-soqano\\.com)|(application@de-treboola\\.com)|(application@de-vasurk\\.com)|(application@de-ygivas\\.com)|(application@es-biloufer\\.com)|(application@es-boulass\\.com)|(application@es-cemaseur\\.com)|(application@es-elixet\\.com)|(application@es-gestona\\.com)|(application@es-glicalol\\.com)|(application@es-griloup\\.com)|(application@es-iblep\\.com)|(application@es-iglere\\.com)|(application@es-jounyl\\.com)|(application@es-klepst\\.com)|(application@es-nofinaj\\.com)|(application@es-ofarnut\\.com)|(application@es-phistouquet\\.com)|(application@es-pronzal\\.com)|(application@es-roterf\\.com)|(application@es-taapas\\.com)|(application@es-tatoflex\\.com)|(application@fr-acomyl\\.com)|(application@fr-avortep\\.com)|(application@fr-blicac\\.com)|(application@fr-bloubil\\.com)|(application@fr-carazouco\\.com)|(application@fr-cichalou\\.com)|(application@fr-consimis\\.com)|(application@fr-cropam\\.com)|(application@fr-deplitg\\.com)|(application@fr-doadoto\\.com)|(application@fr-domeoco\\.com)|(application@fr-domlaji\\.com)|(application@fr-eferif\\.com)|(application@fr-eivlot\\.com)|(application@fr-eristrass\\.com)|(application@fr-ertike\\.com)|(application@fr-esiliq\\.com)|(application@fr-fedurol\\.com)|(application@fr-grilsta\\.com)|(application@fr-hyjouco\\.com)|(application@fr-intramys\\.com)|(application@fr-istrubil\\.com)|(application@fr-javelas\\.com)|(application@fr-jusftip\\.com)|(application@fr-lolaji\\.com)|(application@fr-macoulpa\\.com)|(application@fr-mareps\\.com)|(application@fr-metoun\\.com)|(application@fr-metyga\\.com)|(application@fr-mimaloy\\.com)|(application@fr-monstegou\\.com)|(application@fr-oplaff\\.com)|(application@fr-ortisul\\.com)|(application@fr-pastamicle\\.com)|(application@fr-petrlimado\\.com)|(application@fr-pinadolada\\.com)|(application@fr-raepdi\\.com)|(application@fr-soudamo\\.com)|(application@fr-stoumo\\.com)|(application@fr-stropemer\\.com)|(application@fr-tlapel\\.com)|(application@fr-tresdumil\\.com)|(application@fr-troglit\\.com)|(application@fr-troplip\\.com)|(application@fr-tropset\\.com)|(application@fr-vlouma)|(application@fr-yetras\\.com)|(application@fr-zorbil\\.com)|(application@fr-zoublet\\.com)|(application@it-bipoel\\.com)|(application@it-eneude\\.com)|(application@it-glucmu\\.com)|(application@it-greskof\\.com)|(application@it-gripoal\\.com)|(application@it-janomirg\\.com)|(application@it-lapretofe\\.com)|(application@it-oomatie\\.com)|(application@it-platoks\\.com)|(application@it-plopatic\\.com)|(application@it-riploi\\.com)|(application@it-sabuf\\.com)|(application@it-selbamo\\.com)|(application@it-sjilota\\.com)|(application@it-stoploco\\.com)|(application@it-teryom\\.com)|(application@it-tyhfepa\\.com)|(application@it-ujdilon\\.com)|(application@it-zunelrish\\.com)|(application@uk-ablapol\\.com)|(application@uk-blamap\\.com)|(application@uk-cepamoa\\.com)|(application@uk-cloakyz\\.com)|(application@uk-crisofil\\.com)|(application@uk-donasip\\.com)|(application@uk-fanibi\\.com)|(application@uk-intramys\\.com)|(application@uk-klastaf\\.com)|(application@uk-liloust\\.com)|(application@uk-logmati\\.com))$/", + "prefs": [], + "schema": 1576756704239, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487627", + "why": "Add-ons whose main purpose is to track user browsing behavior.", + "name": "Abusive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "914ec360-d35e-4420-a88f-1bad3513f054", + "last_modified": 1576771657015 + }, + { + "guid": "/^((\\{0bf1c111-c256-4a17-891d-1bc69338162e\\})|(\\{0ffbc4b1-f269-4cff-9552-5f77337ebc1a\\})|(\\{1bbdc69d-55d3-4872-bd03-14eb05e7a7ad\\})|(\\{1ce00b82-ac47-43e6-a69c-f7dc9344168a\\})|(\\{2b01628b-0110-4965-972c-7a0b624fb99f\\})|(\\{3a84e0b0-3151-449e-b6e8-1062036afac6\\})|(\\{3bbcc16b-23f7-40a6-b88c-9ced9d009c93\\})|(\\{3ea48f4a-b585-44a7-aff5-faeb5e5b47d5\\})|(\\{7b161d2c-ee98-4321-a78a-433950672c8a\\})|(\\{8a8d97d8-b879-4024-8321-765e0f395b84\\})|(\\{9c0d6766-debe-4461-b14f-68ddfc13a78a\\})|(\\{014f9781-c104-41a4-a983-fc6aa4690664\\})|(\\{27ffdb27-0a34-4dea-a483-3b357bc6a5fe\\})|(\\{81ba256a-4f32-40df-86b5-e6b9861481e1\\})|(\\{338c7503-fb54-4b69-a84b-916f7452c7fa\\})|(\\{400e053f-55df-4e86-a91a-eae8d7b7bcd1\\})|(\\{617e0484-8346-44f2-851e-60ab89a919f9\\})|(\\{656a0095-d852-4dcc-a107-764df7ad0ec4\\})|(\\{754a330b-efbe-4016-8526-bf0f2e11e45e\\})|(\\{802ba900-013c-42f6-a11a-093c4bf35baa\\})|(\\{2771ce08-4898-4f58-89a5-e2b9d00bfab2\\})|(\\{3906b944-92f3-4d43-89dc-31ad6484a77c\\})|(\\{6516cdbc-9332-437f-89ac-b57470655542\\})|(\\{6847c507-1793-4be2-be86-4c2cc0b445bf\\})|(\\{9687db9b-410c-47f2-8c36-fde63c7c29e4\\})|(\\{0035237e-97ab-40eb-ba9d-c453fb6aa079\\})|(\\{20143127-e0bd-4396-aee9-52229cf9b5eb\\})|(\\{33254399-d5b2-4d84-b90b-9c4d4dc71112\\})|(\\{34621968-1952-4428-909d-df5b220efe74\\})|(\\{83769978-21cf-417c-b4a9-582b4161e395\\})|(\\{aa369db0-4232-47b8-bbbb-49ad31d49dce\\})|(\\{aff733de-d7d8-49c2-824a-7f2b2e282927\\})|(\\{c0b587fe-766b-446f-9aae-bc6edc9f6f4c\\})|(\\{c47a75b9-c6d2-4009-a245-c6dcedeea932\\})|(\\{c51bd197-28bd-442f-8537-dea5ae129047\\})|(\\{cac044a2-b93c-4f24-bf2f-b902741d29a8\\})|(\\{de17ce6f-389f-4368-9675-b9ed93133f17\\})|(\\{e2b105bc-1821-4838-bdf9-0fa4f6781b18\\})|(\\{e6c8bc7f-0133-418a-86ed-ba2c0a6925df\\})|(\\{f4acda5f-a75b-4b3b-8a73-8ca3df8d5f57\\})|(\\{f4fd18ee-8f6a-4708-8338-7e7981b73453\\})|(\\{f2320322-1fff-4998-bc28-4ad61621012a\\})|(\\{ff939f5e-a97c-4c14-b853-9c82519dbf73\\})|(@complete-youtube-downloader)|(@swsearchassist)|(@swsearchassist2)|(@youtube-download-helper-addon-1)|(@youtube-download-helper-addon-3)|(@ytd-support)|(@ytmp4-support)|(@ytu-support)|(18-plus-bypass@agebypass\\.org)|(18plus@sweetytweety\\.jp)|(addon@firefox-addon-s\\.com)|(ageverify@doubletrouble\\.net)|(auto-fill-dhruv\\.techapps@gmail\\.com)|(awesomeaddons@gmail\\.com)|(blndkmebkmenignoajhoemebccmmfjib@chrome-store-foxified--730948579)|(boomerang-for-gmailtm@chrome-store-foxified--1895216441)|(boomerang-for-gmailtm@chrome-store-foxified-1472004183)|(browsing_certificate@easycerts\\.in)|(certs-js@verify\\.org)|(clear-flash-cookies@tubedownload\\.org)|(dghpnfeglanbbjaggjegpbijhcbnfdno@chrome-store-foxified--1026618965)|(dghpnfeglanbbjaggjegpbijhcbnfdno@chrome-store-foxified--1382673267)|(dghpnfeglanbbjaggjegpbijhcbnfdno@chrome-store-foxified-3810896411)|(dhiaggccakkgdfcadnklkbljcgicpckn@chrome-store-foxified-1917762393)|(dhiaggccakkgdfcadnklkbljcgicpckn@chrome-store-foxified-2539369515)|(dhiaggccakkgdfcadnklkbljcgicpckn@chrome-store-foxified-3411285726)|(dhiaggccakkgdfcadnklkbljcgicpckn@chrome-store-foxified-3957753373)|(dhruv@gmail\\.com)|(easy\\.download@youtube\\.com)|(easy18plusverify@agehelper\\.com)|(EasyQR@johndoe)|(easytranslate@johndoehits)|(ecaieeiecbdhkcgknidmfelflleobbnp@chrome-store-foxified-2878848146)|(eurekasakamika@chrome-store-foxified-unsigned)|(faeeclonpikbempnbjbbajfjjajjgfio@chrome-store-foxified--1071037210)|(faeeclonpikbempnbjbbajfjjajjgfio@chrome-store-foxified-335403930)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified--546579415)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified--929033716)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified--1776201342)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-411918147)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-711293137)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-1406852911)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-1805495496)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-2344964585)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-2515600300)|(gefiaaeadjbmhjndnhedfccdjjlgjhho@chrome-store-foxified-2947667317)|(generated-c5el8k8zv2b1gcncleefj9@chrome-store-foxified--1160265316)|(html5-video-everywhere@lejenome\\.me)|(iapifmceeokikomajpccajhjpacjmibe@chrome-store-foxified-6029610)|(info@ytdownloader\\.info))$/", + "prefs": [], + "schema": 1576756923769, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1525880", + "why": "Add-ons that include abusive or malicious remote code.", + "name": "Various abusive add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "be96c9a1-0b01-468e-ac9b-761ba3587162", + "last_modified": 1576771657011 + }, + { + "guid": "/^((\\{7e4a75c1-dddc-4496-9963-1c6ac99cf226\\})|(\\{35af257d-14dd-4cd0-8ebd-2d30c2b30561\\})|(\\{d6362448-1e8f-47bf-9d2f-491648d18e3d\\})|(\\{80a42dcf-193e-43a2-b662-d6b14882403f\\})|(\\{044e39fc-333b-423c-8291-26078a780b02\\})|(\\{68b3c6ce-162f-4ece-9ffa-8279855a4228\\})|(\\{057b93a7-84e6-43ff-9686-d452435ed3c5\\})|(\\{1223cfa2-7aad-4a16-b98a-6bf63b346835\\})|(\\{9815ca8b-a77c-4e4d-beac-aad1c7021dcb\\})|(\\{e3f2795a-cefc-4f7f-9435-5f091b988d2f\\})|(\\{98fd0bd5-f486-4d81-9eb1-e89e9d56bfa2\\})|(\\{f96fafd2-5860-4bfa-9537-3f2ca9dd496e\\})|(\\{da93cdd9-6aca-410e-b2f2-e927da726559\\})|(\\{d97e0506-d330-4547-8a5c-093b8aa08d7a\\})|(\\{425ad6b3-72b8-43c0-be7c-2f6585fa0ec1\\})|(\\{0375f007-f5ba-46ec-86d2-c5da84576562\\})|(\\{e8915f55-6566-4872-97eb-d77fbdbf2fb3\\})|(\\{ebd3a0c4-bf9e-4dfa-b487-f77722055edb\\})|(\\{7cc62e47-ed20-45bc-8c92-bb57128e78d6\\})|(\\{b5a15631-6429-49bd-a670-e83ac41f93a9\\})|(\\{f263d545-3234-460f-b546-a8406a0a729d\\})|(\\{6468c148-9888-4243-8de5-cb6291cac82a\\})|(\\{da2281db-0036-46f6-8878-ff26e1cf6a2b\\})|(\\{63f579ba-eaf5-4e1a-a7c2-c5e889beaf9f\\})|(\\{84569fbb-d367-40ce-b24b-fd3b611283b8\\})|(\\{da2bc16f-b499-401b-8771-9d9f32d88f86\\})|(\\{1a275ad6-5dd3-47e9-a563-41a0bebdfd90\\})|(\\{e07ebf1e-5917-46a2-95d9-61d9b51f3797\\})|(\\{0d6791d2-ce0b-4f78-90e4-8e773703bd35\\})|(\\{502c7ef7-745c-4ea0-8066-a17cf1b74957\\})|(\\{c93f0aeb-ae9b-49d9-835b-c58a6b03aa46\\})|(\\{1f0bf2a4-aff5-42d3-8633-71e65f289250\\})|(\\{28766320-358e-42e3-a2c7-67ec77552424\\})|(\\{74d4fcda-c103-4fb7-810a-4596530c00a4\\})|(\\{7b3fd37a-a127-41a0-9e4d-59ccfa165e41\\})|(\\{787fa0b0-d5f1-4454-8b0c-72d191d6775f\\})|(\\{e2bae2ed-0368-48e7-8671-3bdcc5d7713f\\})|(\\{fee16fb4-830f-438a-a3d5-f7e911d23e02\\})|(\\{72113405-b4a5-46c3-a7c6-5353568b87bd\\})|(\\{5ede50a4-4151-4635-804f-a6f56115a0c6\\})|(\\{c11487a0-d104-4bc3-814b-474f8c29049c\\})|(\\{35690b6e-1979-4ea3-89aa-44a94dda2afa\\})|(\\{e9d698ef-bad4-4960-9df3-8c41605a6d7b\\})|(\\{1472b3c1-cae8-42c4-bbdf-e71134dccf08\\})|(\\{7a40b654-1232-4e76-81e7-d95260db25cd\\})|(\\{f54699c8-c82f-4d6e-a161-919bbe8410de\\})|(\\{dca6a5cd-0d24-442a-afd4-80572bb20c34\\})|(\\{b8d5d169-f076-4098-b671-a3cb8b410f56\\})|(\\{903e6561-0646-4c38-8039-d372d8e7c90a\\})|(\\{b39977b9-bcb2-448b-9d7b-9aec7f62bc26\\})|(\\{059b5c30-b96a-48df-8083-5fff97a8f9bf\\})|(\\{1d0351bb-1d96-4779-b639-44eeceb2ebfb\\})|(\\{80c0bdb4-ba98-472d-ae56-afd8b3021115\\})|(\\{4dfc5596-9655-4b0c-819d-e2ff48fb8556\\})|(\\{d7d3ed3c-6f73-42cb-b724-c33fccc1b465\\})|(\\{b378a858-89bb-492e-8b4d-eb83e910a14b\\})|(\\{ec1fa94c-8700-49d0-ba5d-df99a912519e\\})|(\\{4db5d249-881f-4442-8c01-28536c45ebfd\\})|(\\{7a411d82-fc50-4f20-bd2c-b2b065f18097\\})|(\\{675e002b-e144-4694-a725-9e8cc6a3fa67\\})|(\\{1902a069-c039-421e-b502-1e367c237196\\})|(\\{866bb3a8-82bb-4c9a-bca5-26fd5f37c4ec\\})|(\\{6a4e7017-43cd-4646-bb48-003620bb60fe\\})|(\\{bc5c676e-a75f-475b-a27c-79687b1de3ec\\})|(\\{6b544e1a-932d-4da9-aafe-c4b4bbfe1958\\})|(\\{99631434-ff1e-49d3-88d3-9ac40d0dd1bd\\})|(\\{623b31e0-f289-47cf-995e-5a195e261758\\})|(\\{1b4d88a5-4b5d-44c8-849c-82f129a7dacd\\})|(\\{48ba880a-b7c2-4e4e-af55-9134ac926c61\\})|(\\{4b498e2a-8b17-47c0-a449-89a76b6e737f\\})|(\\{d9cbd45f-cdbc-4be1-bb16-8e60764630ff\\})|(\\{bfaaa94a-1a93-4a1c-9b54-9dbe98f3ef07\\})|(\\{87b93e6e-70a6-4538-9848-e9d0f060e372\\})|(\\{fea4fd50-ed6a-4b8e-b00d-3b2276df6e34\\})|(\\{c15450f8-8da2-4add-a8f6-603d90e8d344\\})|(\\{ec972135-8e5c-49d4-bff8-b6006b21f2d2\\})|(\\{b039f24d-8b51-40d3-abf7-55e1dc502112\\})|(\\{b308870f-ae9a-4972-af28-0218717a47f4\\})|(\\{9349a202-8b8e-4777-ba93-c723810da51e\\})|(\\{798750dc-0057-47e0-a1af-73dec73544fa\\})|(\\{186e4b6a-e3f0-4970-8f7b-05ab6bc50320\\})|(\\{dec8de3e-d3a4-4946-bcbd-c3523fee11c5\\})|(\\{06539c62-00d3-4513-9aa4-048dd273107a\\})|(\\{b200a289-900a-4953-b2c6-b7a323d6fb66\\})|(\\{4080defb-6c6b-4012-bcac-71379e9c430f\\})|(\\{b110855c-90dd-427a-894c-54b93c6572b7\\})|(\\{dd599e99-3a48-4e36-9d83-56f8c0019d4d\\})|(\\{4f43f2c7-c1e6-4091-88fe-c829b3bfe553\\})|(\\{b7a022bc-6b89-4ac1-a1fa-bf02251336b0\\})|(\\{1aa370ca-9865-4c52-89a8-79e95abc82f7\\})|(\\{fb727d0f-7c3d-4bf6-8be4-284e7e8b8f83\\})|(\\{1579b5dd-ef3d-4754-bc59-8a7707fe1219\\})|(\\{66f0cb42-bb3e-4a16-90c1-bed1e3be4aad\\})|(\\{f13a1f79-f63a-4332-a9c9-11fc50328fc2\\})|(\\{29962f4d-bf74-4775-9d02-31fe546d6fa6\\})|(\\{aa539764-9ec3-41a6-af0e-6c2dc46ecbf5\\})|(\\{9412adf1-2714-4cb2-ad5b-13d41096234a\\})|(\\{86f2f4cc-97c5-4cc5-8151-c327ab379fba\\}))$/", + "prefs": [], + "schema": 1576757017958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549214", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "654077f4-a8b3-4822-8962-0bb1cac1d70d", + "last_modified": 1576771657006 + }, + { + "guid": "/^((\\{0209c9a2-f58e-4ca5-ba95-80de8a0bec36\\})|(\\{0429d1cf-e6c7-46b9-9959-4d24263f5b9f\\})|(\\{04aed622-650c-44b2-968e-a8740024bdfe\\})|(\\{054ccccf-bb7d-4d0d-ae7e-94931a469627\\})|(\\{06440d64-2a92-45a7-8e8e-c14a35f9224d\\})|(\\{06e83ce4-370c-41c0-bfb7-62bb0e77cabb\\})|(\\{06fe00d4-24df-497f-90e0-88db402cc9d6\\})|(\\{077e07c1-b948-4e88-9965-226cde465f9f\\})|(\\{07b424b4-8a9d-4fe6-afd5-1f2135f5f4ea\\})|(\\{080f27b3-ad41-4bf4-84bb-b6df1d395b0e\\})|(\\{0a3ba90b-224e-48ae-9440-b92d4ac03ee6\\})|(\\{0bd278d9-fbc6-40dd-914d-5e696c4255cb\\})|(\\{0c3c7c7c-3d15-4cf7-936d-dcf6070e82ce\\})|(\\{0ee32a7c-74b2-4a55-b388-8034b39c6b1d\\})|(\\{0f806e24-dbcc-4f14-b8f5-cfd7f88d6302\\})|(\\{0f9b7554-16d4-4496-8f01-e396256033b7\\})|(\\{1331d9a3-b9ec-43fc-a369-f73a926849b9\\})|(\\{14aca62f-1cc5-4424-a30a-ffb3d424b5bc\\})|(\\{14ec0bb2-f06f-46ad-b951-b810f7651284\\})|(\\{16744b56-7518-4526-bf9a-2531d694fb1a\\})|(\\{179d7013-b6d8-4a89-a861-30e0e8a7faee\\})|(\\{17cff984-12b5-438d-a915-41d7d006de4f\\})|(\\{17ee37ab-fe67-45bd-9666-bcf57a371e46\\})|(\\{19f6f2dd-32df-47a2-9b89-76543a987d46\\})|(\\{1a6ddac5-6ca0-4d59-a8e5-02345c67f703\\})|(\\{1b50ef3b-a364-4089-8ef6-1031cc7a0d1d\\})|(\\{1b830180-08d2-4381-a516-b84aba36e52f\\})|(\\{1cdff066-cb3a-4abf-95d0-39691e53dc75\\})|(\\{1e778837-1740-4a35-9eb1-e16b2c189037\\})|(\\{260d8452-72d2-4860-b14e-dd3fcb779656\\})|(\\{262c435a-42e0-4ca1-a713-f52672691f4e\\})|(\\{26d2406b-5118-44fe-a479-15a8c4f6f2bd\\})|(\\{28a26807-fdc2-4e79-b2a1-efcb1c21d199\\})|(\\{28c24c28-a094-4915-a2ff-5ff91caf076b\\})|(\\{2afddffd-6246-45f6-af19-a7803095bdf0\\})|(\\{2d37fd0a-5ae0-4d83-bc0e-fc7d870587c6\\})|(\\{2df75889-c43e-4f4d-b43b-e51d9b50167c\\})|(\\{2e4e320a-d5f6-4685-89f0-4d7084209c06\\})|(\\{2f93ea6a-1c1b-4456-b821-e8ba50aedece\\})|(\\{3034cb02-b9bb-4e8a-8749-cdd7fd1a6902\\})|(\\{3862859a-78c6-474e-b30e-303e86a7c6a4\\})|(\\{386afcb7-64f4-41db-b3e8-a76602ebb2fa\\})|(\\{38b61e2b-1af3-4f35-bdf0-cc4e3afc4880\\})|(\\{395065fd-1b7f-400f-aecb-9cfbcd9d607e\\})|(\\{3b0055c9-ea2c-43be-a927-ecd342946367\\})|(\\{3b37c6dd-d5f0-494d-9dd2-175db561b99c\\})|(\\{3e5a09c0-5f26-4d40-a5d0-a853f1fa759d\\})|(\\{3f2d032a-29ac-4cbe-9463-563f3ba6eb7f\\})|(\\{3f6ea025-e6c2-4372-adca-cb60b811e4da\\})|(\\{4253e6be-5b91-4b66-b44e-11f6260cee0e\\})|(\\{42c5b340-7cda-4d89-81a4-4a20754b5250\\})|(\\{42d8241e-e5f6-47d7-95f6-b6649143b12a\\})|(\\{46061237-f12b-4738-b1e4-7b7070fc03ca\\})|(\\{46bcaa76-c21e-44a2-aed8-6ba630fcc961\\})|(\\{47f394e0-02be-4a08-b865-287b993ac40e\\})|(\\{497c92fb-4d7d-4b9e-9884-a178e5991ee1\\})|(\\{498d00a0-3d8a-45ff-8e8f-3c27fcd12df6\\})|(\\{49ec4e6d-8152-461e-a2f5-095ede6c3cab\\})|(\\{4a87eeaa-4aa5-4695-b393-1ca4f00b2f3e\\})|(\\{4b0d3b3a-d61c-4968-a338-8de76d044f80\\})|(\\{4b9b2a47-e06f-4948-a20f-78ec1ef4e84c\\})|(\\{4dc32f1c-374e-4886-9a62-80ecfc23ed17\\})|(\\{4e901df2-8301-4588-9bc9-1e9f6c4f996f\\})|(\\{4fb6f5ed-eb5a-4115-a635-57fecad85d50\\})|(\\{50c0ae9a-ebaf-44f7-9ea7-52c7d1066721\\})|(\\{5160a705-c8e9-40b9-900e-6d26559038de\\})|(\\{5232e216-65a2-44d0-ba11-05fc8c332af7\\})|(\\{53e6e44a-a0af-49e2-af72-db4518f153bb\\})|(\\{58c7b5da-a1cc-437a-9401-2a56eb77df7d\\})|(\\{59aa5a90-0034-4350-adfe-76aff37e73ee\\})|(\\{59c5d279-711e-4217-8e5e-1aa1497ffcaa\\})|(\\{5a3f607d-7e1a-4faf-88e2-5806d74d18d4\\})|(\\{5a6364f7-3237-462d-bd3f-7c501830ceb0\\})|(\\{5dc73bfe-4193-4390-ae50-ad928655e21f\\})|(\\{5e085187-2509-4f8f-80ed-78c06351a77a\\})|(\\{5edfb7c3-04a5-447d-9069-2093289a7b98\\})|(\\{6219dabe-8f5f-4130-a650-8cfa971d7182\\})|(\\{62c9c13b-d001-4c42-819c-31b9763973c0\\})|(\\{656da759-0ae4-4f3e-a798-8293a5df9476\\})|(\\{66d70c4a-ad30-4f3c-afb4-b498a60c49b3\\})|(\\{68cb3185-4f55-42cb-97ea-188924b1d6c3\\})|(\\{6b99e0e4-e2e8-4fff-9da5-81c0b9e92b62\\})|(\\{6de81b5e-7556-4fc4-9cac-df56e898f3bf\\})|(\\{7162613f-ea9c-48b3-a0e3-6700ea61a4c8\\})|(\\{71ef8107-d5fd-4d2c-94b7-2dcd07448622\\})|(\\{7284399c-6be5-42ff-8ddc-5cc52d46ab40\\})|(\\{7422ce07-cac7-4fe6-af6b-16f5e7e27d05\\})|(\\{76a7b38d-7044-4e36-8315-38db10506ec8\\})|(\\{7772f851-8dd4-4d96-b426-6cd9f739a599\\})|(\\{797129e6-8cc9-401e-b9fe-0fee15533e9a\\})|(\\{7a3429ae-f293-4a70-a13d-f57f153557e3\\})|(\\{7bed7063-0842-43d9-b672-5e5e55915d5d\\})|(\\{7c0220cc-89e5-4726-ada1-fa2ffa412f28\\})|(\\{7c3c79d6-7e31-4947-b9b4-dd21f461ccd4\\})|(\\{7c70cbc0-e80c-4f3b-97b2-2530c4ac1349\\})|(\\{7fc4f148-2648-40f5-bd99-d057ac1292a6\\})|(\\{808a2093-68ff-4f73-b239-0d0f105c4d98\\})|(\\{8411b8e3-e302-48a2-91ee-550102b938f6\\}))$/", + "prefs": [], + "schema": 1576757094905, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552164", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name while providing unwanted functionality. This is not a legitimate Flash Player add-on.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2a4b5087-eca0-43e8-96f4-6632aabd83d3", + "last_modified": 1576771657003 + }, + { + "guid": "/^((akjbfncbadcmnkopckegnmjgihagponf@chromeStoreFoxified)|(akjbfncbadcmnkopckegnmjgihagponf@chromeStoreFoxified-2563213750)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-3767541208)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-2330732719)|(cidchfadpgepemappcgeafeicnjomaad@chrome-store-foxified-509978031)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-558690357)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-3523362862)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-850818380)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-3686225023)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-3659951669)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-1114585181)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-2593088680)|(edmdnjinnadgoalbaojbhkbognfappih@chrome-store-foxified-206569335)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-3272316989)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-96331909)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-2158751912)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-1554953450)|(kadbillinepbjlgenaliokdhejdmmlgp@chrome-store-foxified-323465212)|(kadbillinepbjlgenaliokdhejdmmlgp@chrome-store-foxified-3112875041)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-1868258955)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-611481225)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-162688242)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-1394660953)|(\\{de07e1ed-1597-45f9-957d-4edc44399451\\})|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified-294092903)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified--2032791676)|(akjbfncbadcmnkopckegnmjgihagponf@chrome-store-foxified--786206880)|(\\{76f8d31f-d1b6-4171-885e-6fcde28ca547\\})|(\\{b7492f2d-72b6-4816-83d5-9c82b3cc5581\\})|(\\{3f0fa616-3f92-42e2-ac1e-69ae7b1c7872\\})|(\\{2e324574-0761-4017-bc96-66270563e277\\})|(\\{950d03c6-722e-498d-90fc-ec9d9c1ab893\\})|(\\{6cb64844-2dca-4f29-82d1-cb59459ad824\\})|(\\{5347a8c7-a156-4455-8301-7d19d269bd2c\\})|(\\{17c69a23-df19-4655-aaa9-e8a35f186ddf\\})|(\\{381eb5ad-0f02-4146-85f4-2cc7c7a7dee4\\})|(\\{e797aab6-f3df-4d0d-89c2-320371720194\\})|(\\{91a95e76-4b27-427f-9554-7c1aa58c8489\\})|(\\{5bd5f5a3-3f30-4c90-bf5c-7ff32eae9fac\\})|(\\{e9cbcded-05e0-4cf0-9163-8507de356646\\})|(\\{4262365c-085f-4f2b-9bd7-048d7d1c90de\\})|(\\{d6d89cdf-36e4-44b5-8ea2-2283e25e99b9\\})|(\\{3ab34cbc-4a18-4fac-b629-3b10091d505e\\})|(\\{28beb080-37b1-42ec-a6e9-89cff276cc3e\\})|(\\{d83baff8-42f1-485c-bc61-0df0a2fa2834\\})|(\\{f1260949-ea01-4f69-b302-87ac898bc677\\})|(\\{f2bb825a-19b7-46ba-b759-557885e28ff9\\})|(\\{d1023b1e-87f6-49d4-b93d-80d94cafb101\\})|(\\{605bf342-f990-43b3-9053-b6ad563cc944\\})|(\\{20da0f4c-c6ee-4c4a-be76-8cb0fdd759b7\\})|(\\{29563a03-2ea3-4187-b3dc-af3027287df8\\})|(\\{9fc76cae-b6b4-45af-aa0e-81d1bf419812\\})|(\\{b83f6a6c-6bb3-492f-aad2-56a6b79a02d4\\})|(\\{4e340962-9d78-486c-8ec8-fdc8ba0448c3\\})|(\\{4f420c0e-824f-408b-8327-418934c566e9\\})|(\\{51057732-1a37-491c-afeb-dccbb18e2341\\})|(\\{ac9415c8-b978-4636-a0f6-99b75f1bfacc\\})|(\\{ba9d81ff-13da-4183-8b32-19cc18a198c3\\})|(\\{614f9cd7-d46e-47a5-bcd6-fc9cefc846ac\\})|(\\{83ab005b-85f8-4185-b500-26c78f29e470\\})|(\\{814b9b95-0470-42f5-9be1-b322ae1a300c\\})|(\\{c565d582-ef45-4ee5-a13d-e0bc544bb483\\})|(\\{bbc0a83c-ff01-4f55-beed-c8dd6256d99b\\})|(\\{00d71c76-8b41-4e12-877b-62ad742c5b5b\\})|(\\{22c15bb7-3cac-4480-ad95-8ef2b4762689\\})|(\\{4ce4a857-3ba4-46d3-83e1-424e608f8a1d\\})|(\\{638ad118-0407-437c-a657-f8bde7b0c511\\})|(\\{c35dba3d-eed7-4ee2-b7ed-b2f636810ea1\\})|(\\{7635e554-de52-4a55-81f4-5d4e7ac9e416\\})|(\\{b768c014-21ff-49c9-9a27-186e33574612\\})|(\\{e31ae098-b80a-4286-8688-8514ace2d0fd\\})|(\\{104607b9-ad49-4760-882a-5cc13164531a\\})|(\\{bf78148e-f4d1-48b7-92b2-93ca2003d619\\})|(\\{877777da-7695-4d7e-a469-2a4b4cfbe0c4\\})|(\\{b09f3de0-26c4-4790-ba8e-50a1d1725053\\})|(\\{a24b471c-9100-455c-825a-689533d24979\\})|(\\{12a8c732-c19a-468e-8be4-a16f9a27c845\\})|(\\{bad6c6a4-6108-4e44-b7e3-c05bed9d4e50\\})|(\\{1b598a16-ca58-41bf-8cc2-3741356363b9\\})|(\\{a5520fcc-b75a-4527-931b-e403aa8772ef\\})|(\\{cec7aeec-9352-4ed1-8362-8e384704ab29\\})|(\\{1bf3e066-3707-41eb-b62d-55da5bbe930d\\})|(\\{1fd8204a-f25b-47d0-bfac-35c41451e2e7\\})|(\\{ab1f1e53-9102-4f4f-a793-0a81f5669e13\\}))$/", + "prefs": [], + "schema": 1576756466412, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554606", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Various remote script injection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8258f92c-5b89-42a7-a984-dd4e94fa301a", + "last_modified": 1576771656999 + }, + { + "guid": "/^((\\{ca33d7f8-5b8c-4215-bf6e-a29b721024b8\\})|(\\{ef107eb3-c699-42ce-9310-1f36890fcac5\\})|(\\{48a3b395-8cb9-4093-b557-d967c653b13f\\})|(\\{27e7c4c8-916b-4dae-ab1d-46573fe889e6\\})|(\\{4795f211-f8b2-45b1-852c-982e1912414e\\})|(\\{000d5ed8-cf10-4929-89b5-f5369f50bbcd\\})|(\\{4b249174-1bc4-49c2-a0ca-eaa51facd4b0\\})|(\\{34d96b10-c44d-4398-9410-8d7d550d023a\\})|(\\{c067681d-dc69-4b05-8052-34fa69549aa8\\})|(\\{5b2dcc2b-08a9-4d36-a249-680a9e994938\\})|(\\{68cb60e7-4bfc-43cf-9875-f9548ad5d913\\})|(\\{ddca4204-8f9c-4e35-a8fe-47e94ffced48\\})|(\\{c5138dc7-3cf8-4117-9988-041c2a85868c\\})|(\\{140c670f-ee4d-4a16-bf83-c4012279b923\\})|(\\{d88779b5-daad-4ae8-abdc-3ff58c80da8a\\})|(\\{ab80f9f6-2a50-4074-b560-f839f1674bb3\\})|(\\{a8187405-efb6-4c1c-a9dc-e90fc064f55d\\})|(\\{f0d5c8c8-0697-4ee9-830d-3271ad125c17\\})|(\\{34e3121a-a2f2-4ada-b271-c661b8e0a215\\})|(\\{c4d8c3ad-ba8e-43e7-ae76-90521132805e\\})|(\\{ec5441c0-ddd4-4e70-8d02-92b99eb5f306\\})|(\\{ba00c2cd-f59d-44c5-984c-fbd066cdabfe\\})|(\\{b41fb99b-8e21-4eb8-b825-c6855daeb9c3\\})|(\\{c8329103-f242-4dee-9fca-b98e2e15c096\\})|(\\{59d0d43f-875f-4ebd-beeb-4dfa213a7d20\\})|(\\{9ce5d8a7-e97b-4341-bf16-c12ad44368c7\\})|(\\{e10ea3cf-17ea-4270-8602-83162b1c8309\\})|(\\{303e86df-ad8c-4a55-b921-5e2a32441834\\})|(\\{8ecebe6c-0ac5-4f3a-a32f-50b1686ee538\\})|(\\{b5150eb9-3cf8-4162-b114-56b289c45f75\\})|(\\{c04252ab-747b-4718-9d1c-bb90c72c4874\\})|(\\{976ecad8-b154-4201-a55e-4478a1651a42\\})|(\\{a0f98d44-f4b9-4726-9f01-7587ee46634c\\})|(\\{6b9c9f21-1108-4ae9-a1d8-d56566e20f13\\})|(\\{fdacc9f4-06f1-4619-bfbc-61f790e279df\\})|(\\{74d5b273-dcf7-4606-9b9d-0c5c38cbab80\\})|(\\{6fee68c0-1b38-483f-963b-43919f4ea797\\})|(\\{017a0e34-4942-47d7-a0ff-2093f14e17ae\\})|(\\{71251d4a-7ff4-4450-9459-163b911d9518\\})|(\\{6da358f8-9746-4c39-957d-b6821561b566\\})|(\\{4613ad29-db02-4d30-b857-b84a8ab412a2\\})|(\\{7993943a-0d47-4d30-8989-ce039ec1636f\\})|(\\{ac2fbafe-f182-49f0-920c-2e0d026b6c1f\\})|(\\{5f79fd50-fb20-42f1-adf5-3021aaf3f0e9\\})|(\\{a4295850-5057-46ab-bb31-2d283dff2474\\})|(\\{74517834-8cb5-4895-9f8b-3de15b771d92\\})|(\\{3dbebe53-9687-4e36-8c1e-79fcb098cecf\\})|(\\{275fc9ab-64fd-4430-8a35-43f73a87e8b9\\})|(\\{a26cdedc-b1eb-4fd2-b331-e71033c489a0\\})|(\\{d8fa69e8-1008-4f32-9db2-13ea7589bcec\\})|(\\{59704739-f6df-4272-968d-32f7c599da09\\})|(\\{208f846e-851a-4c07-a448-a66c40a2294c\\})|(\\{46c7b5ea-c1d0-4c2b-9122-3baa2e3bda3e\\})|(\\{39220be1-e69c-4b22-a5ff-545fcacf215f\\})|(\\{c0975246-6858-46e2-8f09-7d80d810c040\\})|(\\{8f284821-a420-4d79-bb7f-c1aae7a2fa90\\})|(\\{86d3e654-73c5-4b7f-a942-bd2347d4517a\\})|(\\{3d1af64b-542e-47ee-98a3-1f89bfca0f2a\\})|(\\{f0c2850d-101b-4de1-be16-3f09963048ab\\})|(\\{7388541e-8d9e-48a9-ac43-87dfdced6e87\\})|(\\{5d37398b-bea7-4ca7-bc4f-95de295be960\\})|(\\{3d8b3d51-3621-4aa5-b229-731cee83ee64\\})|(\\{ba67c9cf-ef60-4085-b6cf-729e5245089a\\})|(\\{1efe8d5d-ca8d-4a53-b2d4-a41380067041\\})|(\\{7698ee9d-345b-4395-b9e7-0479ed91f98d\\})|(\\{5f233e13-1892-41b6-81c6-a26c702d4a09\\})|(\\{d569420c-50a6-4082-b6d9-41c7bcb33464\\})|(\\{e406fddd-5ba4-4fdc-aa46-d556f97c8ef9\\})|(\\{29066f7e-a4ed-40b1-a02d-38ddf25d9533\\})|(\\{57e3e757-ca29-440c-9ef8-864da0e7ee72\\})|(\\{1237079a-7a08-4660-8fdb-6c3fdcecc787\\})|(\\{60429834-7a98-45e7-b525-6f31d55bbb3c\\})|(\\{bb850649-4ada-4735-a861-072ce9b647dd\\})|(\\{dfb103ab-cfee-49cd-b33a-e134367408c5\\})|(\\{24a0a50a-15d2-4806-9226-78491b3e986c\\})|(\\{2eb4b76a-f057-4d14-8d63-a5afa3571158\\})|(\\{cb5ad67a-5304-4351-bb15-530b159fa1f7\\})|(\\{0bd8bfda-24f5-4652-a434-664621e04a36\\})|(\\{f249d964-ac83-4059-893a-c3c5b38cc746\\})|(\\{94ba7f66-aef6-4590-8044-03aa705a4a2b\\})|(\\{dd178abb-e6fb-4e0a-b242-a64a53b24fb5\\})|(\\{7ff1da48-ae61-45f1-bcbf-2c22b4571d58\\})|(\\{d01909f7-598b-41b0-8907-2a9815c5c457\\})|(\\{93a37679-eb92-4eec-93b2-7bf77b0f94ca\\})|(\\{3ff7ce60-fbe5-4a50-9733-a347a02f09e5\\})|(\\{f076d6ff-1daf-42f3-b485-5b54b13aaf3e\\})|(\\{571d827c-5c84-42cc-8386-9e12abb8209a\\})|(\\{f6515794-8d44-402c-9a3d-3d712cb437bb\\})|(\\{796a59ab-77a7-4add-9481-5c7deee7c037\\})|(\\{a9b99a24-6cc1-491a-a81b-946cb42bc9f2\\})|(\\{9767896a-9561-426e-90d1-03b884d34eda\\})|(\\{95a6ebd9-52bc-4859-b92f-70a7c103e2b8\\})|(\\{4d24059d-463b-40b0-a86a-0a1de38fccb9\\})|(\\{d7c03c15-7287-4fb0-add0-c49744b48410\\})|(\\{6074519d-2d7c-45b6-b239-ea5452e93140\\})|(\\{425dafa9-0997-48df-b971-623847853747\\})|(\\{847688e9-b1d7-4607-b4fd-44c2365c01ba\\})|(\\{1383930d-64e9-4c3d-a629-361c70e3cd26\\}))$/", + "prefs": [], + "schema": 1576756857135, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1558136", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various fake Flash/Avast/etc clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "72593718-af38-4f63-bcb9-7c6afd13de8f", + "last_modified": 1576771656996 + }, + { + "guid": "/^((\\{9aa1f441-7c04-4b00-83b2-6a4362090b41\\})|(\\{3001d016-bc15-49af-a81b-2c8764139321\\})|(\\{0af1d242-b004-49ae-91fc-00fa0f642bf9\\})|(\\{95a5a094-ba8e-4fe1-80bb-6f6c0a01bc2c\\})|(\\{36b33158-36fc-4728-bf08-8e532100af58\\})|(\\{5bda1d03-6533-4d8c-adb4-7179402ddeec\\})|(\\{dc7d18c8-c832-42cd-b9b0-f6a46a737ad1\\})|(\\{56d85baf-c366-491b-b93c-733a4a36009d\\})|(\\{1b08c0c7-d2fd-4905-82ab-d4d759af4051\\})|(\\{1b8a9b33-b3d9-42ea-adce-cec910c44f29\\})|(\\{0be707e8-d7d6-45d5-8212-3fd3784d7de7\\})|(\\{fb2e3c38-be42-480f-b60c-d614d372e218\\})|(\\{c088d705-9f59-40ac-98eb-192f67c44f03\\})|(\\{9570bbbd-d761-4380-850c-d9cc15200916\\})|(\\{2652d5a4-26b1-4e6f-9134-304d7b57af7a\\})|(\\{1e0a2a5f-170c-43c1-b458-c8fc8bdd7dec\\})|(\\{47595710-e0b3-4a88-9bf7-54e1f3bb6772\\})|(\\{c25a320e-dc50-486a-9589-13ef22f75a21\\})|(\\{645237c8-7da2-4298-a789-e11fbaaa580e\\})|(\\{f2356af6-9b9d-4c69-876a-710d446a9124\\})|(\\{98a67ccd-599a-4675-9578-35af1824fdd2\\})|(\\{f3924f49-64a3-4fde-8598-76eec8e67f34\\})|(\\{2a46402d-b6c4-4a0b-87f0-dc90bb24fa93\\})|(\\{40d92297-295d-4a44-8a0f-dd69510c9c30\\})|(\\{a5462b0d-6528-47d5-ada5-4a23d1e0355e\\})|(\\{280f7325-eaf0-451a-ad2d-3b2c4e80e070\\})|(\\{ee5a7045-e216-4836-949a-07f5aa1dabc3\\})|(\\{34ee38f4-e2ba-4e9d-8b1f-dd06e8bd205f\\})|(\\{7c71c234-ba74-467c-b750-727ee7e38382\\})|(\\{a83b3b31-4bfc-4343-beab-761f21b97f57\\})|(\\{b85d35a4-3a03-430d-a1a0-437448a86c22\\})|(\\{8b6c1e29-5009-477e-a798-244b0efb1515\\})|(\\{64891348-7fc0-4299-bd6a-6bfaa6cf21a6\\})|(\\{baa828ff-1723-483b-8034-145ad2795efd\\})|(\\{81fd4851-7ea3-4ea5-8775-49372fe1c8c8\\})|(\\{ce7d8e95-c7d3-49dc-8abf-e860ee707b09\\})|(\\{d84109e8-9945-48bc-90e8-0dd0b1b63b73\\})|(\\{ec12fd66-7294-4167-8fbc-4774150c0fa4\\})|(\\{0c506de9-8467-4a92-8cd3-11c87e121db2\\})|(\\{b1671fe5-c90a-4f68-b8bf-e54a147b5d05\\})|(\\{8b25277a-4df8-4d2d-b3b4-f8219e2ce7d6\\})|(\\{5705cd04-46c2-459f-8a9a-97ce57eee1ae\\})|(\\{8340b371-ed61-4b07-b293-853aa5dbb866\\})|(\\{87bbb065-e195-48ac-989e-ba48ee63b404\\})|(\\{c455fc20-6e9d-418c-9b45-75fd85852b32\\})|(\\{a80c87a7-3366-4192-b9cc-d1e862e1c13d\\})|(\\{5f4ba05c-c1a5-4bc7-b8d3-c14e807b2c64\\})|(\\{0114315a-beda-4d55-89a3-e00f6346e7be\\})|(\\{0fb7b987-721e-4828-9a0e-a72860ded1b2\\})|(\\{c44cd2a5-bf28-4520-ab2d-187752e51a26\\})|(\\{3a34c1a6-3cbf-49a0-bfe9-beff60da5ec6\\})|(\\{677e89a4-ae10-42f3-8e9d-d51be40daf8f\\})|(\\{85d70eae-fde6-4ac0-ab82-0148f2eb1543\\})|(\\{fddac013-6d12-41a4-9924-f5ea7618f22d\\})|(\\{84b5ca75-a431-45c7-995d-6d7268decd0d\\})|(\\{7374dd37-a901-4b65-993c-3323f87e0f3c\\})|(\\{157a5c60-7899-4328-a90c-83d34d0844d4\\})|(\\{5b288e8d-f33c-4602-a945-07f96e43a041\\})|(\\{80f059bd-602a-42d5-9b17-9f2c6a074102\\})|(\\{8e5a8075-8e21-4ab2-b189-5d435208122c\\})|(\\{d5b94c09-0ff8-4b86-b52a-590d5e5ad9af\\})|(\\{a569eb31-9456-49a9-9aa9-e69a8db159d3\\})|(\\{0431a147-f9f5-4ee3-8dca-57303110c226\\})|(\\{88c77421-5ebd-46fb-92a8-e0459b6edd20\\})|(\\{a4a0697d-9a01-4b21-bb88-5ac949cdb7b3\\})|(\\{03e0da0a-da1d-46f2-85c7-08258189fc04\\})|(\\{351f5a4d-a0fd-4ce7-a85a-6cb74fb6c57d\\})|(\\{0e0b22ea-831e-4104-9c2e-612d7ebf82e2\\})|(\\{7b5c604f-ea41-4bfa-88d1-843f27645199\\})|(\\{863f023c-a2e5-4043-8e49-8e3029004b19\\})|(\\{20e4b367-f8ad-4c9a-b590-976be87206aa\\})|(\\{436ca3ce-f10a-4cdc-86c2-a46f086b5fdb\\})|(\\{a53983bc-545e-429a-8aaa-6332e1a6287f\\})|(\\{5fb1995e-ac7e-4c01-a592-8b262856e039\\})|(\\{56086da0-b20d-4118-9670-56e85624c875\\})|(\\{67ef5673-5769-4d5b-902f-ba22cb16a51b\\})|(\\{1d50e81b-0594-4c24-80c2-abc479be4d18\\})|(\\{b190f2cf-f3f0-40e7-a05b-8eed296a0c8d\\})|(\\{490d8bfc-fd42-4ded-9fee-1f009e777468\\})|(\\{1d647c69-8e4b-4fe8-b0f3-d914a1410ee8\\})|(\\{4958ae27-1251-49b4-a9d4-2af7c36c833b\\})|(\\{6e80fdc3-79c7-423a-8e95-0e123c0f2187\\})|(\\{d8d3230d-5642-4c3f-a64e-5ff690f7a466\\})|(\\{ddbe5d7a-e037-4749-89bc-3460d358aca1\\})|(\\{48cd023c-52ef-4fe7-a2ff-101c22c49a5e\\})|(\\{fef34931-b7ca-49a5-8827-bec353efaa08\\})|(\\{331a936b-a87e-4271-b9a6-30935dad77ed\\})|(\\{21084437-803c-49c3-8f84-9a615bfa5dfe\\})|(\\{93511e9d-badf-4b5c-9fbc-17ef6a9786ae\\})|(\\{470e388e-a039-43f8-a7a7-d8f54d273feb\\})|(\\{e4cd5604-caec-4139-9781-94add5f41ba3\\})|(\\{f00bc2b9-2b69-4f68-8bef-91d498686731\\})|(\\{7b3206c4-dc98-4ad9-88ba-5a7b3048fd92\\})|(\\{8e19f5f7-2445-48b1-8910-55d42d9dfe34\\})|(\\{770e30c6-f1b9-4df4-8551-c7f09ddbd8ed\\})|(\\{bb9a8679-1f82-4ab7-8ea2-bac864a38542\\})|(\\{13f5e078-b80a-45fb-a907-c4515a7bb0fa\\})|(\\{d8df47e3-5e86-4669-9a15-a37f8a8593db\\}))$/", + "prefs": [], + "schema": 1576756624851, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1565184", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Private Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9f945be6-12e7-4948-af77-668bc3996ff3", + "last_modified": 1576771656992 + }, + { + "guid": "/^((_0dMembers_@www\\.myaudiotab\\.com)|(_12Members_@free\\.myscrapnook\\.com)|(_14Members_@download\\.totalrecipesearch\\.com)|(_1cMembers_@www\\.bringmesports\\.com)|(_29Members_@www\\.headlinealley\\.com)|(_2bMembers_@www\\.bettercareersearch\\.com)|(_2jMembers_@free\\.recipehub\\.com)|(_2jMembers_@www\\.recipekart\\.com)|(_2vMembers_@www\\.dailybibleguide\\.com)|(_39Members_2@www\\.mapsgalaxy\\.com)|(_49Members_@www\\.utilitychest\\.com)|(_4jMembers_@www\\.radiorage\\.com)|(_4lMembers_@www\\.bibletriviatime\\.com)|(_4zMembers_@www\\.videodownloadconverter\\.com)|(_57Members_@free\\.marineaquariumfree\\.com)|(_5aMembers_@download\\.mywebface\\.com)|(_5eMembers_@www\\.translationbuddy\\.com)|(_5mMembers_@download\\.myfuncards\\.com)|(_5pMembers_@www\\.metrowhiz\\.com)|(_64Members_@www\\.televisionfanatic\\.com)|(_69Members_@www\\.packagetracer\\.com)|(_6xMembers_@www\\.readingfanatic\\.com)|(_73Members_@www\\.easyhomedecorating\\.com)|(_7eMembers_@www\\.homeworksimplified\\.com)|(_7jMembers_@download\\.gardeningenthusiast\\.com)|(_7nMembers_@download\\.crazyforcrafts\\.com)|(_8eMembers_@download\\.howtosimplified\\.com)|(_8hMembers_@download\\.allin1convert\\.com)|(_8iMembers_@download\\.audiotoaudio\\.com)|(_8jMembers_@download\\.myimageconverter\\.com)|(_8lMembers_@free\\.filesharefanatic\\.com)|(_94Members_@www\\.motitags\\.com)|(_9eMembers_@free\\.findmefreebies\\.com)|(_9pMembers_@free\\.onlinemapfinder\\.com)|(_9tMembers_@download\\.internetspeedtracker\\.com)|(_9tMembers_@free\\.internetspeedtracker\\.com)|(_apMembers_@free\\.puzzlegamesdaily\\.com)|(_b7Members_@free\\.mytransitguide\\.com)|(_beMembers_@free\\.dailylocalguide\\.com)|(_brMembers_@free\\.yourtemplatefinder\\.com)|(_ceMembers_@free\\.easypdfcombine\\.com)|(_chMembers_@free\\.discoverancestry\\.com)|(_d0Members_@free\\.gostudyhq\\.com)|(_d1Members_@free\\.mysocialshortcut\\.com)|(_d9Members_@www\\.everydaylookup\\.com)|(_dbMembers_@free\\.getformsonline\\.com)|(_dgMembers_@free\\.trackapackage\\.net)|(_dhMembers_@www\\.packagetracking\\.net)|(_diMembers_@www\\.easymaillogin\\.com)|(_djMembers_@www\\.emailfanatic\\.com)|(_dnMembers_@www\\.webmailworld\\.com)|(_doMembers_@free\\.convertanyfile\\.com)|(_dpMembers_@free\\.findyourmaps\\.com)|(_dqMembers_@www\\.downspeedtest\\.com)|(_drMembers_@free\\.downloadinboxnow\\.com)|(_dsMembers_@free\\.internetspeedutility\\.net)|(_duMembers_@free\\.funpopulargames\\.com)|(_dvMembers_@www\\.testinetspeed\\.com)|(_dxMembers_@www\\.download-freemaps\\.com)|(_dzMembers_@www\\.pconverter\\.com)|(_e1Members_@free\\.actionclassicgames\\.com)|(_e5Members_@www\\.productivityboss\\.com)|(_e6Members_@www\\.freelocalweather\\.com)|(_e7Members_@free\\.gamingassassin\\.com)|(_eaMembers_@www\\.mynewsguide\\.com)|(_ebMembers_@download\\.metrohotspot\\.com)|(_ecMembers_@www\\.instantradioplay\\.com)|(_edMembers_@free\\.myradioaccess\\.com)|(_eeMembers_@download\\.freeradiocast\\.com)|(_efMembers_@free\\.funcustomcreations\\.com)|(_ehMembers_@free\\.dailyrecipeguide\\.com)|(_eiMembers_@www\\.100sofrecipes\\.com)|(_ejMembers_@free\\.downloadrecipesearch\\.com)|(_ekMembers_@free\\.biggamecountdown\\.com)|(_eoMembers_@www\\.transitsimplified\\.com)|(_erMembers_@free\\.getvideoconvert\\.com)|(_esMembers_@free\\.downloadmanagernow\\.com)|(_euMembers_@free\\.filesendsuite\\.com)|(_ewMembers_@free\\.mergedocsonline\\.com)|(_exMembers_@free\\.easydocmerge\\.com)|(_f7Members_@download\\.smsfrombrowser\\.com)|(_fdMembers_@wallpapers\\.myway\\.com)|(_fkMembers_@free\\.getflightinfo\\.com)|(_foMembers_@free\\.flightsearchapp\\.com)|(_fpMembers_@free\\.passwordlogic\\.com)|(_frMembers_@free\\.testforspeed\\.com)|(_fsMembers_@free\\.pdfconverterhq\\.com)|(_ftMembers_@free\\.mytelevisionhq\\.com)|(_fvMembers_@free\\.directionsace\\.com)|(_fwMembers_@free\\.howtosuite\\.com)|(_fxMembers_@free\\.mytransitplanner\\.com)|(_g3Members_@free\\.easyphotoedit\\.com)|(_gcMembers_@www\\.weatherblink\\.com)|(_gpMembers_@free\\.mymapswizard\\.com)|(_gtMembers_@free\\.gamingwonderland\\.com)|(_h2Members_@free\\.calendarspark\\.com)|(_hbMembers_@free\\.quickphotoedit\\.com)|(_hfMembers_@free\\.everydaymanuals\\.com)|(_hgMembers_@free\\.atozmanuals\\.com)|(_hmMembers_@free\\.easyweatheralert\\.com)|(_hnMembers_@free\\.quickweatheralert\\.com)|(_hoMembers_@free\\.directionsbuilder\\.com)|(_hpMembers_@free\\.easyfileconvert\\.com)|(_hqMembers_@free\\.scenichomepage\\.com)|(_hvMembers_@free\\.myfileconvert\\.com))$/", + "prefs": [], + "schema": 1576757193463, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598806", + "why": "These add-ons collect ancillary user data among other policy violations.", + "name": "New tab data collection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1b799430-d1a3-49a8-91a2-f45069528ebe", + "last_modified": 1576771656988 + }, + { + "guid": "/^((\\{02cfe44d-20d1-4275-b325-42888248443d\\})|(\\{03476021-f140-425d-8e62-a6455206a874\\})|(\\{0a50fe22-313f-4a59-9032-2fd63bdd2815\\})|(\\{21c91802-e348-4dd0-820a-07aca1de12a5\\})|(\\{24f43a02-83c5-44cd-a611-0bae2093393f\\})|(\\{257284b0-be1d-44e4-8321-b2a7a56c2eb5\\})|(\\{2610c188-123a-4e4f-a6c7-2ed7e8b70bf6\\})|(\\{26a28ba8-d8e3-4284-ab58-8542144ff657\\})|(\\{27e8838f-91ab-486b-8851-9de8bd633aac\\})|(\\{27f348a5-adb0-4037-bc03-f6832cfd6fc3\\})|(\\{28f5d824-8067-4859-8fe3-22a5f96552bc\\})|(\\{32f66767-230f-4dd8-8538-d6dca8b327f6\\})|(\\{35a35db0-010b-4105-9dc3-d5fe761023ab\\})|(\\{3d61019d-c636-4c0d-91d2-6e2d734014e0\\})|(\\{44c9b9cf-b960-45fc-bf0a-9be9c455c975\\})|(\\{48e711a3-644f-4520-8205-11b377df480e\\})|(\\{50ba963f-1b4a-4ed0-b22f-8e1c18d17364\\})|(\\{53150391-1080-4a50-96ed-de24a6ea24c6\\})|(\\{5570199f-fc69-45eb-a0b0-7cc60089022c\\})|(\\{595c83ad-5a64-4740-a118-39a49aede0e3\\})|(\\{5ae5110f-449e-4c5e-b247-9d1e9971d3bc\\})|(\\{61b734ef-f886-4466-8525-8276e9b19b55\\})|(\\{64e9998b-c477-4c29-bf1d-945146cf9376\\})|(\\{682ff4d6-9b2b-4e1b-99b0-e2dcc2476bd1\\})|(\\{6836a626-1ec1-4c3c-9b25-5e4cd5048ad5\\})|(\\{69f1dda4-a6fa-49f6-a16c-7b3bc3e0e520\\})|(\\{6b7eb4f0-ef7d-4da4-8c3d-21b7c2dfb19f\\})|(\\{725f919c-8adf-4192-9c0e-89ab1027161e\\})|(\\{7320730b-e202-4405-8d76-97a862239701\\})|(\\{79821b50-57ca-4267-9dbf-84aeb061e126\\})|(\\{7ee25d12-df90-4a6a-a068-7a6919651bc7\\})|(\\{816b4ade-7b69-4a73-994a-b37f9939d5d8\\})|(\\{82956d74-9e19-4770-97cc-73caa2c9f711\\})|(\\{839b2467-6857-49f9-a730-baf7b2a98039\\})|(\\{85af3203-f8a3-4ba5-8d0f-c3ee1d38c0d8\\})|(\\{8866f414-2f72-44a6-b7dc-6959891e9ee3\\})|(\\{914f8b0b-f4e0-46d5-bae6-4fadd78ebe8c\\})|(\\{93969099-a7b4-43aa-bcc0-241676204257\\})|(\\{972315f7-e09e-4f7d-b4d7-1ed093e06dc0\\})|(\\{98c7dc13-2e24-4ea0-8f02-34a2e19f19f1\\})|(\\{99ca0fea-aa27-42b9-9dfb-5c2b02601d30\\})|(\\{9cd8f1f0-b739-427a-9378-16dee329a2b6\\})|(\\{9f0a0293-94ca-4e27-b697-2032f455a12f\\})|(\\{a41f8d9a-ea42-4d59-8966-5f9e805246a5\\})|(\\{b21eea41-def0-4ab6-afe4-ec98397bf59c\\})|(\\{b7561855-e073-4a3a-b827-33bd97042442\\})|(\\{b8b11c35-1c11-4634-9b20-6aff2344a65d\\})|(\\{b90bc248-5b37-4de0-be18-0f208c5cf958\\})|(\\{be17a3b2-18a6-4925-aa7c-bb377bec7793\\})|(\\{be82443f-f8e0-46eb-8cda-80ff1d911a16\\})|(\\{ccce69c0-3cc6-4e63-acb6-77a7c63772ed\\})|(\\{daf9a25c-83b8-4e4b-aca2-ff7ca55289d3\\})|(\\{db7d9d1b-1758-44a0-a8fb-0eb2f3ac4d36\\})|(\\{de34ccf4-0634-4ba2-9bdc-42b293c8cdc8\\})|(\\{df2bbd0f-0a8a-45de-a75b-4349835a8576\\})|(\\{e203bc10-6462-49e6-8f2f-223a29ce82d7\\})|(\\{e66e26fe-5092-4fbd-9851-78a1f5c34575\\})|(\\{ebaf7169-6df9-463f-bf15-10f48143bf73\\})|(\\{ebe3c932-d6fe-4ad7-9eb9-7857aa6fd1ca\\})|(\\{f83c3eae-8dd8-4f38-b7c0-272e34599771\\})|(\\{fb00fead-b480-4906-8b4c-4039c272b83d\\})|(\\{fc14529e-1b64-4717-8a1f-6634cc343c7c\\})|(@blackfridaystores)|(@blackfridaystoresco)|(@blah)|(@celebjunky)|(@checkmaps)|(@childt)|(@convertmypdf)|(@convertmypdfco)|(@dailyjobssearch)|(@dogs)|(@email\\.superextension\\.info)|(@flirtywallpapers)|(@justlovepetscom)|(@liveemail)|(@localweathertoday)|(@localweathertodayco)|(@loginpro)|(@mapsfinder)|(@mynetspeed)|(@mynetspeed\\.co)|(@mynetspeedco)|(@qwertyuiop)|(@qwertyuiopasdf)|(@searchprivacy)|(@searchprivacy-fx)|(@searchprivacy-fx-1\\.1\\.15)|(@searchprivacy-fx-1\\.1\\.16)|(@searchprivacy-fx-1\\.1\\.17)|(@searchprivacy-fx-1\\.1\\.18)|(@searchprivacy-fx-1\\.1\\.19)|(@searchprivacy-fx-1\\.1\\.20)|(@sportsbulletin)|(@sportsupdates)|(@thecouponstore)|(@trailertab)|(@wheresmypackage)|(@wheresmypackageco)|(@www\\.blackfridaystores\\.co)|(@www\\.convertmypdf\\.online)|(@www\\.convertthepdf\\.com)|(@www\\.dailyjobssearch\\.net)|(@www\\.easyonlinerecipe\\.info)|(@www\\.EasyOnlineRecipe\\.net)|(@www\\.easyrecipesearch\\.net)|(@www\\.findmaps\\.co)|(@www\\.job-portal\\.co)|(@www\\.justlovepets\\.online)|(@www\\.movie-quest\\.co)|(@www\\.mymapbuddy\\.net)|(@www\\.mynetspeed\\.co)|(@www\\.searchsafe\\.online)|(@www\\.searchsafe\\.site)|(@www\\.searchsafe\\.space)|(@www\\.searchsafe\\.website)|(@www\\.thecouponstore\\.co)|(@www\\.thecouponstore\\.co\\.test2)|(abc_KtOQLgxUgW@abc)|(Access_Mails_Now_YEsNTbvoal@www\\.accessmailsnow\\.com)|(Access_My_Inbox_zPsghWWAPc@www\\.accessmyinbox\\.com)|(Accurate_Weather_Today_clone_vGXzNgIjwr@www\\.accurateweathertoday\\.com)|(Accurate_Weather_Today_clone_zpRLPSpMcU@www\\.accurateweathertoday\\.com)|(alot_Search_unSoxsuUZQ@www\\.searchalot\\.co))$/", + "prefs": [], + "schema": 1576757781685, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598770", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary data and/or by overriding search behavior without user consent or control.", + "name": "Search hijacking and new-tab add-ons collecting data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a0be3662-6b8f-4d91-bbe1-fde9500adb48", + "last_modified": 1576771656985 + }, + { + "guid": "/^((\\{a4b95555-755a-4a3f-bc64-f6999377963d\\})|(djamol@webdownloader)|(\\{b23e59ff-5491-40f4-adb2-577d31d8778a\\})|(\\{90e41842-755d-40e0-9136-8129df55a644\\})|(\\{8e0ccf87-6ea4-4985-b07f-4d503f105d91\\})|(\\{69d0ae35-7d39-4c41-b455-4d751117317e\\})|(\\{df0dfe82-78a9-456e-9dc1-c832a94d43a7\\})|(\\{7bbd26d9-d245-48d3-b3a6-5e19948dfa14\\})|(\\{3d9109d8-463e-4dde-9416-d25c6ea995df\\})|(\\{2726adf8-c1f9-4ed9-baf1-9a3c1742b659\\})|(\\{bfc40cf5-a085-4f0b-a3ef-5e124f2e445e\\})|(\\{37927c18-d5a5-4187-9e6e-6a787364bd9f\\}))$/", + "prefs": [], + "schema": 1576093292597, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1603731", + "why": "These add-ons violate Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b8daa919-7a9f-4a83-8aef-4a5d3fbfb502", + "last_modified": 1576242123974 + }, + { + "guid": "{d69204d4-610c-4144-9d26-8cf8b2ad6e15}", + "prefs": [], + "schema": 1576032043088, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1603086", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "IDM Integration Module" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c3774104-d770-48a5-acd7-c7136d62bb40", + "last_modified": 1576068160484 + }, + { + "guid": "{d69204d4-610c-4144-9d26-8cf8b2ad6e14}", + "prefs": [], + "schema": 1576012075125, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1603001", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "IDM Integration Module" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8c92ab3a-9c31-4f75-b980-19e39b702fd8", + "last_modified": 1576032042722 + }, + { + "guid": "/^((\\{108dfe62-0473-47cd-b33e-8270d062db77\\})|(\\{e4a4e54a-5d49-4ac3-93d7-ec0d30ad06ef\\})|(\\{ccad95df-add6-4d8a-aa5c-cdc484075bad\\}))$/", + "prefs": [], + "schema": 1575996881649, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602162", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fd42cd0b-a72a-4a0f-9a6a-965ee2f29cb6", + "last_modified": 1576012074754 + }, + { + "guid": "{7bb202fa-9247-49c6-898c-ce0d36bc44e3}", + "prefs": [], + "schema": 1575976576210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602821", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "MyPopupBlocker" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "810f4d41-368a-4cfc-8513-81063587b97c", + "last_modified": 1575996881281 + }, + { + "guid": "@cloaking-skull", + "prefs": [], + "schema": 1575920492132, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602736", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Cloaking Skull" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4438f49a-2f78-48d9-bc7e-a897f3f62945", + "last_modified": 1575976575848 + }, + { + "guid": "/^((photoplus@4329fgjfdgkf343\\.com)|(\\{1b1c9b32-9dd3-461c-85c6-bbac4ef2af10\\})|(\\{7DC6D86C-EA66-48C3-9BA7-AE26A8ECB175\\})|(david\\.janitzek@gmail\\.com)|(\\{ac393d9e-1610-4eaa-8951-77655f98b4f7\\}))$/", + "prefs": [], + "schema": 1575890870070, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602406", + "why": "This add-on violates Mozilla's ad-don policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aa154bb5-7fc9-4226-928e-199aa7eded69", + "last_modified": 1575892910839 + }, + { + "guid": "/^((\\{e1d407bc-1986-4577-bc31-a63d07cbae10\\})|(\\{3a2831f6-37df-4a42-baf6-0b81d5b6a68f\\})|(s3google@translator))$/", + "prefs": [], + "schema": 1575889775993, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602293", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "S3 Translator" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "eac7bf16-aae5-4958-8b85-5dcc9a3a1ce8", + "last_modified": 1575890869693 + }, + { + "guid": "/^((\\{87dd9a06-b692-4fa0-91c4-0aead43c25cc\\})|(\\{9082269e-1de7-4d34-9736-1174917f9459\\}))$/", + "prefs": [], + "schema": 1575747691126, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602285", + "why": "These add-ons violate Mozilla's add-on policy by executing remote code and/or collecting ancillary data.", + "name": "HomeTab/FLSearch" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1af528d2-d0e1-4981-acfa-3b10501aed34", + "last_modified": 1575889775630 + }, + { + "guid": "/^((xh5yh45p59fdrtimlot6@xh5yh45p59fdrtimlot6\\.com)|(758ct3wkmbrvd8equs21@758ct3wkmbrvd8equs21\\.com))$/", + "prefs": [], + "schema": 1575661660854, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602098", + "why": "This add-on violates Mozilla's add-on policy by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "235d9439-305c-48db-a6da-5a8639ab9c80", + "last_modified": 1575663903042 + }, + { + "guid": "{ecc522ab-9926-4dc5-90ab-8f979db871ed}", + "prefs": [], + "schema": 1575651664337, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1602087", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "YTMP4DL" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "01645c30-87fc-4332-9c94-255ef9e50120", + "last_modified": 1575661660491 + }, + { + "guid": "/^((\\{81f244fc-e5ab-4233-bad0-f61f334dcd43\\})|(\\{98222415-4241-4b89-b81d-651c1a872881\\}))$/", + "prefs": [], + "schema": 1575628797035, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1601546", + "why": "This add-on violates Mozilla's add-on policies by facilitating access to malicious web content and software.", + "name": "Video download add-ons (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "404e3032-80a6-43f5-a02e-c7d4e8f684d5", + "last_modified": 1575632376941 + }, + { + "guid": "{c5b1783f-29c8-4bb0-ab8b-a6a9a0c5e4e0}", + "prefs": [], + "schema": 1575627603102, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1601747", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "IDM Download faster music (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ef1c681a-ecca-4ea0-b798-6a093765b984", + "last_modified": 1575628796682 + }, + { + "guid": "/^((smartnav@navigation-internet\\.com)|(game@ultimate-cosmo\\.com)|(update@ultimate-cosmo\\.com)|(extension@gum-gum-streaming\\.com)|(extension@one-piece-fighting\\.com)|(extension@manga-vf\\.fr)|(adblock@gum-gum-streaming\\.com)|(adblock@manga-vf\\.fr))$/", + "prefs": [], + "schema": 1575313586028, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600592", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0b0c648b-1f8d-49f4-bd6e-96e69bbebb45", + "last_modified": 1575376981463 + }, + { + "guid": "/^((\\{33a89146-6b66-4834-bea3-18733d7646f8\\})|(\\{55f304c0-a431-4b35-8cd0-472569a9dd86\\})|(\\{581fae86-7847-448d-9950-5a96127b293a\\})|(\\{9f2a514d-e9dd-4bd6-b743-b15492c10167\\})|(\\{28bf22b4-d52d-4d8a-b710-3868b3bc46ce\\})|(\\{3e9246fc-8125-499b-984e-a8b68507bd36\\})|(\\{731784b0-81c4-4fe9-8741-f5960e12088c\\})|(\\{21585a0f-bcb6-480c-9f70-5cd33cf64833\\})|(\\{2741fd42-3faa-4cef-9b08-361f0c4990ea\\})|(\\{4c479c80-2f53-4d38-a366-0f0c3a346d40\\})|(\\{c4471705-e0eb-47ed-8af9-046c1e808fdd\\})|(\\{2b2bc8ab-5a38-4a26-8ce1-2af9fa2ad7bf\\})|(\\{6840b587-8324-44a0-b176-56d15580dde4\\})|(\\{44d8d882-f6a0-4349-93bc-d3a2353a3fd3\\})|(\\{ddf7716b-badc-417c-b3ce-6322fd67cc65\\})|(\\{82abdf82-f361-47ab-bab7-7f862e132e06\\})|(\\{d24e0155-d36c-402e-943f-d5816451e86f\\})|(\\{926f1f58-3c6e-4f07-8847-2998dc0f1dbd\\})|(\\{e8d228cc-ecb9-4ef6-98e3-8052858fd3ea\\})|(\\{86a868c9-707f-46e8-88ca-5edde6a549e2\\})|(\\{7d5a68c2-261f-449f-af8d-b99a7ae7a01f\\})|(\\{e579c6fb-bf80-4ae5-b3d2-613e084af7fa\\})|(\\{ef7a3832-deb4-4798-b61b-a7cc8b8aa1e2\\})|(\\{48532584-e4dc-4902-b27d-ce35cb29c4c7\\})|(\\{b792fe29-4eb1-412b-bd0b-458252dfab39\\})|(\\{c6f8a307-8024-4e6f-a5f1-595ba43d765e\\})|(\\{7c7a26bd-b1cb-4088-a16e-48f26d22d569\\})|(\\{ff3b2825-86da-4525-abe6-c673b70f58cc\\})|(\\{d4def0be-0d09-453a-9cca-ddc1e1cc9a1d\\})|(\\{0024b95a-9610-4e9d-b836-da40c99c959e\\})|(\\{b09a0797-b6d2-485e-8afc-8361055761ea\\})|(\\{f5c86ae7-cf67-43f8-895c-b2b19209ee81\\})|(\\{bc5d5c2b-e895-49d5-966b-cd1aaee9d324\\})|(\\{a6a8b5c9-45da-457e-990c-b89515139e80\\})|(\\{6d00e6c8-2f26-4dad-81e6-888c080ddfc7\\})|(\\{8f4412a8-c670-4298-a0bc-da30dc3a5ae2\\})|(\\{912d27f7-d9ac-489b-8d7c-2873526e11fc\\})|(\\{3d47bef5-277d-4f06-8ad8-802266e57d17\\})|(\\{0e0bbc6c-914e-47e2-96b9-33f94af6f0a7\\})|(\\{e0f289e7-1284-496b-aa09-f052089d61a3\\})|(\\{64832a69-731b-4481-ad59-3c8aa4006643\\})|(\\{c174dc42-ad4e-4378-8f07-8a47025f8407\\})|(\\{28753278-896f-4f48-8ef3-ea3379896a40\\})|(\\{cdb22d53-d13d-4500-af92-7e653e988722\\})|(\\{65d037b6-c44d-49c5-aa10-a947940c61cb\\})|(\\{d4906685-5b71-49a8-92b5-76f19f2b0a66\\})|(\\{2bebc776-9ee0-47c8-9c99-0351b140ffd2\\})|(\\{58d56295-8d0f-4fe1-8538-2d916911e304\\})|(\\{19283d7a-9fb5-417e-9401-b952ce14a268\\})|(\\{7338a117-ff10-4862-8992-b42829b2e47b\\})|(\\{8defb767-abf1-4d39-88df-9cfc6a39a8e3\\})|(\\{15a77f39-2b56-46b5-b5d6-6064a6f1fbdd\\})|(\\{87fd9298-3176-400b-be06-45538ecd0702\\})|(\\{cda1cc52-9961-477d-8faa-95c7f9025498\\})|(\\{c8056cfe-0bd7-43db-88ae-468f78cc8637\\})|(\\{54bf103a-3f38-43aa-b1ed-2ccbcadb4e0b\\})|(\\{bb67004c-57d6-4632-bc89-8991560a011f\\})|(\\{2902f9ee-b79a-415f-99bf-b661c2bf42e0\\})|(\\{81e0bd24-a93d-4db0-8db9-d5560805c153\\})|(\\{3004ff5d-c68c-4e2b-80b4-2dc21bde9195\\})|(\\{25bcebd4-1d72-4502-8927-a1a6ac35d595\\})|(\\{0b440d2f-8a97-4d2e-bacb-703260897903\\})|(\\{7d0cc577-51fb-4994-8926-7df70f5f16fc\\})|(\\{a6c138fd-c83d-416b-90a8-74bfafefb67a\\}))$/", + "prefs": [], + "schema": 1575229292065, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600576", + "why": "This add-on violates Mozilla's add-on policy by collecting data without user disclosure or consent.", + "name": "Add-ons collecting data without user disclosure or consent" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "80a34f3f-1f4e-47cb-99a4-1b343b322455", + "last_modified": 1575285295616 + }, + { + "guid": "/^((_qdMembers_@extmys\\.mysearch\\.com)|(_qjMembers_@www\\.taxcenternow\\.com)|(_ohMembers_@www\\.whizds1\\.com)|(_opMembers_@tvhero\\.thewhizmarketing\\.com)|(_oqMembers_@sportsaddict\\.thewhizproducts\\.com)|(_ooMembers_@yourdailytrailer\\.yournewtab\\.com)|(_olMembers_@screendream\\.yournewtab\\.com)|(_orMembers_@sporthero\\.thewhizmarketing\\.com)|(_osMembers_@gogamego\\.thewhizproducts\\.com)|(_r3Members_@www\\.mysearch\\.com)|(_ojMembers_@www\\.whizds2\\.com)|(_flMemberstest_@free\\.myformsfinder\\.com)|(_dqMemberstest_@www\\.downspeedtest\\.com)|(_flMembersAlt_@free\\.myformsfinder\\.com)|(_dqMembersAlt_@www\\.downspeedtest\\.com)|(_rcMembers_@www\\.extsb\\.searchbetter\\.com)|(_piMembers_@install\\.mysporttab\\.com)|(_phMembers_@install\\.streamfrenzy\\.com)|(_pfMembers_@install\\.myvideotab\\.com)|(_qxMembers_@install\\.utili-site\\.com)|(_qyMembers_@install\\.cryptoconvertertab\\.com)|(_r4Members_@install\\.salah-time\\.com)|(_r9Members_@install\\.speed-exam\\.com)|(_rbMembers_@Install\\.movie-canal\\.com)|(_poMembers_@www\\.linkuryds1\\.com)|(_l9Members_@www\\.plaskodss3\\.com)|(_ppMembers_@www\\.firstofferzds1\\.com)|(_qkMembers_@www\\.exploreads\\.com)|(_kfMembers_@www\\.bluecpads\\.com)|(_k2Members_@www\\.arcadejetds\\.com)|(_qiMembers_@tvguru-lp\\.olympuswaymarketing\\.com)|(_p8Members_@www\\.easywatch\\.online)|(_kcMembers_@www\\.utilitytab\\.com)|(_kdMembers_@www\\.utilitytab\\.com)|(_qrMembers_@www\\.install\\.utilitytab\\.com)|(_qsMembers_@www\\.utilitytab\\.com)|(_izMembers_@www\\.geteasyarcade\\.com)|(_qgMembers_@www\\.getinstantpdf\\.com)|(_p7Members_@www\\.tigersdeal\\.com)|(_jgMembers_@www\\.greathippo\\.com)|(_jdMembers_@www\\.greathippo\\.com)|(_juMembers_@arcadejet\\.com)|(\\{90ac1d06-caf8-46b9-9325-59c82190b687\\}))$/", + "prefs": [], + "schema": 1575056491406, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600273", + "why": "These add-ons collect ancillary user data among other policy violations.", + "name": "New Tab Data Collection Extensions" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "196e36b5-3225-4f8d-a1a0-6696ea8885f9", + "last_modified": 1575066166785 + }, + { + "guid": "{99454877-875a-473e-a0c7-03ab910a8461}", + "prefs": [], + "schema": 1575040644063, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600327", + "why": "The add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Smart Screen Capture" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3f98e9e7-40d0-4af5-969c-9ea1aadbf280", + "last_modified": 1575041576477 + }, + { + "guid": "/^((Stark-vpn\\.5\\.16@firefox\\.com)|(Video_Downloader_Plus@gmail\\.com))$/", + "prefs": [], + "schema": 1575038746215, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600322", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "89027d84-8871-4d44-8a14-18f029df268b", + "last_modified": 1575040643688 + }, + { + "guid": "/^((\\{b3d1c58f-b27e-4da8-bd4c-c3188346139f\\})|(\\{17101510-b5fb-4361-9e02-70a0e714b591\\})|(\\{d46a3635-4ca8-4e70-8bee-e49714896521\\}))$/", + "prefs": [], + "schema": 1574981048393, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1600315", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "36b86fac-a1f2-4df5-937a-d5667e0e824d", + "last_modified": 1575038745843 + }, + { + "guid": "/^((_orMembers_@www\\.apn\\.ask\\.com)|(_jsMembers_@www\\.apn\\.ask\\.com)|(_osMembers_@www\\.apn\\.ask\\.com)|(_otMembers_@www\\.apn\\.ask\\.com)|(_owMembers_@www\\.apn\\.ask\\.com)|(_ogMembers_@seen-on-screen\\.thewhizmarketing\\.com)|(_qeMembers_@app\\.mysearch\\.com)|(_otMembers_@muzikfury\\.thewhizmarketing\\.com)|(_oiMembers_@screenaddict\\.thewhizproducts\\.com)|(_omMembers_@screenwatch\\.yournewtab\\.com)|(_okMembers_@www\\.whizds3\\.com)|(_j0Members_@www\\.gettvstreamnow\\.com)|(_64Members_b@www\\.televisionfanatic\\.com)|(_ruMembers_@getscreenmania\\.com)|(_rxMembers_@www\\.gamelocket\\.com)|(_s1Members_@www\\.deadlockds\\.com)|(_s3Members_@www\\.mysearchappwhiz\\.com)|(_rvMembers_@www\\.groovymediads\\.com)|(_s8Members_@secure\\.norton\\.myway\\.com)|(_r3Members_@free\\.mysearch\\.com)|(_sgMembers_@www\\.easyhowtoguide\\.com)|(_shMembers_@www\\.fastformsfinder\\.com)|(_siMembers_@www\\.mydirectionsfinder\\.com)|(_seMembers_@www\\.brightcast\\.com)|(_spMembers_@www\\.onlinemapsnow\\.com)|(_slMembers_@www\\.creativeinternetds\\.com)|(_hpMembers0619_@free\\.easyfileconvert\\.com)|(_ceMembers0619_@free\\.easypdfcombine\\.com)|(_euMembers0619_@free\\.filesendsuite\\.com)|(_dpMembers0619_@free\\.findyourmaps\\.com)|(_dxMembers0619_@www\\.download-freemaps\\.com)|(_65Members0619_@download\\.fromdoctopdf\\.com)|(_dbMembers0619_@free\\.getformsonline\\.com)|(_39Members0619_@www\\.mapsgalaxy\\.com)|(_ewMembers0619_@free\\.mergedocsonline\\.com)|(_flMembers0619_@free\\.myformsfinder\\.com)|(_d1Members0619_@free\\.mysocialshortcut\\.com)|(_b7Members0619_@free\\.mytransitguide\\.com)|(_ozMembers0619_@free\\.newnotecenter\\.com)|(_jbMembers0619_@www\\.onlinemapsearch\\.com)|(_jjMembers0619_@www\\.onlineroutefinder\\.com)|(_69Members0619_@www\\.packagetracer\\.com)|(_fsMembers0619_@free\\.pdfconverterhq\\.com)|(_jnMembers0619_@www\\.pdfconverttools\\.com)|(_gcMembers0619_@www\\.weatherblink\\.com)|(_brMembers0619_@free\\.yourtemplatefinder\\.com)|(_s5Members_@www\\.bluecpads1\\.com)|(_j5Members0619_@ext\\.ask\\.com)|(_5zMembersff0619_@www\\.couponxplorer\\.com)|(_1gMembersff0619_@www\\.inboxace\\.com)|(_flMembersff1219_@free\\.myformsfinder\\.com)|(_j5Membersff1219_@ext\\.ask\\.com)|(_8eMembersff1219_@download\\.howtosimplified\\.com)|(_8jMembersff1219_@download\\.myimageconverter\\.com)|(_94Membersff1219_@www\\.motitags\\.com)|(_ceMembersff1219_@free\\.easypdfcombine\\.com)|(_d1Membersff1219_@free\\.mysocialshortcut\\.com)|(_dqMembersff1219_@www\\.downspeedtest\\.com)|(_dzMembersff1219_@www\\.pconverter\\.com)|(_dpMembersff1219_@free\\.findyourmaps\\.com)|(_e1Membersff1219_@free\\.actionclassicgames\\.com)|(_e5Membersff1219_@www\\.productivityboss\\.com)|(_ewMembersff1219_@free\\.mergedocsonline\\.com)|(_euMembersff1219_@free\\.filesendsuite\\.com)|(_dsMembersff1219_@free\\.internetspeedutility\\.net)|(_fsMembersff1219_@free\\.pdfconverterhq\\.com)|(_iwMembersff1219_@free\\.allinonedocs\\.com)|(_hoMembersff1219_@free\\.directionsbuilder\\.com)|(_hpMembersff1219_@free\\.easyfileconvert\\.com)|(_hxMembersff1219_@free\\.mergedocsnow\\.com)|(_hyMembersff1219_@free\\.formfetcherpro\\.com)|(_i2Membersff1219_@free\\.streamlineddiy\\.com)|(_i4Membersff1219_@free\\.fileconvertonline\\.com)|(_j6Membersff1219_@www\\.freemanualsindex\\.com)|(_jbMembersff1219_@www\\.onlinemapsearch\\.com)|(_jaMembersff1219_@www\\.testonlinespeed\\.com)|(_joMembersff1219_@www\\.onlineformfinder\\.com)|(_jnMembersff1219_@www\\.pdfconverttools\\.com)|(_k8Membersff1219_@www\\.mymapsexpress\\.com)|(_koMembersff1219_@www\\.quickpdfmerger\\.com)|(_kxMembersff1219_@www\\.smarteasymaps\\.com)|(_l6Membersff1219_@www\\.propdfconverter\\.com)|(_l4Membersff1219_@www\\.quicktemplatefinder\\.com)|(_l1Membersff1219_@www\\.videoconverterhd\\.com)|(_ozMembersff1219_@free\\.newnotecenter\\.com)|(_lbMembersff1219_@free\\.worldofnotes\\.com)|(_q8Membersff1219_@www\\.getformsfree\\.com)|(_psMembersff1219_@www\\.freetemplatefinder\\.com)|(_pvMembersff1219_@www\\.mapmywayfree\\.com)|(_gcMembersff1219_@www\\.weatherblink\\.com)|(_65Membersff1219_@download\\.fromdoctopdf\\.com)|(_sxMembers_@www\\.mapsmasteronline\\.com)|(_j6Members_@ext\\.ask\\.com)|(_66Members_@download\\.fromdoctopdf\\.com)|(_htMembers_@free\\.gamingwonderland\\.com)|(_5zMembers_@www\\.videodownloadconverter\\.com))$/", + "prefs": [], + "schema": 1574953312771, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598777", + "why": "These add-ons collect ancillary user data among other policy violations.", + "name": "New Tab Data Collection Extensions" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f541ab9d-f671-421f-9aed-b27c63d11377", + "last_modified": 1574981047945 + }, + { + "guid": "/^((\\{7410146d-cf60-4175-a74a-0a7f7785bbff\\})|(\\{e9083fad-c9c7-4ff3-8bb5-b300d3f33546\\})|(\\{6bed141b-56b5-4209-84e1-93c67bf87711\\}))$/", + "prefs": [], + "schema": 1574883692223, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1599563", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "51353b78-a8ca-4411-98b8-e9585c03f8c7", + "last_modified": 1574953312403 + }, + { + "guid": "/^((\\{d251b448-d876-4f25-87b3-9dd858a077df\\})|(\\{3f711bc3-65bc-41bd-842f-d21b6cb90313\\}))$/", + "prefs": [], + "schema": 1574869966668, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1599833", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ff958539-ae4f-4008-8413-a3fd86950380", + "last_modified": 1574876724352 + }, + { + "guid": "/^((\\{cce3a95c-5f17-4ca1-aa0b-b5a8dc3a1b45\\})|(\\{f7c272df-f8e3-419f-b48d-759b9326d51a\\})|(\\{75a25121-873d-4da0-9aec-c4fa83cf13d3\\})|(\\{042080fd-5127-409c-88e7-47136fda641f\\})|(\\{12b167db-911b-4091-b8e5-e9292643610f\\})|(\\{d386a8b2-fba8-47ab-924d-f43d86d9edd0\\})|(\\{995f0889-b95c-4ae7-9510-e6fddf76b62c\\})|(\\{9be5400b-715c-4f49-9085-e1bdfda4a873\\})|(\\{6680a4ae-e659-47f5-9e4e-0ee72494e3af\\})|(\\{2215fd58-f9b5-4d6d-be8a-805716380cac\\})|(\\{041e6a70-edc0-4e6b-a799-0b15d1c95ddb\\})|(\\{4ba68aef-ec5e-412b-9516-d2a7401c0a22\\})|(\\{5c97c09b-6a3f-43cb-950e-c02074193751\\})|(\\{de5d0076-fa35-468e-8b72-6b5524be0684\\})|(\\{702841c9-cd9b-411f-9c8f-7616e75ce749\\})|(\\{bdfaccd7-c343-435a-b22a-fbb8052bd40b\\})|(\\{3a37182d-5653-4c5e-9729-eee005d24dfd\\})|(\\{18a44708-ff7b-45f6-96de-f2ce4373d185\\})|(\\{6c632bb3-04d3-4744-b04d-cd5dbfef41d2\\})|(\\{7ae0db73-6402-44f7-b4f3-81b7ce81bf6a\\})|(\\{716440c3-3ced-41a7-a5b3-ee1da1bfa9f2\\})|(\\{abf84dc9-1ff8-47df-a68b-70e2e8627610\\})|(\\{a369dded-b0bc-446a-b900-c56eaa74042d\\})|(\\{828e6aed-955b-4a9f-9ec2-126f7dc51f37\\})|(\\{58f9ab34-78e6-4432-912a-c13fafc24a03\\})|(\\{68b7cc55-0874-4f91-a519-7b8283b7e974\\})|(\\{71545b8d-0ebb-4767-8864-5fc8dbdbbdee\\})|(\\{217e14c5-4d4b-4691-8428-48ad484f8c04\\})|(\\{6d20b3d7-ccd6-4fa5-ae08-0343193b5518\\})|(\\{ffdb8967-9d19-4377-a728-735f21941d15\\})|(\\{5edc78a5-f41b-4e4b-90e3-ef4fa429feef\\})|(\\{12fb0651-0d2f-4c97-aa37-9121bade678c\\})|(\\{b7c5d63f-b160-48fb-a1fc-485d5e014c63\\})|(\\{4e72d89b-8481-40ae-93d9-93d3316a5d40\\})|(\\{edd994cd-7724-43d3-807b-19bc2749187a\\})|(\\{238a0064-70ee-4d1e-8dca-4bb5a893b44f\\})|(\\{3671ddcb-11ea-4f05-b97d-989d94c42279\\}))$/", + "prefs": [], + "schema": 1574789062671, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1599537", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "310e84ca-b967-4d81-b990-4d0697d62a6e", + "last_modified": 1574869966292 + }, + { + "guid": "/^((oawef0q29r43oq2\\.com@oawef0q29r43oq2\\.com)|(950opeokfp242@950opeokfp242\\.com)|(jggzkdudsuumeasqvler@jggzkdudsuumeasqvler\\.com)|(bdcixfqemzjezxbnzicj@bdcixfqemzjezxbnzicj\\.com)|(ne3787m9ykowhnicg8yx@ne3787m9ykowhnicg8yx\\.com)|(799v8e4cklkpetj0l56x@799v8e4cklkpetj0l56x\\.com)|(o7b1gs6trq2nkaocoejp@o7b1gs6trq2nkaocoejp\\.com)|(sta528sfzizg0ay009cs@sta528sfzizg0ay009cs\\.com)|(8x837zsyl6iw9hpjxu2w@8x837zsyl6iw9hpjxu2w\\.com)|(3bwrbcltn5fjo73lci00@3bwrbcltn5fjo73lci00\\.com)|(kmqm7cm5tk1l5s55yq7j@kmqm7cm5tk1l5s55yq7j\\.com)|(9n4v5kw9bf8e1f4dl0rq@9n4v5kw9bf8e1f4dl0rq\\.com)|(o4d5u4gzx14iqfuyobjr@o4d5u4gzx14iqfuyobjr\\.com)|(2ln3gtttxoz3tjerbbta@2ln3gtttxoz3tjerbbta\\.com))$/", + "prefs": [], + "schema": 1574538091411, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1599186", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Add-ons using obfuscated code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0ee9e971-ba48-4d51-a907-a3b5533e8e28", + "last_modified": 1574707139043 + }, + { + "guid": "{3d09bd90-da37-41b5-a719-8da173e9870f}", + "prefs": [], + "schema": 1574441398813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598708", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Google Translate (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e6a12342-3936-41cf-910d-cc133db44d2a", + "last_modified": 1574445055964 + }, + { + "guid": "Stark-vpn.5.14@firefox.com", + "prefs": [], + "schema": 1574438767458, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598688", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Stark VPN" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5d2478e3-cbaa-4192-89f0-5abf3ea10656", + "last_modified": 1574441398416 + }, + { + "guid": "/^((\\{ac9ec764-a247-4d71-8807-20aa20f93e17\\})|(\\{4253de43-775d-48bc-8e08-fb3f58a2ddaf\\}))$/", + "prefs": [], + "schema": 1574434609524, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598672", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search-hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fa044b4d-b39e-4ee0-a737-07aca2d672ef", + "last_modified": 1574438767139 + }, + { + "guid": "/^((\\{cc08012a-f9cd-4bfc-b526-ad8773934a0c\\})|(\\{16fd1825-3f7d-4c38-aa98-78cc4f0a6758\\})|(\\{87261aed-8d85-4037-81fb-4988ae80ee23\\})|(\\{90f51854-74d4-4df9-bf4c-b86f30346bd8\\}))$/", + "prefs": [], + "schema": 1574365292775, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598646", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "YD" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "46ed3cc3-8534-484a-bc58-349824debec6", + "last_modified": 1574434609189 + }, + { + "guid": "@youtube-adblocker-addon", + "prefs": [], + "schema": 1574334260915, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598287", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "YouTube Adblocker" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "126a0720-a22f-41f1-876f-0a3e18b79d2b", + "last_modified": 1574342360895 + }, + { + "guid": "/^((youtubetomp3@addons\\.youtube\\.com)|(addons-mozilla@youtube-to-mp4))$/", + "prefs": [], + "schema": 1574260409481, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1598242", + "why": "The add-on violates Mozilla's add-on policy by opening websites with malicious intent.", + "name": "YOUTUBE to MP3" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9729654a-6cd1-4e20-8cf5-47a3ff544ef5", + "last_modified": 1574334260570 + }, + { + "guid": "/^((\\{333d6dd8-43ed-4f01-944d-b7c737a5db72\\})|(\\{f5712532-777e-4080-b6b9-d548040f7675\\})|(\\{abf3f8d7-bc95-4dd5-ab96-c390ad5f8756\\})|(\\{f769002a-608d-4a8c-adc4-05f4857ff3ae\\})|(\\{1980667f-9bde-4e7e-8d0c-132db12c1b30\\}))$/", + "prefs": [], + "schema": 1574192492672, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1597784", + "why": "Use of affiliate redirects in violation of policy", + "name": "Affiliate Redidrecting Add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "28831b71-8a9a-466e-9f34-1f40f2f0e27a", + "last_modified": 1574260409142 + }, + { + "guid": "/^((_1gMembers_@www\\.inboxace\\.com)|(_39Members_@www\\.mapsgalaxy\\.com)|(_5zMembers_@www\\.couponxplorer\\.com)|(_65Members_@download\\.fromdoctopdf\\.com)|(_flMembers_@free\\.myformsfinder\\.com))$/", + "prefs": [], + "schema": 1573749618972, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1594461", + "why": "This add-on does not provide users with an opportunity to refuse the storage of or access to cookies.", + "name": "New Tab add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d39750c3-a305-4487-bc0f-21500c3597dd", + "last_modified": 1573804224556 + }, + { + "guid": "/^((\\{c8476e06-0a50-41ec-a840-a2db436cf38c\\})|(youtube\\.downloader@firefox\\.dev)|(youtubedownloader@firefox\\.com)|(youtubehddownloader@firefox\\.com)|(youtube\\.d@firefox\\.dev)|(advblock@blocker)|(YouTube@HD\\.Downloader)|(adt-3\\.0\\.7@blocker)|(\\{7131880e-d327-4802-b5ed-fee33c281abd\\})|(\\{5cb84843-504e-406e-8fb7-051c7fc3c9d3\\})|(\\{d8686bde-e666-4084-ae01-c75aa7a30f93\\})|(\\{96d35545-d94a-4ee1-bc43-d3055650587c\\})|(ali-image-search@4\\.0)|(\\{e7634c48-0d36-448e-891e-b2036beebcd0\\})|(\\{442de29c-b710-45d4-b121-7b4be387c327\\})|(lite-vpn-4\\.1\\.14@gmail\\.com))$/", + "prefs": [], + "schema": 1573674093610, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1596468", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons injecting remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "19c04aaf-c02d-4583-9978-c519245cd4fb", + "last_modified": 1573749588529 + }, + { + "guid": "extension@safeguard.ws", + "prefs": [], + "schema": 1573501291938, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1595616", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Safe-Guard" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "53d8f35e-99ec-44fd-8082-3b713a5afcb3", + "last_modified": 1573507879434 + }, + { + "guid": "crisorgblack@rampampam", + "prefs": [], + "schema": 1573155692372, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1595200", + "why": "This add-on contains unexpected features in violation of our policies.", + "name": "Flash Update" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2bb20972-0d56-44c8-a5d6-0e73a1827e92", + "last_modified": 1573247974595 + }, + { + "guid": "/^((\\{be2f72d6-0c71-4fd0-8914-e27057e51099\\})|(adblock-2019@youtube-addons))$/", + "prefs": [], + "schema": 1573060222748, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1594498", + "why": "These add-ons are suspected deceptive and have been disabled for your protection.", + "name": "Deceptive ad-blocking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "477174ca-470a-40ff-bf8d-2de81c2dc458", + "last_modified": 1573063322832 + }, + { + "guid": "/^((\\{8f11d935-4bcf-42c8-88c1-7e32e4541c98\\})|(\\{b0a6cb7d-3e77-4a96-aea5-7d0090e55832\\})|(\\{c5a5fd9f-8e84-460d-93dd-499e23b530e9\\})|(\\{ac7c91e8-6797-42ec-a202-5b34dacaa83a\\})|(\\{3784e311-4758-4180-b3e2-2ae42bfb081d\\})|(\\{10f3b1e0-4b97-4cc2-9af6-06a9162ec022\\})|(\\{e1109008-6d70-453d-a84e-3cc0c44af07e\\})|(\\{bb7190ac-8aeb-4a8d-83cf-89c179a4d65e\\})|(\\{a752512a-8287-40c0-97f2-071146da2caf\\})|(\\{185dbeea-416b-4fd6-8512-9ddb941e50b5\\})|(\\{d199eeb9-0964-4dff-94d2-69116fd03418\\})|(\\{d37310a3-08dd-4cb8-a276-3f2bee174555\\})|(\\{b239ce6b-2513-4758-a74e-eae3c8f0a04a\\})|(\\{f0f240d7-ae38-4bf4-a8e2-23d775eb07f4\\})|(\\{1804789c-d056-4887-839c-7a0dcab3be83\\})|(\\{d6115dfe-e3d2-44af-8361-1308c234b14c\\})|(\\{48a804b3-cf0f-4aa1-b798-82e944d7afb0\\})|(\\{3026b451-392b-47d5-b60f-995fb075cfd1\\})|(\\{e050f24c-8e06-4d6f-a962-c6073788ac29\\})|(\\{f45a7c28-423a-46ff-abb6-fc855912d074\\})|(\\{699d463b-dd6c-4cde-bad8-81beb345cb85\\})|(\\{f3a3544e-905d-4cc2-99e2-dd4c928bfc24\\})|(\\{2fa8e43c-084a-4fd7-9873-2d462f535929\\})|(\\{a7a69509-aa1d-42b3-8c8d-b3a2885f9aa7\\})|(\\{f4ca82a2-58cc-4d4f-ab95-1ca0424dcd52\\})|(\\{6f9cc262-0164-47e5-b138-024a525d2498\\})|(\\{b3061ee4-1281-482a-8022-f351c023db8d\\})|(\\{48d4fb44-5ebe-49a0-a731-541f11491d92\\})|(\\{fe8a3e05-e9ff-48d2-9291-b97bd3cba97b\\})|(\\{b7eb6ae4-2476-4ba0-a005-b868eb4bf9b2\\})|(\\{2992ca99-6c98-4864-afbf-d51c9c35ed5a\\})|(\\{2017e4f8-a7fb-421f-a1c5-d7b62189f1db\\})|(\\{d44be860-e0d3-4df2-97e1-eef2075474bc\\})|(\\{d6d4e269-00c4-4eeb-b458-d76222ff52e9\\})|(\\{a0242f56-118b-415c-83d6-8eac264dad4c\\})|(\\{25e8b980-6548-45c4-bb58-a641c13b9807\\})|(\\{40fae6f3-c629-4b2d-b690-bf022ebb2cd8\\})|(\\{359c22b8-d670-4b0e-ba77-e29615b633fb\\})|(\\{8fce6deb-83f3-4695-9c5d-76bf366757bf\\})|(\\{1edcaf80-0c4a-4380-9752-4f2953b8a053\\})|(\\{664101ff-e2e4-43b3-b470-ddb71642df7a\\})|(\\{fee7c990-6f9c-4b44-91fb-74998b2498c5\\}))$/", + "prefs": [], + "schema": 1573055872812, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1594479", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5e200b1f-e1d0-4819-89e3-3bd34f90c5c1", + "last_modified": 1573059528009 + }, + { + "guid": "/^((\\{697995ae-b9ee-4556-824b-8e82621f45dd\\})|(\\{6971a39a-916e-4b3f-a14f-bac10571b9f0\\})|(\\{d1047c9a-157b-4f87-a223-a6e6a150e1f8\\})|(\\{f23c6402-4b76-4aa4-b49a-6d083f92ae88\\})|(\\{1a7401bd-96e7-4857-bc5b-7ee66cdecc3c\\})|(\\{ccec0135-ee34-4713-a10c-607a45956d9b\\})|(\\{70428092-3df7-4151-b973-974ca511ae33\\})|(\\{cf2cc560-d765-4b59-b7b7-09a0ae4292f2\\})|(\\{880b8863-e204-47fe-8c8d-db29a10b8c8a\\})|(\\{c95951f5-2b2b-481a-9975-e38abf1f881b\\})|(\\{865df6b7-3ac1-4bd8-be0b-4cc8bb6aab7e\\})|(\\{b6124efd-aa84-42bf-80c7-01c078dc9ef2\\})|(\\{f888f1c9-a936-4ed1-a7f1-5a835ea9e6a0\\})|(\\{c434dae6-9ece-4488-8e9d-0d06d523d95a\\})|(\\{5531f988-1512-4b18-937c-7bc7e209c70f\\})|(\\{fd4fc8ae-fe95-490f-8013-6964455afb34\\})|(\\{84189ee5-4494-4068-b467-49a1b946cba1\\})|(\\{21f0db68-3156-41b4-8180-a48824b35962\\})|(\\{6a3e8874-61b4-40bf-923c-199493cfd284\\})|(\\{683172b8-3a91-4cbc-92f3-2a385f321c98\\})|(\\{269136f3-cd51-4e2d-b1eb-74878262fcef\\})|(\\{1ca4e866-0d45-4898-8138-398f49925cab\\})|(\\{9d53c5bc-9cf2-4c50-9ce5-8aaa113de07f\\})|(\\{19b80768-1437-4d18-972b-41df04a3b0a9\\})|(\\{7090cae8-4f58-4f07-9e53-b37cb4f3f8ad\\})|(\\{31911e49-6d40-4435-b152-c333096ae185\\})|(\\{4910e0bd-d803-4785-9ec4-6e8c9414a53e\\})|(\\{f3a131a1-73fe-4e8c-b499-629aa7a3e405\\})|(\\{94e53c23-cb3e-43c9-b29f-43ea953c61f6\\})|(\\{0a024f33-43b3-4d15-89dc-694964063869\\})|(\\{fa561d95-e80a-4ab9-b71c-556c7fce3b83\\})|(\\{f78a3354-dafd-48e6-8392-0ed1aa8e8a91\\})|(\\{a6b84ecd-25df-41c5-8979-a1a9d962d78b\\})|(\\{5d7282aa-0600-4a16-ad0e-917d20232d2a\\})|(\\{90e0c87b-f534-486f-b5fe-beddad5cfcfb\\})|(\\{c64d3989-b6f6-4eec-b86f-b549a5b2056a\\})|(\\{ccdd4a2e-11a4-4922-8bd2-d8ff05d3194f\\})|(\\{b9f73347-01c2-4727-85cd-7521a2a2841c\\})|(\\{1be85529-e82e-4dfe-aa33-3c62fc37ae8e\\})|(\\{b91fff1b-d45e-47b2-95da-5d774319b88d\\})|(\\{8c81cdd1-4fc2-4510-83c2-641ea7ee7c24\\})|(\\{e5db6079-1786-4072-80f5-d5c894074cc2\\})|(\\{5981e135-789c-4709-8b22-85e0665c8396\\})|(\\{a235175f-3391-43c6-bf58-34c1f6b8f4a6\\})|(\\{617ebed4-628f-4ca3-92de-bec510cab1dd\\})|(\\{3aaa37bc-0fa9-4e34-abbb-d5a34ff729a3\\})|(\\{2d3521bc-914f-4990-ba0a-5d488f176f4e\\})|(\\{17c7768d-c672-4e99-aef2-2fdd35a96804\\})|(\\{7e517ca8-6f65-4de2-869f-dddce73a274c\\})|(\\{9c99bd01-7567-47e6-80f2-bf229b97415f\\})|(\\{5357eef8-cc2f-496a-82d2-5f3b6ca5f7b1\\})|(\\{b83a8605-057b-46ef-86ed-d53db9a9f16f\\})|(\\{55383502-1800-4cf8-8522-df16d6cb9809\\})|(\\{14d742cf-cd34-46a5-984e-1e28fc16e55c\\})|(\\{f5d6d1af-6a02-4eb4-a7fb-ec3486239875\\})|(\\{fac56640-6cad-49a2-b224-9424e18b4009\\})|(\\{79c16aa2-f8a9-40d3-9e81-74884b987e48\\})|(\\{b02eae40-18d6-4e28-8af7-6b161a458769\\})|(\\{9398d059-fc67-43e2-8548-bc60cfa82998\\})|(\\{cd0a4138-8670-4a6b-b15c-0cad1ef290e6\\})|(\\{503dc84b-9967-4b1f-8199-cee55bcd919e\\})|(\\{d0245186-049d-4594-88f9-3fe54e735551\\})|(\\{7208c980-005e-426c-991f-1cd22227b7fb\\})|(\\{19dcb8c5-3577-4279-b70f-eb48a594f821\\})|(\\{fda599e0-d842-47d9-8c8e-21c26ef0afa6\\})|(\\{470eab98-7ad9-49a5-883f-42cb51996fc7\\})|(\\{8c353e56-0d27-4b83-b081-6e012bda5601\\})|(\\{a1931145-d58d-41a2-b190-04549ef9c225\\})|(\\{916d2e1d-aabf-45aa-84ec-a9bb9ee40391\\})|(\\{f2aa9466-c326-440c-b9a8-f2fef8571341\\})|(\\{52a02f53-b805-41d1-af02-0283da3ef383\\})|(\\{aad686bf-b881-4177-8a8f-12c9455e319f\\})|(\\{cec578f9-9103-44f8-9b41-bbe26ba8a993\\})|(\\{6891bcc0-4ad2-4531-9699-ce82528ddcc0\\})|(\\{a5d93cd7-b187-46f8-961b-9f4fc0b4f37f\\})|(\\{b220428d-2f07-4ecd-9114-3a3261d5f7fc\\})|(\\{b0f4740a-6e0f-489e-ab86-3fb3abb7dd15\\})|(\\{611781ad-a17d-4487-8caf-bf72818bacfc\\})|(\\{0b4efcee-b7eb-4151-bdd2-b2f66fedd4fa\\})|(\\{25da4f88-e61c-4823-9c32-864c3a348aab\\})|(\\{e44b4144-0a1a-4c29-95c0-50083ed5c606\\})|(\\{4dbf4218-1a54-4fa1-8448-0521da5b5391\\})|(\\{7140247a-7ee6-4474-9fe9-0cdf6ec7e2e7\\})|(\\{50eac97c-bb52-4107-a3be-6d40eba5b229\\})|(\\{4bd98a1f-d8f6-411a-a1e8-0cf1b482c682\\})|(\\{a7d60016-47ac-4480-b2bc-83dd4e20b096\\})|(\\{a2657d9a-94d1-41e8-a1e5-72e60c032afe\\}))$/", + "prefs": [], + "schema": 1572982905588, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1594460", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f1eaa18e-75c8-4134-87cf-5a02e3349e44", + "last_modified": 1573055860762 + }, + { + "guid": "/^((web@3753c687-a0c6-4cd1-b8ff-bab3c76b1236)|(ext@286f29a6-184e-4195-b1e5-1212ebf372a0)|(web@fd9b3d8a-1178-45ab-92a8-a172d0b70000)|(web@e7a72615-19b1-42a5-8e34-ddfa89ce0000)|(web@af1e58bc-4ead-11e8-81fc-065ad97f0000)|(web@7398cc9a-684e-40a4-afe1-b620e1a80000)|(web@2DC2452E-6999-11E8-A1CA-6C6318C60000)|(web@B7CCDA78-8455-11E8-91A4-ED179E46D000)|(web@8842eb7c-5f5c-4d33-aabe-81c27ae87000)|(web@ca5a2803-6421-4582-97e1-9e30fe440000)|(web@00022358-d56d-4f5e-a89c-d4534d7c5565)|(\\{ff26fde6-4d73-49a3-bd6c-1ca7876484fc\\})|(290e9605-6fb1-4c8b-a3d4-0084bfad201e@UniversalConverter)|(web@EmailNewTab)|(web@WhatsMySpeed)|(test@FormsHub)|(web@Forms2)|(newweb@MapsNewTab)|(\\{4b1d718d-58ae-404b-a5d2-b7b977cfcb56\\}))$/", + "prefs": [], + "schema": 1572291693826, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1592597", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3bdd5b53-ddb1-4fb0-b250-d75a604a5546", + "last_modified": 1572443230107 + }, + { + "guid": "/^((\\{a059a924-e43a-495d-9620-ad8c111d62d9\\})|(\\{79f4bfc6-b1da-4dc4-85cc-ecbcc5dd152e\\}))$/", + "prefs": [], + "schema": 1571600493541, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1589974", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Page Translator" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9e8f80d6-a818-4004-9a20-deec55f3fb96", + "last_modified": 1571605630002 + }, + { + "guid": "/^((firefox@browser-security\\.de)|(firefox@smarttube\\.io)|({0fde9597-0508-47ff-ad8a-793fa059c4e7})|(info@browser-privacy\\.com)|({d3b98a68-fd64-4763-8b66-e15e47ef000a})|({36ea170d-2586-45fb-9f48-5f6b6fd59da7})|(youtubemp3converter@yttools\\.io)|(simplysearch@dirtylittlehelpers\\.com)|(extreme@smarttube\\.io)|(selfdestructingcookies@dirtylittlehelpers\\.com)|({27a1b6d8-c6c9-4ddd-bf20-3afa0ccf5040})|({2e9cae8b-ee3f-4762-a39e-b53d31dffd37})|(adblock@smarttube\\.io)|({a659bdfa-dbbe-4e58-baf8-70a6975e47d0})|({f9455ec1-203a-4fe8-95b6-f6c54a9e56af})|({8c85526d-1be9-4b96-9462-aa48a811f4cf})|(mail@quick-buttons\\.de)|(youtubeadblocker@yttools\\.io)|(extension@browser-safety\\.org)|(contact@web-security\\.com)|(videodownloader@dirtylittlehelpers\\.com)|(googlenotrack@dirtylittlehelpers\\.com)|(develop@quick-amz\\.com))$/", + "prefs": [], + "schema": 1571235422841, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1483995", + "why": "Sending user data to remote servers unnecessarily, and potential for remote code execution. Suspicious account activity for multiple accounts on AMO.", + "name": "Web Security and others" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.0.9", + "minVersion": "0" + } + ], + "id": "96b2e7d5-d4e4-425e-b275-086dc7ccd6ad", + "last_modified": 1571235848152 + }, + { + "guid": "{381f21b1-95bf-4042-bc5c-3a40b2a03f10}", + "prefs": [], + "schema": 1571235400908, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1583468", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Francezon" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.0", + "minVersion": "0" + } + ], + "id": "dd1261a3-6944-4f51-8118-b0a8f2055d69", + "last_modified": 1571235422828 + }, + { + "guid": "addon@shoppingguru.info", + "prefs": [], + "schema": 1571235356487, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1583862", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "ShoppingGuru" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2104.1111.1110.4221", + "minVersion": "0" + } + ], + "id": "70655a4b-064d-44ab-8d0e-3bec419343ee", + "last_modified": 1571235400895 + }, + { + "guid": "sparalarm@chip.de", + "prefs": [], + "schema": 1571082106908, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1586677", + "why": "The add-on is force-installed for users through sideloading, bypassing user consent.", + "name": "Sparalarm" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "14.39.1", + "minVersion": "0" + } + ], + "id": "19f599bd-2226-49e2-90fd-685fd106fc3d", + "last_modified": 1571235356473 + }, + { + "guid": "/^((\\{65a93e3b-e350-440d-bf8f-68e18e38d27d\\})|(\\{9db1fb44-b661-4719-9d90-67af3e6a314c\\}))$/", + "prefs": [], + "schema": 1571059482617, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1586834", + "why": "This add-on contains an unexpected feature that is collecting ancillary data.", + "name": "Google Custom Logo and Search Counter" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "be5d02cb-0a36-4c20-b184-509b86d243c5", + "last_modified": 1571060494402 + }, + { + "guid": "{c4d46c5f-9832-4057-8a1d-635949ed6a55}", + "prefs": [], + "schema": 1570909292250, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1587121", + "why": "This add-on makes use of search settings with unexpected behavior.", + "name": "LM Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "57c094fd-9a37-4ede-9bb0-a1c2d66415f0", + "last_modified": 1571059471124 + }, + { + "guid": "/^((\\{2B0EC7FF-F330-4e0c-8B33-EFFEC8D39E70\\})|(\\{f70c89b0-bbf3-41a9-bc1f-0912dcf53f33\\})|(@Classifieds)|(@Converter)|(@Coupons)|(@Directions)|(@DownloadManager)|(@Email)|(@Fitness)|(@Flights)|(@FormsApp)|(@Games)|(@Maps)|(@News)|(@Package)|(@Photo)|(@Radio)|(@Recipes)|(@search-encrypt)|(@search-incognito)|(@searchencrypt-b)|(@searchencryptblocker)|(@Speedtest)|(@Sports)|(@Transit)|(@TV)|(@Weather)|(aweapps@Email)|(classified@jetpack)|(email@searchleasier\\.com)|(foo-bar@example\\.com)|(games@jetpack)|(JS@Converter)|(login@easier)|(maps-webext@jetpack)|(Maps@SSA)|(web-ext@games\\.com)|(web@ShoppingNewTab)|(web@SocialNewTab)|(web@WebDesignNewTab)|(webapp@LoginAssistantTab)|(webex@Converter)|(webex@Email)|(webtab@Shopping)|(webtab@Social)|(webtab@WebDesign))$/", + "prefs": [], + "schema": 1570701667316, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1587782", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Addons collection ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "986aac02-beba-40b2-b463-d8447a778a2e", + "last_modified": 1570713923845 + }, + { + "guid": "{2a78ab07-91b2-4086-889d-619e43d5e5f8}", + "prefs": [], + "schema": 1570563693690, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1587734", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "SVSrch" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bb76b262-7087-4cf2-a82f-12cfbf91239a", + "last_modified": 1570701652245 + }, + { + "guid": "{2e106fa4-ee23-4b4a-9ed0-f93edee539b5}", + "prefs": [], + "schema": 1570534326932, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1587074", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "SVsrch" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ab0b6868-6077-4d47-bd58-1df3f572c04d", + "last_modified": 1570540061375 + }, + { + "guid": "/^((oigfsj9434lavvv@oigfsj9434lavvv\\.com)|(mity82900jf2@mity82900jf2\\.com))$/", + "prefs": [], + "schema": 1570527949565, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1586780", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Page organizer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3ece7523-cdec-46cf-ac20-9e939a4d4802", + "last_modified": 1570534316570 + }, + { + "guid": "/^((web@MapsNewTab)|(ext@WhatsMySpeed)|(ext@MyEmailCenter)|(ext@TemplateHelper)|(ext@FreeLiveRadio)|(ext@FreeFormsNow)|(web@ConverterNewTab)|(web@EmailAccountNewTab)|(spweb@EmailNewTab)|(web@PackagesNewTab)|(web@ManualsNewTab)|(web@ClassifiedsNewTab)|(web@FormsNewTab)|(web@AncestryNewTab)|(web@RecipesNewTab)|(web@TVNewTab)|(web@WeatherNewTab)|(web@translationsNewTab)|(web@SportsNewTab)|(web@CouponsNewTab)|(web@FlightsNewTab)|(test@InstaFormsFinder)|(web@NewsNewTab)|(web@SpeedTestNewTab)|(web@TransitNewTab)|(web@BibleNewTab)|(web@RadioNewTab)|(web@TemplateNewTab)|(web@PhotoEditorNewTab)|(web@AudioConverterNewTab)|(web@GamesNewTab)|(web@VideoConverterNewTab)|(test@TheWeatherChecker)|(web@InterestsNewTab)|(test@SearchHub)|(web@CalendarNewTab)|(web@TaxesNewTab)|(web@BankNewTab)|(web@CrimeReportNewTab)|(web@CryptoNewTab)|(web@eBooksNewTab)|(web@FinanceNewTab)|(web@FitnessNewTab)|(web@JobsNewTab)|(web@MoviesNewTab)|(web@NotepadNewTab)|(web@OfficeNewTab)|(web@PCTextingNewTab)|(web@PhotoNewTab)|(web@ScrapbookNewTab)|(web@UtilityNewTab)|(test@FlightTrackerUpdate)|(test@FreeForms)|(test@UniversalConverter)|(test@MyEmailCenter)|(test@TemplateHelper)|(test@FreeLiveRadio)|(test@FreeFormsNow)|(ext@FormsHub)|(webext@WhatsMySpeed)|(webext@WatchTelevision)|(webext@EmailExpressTab)|(webext@MyConverterTab)|(webext@TemplateCreatorTab)|(webext@LocalForecastTab)|(webext@EZDirectionsandMapsTab)|(webext@MapsNDirectionsTab)|(webext@MyEmailCenter)|(webext@TemplateHelper)|(webext@FreeLiveRadio)|(webext@FreeFormsNow)|(webext@InstaFormsFinder)|(webext@TheWeatherChecker)|(webext@FlightTrackerUpdate)|(webext@FreeForms)|(webext@UniversalConverter)|(webext@FormsHub)|(webext@SearchHub)|(ds@DirectSearchPro)|(webext@AppDiscoveryTools)|(webext@LiveRadioProTab)|(webext@TheWeatherPilotTab)|(webext@ExpressPackageFinderTab)|(webext@LoginAssistantTab)|(webext@SelectSearch)|(addon@SelectSearch)|(webext@ExpressSpeedChecker)|(webext@BreakingNewsPlus)|(webext@WorldEventsToday)|(addon@WorldEventsToday)|(webext@OnlineTVAccess)|(webext@FindingFormsPro)|(webext@EasyClassifieds)|(webext@SpeedCheckerPlus)|(webext@GetFreeCoupons)|(addon@MyEmailChecker)|(app@FlightTrackerUpdate)|(app@WatchTelevision)|(app@WhatsMySpeed)|(app@MyEmailCenter)|(app@TemplateCreatorTab)|(app@FreeLiveRadio)|(app@FreeFormsNow)|(app@InstaFormsFinder)|(app@SearchHub)|(app@TheWeatherChecker)|(app@FormsHub)|(webtab@Maps)|(app@FreeForms)|(app@UniversalConverter)|(app@EmailExpressTab)|(app@MyConverterTab)|(app@TemplateHelper)|(app@LocalForecastTab)|(app@EZDirectionsandMapsTab)|(app@MapsNDirectionsTab)|(app@AppDiscoveryTools)|(app@LiveRadioProTab)|(app@TheWeatherPilotTab)|(app@ExpressPackageFinderTab)|(app@WorldEventsToday)|(app@OnlineTVAccess)|(app@SpeedCheckerPlus)|(app@EasyClassifieds)|(app@GetFreeCoupons)|(app@FindingFormsPro)|(app@TemplatesHereTab)|(webtab@Packages)|(webtab@Forms)|(webtab@Email)|(webtab@Radio)|(app@MyEmailChecker)|(webtab@TV)|(webtab@Classifieds)|(webtab@Ancestry)|(webtab@Weather)|(webtab@Manuals)|(webtab@SpeedTest)|(app@MyDailyCalendar)|(webtab@Transit)|(webtab@Converter)|(webtab@Coupons)|(webtab@Recipes)|(webtab@News)|(webtab@Sports)|(webtab@translations)|(webtab@Template)|(webtab@AudioConverter)|(webtab@Flights)|(webtab@Photo)|(webtab@Bible)|(webtab@PhotoEditor)|(app@MyVideoConverter)|(webtab@Games)|(app@MyLoginHelper)|(app@QuickEmailAccess)|(app@MyRecipeFinder)|(app@MyFlightFinder)|(webtab@Fitness)|(webtab@VideoConverter)|(webtab@Taxes)|(webtab@Bank)|(webtab@Calendar)|(webtab@CrimeReport)|(webtab@Crypto)|(webtab@eBooks)|(webtab@Finance)|(webtab@Interests)|(webtab@Jobs)|(webtab@Notepad)|(webtab@Movies)|(webtab@Office)|(webtab@PCTexting)|(webtab@Scrapbook)|(webtab@Utility))$/", + "prefs": [], + "schema": 1570483391885, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1587028", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bd51d01b-20bf-4682-a588-c03e988eb746", + "last_modified": 1570527938865 + }, + { + "guid": "{67d4f93e-6857-45ab-9e7a-158cc61f15d2}", + "prefs": [], + "schema": 1570052453260, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585735", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "zidaza" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6ce8f6d0-5cef-457c-b7e4-d5c08d61531a", + "last_modified": 1570053270833 + }, + { + "guid": "/^((\\{049aedf7-b1dc-49b7-9ef4-51cd026f8592\\})|(\\{27c743d8-4731-44ff-9194-9f663df6a3c3\\})|(\\{8bf2bae7-273a-4496-9073-c1ad6688cda8\\})|(\\{b9cfeaaa-2465-4b21-903a-7955fcd4e59a\\})|(\\{07848e3a-f42b-48bf-ac70-f918a7b79258\\})|(\\{02b2eed4-2ca5-4d6f-b19a-a8b1738501f0\\})|(\\{d2af93ad-a542-4b30-b929-7437c2b02afe\\})|(\\{b8e33742-5b78-4c08-89e1-94ebe22fbafe\\})|(\\{502b29ef-b05f-4043-949b-069160f39e32\\})|(\\{2c60eb88-2f38-4b1a-9329-eaa18ad41720\\})|(\\{ccb18b6f-0680-4347-afd5-5ed864114f05\\})|(\\{c9622009-2b37-44f1-a5cf-f0aa869a6bff\\})|(\\{cc241172-6e33-4395-bc68-dd76fa6b6091\\})|(\\{904a47b7-c2c3-465d-a7f9-326c51e8fee0\\})|(\\{365ae96e-15f8-445f-816e-74cd5897613c\\})|(\\{527ccde6-f15c-4437-a061-052593c2ac5b\\})|(\\{063b5c1a-b708-4f05-873b-b3e161d1d49a\\})|(\\{d5422b3f-fcb3-4eca-b2a9-b4b8010fd4c1\\})|(\\{60bab1b2-e7fc-4683-85ef-2ec6cdb5e148\\})|(\\{aad32311-3e2f-419d-8e6b-82c4e28c44d3\\}))$/", + "prefs": [], + "schema": 1570045305267, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585810", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Add-ons using obfuscating code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a942e2e5-6a55-439f-b078-6288da1cfe68", + "last_modified": 1570052444069 + }, + { + "guid": "/^((\\{6be4ca4f-8b7e-4d91-994b-24a7c5384086\\})|(\\{1709abf0-19ba-48a8-a9dc-03e931b17f48\\})|(\\{5d5af81d-6501-4d77-a64c-ad2cdc34ceae\\})|(\\{b7de0a65-0b4b-4b74-a4e6-70fc2c36a80a\\})|(\\{64704bd2-ced7-4dd8-a5d2-d20df624288f\\}))$/", + "prefs": [], + "schema": 1570036215253, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585723", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Add-ons using obfuscated code." + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4e487385-fe8d-451a-909e-a6f6609b0d7b", + "last_modified": 1570036367326 + }, + { + "guid": "{5dbbb375-3520-4ace-bb84-df9d92ae1a25}", + "prefs": [], + "schema": 1570017905113, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585708", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Wappalyzer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9a12e67a-ef6d-4f06-9de6-7dc8bcb19517", + "last_modified": 1570036206290 + }, + { + "guid": "{d41cf5b5-67b7-4510-8633-d8e2c0ec5d46}", + "prefs": [], + "schema": 1570001773270, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585625", + "why": "This add-on violates Mozilla's add-on policies by using obfuscated code.", + "name": "Xisey" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "87d99543-1e31-46b3-9fd1-91d9c79ff592", + "last_modified": 1570017897427 + }, + { + "guid": "mozilla_cc3@internetdownloadmanager.com", + "prefs": [], + "schema": 1569844559791, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1585071", + "why": "Some versions of this add-on violate Mozilla's add-on policies by using unreviewable source code. Please update to a newer version.", + "name": "IDM Integration Module" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "6.35.5", + "minVersion": "0" + } + ], + "id": "21f14cff-afef-4e0e-97e8-4dbc0207a7a6", + "last_modified": 1569865071841 + }, + { + "guid": "{546bc2af-d6e7-499f-90b6-58305b836702}", + "prefs": [], + "schema": 1569699691574, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1583809", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "MapsFrontier" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f3133639-f5e5-4b47-a80f-2993fa97ca4a", + "last_modified": 1569844523596 + }, + { + "guid": "{f39b7905-00d5-4391-9a4b-751ca08dd6b2}", + "prefs": [], + "schema": 1569440826824, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1583814", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Tic-Tac-Toe Evolution_G" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4c3153a8-7551-48f3-a4fa-8ad6cb28faa8", + "last_modified": 1569441800696 + }, + { + "guid": "/^((ehfiibbkgllccnbifchmillffgdlmidi@chrome-store-foxified-3843796584)|(ehfiibbkgllccnbifchmillffgdlmidi@chrome-store-foxified-3264908934))$/", + "prefs": [], + "schema": 1569242005272, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1582781", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Proxy add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "72464ab8-9b51-420f-8e54-bc0f8bb01cf1", + "last_modified": 1569244094631 + }, + { + "guid": "/^((@searchincognito)|(@si-defaultsearch)|(@si-defaultsearch-listed)|(@searchassistincognito)|(@DiscreteSearch)|(@Discrete-Search)|(@searchsafe)|(@SearchSafeOrg)|(ffredirector@discretesearch\\.com)|(ffredirector@encryptedsearch\\.org)|(ffredirector@searchdefence\\.com)|(ffredirector@searchencrypt\\.com)|(ffredirector@searchencrypted\\.com)|(ffredirector@searchincognito\\.com)|(ffredirector@searchsafe\\.co)|(ff_redirector@discretesearch\\.com)|(ff_redirector@encryptedsearch\\.org)|(ff_redirector@searchdefence\\.com)|(ff_redirector@searchencrypt\\.com)|(ff_redirector@searchencrypted\\.com)|(ff_redirector@searchincognito\\.com)|(ff_redirector@searchsafe\\.co)|(@encryptedsearch)|(@searchdefence)|(@searchencrypted)|(@42e62954-834c-11e7-bb31-be2e44b06b34)|(@DiscreteSearchx)|(@4aec09f1-f1c9-456d-8c40-e0e86f302a0d)|(@566ff1c3-9432-4ed4-bd3d-b43cba47e051)|(@1df4e663-b9f3-4708-9f5d-44265b33397e)|(ff_redirector@searchsafe)|(\\{9b62bdf8-a3c7-43d3-ba7f-0970cabffdaa\\})|(\\{95b48d11-b256-48ad-8ba1-bfe52f0a8bb8\\})|(\\{9e35a2be-64bd-49e3-aa47-fbeedf1834eb\\})|(\\{3ba10b5f-d9fa-4b40-8683-034d3dfc71d4\\})|(\\{20c31601-ebee-4677-a2f0-40e178bf7c77\\})|(\\{98e02622-f905-434e-9003-6c061b5c11c0\\})|(@tabwow)|(gaidpiakchgkapdgbnoglpnbccdepnpk@chrome-store-foxified-258456913)|(@tabwow2)|(\\{be8901e4-2a07-4554-aa05-a64351496e29\\})|(moviestmpd@mozilla\\.com)|(gaidpiakchgkapdgbnoglpnbccdepnpk@chrome-store-foxified-876542484)|(\\{4a8ef415-e453-458f-bfbd-ae92569214db\\})|(fireaction@mozilla\\.com)|(\\{bd9c448c-58b3-434f-9bb6-4ed2c155ba8e\\})|(\\{ebdfa19b-0906-4f78-9e95-7ef74d34c335\\})|(websecure-unlisted@mozilla\\.com)|(\\{2d06d70b-8f32-4007-8f8b-1e0445bcebe7\\})|(\\{ddbe7345-acf4-4ebb-9baf-cd6d2df73b28\\})|(\\{b09d5b98-2d65-46fb-990c-69710577efa0\\})|(\\{3894384e-c719-4a0c-8d24-3816160fc56b\\})|(search-encrypt-tab@mozilla\\.xpi)|(\\{1dafa1da-3894-48b9-ac8f-00bdc4f1868a\\})|(\\{99cfe634-328a-41a5-9a23-64094e4f4919\\})|(inco-plugin@mozilla\\.xpi)|(incognito-window@mozilla\\.xpi)|(mac-search@mozilla\\.xpi)|(fvdplayer@fvd\\.com)|(playernewpp@ext\\.com)|(\\{492936c6-9121-4e54-8d4f-97f544e5bf98\\})|(\\{108a22ea-f316-4c2f-8427-fe65e02f9e2c\\})|(cold@being\\.net)|(\\{38b99237-6c28-406f-898c-cc89df86051d\\})|(search_redirect@mozilla\\.xpi)|(\\{d2ef4a8d-6ec0-4733-9f3f-2394178ecbf3\\})|(tab_plugin@mozilla\\.xpi)|(\\{ae228e30-f40a-41a3-9e7e-53a094dcb8c6\\})|(\\{00ee7237-53cb-4036-8d4f-e78d78ca89e7\\})|(\\{d2f4002c-031b-4ad3-9fb1-afb003e8f932\\})|(\\{c0f366b3-7b3d-4486-a6f3-4ca1d7045091\\})|(\\{ccc6cfc4-3832-4d05-bf28-43a9722de93f\\})|(\\{dd02f638-ce6d-464e-8add-6ea0f314b1d1\\})|(\\{749ed3ff-4d23-4b32-812e-a35e3cf8c000\\})|(tab_cleanup@mozilla\\.xpi)|(incognito_tab@mozilla\\.xpi)|(\\{47c51f55-4f0b-499f-9fdd-c7c66bf4796a\\})|(\\{cd70c7c8-557d-46fa-9688-399c7c8d3d66\\})|(\\{681ad8e0-d1df-4cd2-a4cf-b97c1d6502a3\\})|(\\{0d58e690-bd48-4e3a-baf3-67aa40bc286a\\})|(\\{77bfbf26-4618-4120-9cb6-1fc7c92b8ddc\\})|(\\{037c6f6a-71f8-405b-9cff-fadf2ded6c47\\})|(\\{91cc3274-90d5-4e16-80e3-cd02fc513689\\})|(\\{2225b2af-0c3c-4345-adac-4f5bd40c2182\\})|(\\{81ca6b1e-a95b-4b44-9638-3ff3ea1a571d\\})|(\\{1e32acf8-fc1e-40ae-8783-c501ce50d597\\})|(\\{19670785-b1db-4d69-9538-2880ad8fdf20\\})|(\\{0113b4ad-15ca-4215-adeb-f0404f619ca6\\})|(\\{c7245149-4224-4c5c-91a4-84ea189f2227\\})|(\\{04dd2232-f1b1-4275-ae74-8bd27f3d850c\\})|(prosearch@mozilla\\.xpi)|(\\{d549a064-98e7-49ed-ba9e-a724e79a004f\\})|(\\{fddd3bc6-9d4e-4ee7-b490-0d6141ff7d7f\\})|(\\{122795b5-ae28-4371-9b61-878f5db888ac\\})|(\\{e3d491de-802a-4f82-91eb-9403c9f43637\\}))$/", + "prefs": [], + "schema": 1569181301396, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1561752", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Various search redirectors" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bea9680c-28c0-48a1-b8d4-e418adeba748", + "last_modified": 1569241020387 + }, + { + "guid": "{87bd05d5-d79e-4421-9c78-5c98ea78c351}", + "prefs": [], + "schema": 1568901038866, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1582168", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "TypeScript-Console" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b9d746da-8881-4854-8026-ecb708a588f6", + "last_modified": 1568905123995 + }, + { + "guid": "/^((hd@youtube\\.com)|(@youtube-to-mp4)|(youtube-downloader@youtube\\.com))$/", + "prefs": [], + "schema": 1568885060842, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1579965", + "why": "These add-ons violate Mozilla's add-on policies by including unexpected features without user consent.", + "name": "More Youtube Downloaders" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ea29de20-dfe2-40c0-a5ec-45b1c780815d", + "last_modified": 1568901023136 + }, + { + "guid": "/^((\\{d81c0c7d-7420-4737-a3b9-dd9edeb4412f\\})|(\\{2bc89af7-d0ff-4b22-b7f6-ec87d15d999e\\})|(\\{3ee12352-a9db-4370-aa27-7e1d9acb628a\\}))$/", + "prefs": [], + "schema": 1568816167726, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1581368", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "FSCH" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1e56fd0a-77b6-477d-bada-78a5c18a37dd", + "last_modified": 1568816392824 + }, + { + "guid": "/^((@yvd-addon)|(ydh@downloader\\.youtube\\.com)|(tomp3@youtube\\.com))$/", + "prefs": [], + "schema": 1568814899255, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1579965", + "why": "These add-ons violate Mozilla's add-on policies by including unexpected features without user consent", + "name": "Youtube Downloaders" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "73cae2b8-f60e-47b3-afca-a1ff555f4ec5", + "last_modified": 1568816154119 + }, + { + "guid": "{91c43d32-3a20-40cb-933b-47fd7c4b5a4e}", + "prefs": [], + "schema": 1568662894970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1581111", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Cookies Next" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e22dbf2b-70c8-41ed-b17a-32f53a22055b", + "last_modified": 1568814886596 + }, + { + "guid": "{23db2a76-49ca-4af2-af50-fccedd607e12}", + "prefs": [], + "schema": 1568317295345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1580616", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "YUI" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "13056233-543d-4c51-b89a-2a6b96ee324e", + "last_modified": 1568374273725 + }, + { + "guid": "{1056b983-063b-4bd0-b7f7-1295f7e04ade}", + "prefs": [], + "schema": 1568226375813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1580749", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fast Browser" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "886e480a-8838-43c7-b9ca-b02592e23065", + "last_modified": 1568285711403 + }, + { + "guid": "/^((\\{8d0073fa-3193-4a7f-8c95-6a4e3d9272ba\\})|(445ac9f4aa1b833ce2dc75d6d6d6c76d0cef7cc7@temporary-addon)|(\\{cd0672d3-72dc-43d2-ae77-6cda31fb7c88\\})|(\\{403321a6-be8d-4ae3-a66d-e5c846f993b8\\}))$/", + "prefs": [], + "schema": 1568154081436, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1580503", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Proxy Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6a54f1c3-93e1-4a38-885c-afb98e7bc4f2", + "last_modified": 1568224845558 + }, + { + "guid": "{0fbe26d1-6891-475e-af3e-34f38a30348d}", + "prefs": [], + "schema": 1567971701994, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1580118", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Lookbox" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "67a61ba7-ad12-4eae-bdcc-e5b9b856dff4", + "last_modified": 1568105279739 + }, + { + "guid": "{a59679da-f097-4db4-b2bc-6ad7b645e127}", + "prefs": [], + "schema": 1567019346545, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "GetMedia - Movies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.18", + "minVersion": "0" + } + ], + "id": "db043b8d-c91a-4d1d-b420-58087beef552", + "last_modified": 1567022112963 + }, + { + "guid": "{850be3a2-ca5f-47ad-838c-fe39b006e0da}", + "prefs": [], + "schema": 1567018965949, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Safe Browsing" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.19", + "minVersion": "0" + } + ], + "id": "aeb6bc24-36cf-4199-94d9-68f4822fa2cf", + "last_modified": 1567019082710 + }, + { + "guid": "{ecb03616-f3c2-4580-99dd-6a233047abdd}", + "prefs": [], + "schema": 1567018902311, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Sport TV" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.19", + "minVersion": "0" + } + ], + "id": "7e71a557-5664-4319-a792-c94b2bf744d8", + "last_modified": 1567018965935 + }, + { + "guid": "{8387ccbe-b9ac-438d-b049-c86b30a6dacb}", + "prefs": [], + "schema": 1567017236054, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": " This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "GoMusic" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.17", + "minVersion": "0" + } + ], + "id": "9d598ecb-2473-4416-98b9-26658ea7746e", + "last_modified": 1567018491638 + }, + { + "guid": "{7ff51e81-f4b1-4682-9f45-43a771d80748}", + "prefs": [], + "schema": 1567017189557, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "GoMovies" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.16", + "minVersion": "0" + } + ], + "id": "627a5309-d310-4130-9827-d3e6d3116ba2", + "last_modified": 1567017236040 + }, + { + "guid": "{2ef58672-740c-46bd-a50d-b9880986b574}", + "prefs": [], + "schema": 1567017167776, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Universe Start" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.5", + "minVersion": "0" + } + ], + "id": "c0767051-017a-45d7-b96f-632d7cae7c47", + "last_modified": 1567017189541 + }, + { + "guid": "{df9f6ab1-c82c-41d4-85ce-86dcfe839ce9}", + "prefs": [], + "schema": 1567017037001, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530194", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Media Start" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.0", + "minVersion": "0" + } + ], + "id": "6e9ea480-7f5f-49c7-b130-fb8af66bec0b", + "last_modified": 1567017167761 + }, + { + "guid": "{b89efd87-232e-4829-87d2-22148919d72f}", + "prefs": [], + "schema": 1566762089721, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1576681", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Ad-Blocker" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "04365e2c-fbc2-4393-b2b3-a23ea5de6b79", + "last_modified": 1566934428180 + }, + { + "guid": "/^((\\{fb2cbb8e-a6f9-464b-97c7-aca958a404d6\\})|(\\{8cc60aa4-fceb-4a74-bef4-bbbdc23b85fb\\}))$/", + "prefs": [], + "schema": 1566419767711, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1573237", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Adobe Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "055d2447-c7b7-49a2-bb27-b1c09a592f32", + "last_modified": 1566422986828 + }, + { + "guid": "/^((AKMWWOEASKZXXCCRFA@AXXX)|(RF_WOEKTAMS@BASDFZMD)|(TI_BOAMZKXFFEE@ZCAOKEE)|(BMMAOSIKZX_TI_EE@BOASLL)|(ZN_ASVVZXLEOOO@ABOVVAE)|(MZXVCKMSSD_ZN_EF@GBOAAOS)|(UA_CZVVMSOEEE@DOPEEZXA)|(ZXCLASKDASD_UA_VV@AVLLZAA)|(VS_SOEOKAKXC@BOOCLLAA)|(AIWEKDZSV_VS_ASCZ@SAAAOOBB)|(IF_AZXOOVVVAA@EALXCOAA)|(VVALLEIASD_IF_AZ@OOBAAXXA)|(ZN_BF_AOXZAOKSD@VZXMASLX)|(VMZKMALSKD_BF_ZN@VIAAEOOSL)|(aunastralaa_1@auzn\\.ne)|(VXKCVLSO_ON_SOC@KIZIAO)|(ON_OPPOWOAKKS@BLZOAAAASL)|(nostallkka@iofjjakk\\.me)|(VZKKAKOOEE_LN_FK@BOOAASSDD)|(LN_VSOAJKXXXZZ@UDPPPADK)|(BNKZKASKDIE_EB_AKK@BLALEEKK)|(EB_AISJIKZLAA@DFKKAKE)|(NLDFKOBASOKCK@KFOLAAAEE)|(BBDMLDSFKER_AF_GG@AYYRR)|(OMVXNSDF_R_F_W@BMAMSEEE)|(EIGKFDAODS_RF_PDNAP_WEN@HUUUHJAA)|(GOEORG_RF_ANA_N@BDOFKOKK)|(BFDISDF_RG_AZXC@BDOFKAQ)|(OKOKDFBDFBDFG_RG_KS_BDK@AAAJAHU)|(ODKFOBKDPFB_PJ_FGK_AKK@AJIIJAA)|(UBXODJ_PA_PJ@XMVCOAKS))$/", + "prefs": [], + "schema": 1565206889166, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1572346", + "why": "This add-on violates Mozilla's add-on policies by executing unwanted actions on websites without the user's consent or control.", + "name": "Page update (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "85411892-1225-46fb-8f1b-5aa424bc2c01", + "last_modified": 1565344753436 + }, + { + "guid": "Shield_My_Searches_ehHQAdRPxE@shieldmysearches.com", + "prefs": [], + "schema": 1565034099452, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1572066", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Shield My Searches" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "74e070ac-891b-4f79-ac76-09a170d21a91", + "last_modified": 1565181472444 + }, + { + "guid": "{4f71ae77-8abe-43df-bb9f-cf440d6f7756}", + "prefs": [], + "schema": 1564993164184, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1571059", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Adblock for Youtube2019" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0f73f6c1-38cf-4255-bb8f-8a61f19afd99", + "last_modified": 1565003019901 + }, + { + "guid": "{f2539eac-e545-475a-85b4-822347022dd8}", + "prefs": [], + "schema": 1564861308712, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1571130", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Woor" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ae64120f-d93a-4224-ac75-4038b0d2a33b", + "last_modified": 1564993145778 + }, + { + "guid": "/^((\\{8842eb7c-5f5c-4d33-aabe-81c27ae87dcc\\})|(web@1A72D794-697C-11E8-9D8C-1C0018C6383E)|(web@2a366bba-0a94-11e8-ba89-0ed5f89f718b)|(web@2bf1a18d-3240-42ad-9f7d-46017edc432c)|(web@2DC2452E-6999-11E8-A1CA-6C6318C6383E)|(web@2DC2452E-6999-11E8-A1CA-6C6318C63000)|(web@3)|(web@3ab4d660-8caf-412a-84eb-9cf2924f54c7)|(web@4)|(web@5)|(web@48F0D4BC-6FB8-11E8-B36A-905EE70C2B9F)|(web@82FCE0DC-836E-11E8-9E9B-164B9D46D017)|(web@90c91611-b4c8-470a-b251-77bb1f859dba)|(web@899ce5cc-06fc-436e-a59e-93a70eb4c810)|(web@3681a37c-6383-4e94-8076-28496af53983)|(web@7398cc9a-684e-40a4-afe1-b620e1a863b2)|(web@7398cc9a-684e-40a4-afe1-b620e1a86000)|(web@8842eb7c-5f5c-4d33-aabe-81c27ae87dcc)|(web@10722358-d56d-4f5e-a89c-d4534d7c5000)|(web@10722358-d56d-4f5e-a89c-d4534d7c5565)|(web@67887931-77b9-4b1b-baee-9f23a4a384de)|(web@a3ea9864-1034-47b9-a25a-e9cc207a9319)|(web@af1e58bc-4ead-11e8-81fc-065ad97f23a5)|(web@af1e58bc-4ead-11e8-81fc-065ad97f2000)|(web@B7CCDA78-8455-11E8-91A4-ED179E46D017)|(web@ca5a2803-6421-4582-97e1-9e30fe44e100)|(web@ca5a2803-6421-4582-97e1-9e30fe44ee00)|(web@ca5a2803-6421-4582-97e1-9e30fe44eee0)|(web@cacf1377-a1b0-43e6-84bc-c0518922b22c)|(web@e7a72615-19b1-42a5-8e34-ddfa89ce006e)|(web@e7a72615-19b1-42a5-8e34-ddfa89ce0060)|(web@e7a72615-19b1-42a5-8e34-ddfa89ced000)|(web@e7a72615-19b1-42a5-8e34-ddfa89cee000)|(web@e4058a8a-59ca-4ba7-b503-dfcf75639305)|(web@f3ac4769-1d70-444e-aa46-06d0427473b9)|(web@FACD83DA-68CC-11E8-8484-3DA118C6383E)|(web@fd9b3d8a-1178-45ab-92a8-a172d0b7c000)|(web@fd9b3d8a-1178-45ab-92a8-a172d0b7c32e)|(web@fd9b3d8a-1178-45ab-92a8-a172d0b7c39e)|(web@ourdom)|(web@oursrchdom)|(web@test)|(web@testss))$/", + "prefs": [], + "schema": 1564669690124, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570659", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5aa0a7da-cf9e-4bac-bbed-1b7625109cc7", + "last_modified": 1564671502746 + }, + { + "guid": "{97ab6723-bc9a-4c5b-a08b-5b162d29ad4f}", + "prefs": [], + "schema": 1564669434797, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570385", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Toors" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "30d0ec83-70ac-4a03-9239-7af6c4061cde", + "last_modified": 1564669681391 + }, + { + "guid": "@weatherhubpro", + "prefs": [], + "schema": 1564668992535, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570380", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "FreeWeather Pro" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "47315374-a775-4701-adc3-d0cfb61a44b8", + "last_modified": 1564669425544 + }, + { + "guid": "YouTube@develop.com", + "prefs": [], + "schema": 1564662247837, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570377", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Youtube Downloader (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "176a0ef0-bd71-4b96-a3e3-14c17eaad3dd", + "last_modified": 1564668305895 + }, + { + "guid": "/^((nickrr878@gmail\\.com)|(\\{a06de0b3-b00f-472c-a34e-3a74b64d1747\\})|(spar\\.team@spar\\.team\\.com)|(\\{59904ffa-b247-41ea-9ac1-2ce0a2da8c98\\})|(\\{69c49344-90ec-458d-9811-a55878e26bd1\\}))$/", + "prefs": [], + "schema": 1564660970729, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570622", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Shopping add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "26b10e69-d842-40ef-90ce-f96552e269a3", + "last_modified": 1564661731567 + }, + { + "guid": "/^((web@AppConverter)|(web@SearchManager)|(web@Email2)|(web@DefaultSearch)|(web@AudioConverter)|(web@translations)|(web@Manuals)|(web@HowTo)|(web@Property)|(my@WeatherTab)|(login@EmailAccount)|(web@WebDesign)|(web@Utility)|(web@Ancestry)|(web@Bank)|(web@CrimeReport)|(web@Taxes)|(web@Notepad)|(web@Crypto)|(web@peoplesearch)|(web@Weather)|(web@VideoConverter)|(web@TV)|(web@Transit)|(web@Template)|(web@Sports)|(web@SpeedTest)|(web@Social)|(web@Shopping)|(web@Scrapbook)|(web@Recipes)|(web@Radio)|(web@PhotoEditor)|(web@Photo)|(web@PCTexting)|(web@Packages)|(web@Office)|(web@News)|(web@Movies)|(web@Maps)|(web@Jobs)|(web@Interests)|(web@Games)|(web@Forms)|(web@Flights)|(web@Fitness)|(web@EmailAccount)|(web@Finance)|(web@Email)|(web@eBooks)|(web@DM)|(web@Coupons)|(web@Classifieds)|(web@Calendar)|(web@BrandedTab)|(web@Bible)|(web@Converter))$/", + "prefs": [], + "schema": 1564599359459, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1570620", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b5e2f528-3a9c-43af-9f6d-98040157a59a", + "last_modified": 1564660457769 + }, + { + "guid": "{795697ee-695f-45d4-a997-6fe845b473d5}", + "prefs": [], + "schema": 1564342890085, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1569556", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "IDM integration (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "21bbd8dd-b79e-41b8-bd18-db1592e743c9", + "last_modified": 1564399822770 + }, + { + "guid": "/^((\\{86f185bb-b672-4a24-b3d9-a8751231b687\\})|(\\{8d98cfc9-3757-4fd9-b017-30ad60fb94ed\\})|(\\{288a7d51-4627-44c9-8cf0-cf18742a6f67\\})|(\\{49e5562b-38e2-4292-8080-ca3ffe8cea42\\}))$/", + "prefs": [], + "schema": 1563809747757, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1567876", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "eeaa940b-b984-43da-88ce-d26891347e3d", + "last_modified": 1563810026497 + }, + { + "guid": "/^((\\{e656b354-3c95-4a63-a271-fe301b294da0\\})|(\\{f5ab4224-43ca-4654-b189-23aa9c960803\\})|(\\{fb68ec9f-d02a-48c9-a356-3020bd1d3e21\\})|(\\{2eabd9de-b8bc-43f2-9e77-624a0e04e38d\\})|(\\{426fe5e4-5da1-41b3-81d4-28bd59724f84\\})|(\\{4b1d9906-5cc4-44a6-ad64-a6c7b0e2ebba\\})|(\\{bbe2e2b3-4a77-4108-9183-e0b02676c09d\\})|(\\{01aef979-833c-4f7d-85a0-6be87462c05e\\})|(\\{9b51394e-a1a9-4864-9876-cc1d6f1a47d5\\})|(\\{7c4f0798-6edc-417b-8702-d97ca1c894c6\\})|(\\{ad93f537-5824-4057-a44f-cef1f97c2d68\\})|(\\{ff62fb9d-c5b7-414d-8c2b-bc5d796475e8\\})|(\\{46998928-4162-46e0-b4c7-260a8520aad9\\})|(\\{731367f8-f5e8-4ade-b8cf-5aaf8c2a455b\\})|(\\{eb6b098d-7811-4a20-a94f-ca91721d4aab\\})|(\\{6ddb9deb-d435-4ec2-be8b-ca65900e43e9\\})|(\\{288e4bb9-454e-4374-8734-1069241d618e\\}))$/", + "prefs": [], + "schema": 1563306101159, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1566026", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons collecting ancillary data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1f35915e-2de0-4973-adf4-4e77073e0b42", + "last_modified": 1563367115113 + }, + { + "guid": "/^((\\{7e9c2e2c-8d2d-4406-bb3d-993176f59b34\\})|(\\{f743e1bf-9a1c-43ab-b8ab-57972761b919\\})|(\\{b8274b35-eeaf-4d98-8a6a-cc4fd56603ed\\})|(\\{fd90393d-6ac6-4245-a048-9d423baacbd1\\})|(\\{3f7b376e-23ad-4296-8fd1-77fb254610bc\\})|(\\{fd918017-a23c-40f0-88e1-798ed6fc51f7\\})|(\\{bac48192-5c1f-4dae-aa1c-2fa9ca65dbe0\\}))$/", + "prefs": [], + "schema": 1563284869997, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1566557", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Add-ons overriding search settings" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "59cf2549-3930-4a60-a212-af84facbee32", + "last_modified": 1563302090004 + }, + { + "guid": "/^((general@mapseasy\\.net)|(\\{fd4b0f58-5268-4d52-b09b-547e867fef27\\})|(\\{efe13936-3bfa-44ce-9224-e8cb27f0f216\\})|(\\{c6b767a7-b0c1-4b68-8ac6-d08fee14c5a0\\})|(\\{a9df2ef3-9746-4d5e-b7aa-0baa531538d2\\})|(\\{2d947dd9-fa0f-4f20-8812-31ce9e29081b\\})|(\\{3e800ada-345b-4ac4-82a5-0dbfd00d7877\\})|(\\{a5fcf820-575a-42c2-aeb1-de1a794db1b9\\})|(\\{da844d4c-44e1-4799-ac53-9bb1ad2c8227\\})|(private-tab@mozilla\\.xpi)|(\\{56dbb679-8db4-47ce-851b-a7eab0e215cc\\})|(\\{c1415289-4471-4b6a-b7b7-4feaf9506b38\\})|(\\{29439407-f908-4779-9078-eb190f21dc4f\\})|(\\{5401ea8b-def8-4df3-bf9a-f520b147df69\\})|(newtab-plugin@mozilla\\.xpi))$/", + "prefs": [], + "schema": 1563133289502, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1565994", + "why": "These add-ons violate Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Search hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c349ecdc-5fa8-4b87-bfa2-c732dfde094d", + "last_modified": 1563186751969 + }, + { + "guid": "/^((langpacasdjasdk-de@firefox\\.mozilla\\.org)|(langpack-de-7@niklasb)|(langpack-de-8@niklasb)|(langpack-de-9@niklasb)|(langpack-de-10@niklasb)|(langpack-de-nightly-1@firefox\\.mozilla\\.org))$/", + "prefs": [], + "schema": 1562673203781, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1564401", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "German language pack (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d38035ff-7945-49cc-88fb-6de9f8a13658", + "last_modified": 1562673628501 + }, + { + "guid": "/^((\\{6d0887bd-1f51-4852-9307-cf2a223c3a9a\\})|(\\{4cb2b177-e2a2-4ae1-b759-03606a85df61\\})|(\\{df28b68e-ad39-4300-add2-b2dc8bf54f71\\})|(\\{eff0a5d5-3f53-4b75-b451-5acffd5bb61d\\})|(\\{7561d328-6b59-449a-bf7a-46fea5027be4\\})|(\\{854246c0-f678-44ff-85f3-f340f33c5da9\\})|(\\{9e976e36-9eb7-44bd-97a3-4901ab3e1021\\})|(\\{2e9a0a60-8423-4ffc-89ef-74a02ca8c5e8\\})|(\\{a5e298c3-2d74-4268-8d13-e0efcb77d896\\})|(\\{39838189-7836-432f-9a34-a009886a61f8\\})|(\\{a3f781a4-adc7-4a12-9812-20da06e7b6d9\\})|(\\{365b3845-1e12-4096-80f1-8be24456d741\\})|(\\{701511ef-2e5a-458f-b735-c789b7ae6feb\\})|(\\{888807df-4517-4b97-ac73-e4294865e375\\})|(\\{ea7c7094-9d83-416d-bd13-e85fcef481a5\\})|(\\{1f67e4bd-6eb6-4fd7-a694-b8b360494cc3\\})|(\\{4f9520a5-caa6-4832-9582-2b26b8739305\\})|(\\{06f5112b-da03-481b-bef1-bf752ddbe7a2\\}))$/", + "prefs": [], + "schema": 1562269304530, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1563454", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Fake Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f8e35175-572a-4b00-b5d3-9c8df255f1ec", + "last_modified": 1562320360370 + }, + { + "guid": "/^((\\{fc2dbae0-1ab3-40fe-9a38-cbee911b903e\\})|(\\{82e3e07c-0cf3-4b9f-a625-65d378698af3\\})|(\\{2904a7ee-0b54-4adb-a500-b932b77b6936\\})|(\\{f6932562-1f6d-4779-b5a4-f4c4654980dd\\})|(\\{97f0cc0f-69cf-4e5a-af68-f404aa3f1ce5\\})|(\\{c346f3ce-08bc-44b7-9410-bd8ad65f32d5\\})|(\\{f68f3bd2-ece8-4a85-b071-cea253cd78ac\\})|(\\{d9b6a925-e00c-4e36-a282-e6b76833e5b5\\})|(\\{e8a67ebd-655e-48f9-99e7-619c850f6bcc\\})|(\\{15942451-8c62-457f-8ff1-8525ce647c0d\\})|(\\{8fcdb966-eec2-4cd0-865e-1d105e9b59e2\\})|(\\{6ae58312-7d81-4d39-84f1-454ae6ace826\\})|(\\{023cd859-1b7c-4384-80e5-eca82c68a21c\\})|(\\{694e912f-011f-4be4-add9-25c85af8014d\\})|(\\{b889539e-4b8c-4e52-a605-a0b33532fd05\\})|(\\{596c6d7c-49ab-440e-a50d-220e5db393f7\\})|(\\{f0fd5c9c-1fcd-4085-ac41-47c379517420\\})|(\\{545dbede-b51c-477b-b23b-936dd5e7a428\\})|(\\{c3f9f4e6-b5ed-49fb-82c6-313b9617cddb\\})|(\\{309908bd-c2fe-4066-82c5-0631571e77fc\\})|(\\{75f93037-6366-4f88-b92b-c3174d68a836\\})|(\\{36e57809-88bb-47ef-9b6c-90170bb753d5\\})|(\\{da83ef9e-f36a-4416-a4ab-29a09c981690\\})|(\\{09e50933-f19f-435a-8e6a-7663715ea3fc\\})|(\\{7f35f6f3-714d-4c0d-befa-5a6843c62b6f\\})|(\\{b3b3f9c6-6b64-47d0-bf5a-f9796d1d7cfc\\})|(\\{116c6521-bda8-469c-9ca6-0702860aca67\\})|(\\{98566ed5-4c57-4da4-946f-03beeaa6145b\\})|(\\{8fc257d4-612c-4196-9688-dd7de0979c44\\})|(\\{9b7f873c-cf3f-4d8d-9cd2-3c68ce0f831f\\}))$/", + "prefs": [], + "schema": 1562096490180, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1563454", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Fake Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7f55f27d-37e5-4c3e-bd9d-02924496ba3d", + "last_modified": 1562227635293 + }, + { + "guid": "/^((\\{a3250b1a-de19-432b-829f-3d4c18ff4549\\})|(\\{b315912c-03f9-4ca8-a9fd-9410786d242a\\})|(\\{d1e4d5e2-9c6d-4e1d-9638-d329d64e5484\\})|(\\{898e64b3-38b0-4748-93a2-7e68874a73c9\\})|(\\{062f9d2e-1e5d-47e1-a9e6-0a4eb1e8182c\\})|(\\{434cedce-3d36-4ec1-b99d-e2b5ec929e8f\\})|(\\{b429f2f0-62de-4c72-9722-9ccbcc43500c\\})|(\\{7405ed28-67e9-4836-9c38-26bb7175da3b\\})|(\\{ebb5fe5c-5561-47e8-8240-bae4ba4b0389\\})|(\\{a9a5ac46-ade8-4927-85db-5c36bb26fd2c\\})|(\\{34d93cc4-a468-4ddd-98a4-31d1237b9986\\})|(\\{394a7da5-0f59-425c-ae83-49ad47c30a51\\})|(\\{15532ccb-c575-4b8b-9a62-ca2e4b9cbd7d\\})|(\\{d0579f20-4e35-4d7e-be6a-3da1b7660ce7\\})|(\\{f39c789b-9a2f-477e-885e-675f499e8307\\})|(\\{7adb91a0-6c6b-4fc6-911c-63a0c10bb363\\})|(\\{8b51d36c-f5d2-4c9d-b431-2c5011168470\\}))$/", + "prefs": [], + "schema": 1562061057131, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562598", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various Fake Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "432368eb-333d-4280-846d-e9508ac0a8c4", + "last_modified": 1562061250280 + }, + { + "guid": "/^((chromelogger@usernamewilson\\.com)|(firefoxsecurity@usernamewilson\\.com))$/", + "prefs": [], + "schema": 1562001599116, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562597", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fire Security" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c4cd1e9e-b81f-40ef-8c0d-86b42d884a7e", + "last_modified": 1562061057121 + }, + { + "guid": "/^((\\{02421745-5b6e-45e3-925c-670b72162fa5\\})|(\\{165626db-f5c5-4e96-b7c5-dafaed6357f4\\})|(\\{87115bc9-2e52-455a-b82d-2753d3303d0c\\})|(\\{be0863e2-c325-48cf-9623-88bcc6d01970\\})|(\\{09298d3c-f8f1-401d-bd70-43cb66af1594\\}))$/", + "prefs": [], + "schema": 1561988438120, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562634", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3031bfa1-90c6-4b7a-ba03-7fcd7e010914", + "last_modified": 1561988914045 + }, + { + "guid": "/^((\\{fd605874-13ff-4b86-8018-9167923792c9\\})|(\\{687ac7de-0c7c-4e5d-9fac-10757591026d\\})|(\\{c9c11fd7-6774-4657-afe4-0cbf1081b505\\})|(\\{5970609b-547d-4094-b806-a9b0a2996c62\\})|(\\{f77d7ff3-05df-4455-aa23-8031dd341ec0\\})|(\\{d5238802-9ff3-49fd-a427-8eb7a73a08e1\\})|(\\{93a425f5-cc32-42bb-ab4d-63b53512cb52\\})|(\\{23de42fd-b546-4792-ae2f-e2f808e13b52\\})|(\\{209b8c63-d260-4fc7-be92-20e3b04eb944\\})|(\\{e6610c1e-4bd2-4ced-9d40-0a4fc125fd42\\})|(\\{6631ea49-4664-4acc-97dc-0d7e4a67a417\\})|(\\{b811cceb-fcd6-4eb6-b07b-c8c01794eaad\\})|(\\{8ad9f113-7b59-4c45-9c90-59609e7ee91a\\})|(\\{130f1820-9c73-439d-a9c8-114702e1d415\\})|(\\{6dd75c5f-407d-4f3b-9c29-90c422c82d85\\})|(\\{99968b5b-6b54-42e8-932f-0414bfd17405\\})|(\\{1a69e311-e730-48af-b050-f60247a32fe9\\})|(\\{67f715b0-1262-4660-9fa2-c6f7bc29ecc9\\})|(\\{3a8513a4-cac1-4619-ae36-9686bcbb42a2\\})|(\\{27af1be9-f9d9-40d7-878c-78d8bcd94dd5\\})|(\\{7b59b760-964a-43af-83b6-b4ef08fd2e07\\})|(\\{861bd389-6024-4aae-a31b-f334163c1406\\})|(\\{cdcdbb24-88fb-42f8-872c-3df116f82cad\\})|(\\{8c60aa85-e195-4203-8529-073a6db56c54\\})|(\\{c5781fde-fe4f-4792-9e01-4e1239c00b08\\})|(\\{1361b734-0efa-4d8d-8108-ead9400b50d8\\})|(\\{ce79c320-0b55-430a-a431-2cd69cc2919e\\})|(\\{cb976837-ff98-4f37-b81b-98571f3a7828\\})|(\\{21dd7ebb-6736-470a-afe0-5dcea38b3db7\\})|(\\{84b09a06-0f25-40ff-8198-98e2e54a739b\\})|(\\{b869d674-37ba-4df4-a34d-dc9be47a963d\\})|(\\{e18783e4-8e04-457e-9b35-611d20fc12e8\\})|(\\{04042b52-3398-4e42-a638-ef9200c589f4\\})|(\\{96292080-d058-458d-a6c6-bff1d52425a2\\})|(\\{aa584391-2c81-4dfb-b06e-5118b026104e\\})|(\\{6285eae8-ff36-4887-b46c-3772ec04390f\\})|(\\{ea28f6ac-affc-45cb-a536-1eb1bceae142\\})|(\\{340afda6-c1e6-4ac4-8e5f-a5b439a9dfa1\\})|(\\{62202f8d-8e11-4ce0-af66-2235ad17051c\\})|(\\{d4e05835-d503-4d21-9701-a24dd61b0513\\})|(\\{3e8d4f85-d823-4dac-84fd-4b87b33e7852\\})|(\\{c04dbf0d-9b39-4730-93fa-6680aa0909f1\\})|(\\{019be100-cda8-43ae-8136-a014db9e7a9f\\})|(\\{73c512b3-a9c9-4690-bba5-fbfb94aeabd7\\})|(\\{0c78c766-7ebb-4b2b-85dc-d4fcd4da6e9e\\})|(\\{a318cd63-2e84-4d05-96b8-13b721fdec8c\\})|(\\{97d03ad5-3911-4c72-8ff9-5f4b9beade68\\})|(\\{77c5010c-c799-469c-85b8-5e7e0140fb10\\})|(\\{16458eb7-545e-4626-8620-e31d71cfbcc3\\})|(\\{a5a61ddf-c248-4109-a1ba-b8bf84e728c3\\})|(\\{6b18c850-65e3-4cc8-b3f6-e78969c9a428\\})|(\\{4ae632f1-6735-4077-8b62-f73d68eab36c\\})|(\\{db7b2525-ccd1-4ee6-8daa-890d7879ed07\\})|(\\{9e382691-320d-4500-b378-90c9ad922422\\})|(\\{1bf86700-b428-4e67-9701-536f66ec0a2c\\})|(\\{d6841f20-fd15-4373-944d-a0dd6a286d69\\})|(\\{18169628-a7de-4c93-9d30-efb66f45b5a4\\})|(\\{d9979d00-eb14-4cb3-bb21-452d2c02e3dd\\})|(\\{cb868690-a1dc-4fe0-bd2e-2ab291cd54a4\\})|(\\{230faf82-9048-43e9-ab19-94bfe1113ad9\\})|(\\{e0723460-a1a2-4877-bb20-a3f14e01e594\\})|(\\{2431dfee-a855-46b4-a740-6d0e4dbaa662\\})|(\\{1d44053a-5ff6-494c-8fef-0084039eb8ac\\})|(\\{bc77d204-ecd7-42ee-8006-cd54be0a400e\\})|(\\{9e22fccc-6b4a-4674-87f2-e6ecd4b409d6\\})|(\\{62474e9a-8a3a-41cc-9530-97baf6f8b7af\\})|(\\{6932ebe8-ed27-4291-86fd-d5147e0f5702\\})|(\\{aa5b9279-e324-4951-8a12-b0712f37e233\\})|(\\{420e769f-b577-420d-bf07-182299d75882\\})|(\\{11a0fbd7-b2c9-427a-8d33-e7fd8e845630\\})|(\\{a4dfd321-45e6-42dc-9ac8-9d606ad4a672\\})|(\\{54685c20-9401-48d7-a950-81e7b10bd9ed\\})|(\\{2605a51d-53d1-4345-9fbc-380fab2a0c4e\\})|(\\{c3699f84-dacd-4734-a60c-7ecb1b28289a\\})|(\\{074b0ddb-5187-484b-9783-22d187e6dd08\\}))$/", + "prefs": [], + "schema": 1561923700391, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562630", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "56849c09-6253-44ed-bc59-1cd7a61ce961", + "last_modified": 1561988438110 + }, + { + "guid": "tab-api@mozilla.xpi", + "prefs": [], + "schema": 1561710153929, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562154", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Tab API" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9f484302-44da-4b6a-afd8-94113b83c0f6", + "last_modified": 1561710520557 + }, + { + "guid": "/^((\\{b7a0ecf9-212b-49ca-bec1-ead0c6dc2128\\})|(\\{6e977a6d-b31d-4735-a586-41dc25df2100\\})|(\\{67155a2a-6538-42b1-bdc9-f48b442f57e7\\})|(\\{b4d4abc0-5e6e-4a34-a7e3-bfe7319160b8\\})|(\\{2102c5a9-f3c4-4f71-bb6e-c45c9d07b6c8\\})|(\\{071c1c7a-cde3-4a22-aefe-7c8f98df6277\\})|(\\{aa2f3e70-7dcf-4b4e-92c5-54c8280b98de\\})|(\\{3b376420-b276-4a0c-9e28-340dcc0547ce\\})|(\\{ed192371-abcc-4520-ab76-d22afbe51dff\\})|(\\{ad5a457f-59c8-4d90-8e3e-59f13a3bc2b2\\})|(\\{06aa60ab-91ad-4b8a-bfda-98e33b65fbb5\\})|(\\{c2875a12-da6a-4f90-a919-1d2bef57fbff\\})|(\\{b01d1c5b-58b5-4411-86d0-555131c7bd07\\})|(\\{0a79c7eb-5fe9-4e37-841e-18686bc86a20\\})|(\\{341ca205-d6e0-4d03-93be-04939c429412\\})|(\\{855e09d9-ac3a-4885-828d-557734060c1f\\})|(\\{8ac01eb1-9819-4c41-b2b7-042d8cdb3f2e\\}))$/", + "prefs": [], + "schema": 1561657651090, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1562153", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player Clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3460b6b7-8251-4588-8f12-81ac8d288c88", + "last_modified": 1561710153920 + }, + { + "guid": "/^((\\{65b88db7-9c07-4d03-80eb-2e5cf6cd7aa8\\})|(\\{aa2ef90f-db17-4ece-abab-4f87830457db\\})|(\\{e50969c9-088c-4978-9ffb-5d78015dabcc\\})|(\\{15fd1a8e-db53-41fa-9c46-93ec5b7473c1\\})|(\\{ed84b63e-faa2-4c48-b080-e9612cbc2e49\\})|(\\{c784f63e-5609-47a8-92ee-33a2bcb3239b\\})|(\\{1641b1ec-9a3d-4e3c-b52e-bc80671349f9\\}))$/", + "prefs": [], + "schema": 1561587664411, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1561854", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3e88dad8-f640-46dd-8b00-4b955eea7b24", + "last_modified": 1561626172388 + }, + { + "guid": "/^((\\{f0df6aa3-9792-4476-daa6-4709f93bbce3\\})|(\\{fe934134-3d0f-462b-d56e-e7187dfa8c98\\})|(\\{429999c4-1b8b-46fb-863f-ce19a08afc9c\\})|(\\{b8003074-2123-45be-91cf-654ef9671e1a\\})|(\\{9712066a-d491-4293-cd31-8ef8ee907d40\\})|(\\{dcfbb98b-783b-4df0-8427-e269114736cb\\})|(\\{66c44e3b-2df2-4741-ff07-0067cca4fe95\\})|(\\{af0a4d96-3403-496f-9d9a-5c766bf44bac\\})|(\\{82c60958-45da-4e6a-de21-879775c5473a\\})|(\\{c9118234-5787-488d-b30c-7d0a904fbabb\\})|(\\{f07d3da6-81ea-464f-9bef-6ff5470b307b\\})|(\\{c2454a12-7f57-440e-f695-0a9618f48b80\\})|(\\{f6e1d884-8100-49e7-88b9-bff8d9295cd2\\}))$/", + "prefs": [], + "schema": 1561552773231, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1561603", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Various script injection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0c227983-1180-4b4a-b25b-8160738e7238", + "last_modified": 1561554563187 + }, + { + "guid": "/^((\\{4200b565-5c4a-410f-b4fb-478daae2537f\\})|(\\{a0bba103-21d5-49c8-96f3-4eabbe78ced3\\})|(\\{ec46fe21-5690-4757-8ebc-1c77f826fe6b\\})|(\\{ce45d605-3bb6-4fad-8c1b-238ecee0d3df\\})|(\\{c70bd1fe-1d7d-4ae5-a731-3d513e6c46ba\\})|(\\{aeec96ca-81b9-405c-bd70-01ea6a50be9d\\})|(\\{0a1603a8-839f-4890-b1e3-1b8e00a7a0c9\\})|(\\{45febc8f-eaeb-4cec-90ea-07a7edc22793\\})|(\\{a7c7febd-6726-4d0e-9947-2edfd8bea35a\\})|(\\{eda3389e-ae07-4a2c-9b50-ce4e9008f297\\})|(\\{0e5d1d65-4fbb-4dd9-9042-3b568d9e2489\\})|(\\{1461f0e5-3c4a-453e-aed2-ca45ff5db747\\})|(\\{e842e73d-9d8a-45a8-bf0d-ef974ab24767\\})|(\\{e1d4fa8a-3da0-4fee-8b4f-0c7233fcb49a\\}))$/", + "prefs": [], + "schema": 1561541784349, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1561595", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2e886e0c-00ab-44c2-bcbe-7c8793d46d89", + "last_modified": 1561551163572 + }, + { + "guid": "/^((\\{0eddfecf-eb8a-4a08-8189-004932a77d5b\\})|(\\{b8181b05-a263-410c-8c07-5d7e8c80f9a2\\})|(\\{561d3a53-8e1f-417c-9b46-af1ea9942c4d\\})|(\\{cae0f640-a4b8-4ea0-8667-39ec00651b54\\})|(\\{142aae9b-ff6a-4ae3-b4c4-75e99534e661\\})|(\\{0592cc75-3027-420c-9a9c-22b23a21af5b\\})|(\\{23f8f54b-1f6a-4760-bd9a-414aba8d93c4\\})|(\\{09d0cd99-4cde-42d2-9a4e-8002f7595834\\}))$/", + "prefs": [], + "schema": 1561380181925, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560886", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "ADB amazon" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dba9e10e-ff77-45dd-a2fc-e7daf5f09fd5", + "last_modified": 1561381085652 + }, + { + "guid": "/^((\\{99a325df-ca23-41da-84ce-477796f46737\\})|(\\{86a9b963-ebd2-4ef5-abaf-5ffd7cf86387\\})|(\\{9c05da6f-e5e2-4455-bae3-2754e1b36db4\\})|(\\{002d0276-03dc-439b-bea4-576976bbd7c4\\})|(\\{4476484a-2a0a-4a7a-8612-18ac22e02ac9\\})|(\\{edb28501-65bc-49f1-b168-1ea5e84d4a19\\})|(\\{13b0c2d6-0a44-47e0-aced-0664877b8a4e\\})|(\\{fdd30cab-1f29-439b-829d-80c5546087e0\\})|(\\{2abed6fc-d8ac-479f-8fdb-f5d20b0a5c27\\})|(\\{7afb7eb5-7837-474f-a925-62728be18488\\})|(\\{ff86f12d-e38b-4c70-ab00-9cd20174ddcd\\})|(\\{31b72d81-14d8-40e5-a2c2-7259a7d40d96\\})|(\\{84e3002a-a0ea-42f8-b30c-1739cb21b105\\})|(\\{47d8027c-a331-4f8b-8c69-4c95680caba5\\})|(\\{d03ae30a-58b5-4dc1-afd9-bc4ea8efc761\\})|(\\{eb745394-234d-48b4-bf1e-cdec66de26d3\\})|(\\{b5614c0a-878a-412f-ad7b-bc5a7916b3bf\\})|(\\{1d5916f7-3a78-40ce-92a0-35989646fe8d\\})|(\\{34cfd020-1dce-4f12-9499-f7e3b02582d0\\})|(\\{7fbdd1c1-82a4-4cfc-a3c2-ad192f5c8cf8\\})|(\\{04fbbb51-3b76-484a-99ca-ccd3e484da26\\}))$/", + "prefs": [], + "schema": 1561379624735, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560888", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5525a8a4-2b7f-4cb8-8932-5daf35a2acc9", + "last_modified": 1561380181911 + }, + { + "guid": "/^((superzoom-unlisted@funnerapps\\.com)|(superzoom-unlisted-test@funnerapps\\.com)|(superzoom-hosted@funnerapps\\.com))$/", + "prefs": [], + "schema": 1561260352539, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560927", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "SuperZoom" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a7a12a25-37c2-4ec7-8fa2-e016442b4457", + "last_modified": 1561379624719 + }, + { + "guid": "/^((\\{dfc97392-12f9-474c-9a61-fba12334d09f\\})|(\\{ca0e7543-892d-4625-8565-3671141a4ac4\\})|(\\{d50b1b61-8ba1-471b-833e-c5526ea4f307\\})|(\\{ae53880e-f5b2-4020-bdb5-0872cc1196af\\})|(\\{cb8982c0-1f56-439e-94a0-83a0308ea952\\})|(\\{91ca701d-73e3-4d76-80bc-4cbefc16beae\\})|(\\{f238a031-3366-4792-89b0-736f5e1af888\\})|(\\{35f7fa51-338b-4a7c-9f74-055708e2c941\\}))$/", + "prefs": [], + "schema": 1561232502012, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560614", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More fake Flash players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9a6e32f3-3d97-4393-8733-1a24b8796aa3", + "last_modified": 1561246819959 + }, + { + "guid": "/^((\\{eb585608-4896-4892-8a9b-1786929a6517\\})|(\\{b53cec3a-0479-4887-93b2-4732059e6f95\\})|(\\{52e84405-484c-4bfb-a279-da57dc8f89d4\\})|(\\{65136894-4082-4eac-a968-ad5dfd1771f8\\})|(\\{98d5570d-3606-40f8-8af2-2d2144698a92\\})|(\\{5ced4e6d-5c50-4e26-9d8b-b82a8d9b4e87\\})|(\\{fc97e534-4690-462b-8227-af38c67b57e2\\})|(\\{8aa21cc1-d9ac-4c43-b205-bfbd87aa8163\\})|(\\{f3dd0563-975b-475b-b295-2922f6e69717\\})|(\\{b1ee58e1-90de-425b-ab76-dd8e034305c8\\})|(\\{ae026b4b-7159-4415-98d4-1fb712092028\\})|(\\{452ec3ac-8fba-4de8-a5dd-db1cf9b89c35\\})|(\\{4c0a3c25-f706-4160-8128-197137e814b8\\})|(\\{30c347c6-642c-46cf-899d-442632d90e25\\})|(\\{7dd79020-d8d4-4241-b1fe-efca66530d5d\\}))$/", + "prefs": [], + "schema": 1561059702370, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560614", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More fake Flash players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "61f4423b-7171-4a82-8aae-71621233ee2a", + "last_modified": 1561148779474 + }, + { + "guid": "/^((\\{2adb8263-d69a-4436-a46e-b8595c35d3e4\\})|(\\{d2d96579-be7c-4bc0-9d94-7eb8920ed437\\})|(\\{3ef03e82-ae5c-4bb4-85f2-d0925d3b1d8f\\})|(\\{5f89e2ff-b113-45a7-bd44-a8cbb6b3e18e\\})|(\\{d0a65809-897d-40af-974c-df6baa16e0d6\\})|(\\{17d71711-a428-4231-93f7-25f65dc7a05f\\})|(\\{88bf0d44-b815-4654-b177-25ed224587de\\})|(\\{0bc2f2bb-b040-4512-b0bf-a2a875bfbcc7\\})|(\\{c05607b1-ce5d-4a14-ac71-91d93e5adc7c\\})|(\\{14d1d1c6-3982-4379-bf48-67aff16b0b40\\})|(\\{db395935-5b36-43e0-9c21-2fc546e3504b\\})|(\\{031a0658-d9a4-4dab-8b9a-3608a92d3d9f\\})|(\\{28a6ee49-33dd-4cc1-8650-7abc95ff30c1\\})|(\\{578c5cb1-ed39-4270-9010-3d7c623e4ed3\\}))$/", + "prefs": [], + "schema": 1560890279256, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1560126", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More fake flash players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "39d92b03-d7b7-41d3-8d14-73088972f735", + "last_modified": 1560959139660 + }, + { + "guid": "/^((\\{52e29477-bb59-452a-a929-7d238ab68dc8\\})|(\\{e7c3e8d7-0cd7-4cea-8fe6-afd0dda61f56\\})|(\\{f57df33b-b222-4524-86c3-531a6d20b4c2\\})|(\\{5bfc5ee1-d8de-4efd-80f5-966b94eec12b\\})|(\\{ed229f56-afbb-48e5-8422-2ad940afa02f\\})|(\\{c87d1f11-ce0e-46eb-8710-1288416b709b\\})|(\\{177b00c2-4fb2-4268-b0c7-cb5a1ad08d83\\})|(\\{33850c97-5260-409e-9796-bd9e03aeb411\\}))$/", + "prefs": [], + "schema": 1560874921856, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1559787", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b44844c7-0ba9-477c-b0f3-bd12725c620e", + "last_modified": 1560875340234 + }, + { + "guid": "/^((\\{004b2982-0956-4c66-a7f3-7dba26eeda94\\})|(\\{31606ee8-6af1-4d3e-98d0-451b8483a498\\})|(\\{914f7369-d844-4e72-a8de-043378710864\\})|(\\{87506f0b-af77-4113-8358-fbb0a9f6daa7\\})|(\\{74832b41-91e6-4bf0-a6a9-5e74bf3e5683\\})|(\\{94e526a9-70ec-4566-995c-53e597166c8c\\})|(\\{1679b342-31d5-44b2-ae2b-91c487b2654f\\})|(\\{ead96242-a6c8-4478-88c5-5e2c54d9ace1\\})|(\\{ab0e69c5-d215-4825-8e40-de0bcae97da9\\})|(\\{2607b07a-90e6-4c0e-9bd8-94eb16982303\\})|(\\{c8336a7e-f5ef-41d8-9754-31676cb4f6c4\\})|(\\{49185403-71d8-40ed-9e30-71171231a2c0\\})|(\\{724540be-a261-4d92-bee5-ede7c6375ed6\\})|(\\{6b687abb-9aa2-4e76-bdc9-cb542809cf7a\\})|(\\{faec57e2-f33f-4974-b29c-3afc2d710ae5\\})|(\\{2dc254ac-f312-4db3-84b2-29690e20ce4d\\})|(\\{c60eb214-f702-48fd-b173-756b528cee4b\\})|(\\{6cf50082-5b79-400b-846a-8902d6609a37\\})|(\\{a45c880d-5037-4428-9e1c-ec1cd45fe830\\})|(\\{d410777f-d023-44d5-bdb8-a54b0c927daa\\})|(\\{36fbd9e9-1d2d-411f-981d-b57fbc1067db\\})|(\\{170130b4-3178-4dc2-a1f6-98a788299b16\\})|(\\{aa54e92d-20ad-4f3f-a0c7-95a97bd5e99d\\})|(\\{6e73d781-bce5-40e0-a847-63a936f58ca6\\})|(\\{861af4ed-838b-4e5f-94c7-0e95bc6b709b\\})|(\\{3e000c1f-3ad5-455c-9a20-f18035273746\\})|(\\{aeacd5ad-8949-46a0-96b0-96c9f93f0b8b\\})|(\\{c7f65d43-1a36-4683-864f-c7224037289e\\}))$/", + "prefs": [], + "schema": 1560874615935, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1559776", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Norton add-ons and clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7434a6e0-c434-4017-b475-625bd0f85890", + "last_modified": 1560874921842 + }, + { + "guid": "/^((\\{c3ff7a71-7392-4aa1-a193-95fd393a7389\\})|(\\{9255568f-d790-4b45-9fe7-d4d1bcc193cd\\})|(\\{76d7025a-8c31-4b0b-a9de-c6679919ef81\\})|(\\{6b93c35f-74c4-4d79-8557-b3fcb090049a\\})|(\\{f9dbfda2-5680-43f1-9575-5cb044264f7c\\})|(\\{096e84e1-36c8-48fe-b642-03c91c1ef14f\\})|(\\{a11e0be8-9b45-4d69-9aad-339d3220147c\\})|(\\{fb754f2e-c021-4190-96fd-7142cbcb985e\\})|(\\{69187792-e951-4ea9-ad26-378f25efee81\\})|(\\{6468fac9-e37b-43e9-9895-36143902e431\\})|(\\{ff1db01d-4e0d-4917-b487-7f3c28d7f5da\\})|(\\{1adb7040-3a78-4270-b4d3-b926819d4c72\\})|(\\{9b3d09a1-2134-48e3-bf0c-a6dc659aad93\\})|(\\{bfa38150-f24e-4443-9d07-875b21ff479e\\})|(\\{05c8e9b0-0b8a-4da2-9e44-a215e691302c\\})|(\\{e4868162-b7f2-40dc-9101-4eab9858876b\\})|(\\{541118b3-1905-4d4a-9059-3ac745b0b043\\})|(\\{d38507bd-22ee-4839-be07-cae4806ac227\\})|(\\{dbef35f1-fc95-42e3-a4a6-b94a970b8a7a\\})|(\\{f34d7289-64c7-4720-90e9-6a6cad0ddc9c\\})|(\\{96b900d3-784a-4e93-8b9f-5f7885424117\\})|(\\{a6ccaf93-4d0a-4bcd-b574-b6d1417bdb0d\\})|(\\{186d942f-cc7f-4054-9673-067f9aaae190\\})|(\\{f7c0f615-d406-4cf8-b5de-bde347f7d9f9\\})|(\\{ddf49e42-2db2-448f-9717-96a93bdb078d\\})|(\\{add62eb4-d1c1-4217-920c-dfb462e955aa\\})|(\\{71ef4372-6321-4e99-937a-0a4a03476348\\})|(\\{053307b4-d841-4d42-8fda-881aa7f7777d\\})|(\\{458e497b-8e5c-4901-82be-1e33832bdac2\\})|(\\{3bf07b01-bc56-4b28-acb4-7d56bb6f5fc8\\})|(\\{7e50978d-cf4e-429b-8482-946c86991bfd\\})|(\\{7691a931-7d5e-4daf-ad20-14539572c215\\})|(\\{c1d8d622-aa7b-4e36-9d5e-e1de1b1044da\\})|(\\{f1b85cd5-bc61-4994-96fb-74df9c62d385\\})|(\\{e26cab94-6216-47e7-b725-948613f2a08c\\})|(\\{2278a05e-3b98-4fe4-83f3-f90d42ce0870\\})|(\\{85240094-c94c-40b7-84ac-6dcf1d50cee5\\})|(\\{7e5f5b7a-ef9d-41a8-8137-da6399afdd5d\\})|(\\{85d01efb-0331-4f7c-9ef2-f5f35c0df0a9\\})|(\\{f455923d-856d-4f2e-8e1b-8cffb0b7a4a4\\})|(\\{cd4fab1b-03b4-42eb-9800-1664c4de06c2\\})|(\\{44ad1c10-baa5-4efe-96bf-743d6a86079e\\})|(\\{73cf3b22-f479-495e-8bab-54ca07e3341f\\})|(\\{c4e856fe-5594-4484-9463-a139eb6071e5\\})|(\\{6f110a84-aa60-4849-8408-9ee70c868e8e\\})|(\\{67d91fb9-3da3-481c-a426-a350788764f2\\})|(\\{eb7699e3-9886-40d5-863e-0bf6862f2f98\\})|(\\{e18709ec-f4d2-40de-8e88-ef7a6f1d4fef\\})|(\\{f83cd650-3411-454d-aadd-79bbd82f3793\\})|(\\{edb44a75-f988-4ce7-ad83-1b7cfe3da54b\\})|(\\{b328a40e-52a7-48df-8960-8f79927bfd35\\})|(\\{f8681a1c-062e-4934-a1ff-6479f179aa97\\})|(\\{f052ef52-7b0e-4a99-9490-892b515d7ace\\}))$/", + "prefs": [], + "schema": 1560714091988, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1559772", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Adobe Flash and clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "34b4e88a-ca61-48fc-a6c7-7e08aedf7887", + "last_modified": 1560874615921 + }, + { + "guid": "/^((\\{c5ab9361-f8d8-454a-b268-a4b355b37a83\\})|(\\{f7bbdf7b-7f4e-4319-b9c6-ba62f38e1d5c\\})|(\\{97154c2a-2a3d-4ddb-814d-5451e3c35103\\})|(\\{1665bb10-e8d5-44ea-8cd3-531b6ebfaef9\\})|(\\{f3b474a6-b76c-4a9e-ae57-df0a3992d8f1\\}))$/", + "prefs": [], + "schema": 1560430131572, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1559330", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "436f1b52-16b8-4c70-b67d-ad6c39f6bd39", + "last_modified": 1560502059138 + }, + { + "guid": "/^((de\\.firefoxextension12345@asdf\\.pl)|(deex1@de\\.com)|(esex1@ese\\.com)|(estrellach@protonmail\\.com)|(fifi312@protonmail\\.com)|(finex1@fin\\.com)|(firefoxextension123@asdf\\.pl)|(firefoxextension1234@asdf\\.pl)|(firefoxextension12345@asdf\\.pl)|(firefoxextension123456@asdf\\.pl)|(frexff1@frexff1\\.com)|(frexff2@frexff2\\.com)|(frexff3@frexff3\\.com)|(ind@niepodam\\.pl)|(jacob4311@protonmail\\.com)|(javonnu144@protonmail\\.com)|(keellon33-ff@protonmail\\.com)|(keellon33@protonmail\\.com)|(masetoo4113@protonmail\\.com)|(mikecosenti11@protonmail\\.com)|(paigecho@protonmail\\.com)|(salooo12@protonmail\\.com)|(swex1@swe\\.com)|(swex2@swe\\.com)|(swex3@swe\\.com)|(willburpoor@protonmail\\.com)|(williamhibburn@protonmail\\.com))$/", + "prefs": [], + "schema": 1560426922081, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1483769", + "why": "Malware targeting Facebook", + "name": "Facebook malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "202fbae4-e904-430a-a244-63b0fb04385f", + "last_modified": 1560429929334 + }, + { + "guid": "/^((\\{50c31b2c-af26-4238-bbbc-f11218d19682\\})|(\\{7c244b9b-4058-4362-9c3f-6f553c75d051\\})|(\\{bc90f38a-c295-45c4-a09f-7038ed6139a7\\})|(\\{d55311d8-83ce-4320-b30f-a7acac9224ac\\})|(\\{39d2838c-8b0d-453d-b685-71b2af4f3ff5\\})|(\\{95fdb905-de6c-4499-b7f2-372910a44405\\}))$/", + "prefs": [], + "schema": 1560371525142, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1558791", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5bcce386-ce8e-4e59-838d-a02d35de154b", + "last_modified": 1560417609081 + }, + { + "guid": "/^((\\{615a653c-240f-4dac-a42b-66751cfe7405\\})|(\\{3119fa71-b8b7-430e-ba44-4c25d5d2a4e8\\})|(\\{98075bd1-491f-4506-9e0a-262b08042e5b\\})|(\\{6102d260-a977-46a0-8635-7cdf3f3b72b3\\})|(\\{593356cd-e02b-4529-9d14-c5e4740fe749\\})|(\\{15b2bf6b-274d-477e-8456-3efc218f9fe4\\})|(\\{78311b1c-ffbe-413a-86c2-86b136aacd17\\})|(\\{98f992f6-d311-4248-939c-05f5db60ee78\\})|(\\{b1f0bcd0-0bf2-43c5-b61c-ee1fdf7f88fe\\})|(\\{7113cc05-ebbf-4c1a-9c6e-a9f959817851\\})|(\\{5325d52b-99b5-4a98-8625-24bbc8098b7f\\})|(\\{b6e3b23b-adfc-4f23-986b-4e62faedf402\\})|(\\{fd000b48-0259-4356-9c5e-2ff22d8784b7\\})|(\\{7fae693e-a917-40eb-9881-769c85f64ab3\\})|(\\{c0b9ae3d-d604-4327-95a7-67733d00bc89\\})|(\\{bc6b6fa2-ef8d-41f8-99de-ad838ad09594\\})|(\\{9dfbbcab-e7d0-4483-85ca-ca71ba03b769\\})|(\\{1d4ef484-d567-436c-ba0b-9cc0fb224708\\})|(\\{9874efa4-3a66-459a-aa77-ecf9ac8d1fe4\\})|(\\{47faaf0b-ec1e-46d3-ad59-bb44345b86d2\\})|(\\{b4b433dd-adcd-4491-8f80-ecdfb4788dc4\\}))$/", + "prefs": [], + "schema": 1560340682934, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1558791", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c016d195-ae3c-45d9-ab13-1010f0711b88", + "last_modified": 1560340840541 + }, + { + "guid": "/^((\\{43c62192-abed-474e-a619-9d486383f5d2\\})|(\\{9506f10a-e5c1-4a0a-83e7-8e83b3fb77d2\\})|(\\{a4fd5625-6912-4cc3-861e-f5338e4c36f8\\})|(\\{2ff20211-5540-4c13-8c06-6769902b0e15\\})|(\\{e15e3074-88a0-4a6d-bd1f-a7149eff3a6a\\})|(\\{e5800ff7-8cf3-410c-91a6-4b61838e4486\\})|(\\{22754446-97f4-473f-8da2-3ffbd44abdc3\\})|(\\{eea1ab8b-a4bd-4905-aa02-80874a452fff\\})|(\\{b0bfaa8c-702e-47ea-84ff-6b20ef979385\\})|(\\{f13622f7-470e-4ad3-85a5-25a0fd43f9fa\\})|(\\{f75cba29-48aa-4c02-8d7e-704532de1aa6\\})|(\\{43325b83-0f60-4d46-9b43-f9be2e91682c\\}))$/", + "prefs": [], + "schema": 1560251614258, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1558543", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Various fake clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2399177f-4b21-4c46-ae74-ab7db1edb879", + "last_modified": 1560339940028 + }, + { + "guid": "{e1bb4a20-9e0d-443a-b171-4d3b71f27211}", + "prefs": [], + "schema": 1560247588269, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1557680", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Biis" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f42edc78-6eda-4dfc-9f7b-fbaaa869852e", + "last_modified": 1560248603844 + }, + { + "guid": "/^((\\{f7b6b9aa-4564-443b-9da0-a194492cecb9\\})|(\\{f20526fe-45cb-441b-96e1-6d8aad20b97f\\})|(\\{2a7ada19-27c1-4f16-b67d-64344e85565f\\})|(\\{7faf6c45-9883-49c8-9d90-c70fd4fb72bd\\}))$/", + "prefs": [], + "schema": 1559835473927, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1557381", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3f781593-e000-464e-b46e-204201f42003", + "last_modified": 1559836209248 + }, + { + "guid": "/^((\\{4580e89a-d987-4a54-acd5-103eb91374df\\})|(\\{04767859-e649-473a-9ff8-1491f9217a2f\\})|(add-on@fdvoyzaxvni)|(add-on@mxdshahek)|(add-on@plgargwikihoves)|(add-on@plgbrhovtwitpor)|(add-on@plgeshovwikies)|(add-on@plgfrhovinstafr)|(add-on@plgfrscroltwofr)|(add-on@plgnlscroltwonl)|(add-on@plgsngicosnsac)|(app@clzohispatolas)|(app@plgarghovtwitt)|(app@plgargtydownesp)|(app@plgautriczoomde)|(app@plgcohovtwites)|(app@plgdehotwittde)|(app@plgdeudownlde)|(app@plgeshovtwittes)|(app@plgindhovtwiten)|(app@plgitscroltwoit)|(app@plgnlhovtwittnl)|(app@plgphscroltwoen)|(app@plgsajjhamzooph)|(app@plgsingchewmve)|(appapp@plgindzoplshind)|(application@arzoplgirasta)|(application@blibluk)|(application@breplgdownporbr)|(application@es9hsgaedr)|(application@es10gfjqzma)|(application@gerluk)|(application@grasow)|(application@plgbehovtwittnl)|(application@plgbelgdownflam)|(application@plgbescroltwonl)|(application@plgbrhovwikibr)|(application@plgdescroltwode)|(application@plgfihovinstafi)|(application@plgindiendownen)|(application@plgindzoplshind)|(application@plgphhovtwiten)|(application@twexispolavieda)|(ext@es8dffdsghe)|(ext@plgagscroltwoes)|(ext@plgbehovtwittfr)|(ext@plgbelgizompnl)|(ext@plgbgzowomawfr)|(ext@plgesscroltwoes)|(ext@plgithovinstait)|(ext@plgsescroltwose)|(ext@plgukscroltwoen)|(ext@sgdlpictomagi)|(ext@uksfdahdhsc)|(extension@es5dssdsj)|(extension@itsfahqiaxb)|(extension@plgauthovtwitde)|(extension@plgbrscroltwopt)|(extension@plgdehoverwikde)|(extension@plgfrdownlnewfr)|(extension@plgfrhoverwikfr)|(extension@plgfrsearchfr)|(plg@defdgajbisl)|(plg@es6fdhfec)|(plg@es7fdsfddqa)|(plg@esfdhalmbwn)|(plg@frhadiadsk)|(plg@indplgomenawc)|(plg@plgbescroltwofr)|(plg@plgbrhovinstapt)|(plg@plgbrsearchpt)|(plg@plgesdownopenew)|(plg@plgitadownaudit)|(plg@plgithovertwitt)|(plg@plgnlhovwikinl)|(plg@plgnlscroltwonl)|(plg@plgnorvegzoom)|(plg@plgsuhovtwittde)|(plg@plgukhovwikien)|(plg@singplganowong)|(plugin@frmdehpzamdoas)|(plugin@pldinddowninen)|(plugin@plgdahovinstada)|(plugin@plgdesearchde)|(plugin@plghowtwifr)|(plugin@plgitsearchit)|(plugin@plgitwikihoveit)|(plugin@plgnohovtwitno)|(plugin@plgpaysbasdownl)|(plugin@plgukendowauden)|(plugin@plgukhovtwitten)|(plugin@ptgouloumette)|(plugin@sgpongextejmk))$/", + "prefs": [], + "schema": 1559815025832, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1557258", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "67ab85b2-4241-4f2a-8589-801b4221b79d", + "last_modified": 1559835396847 + }, + { + "guid": "{ca6a76c4-1831-4e90-9ed8-2a3768114563}", + "prefs": [], + "schema": 1559739635606, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1556974", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Xtif" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5c7614a2-793c-454d-967e-35b06de2cbd1", + "last_modified": 1559814857242 + }, + { + "guid": "/^((\\{8dcc43e0-bd17-44db-a0a1-4cf179f4a97f\\})|(\\{a4ef8092-6d9b-4ca2-bc6a-021fc9e8e156\\})|(\\{9fb5bfd6-27d6-4321-8af9-47b6ceacac3f\\})|(\\{3c9052cd-10e7-4ccf-9dda-8524cccf66a7\\})|(\\{da554ce8-5c96-4511-93f2-4e00a85aeb20\\}))$/", + "prefs": [], + "schema": 1559677299211, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1557027", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a7bc4a56-2f54-4791-b644-0ed2403bb9db", + "last_modified": 1559739185033 + }, + { + "guid": "/^((__MSG_name__@crx-to-xpi)|(\\{3fedc417-0513-4d15-a7e5-978d044cc9b9\\})|(\\{5c0f4cf6-b6f5-4485-a941-6c25f65c54eb\\})|(\\{5e1948ce-2889-4504-9e53-170753e731f6\\})|(\\{5fbf70b1-6dd8-4cd3-83fd-fbc53c59b7f7\\})|(\\{6ed4cb78-09ec-4a85-ad8c-d29382beb6d2\\})|(\\{6f7b3f15-30ab-40ce-9424-9a03a4969bea\\})|(\\{7d516686-2f77-4f2f-8a24-2441f9661187\\})|(\\{8c50589a-419d-40b5-8edb-4027a509725a\\})|(\\{8cf8a461-47f0-4eef-966c-faf0ae9593f8\\})|(\\{9ae1dd88-5e97-4ff4-b6a6-907689b6289b\\})|(\\{17a98bce-6b45-4c91-82ac-2916264e79ad\\})|(\\{50bf8563-c1e1-4c6d-80f8-281cd0d9f453\\})|(\\{51c03d30-c160-409f-be23-a576bf6ee2a6\\})|(\\{76a4a4cd-0bbb-4c6b-b285-d3aa4b55a99e\\})|(\\{94c8b1f5-7675-48fb-8c2b-3a8c7faa059e\\})|(\\{110b4b76-4314-4c63-9ae7-01e0e30f995f\\})|(\\{204d5f5f-d2bf-4892-a35d-e02d7436a410\\})|(\\{358af48c-726e-4853-a941-a19f9f4bcf8a\\})|(\\{02110a55-b817-49e7-bddd-5ce06d7f66e3\\})|(\\{9521b96b-f232-4689-81eb-907ba68872fb\\})|(\\{83034ff5-c83d-4560-8e6e-646862d2f405\\})|(\\{167357db-2afa-43a2-90df-ca2c6527ed78\\})|(\\{08429889-d4dd-4dc0-a607-5e26d976f376\\})|(\\{20289793-a4b4-48de-b640-672e6be44f5e\\})|(\\{a1be3447-d87d-409b-8721-d895935f65b8\\})|(\\{bf599b22-fc5d-43ac-ae24-343016cf6a94\\})|(\\{c8faeded-34a7-4eb6-8e21-407c82040ca1\\})|(\\{d8b5848f-b314-483b-8fd3-9f919f0f3b8b\\})|(\\{d768a761-3df5-4676-8cbe-5e3f4f426f6d\\})|(\\{e5d45ac7-1a6d-4ec8-89ae-d75ff07b89b3\\})|(\\{e5705290-3609-4b11-a062-6c55fd074d80\\})|(\\{ec92f712-38a6-4d81-80fb-529990a5b83d\\})|(\\{f14fbd1c-1df9-4ab3-a983-a8889f88fec2\\})|(\\{fcdb43b5-add6-49f2-a102-761147cb88fe\\})|(\\{fddfb568-5055-4dc7-ac3c-5eabc69c3c75\\})|(anhjddeakbabimdgmonfbnpbainknbfa@chrome-store-foxified--782543786)|(anhjddeakbabimdgmonfbnpbainknbfa@chrome-store-foxified-875652714)|(darktheme@addon\\.com))$/", + "prefs": [], + "schema": 1559660128156, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1556759", + "why": "This add-on violates Mozilla's add-on policies by changing request setting without user control or consent.", + "name": "Malicious request manipulation add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6016087e-a255-4d70-8ffa-d7095af42e00", + "last_modified": 1559660502221 + }, + { + "guid": "{31dea008-38a8-44c5-8404-d1b110f47ab5}", + "prefs": [], + "schema": 1559648801945, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555768", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "FastProxy - Украина" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.2", + "minVersion": "0" + } + ], + "id": "dc8887b5-eae3-4cc9-937f-229b8830ba83", + "last_modified": 1559649064547 + }, + { + "guid": "{4bf7c817-a8ab-4d98-b84d-65f79f05415d}", + "prefs": [], + "schema": 1559647486096, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555768", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "FastProxy - Россия" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.0.14", + "minVersion": "0" + } + ], + "id": "96dbcd8a-d40a-4949-957b-b6eda96d0b95", + "last_modified": 1559648801932 + }, + { + "guid": "/^((\\{366bbde3-553b-4587-99d1-ce34f6b52e1b\\})|(\\{af48b3a0-a899-4722-a6b0-72ea823c9c57\\})|(\\{bc4dba63-23da-47ce-9ed5-574859c80ae2\\})|(\\{e390c529-7e38-4191-9cee-7b6902c9d833\\}))$/", + "prefs": [], + "schema": 1559647399044, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1556700", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0561368d-755a-498b-a3ba-55274d9bc925", + "last_modified": 1559647486083 + }, + { + "guid": "{5a3477ec-7abe-4efc-b44a-7ede3cc02217}", + "prefs": [], + "schema": 1559563381261, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1556698", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "youtube video download pro" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "94404f29-bf39-473a-a299-57d922f51c6c", + "last_modified": 1559647399030 + }, + { + "guid": "/^((jid1-93CWPmRaVPjRQA@jetpack)|(\\{3c78ca35-67af-4917-a24b-7699456ca61e\\})|(\\{8a8f48ed-3daf-41b9-88f4-73730459aaf6\\})|(\\{f906b9c7-576c-45ad-8f48-358271e15e5b\\})|(\\{de4f3cf2-cd35-4a2f-a405-33a4a29918a2\\})|(\\{7a365978-94e2-49d4-b5f7-6178dc80ed69\\})|(\\{dbc00301-7930-4421-91d8-23bf463228c2\\})|(\\{4f42055b-fd40-4b98-b71f-a58725d729d1\\})|(\\{8b6ae26a-4479-4727-bd89-46aef6f6e5d6\\})|(\\{b6a95353-bf1f-4c03-b4e5-43eca107ecbf\\})|(\\{933d68b5-80f4-44ba-bceb-2a6338c0ac7e\\})|(\\{cc9eb257-0f4e-423a-b124-318c59271a3b\\})|(\\{d0dc10cb-6b80-4e88-aec9-605d4e75ae59\\})|(\\{8dc1a829-e888-4ce5-a19e-614277de4d7a\\})|(\\{81ad5a42-34cb-4c35-b354-2306dbe418fb\\})|(\\{2fe8edfa-61ab-4257-848b-59def269a511\\})|(\\{55de894d-cfd0-4654-af30-719020792149\\})|(\\{4b9ffc29-0e02-4583-89d9-495084f96b43\\})|(\\{aa94dd9b-7d23-4776-ac2e-9939a3cf7bb0\\})|(\\{b43a0fd7-b010-42ae-a613-db7e792c995f\\})|(\\{af57625d-4b59-4028-874d-767a37bc9ebd\\})|(\\{84387d47-147e-4d50-8a3a-6e2bb4b522e5\\}))$/", + "prefs": [], + "schema": 1559562690935, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555945", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Adobe Flash Players and related add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a9526a76-d420-4adf-9a57-7096172d6077", + "last_modified": 1559563142131 + }, + { + "guid": "/^((\\{e914f532-5b99-4c2c-a6d9-56c6f9e07f09\\})|(\\{d7904110-ef0b-4daf-8fe1-1627c9cd14b9\\})|(\\{091d1ebc-55f9-4af0-871a-b3b383b70241\\})|(\\{eaf253cb-0418-4994-98cd-4fcfb5b827c1\\}))$/", + "prefs": [], + "schema": 1559562473865, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555848", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "WFot and others" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "740446f0-e16d-484d-a946-873f92d78c79", + "last_modified": 1559562690921 + }, + { + "guid": "/^((\\{3d35fa06-3f8e-457c-9ffa-a115f33e8e7b\\})|(\\{fd834065-2938-4522-ae8c-55592bf7e870\\})|(\\{d52afc01-3504-45eb-aa06-3fa55d2dc7cd\\})|(\\{2a34eba4-2a55-4863-91a6-c389d8e108c9\\})|(\\{45082e42-ceef-4df8-9f27-b5adf766ed8d\\})|(\\{8f77cfd1-6eee-4489-93d0-ae8627d8211d\\}))$/", + "prefs": [], + "schema": 1559562343779, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555481", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "BatchSerialized and others" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "10b029a4-72af-4e6a-b53a-492b94d1813f", + "last_modified": 1559562473850 + }, + { + "guid": "/^((\\{0c955fab-3d51-4989-aca5-d806cf0f2ea4\\})|(\\{1b382a66-46e8-4d33-a8a4-a857dc03cf12\\})|(\\{3f41aff5-f248-4b99-9c76-b7d192d9e979\\})|(\\{5c3d28a2-b778-4b48-9d71-da6fc2c37ccd\\})|(\\{8ef9fda9-1384-4efa-bbac-02f76f9a75dc\\})|(\\{8fd2e113-76cc-45cd-b4b0-cbef46166d76\\})|(\\{9c914f01-3e9f-45af-8d53-618ce7659d96\\})|(\\{70d353ca-2edb-4196-9294-d80cbca9ae3a\\})|(\\{79fba572-6a81-42be-abf8-92037bd908db\\})|(\\{106c67c5-a780-4076-af33-94945b16d4f1\\})|(\\{177bb2d7-15a6-475a-a34a-94f964b22731\\})|(\\{248d26ad-c8d7-4a1c-8d7e-22a36226f52b\\})|(\\{679b892e-c913-4993-bc18-1b737ac5de8d\\})|(\\{877e11b1-0725-41f9-b0e0-1b89cd88097a\\})|(\\{902b9979-3273-44cd-a717-8c55aec563fe\\})|(\\{3515e081-cd1d-482c-898a-62ade3007f8d\\})|(\\{4080f1c9-b07f-408e-9678-bdda4822f536\\})|(\\{7631efe5-237b-44e3-9193-205346744e31\\})|(\\{8157ad65-a32e-41f3-b99f-5c6f60a82d3e\\})|(\\{9434fbdd-d45c-43ee-98ac-a1c794f89c43\\})|(\\{19976dc8-c059-4058-a7b4-ba734cde798f\\})|(\\{32678e4c-b8df-4a19-91d8-3294b84ce78a\\})|(\\{773450d4-d2d7-48c2-9378-5affb64c4575\\})|(\\{894136b3-8133-432a-b46d-6f528608aa49\\})|(\\{7804268c-4d4f-44c1-a53c-2680e61b6687\\})|(\\{a12fdc00-7623-459d-8188-8e954b6f6eb7\\})|(\\{a46f0ac4-bd96-475d-bba6-2ab01575e06f\\})|(\\{c7dfadc5-8d54-4f00-bc2f-c1ba1483eb41\\})|(\\{cc89ddb5-7f2d-4b9c-bcf6-fc8057869838\\})|(\\{d7c4c15f-e91d-496d-8f78-79809c114a57\\})|(\\{d2925b02-9e1e-4b81-9a72-714a772be945\\})|(\\{dc59a997-f35a-4fa6-9ac0-40c1dce4829b\\})|(\\{def69fda-720f-4e2b-8783-93491939d9a1\\})|(\\{dfbe87b9-0fb6-41fb-a143-f8e0a47452f7\\})|(\\{f778e9f0-ea54-4264-83fb-6783906bce17\\}))$/", + "prefs": [], + "schema": 1559504489486, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1556339", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keylogger Add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e57383a8-d67b-40b9-8df0-ac8a8a6321fb", + "last_modified": 1559555647852 + }, + { + "guid": "@yt-adblock", + "prefs": [], + "schema": 1559145199457, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555403", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "YouTube Adblock" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c34988ff-b712-4123-ad14-11d24d2d3a18", + "last_modified": 1559147777278 + }, + { + "guid": "/^((\\{6f35f413-21ef-4d85-96f7-64b7cccb947b\\})|(\\{7ec94ecf-7215-4cd1-a193-402c3b6e8474\\})|(\\{767e59b0-41da-4cf5-bfe1-56c8402c1ca2\\})|(\\{0770eaac-f694-49a9-bac0-39933e62862a\\})|(\\{5319ec46-b72f-4b1c-90a7-67b1b392af05\\})|(\\{7765a798-ae6b-4ea9-920d-fe7f6d07043a\\})|(\\{93927b7e-0c83-4ce8-b66e-36bb88e2551f\\})|(\\{bd6bd2fb-8614-4302-a67d-bf4f7da55e20\\}))$/", + "prefs": [], + "schema": 1559136970465, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555357", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Add-ons exfiltrating user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a8ca8b1b-1897-45b6-b12a-b64c45d8959e", + "last_modified": 1559139447403 + }, + { + "guid": "{f6bca217-8cdc-4c85-b8ae-30da228dbd71}", + "prefs": [], + "schema": 1559127564301, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555026", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Gmail checker plus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5a110749-4dbf-46c9-a6a8-56c19d6eb346", + "last_modified": 1559136956255 + }, + { + "guid": "/^((\\{cc9f487a-618e-4c48-b0a9-65f25d53c887\\})|(\\{8fcc31d3-f865-40fc-9f31-a38ab9973e9a\\}))$/", + "prefs": [], + "schema": 1559124226003, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555282", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Remote script add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c3025513-0716-4a0b-8a1b-9d5636c90f79", + "last_modified": 1559124311799 + }, + { + "guid": "/^((\\{207df7ea-6d21-4fdb-b4fb-566ae3666245\\})|(\\{e622e0a7-d0c6-4747-bf5f-fe0321da85e6\\})|(\\{8ea990c1-ea81-4aa8-8f0b-ab6ddb888bbc\\})|(\\{1f0f312f-85ae-4603-9761-4dea6a699227\\})|(\\{f9b0e524-7ccc-4392-9130-09a7c84f9730\\})|(\\{f9567a86-accf-4710-bf33-d5ff890416af\\}))$/", + "prefs": [], + "schema": 1559123848301, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555280", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keylogger add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "32891270-8ebe-41b1-b89c-9caf80944c89", + "last_modified": 1559124225988 + }, + { + "guid": "/^((\\{8fad4bef-56e9-4879-8780-ca7c18aa1171\\})|(\\{a6ce9b9f-cea4-44b6-ada5-a96c6bbf6d83\\})|(\\{a14b3807-8409-4b4d-bb16-5d1996492672\\}))$/", + "prefs": [], + "schema": 1559122494410, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1555273", + "why": "This add-on violates Mozilla's add-on policies by collecting ancillary user data.", + "name": "Keylogger malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "57a3e7c7-3cf9-46a5-88ac-3f87029e4b5a", + "last_modified": 1559122823674 + }, + { + "guid": "/^((\\{5d840f65-c476-479b-a2e3-5a19b7a0a853\\})|(\\{021b8ac0-4a36-4294-8261-662d947a83d4\\})|(\\{038b50d5-8590-4479-93d2-4c07b619f402\\})|(\\{882bf6a6-47d7-47c3-8bbf-4f8fb259358d\\})|(\\{0918fee5-aee0-4e84-9613-a8b1e59dfcff\\})|(\\{90854ba5-e748-4f74-b8c2-9a6aa409894c\\})|(\\{a22263da-63d6-44fc-bdb8-381ba7e3c36a\\})|(\\{ec50f1de-0bae-4bfa-b665-59254094089e\\})|(\\{f48a449e-54f7-44fd-90f2-34a9526d5766\\}))$/", + "prefs": [], + "schema": 1559046153546, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554962", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keylogger add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6c8516bd-c427-4a7a-9f52-10c942e083a2", + "last_modified": 1559046242210 + }, + { + "guid": "/^((\\{a43383bc-1d39-4d94-b353-ceaf942c7d52\\})|(\\{0a727455-07c0-4c8b-a0e2-7853347e13e4\\})|(\\{3810f596-bf50-74e2-a47c-9894ebdc5179\\})|(\\{03ce6944-6491-4f7c-ae42-3f2a9c3f9c99\\})|(\\{3523e8cd-09e2-4442-ae13-e1d1575d4b27\\})|(\\{a08f79e0-c70f-4021-80e9-44614d5f8b5a\\})|(\\{e43b625a-f337-4cd6-b3d1-6763b5213223\\})|(\\{744c464d-4cc3-4303-b3d1-5b756144cd5e\\})|(\\{c948603c-496d-426f-a7ff-9af3d7ac1380\\})|(\\{ceaea029-439b-4d0d-99ca-a261de44d0dd\\}))$/", + "prefs": [], + "schema": 1559039806318, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554942", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Keylogger add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "940f09a9-e0ba-4b98-8dd1-edb78573b62f", + "last_modified": 1559039956017 + }, + { + "guid": "/^((\\{d227e621-ef26-4b4d-b724-0138e5bb03dc\\})|(\\{ec418296-8754-46fa-a265-9856f1706f8d\\})|(\\{e64aeb61-251e-46ed-863c-b9a7c4849cfd\\}))$/", + "prefs": [], + "schema": 1559039652168, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554941", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Service Processor and others" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2ea28df4-7fdc-4571-95ba-10b61e270c72", + "last_modified": 1559039806304 + }, + { + "guid": "/^((\\{e7f0881e-39e9-4569-85b5-71b925294de3\\})|(\\{4d19382a-c81d-488a-98ac-f73484a6dd2b\\})|(\\{6d68222e-1982-4b74-9fd1-52b6a4b4c2a5\\}))$/", + "prefs": [], + "schema": 1558986099090, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554675", + "why": "These add-ons violate Mozilla’s add-on policies by executing remote code.", + "name": "Microsoft .NET Framework Helper" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2b5e298d-9dfd-4c66-ae18-1671001f4cf2", + "last_modified": 1559039652155 + }, + { + "guid": "/^((\\{59feb34b-bb64-4063-933b-d5af131da847\\})|(\\{9d84eba6-c1cb-4ec2-8b4b-b6be1ecd902a\\})|(\\{7c98044a-2ab6-481b-bc75-e2e5df6c4de8\\})|(\\{62457714-ded1-44fd-b107-b14da63a2850\\}))$/", + "prefs": [], + "schema": 1558975224053, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554740", + "why": "These add-ons violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Youtube Downloader" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6a5c8e26-d7c8-4c45-91cc-d86bd1aaa0f3", + "last_modified": 1558976967835 + }, + { + "guid": "/^((\\{6f2cec94-2f78-4812-9898-1bf98d7ccbfe\\})|(\\{e8cff71e-5c43-4fd3-b63b-7b9f6c29d54c\\}))$/", + "prefs": [], + "schema": 1558959050284, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554676", + "why": "This add-on violates Mozilla's add-on policies by using a deceiving name and exfiltrating user data.", + "name": "Data exfiltration malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "98aa70f7-d993-490b-ab07-108cc5a8f38c", + "last_modified": 1558967785430 + }, + { + "guid": "/^((\\{b3c1723b-6e69-4a3d-b3f6-90793bbd6500\\})|(\\{ba38e6b7-7480-4e58-9f5b-162e83c6ece8\\})|(\\{ff08056a-a34e-44b1-bcae-805d3770738f\\})|(\\{29806031-d49c-4ef3-a37a-32ee29e5fc0b\\})|(\\{541e33f8-ec74-4311-b7a3-8baa185aeb7e\\})|(\\{d8196867-5419-450c-aee4-1e349b4b0b5f\\})|(\\{ebd7a4e7-056e-4009-bb5e-753c1d1eed93\\})|(\\{01935a63-d565-478a-9f91-9ff9aa49ce61\\})|(\\{d0e7ce73-9829-4a10-b5f2-83f8bf2c730b\\})|(\\{b70f302a-84ad-4f10-8af3-f6ea4aa327fb\\})|(\\{e5f1a2e5-798b-4915-b109-5ebbe2b57821\\})|(\\{7921669d-959a-4992-857d-f47761b5b4ac\\})|(\\{80808d17-bf74-4b91-8fa5-694c3107950d\\})|(\\{84984575-1b73-4b96-ba1c-d9959393e742\\})|(\\{20587256-b018-41c2-91fc-5a278a2837f2\\})|(\\{577f8c9b-c791-4999-9c39-1e4584f4e9d6\\})|(\\{3691584f-c3a3-4fde-8817-b2501802ef54\\})|(\\{e1680c37-e6ff-4280-8f97-943d77e63022\\})|(\\{013ae092-188d-4e95-9138-2b4d98dda7cd\\})|(\\{05e3806c-00e6-40c7-8503-9c30f3db8e63\\})|(\\{9552ab33-c43e-4338-a020-80dc3636f577\\})|(\\{8fd3c74f-57d7-4e1b-9e52-6c4517ef89f0\\})|(\\{9b0ad6aa-7c54-4655-aca5-78e973b0ebd4\\})|(\\{e648ecf7-4858-40f8-9d85-5cc5f68eae6c\\})|(\\{9430fbaf-aa5d-4492-92c2-0f1354c5c860\\})|(\\{d58bd1fd-6451-42d5-b066-4baf7d4271f9\\})|(\\{948790d7-57d3-4db1-8fc7-7ccee4abf047\\})|(\\{1b8d5392-1641-43c1-a6d6-d1429e9d4109\\})|(\\{3ae072ea-3ffc-4395-8f3c-ebe92f355c3d\\})|(\\{32f9b8a8-505a-4621-979b-b6919633f787\\})|(\\{e939e079-1e15-4491-95b3-6fb8a836e80b\\}))$/", + "prefs": [], + "schema": 1558954910531, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554635", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name while providing unwanted functionality. This is not a legitimate Flash Player add-on.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e454fe70-d5d7-40c0-a571-e9253d1361d5", + "last_modified": 1558955090783 + }, + { + "guid": "{19ed30e8-28ad-405a-a7e4-18a8c78b1078}", + "prefs": [], + "schema": 1558951086426, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554624", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "OpTurs" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3a3b8837-605f-48dd-8b40-a66777f45108", + "last_modified": 1558951513292 + }, + { + "guid": "{9834ff7f-e3ea-485a-b861-801a2e33f822}", + "prefs": [], + "schema": 1558813299527, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554465", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "LinkT" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b9496c5f-b37d-4b8e-abaf-509b980ab553", + "last_modified": 1558868765210 + }, + { + "guid": "/^((\\{0e9ab3be-c296-4825-aecd-3923ded051f6\\})|(\\{9f737295-e8d2-4e70-b226-8809f6b135c9\\})|(\\{68e1d557-8fc1-40e0-b197-43f8f3d36239\\})|(\\{90221614-a0b9-4301-b141-3f8a23fb4835\\})|(\\{d3255cb0-bf30-43b0-afd3-db97bfeeede4\\})|(\\{b4498268-c0d0-435c-944e-8dd6e8518654\\})|(\\{93d90a45-a10e-47df-a106-2ffeefe3052a\\})|(\\{d7b04034-ea8b-4219-ad1c-ffa061a2e0cb\\})|(\\{391772ba-a23c-4892-b30d-45d2a935be3c\\})|(\\{0b2aaa98-1f4b-483a-815f-3f864711a737\\})|(\\{2564ed8f-305b-4ade-a787-6fae696c14ab\\})|(\\{fc2fe0a7-9886-4a7e-9850-cccc2879b0e7\\}))$/", + "prefs": [], + "schema": 1558712940017, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554227", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "768bde60-2086-487f-b374-ca3fa6e712fd", + "last_modified": 1558713145915 + }, + { + "guid": "{4ee078c0-ded1-4f82-9bb1-de645e778924}", + "prefs": [], + "schema": 1558712019540, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554097", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Switch" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a0508904-1f0b-4352-a5e6-a33f8fa26ce8", + "last_modified": 1558712846698 + }, + { + "guid": "/^((spp@avast\\.com)|(\\{056790bb-9676-40fb-845a-feae6dedfbee\\})|(\\{15d51e39-3ccf-4ce2-a434-dbbf1785e867\\})|(\\{2bbdf86f-3c6b-48d6-9934-9051ce5f5976\\})|(\\{2f6d1519-33b5-4970-a7ec-561f5e067ba0\\})|(\\{2fd10339-a9db-4846-bdd7-ee41cea97312\\})|(\\{31390faf-ef95-4f4b-a1a4-3c3a09dd7b5a\\})|(\\{411bfbf9-646d-401c-b87d-e77d812a68ce\\})|(\\{44e4b2cf-77ba-4f76-aca7-f3fcbc2dda2f\\})|(\\{5422d0cd-3b45-4fcd-9886-463be7e1a05f\\})|(\\{5ae5a1f8-a994-4e61-8c99-54a9fe58a9c4\\})|(\\{5d4c1f36-196d-4e9a-909b-8ad138546f79\\})|(\\{7150cd87-1b5f-41ea-b659-5cae4b753e2d\\})|(\\{78a02646-2bf6-417e-9498-32f29a4ef89a\\})|(\\{7bdac7a1-be1d-4ecd-8cf1-a1db64adfaaf\\})|(\\{80686e70-c06a-4ab3-b7bf-fd4c05985c1b\\})|(\\{83830f14-c5d0-4546-af99-cbaba3ab832d\\})|(\\{869a5e06-732e-4635-8da3-90a2802f9c80\\})|(\\{87ea875a-396a-4c7b-b202-cecd5a4fe0d4\\})|(\\{94847025-c5a9-4dd7-83df-54c17b79eeb8\\})|(\\{992e4d3d-f56b-4f71-b826-0dd976681228\\})|(\\{a259d36e-9c24-4216-8b28-d3e83c07a832\\})|(\\{a669b31a-3a2b-4c75-838c-a8542f77c79f\\})|(\\{af35bf73-7d25-4286-9be6-fa822818ac82\\})|(\\{b01f0319-b398-4a6e-b9c9-e59e2d99eee7\\})|(\\{c516baf9-a911-453e-be0e-26389cfb33ac\\})|(\\{c88fc74d-31b5-40d4-bb8a-008f2d7a1ea0\\})|(\\{ca6b87f3-2d8b-49ea-9627-95e900c5f108\\})|(\\{cdc01730-6108-4581-b5da-36f7fa8e3d2e\\})|(\\{cfbbd54d-26dd-4f20-b0c9-26b2d920bc04\\})|(\\{d384c2ef-9e42-4dfa-bba5-73b9b6ad2e61\\})|(\\{d7ef08b6-ef77-43b6-ad60-74ea67495674\\})|(\\{dec788dd-9a21-416d-91c7-bf79250cab04\\})|(\\{fb182266-3336-4dcb-8898-859affe73e7f\\})|(\\{fe17e98b-1ed8-45fe-a6e5-8280902d2500\\})|(\\{febfdee8-5724-4aea-8b70-6be9e22248fc\\})|(\\{ff471567-6ff5-48d9-8db6-d2c9134f0aed\\}))$/", + "prefs": [], + "schema": 1558674107244, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1554004", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Keyloggers and Fake Anti-Virus or VPN add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3b36e078-7c88-411c-9366-033ac185c66d", + "last_modified": 1558711200727 + }, + { + "guid": "{e256d52b-d9ae-4709-aa9f-ba4d1eb1b284}", + "prefs": [], + "schema": 1558637428118, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553531", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Reading Cursors" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4b943d8a-828f-45d2-b8e7-f16e6c3f860c", + "last_modified": 1558637573461 + }, + { + "guid": "{7d3c46ed-b9f7-497e-bccc-e6d878032d14}", + "prefs": [], + "schema": 1558636256156, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553746", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Zoom" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1282ea00-9aa1-47c6-9a93-4fc17aa4fcc4", + "last_modified": 1558637428108 + }, + { + "guid": "{cc02a70f-0610-456c-bc5e-5eefb6716904}", + "prefs": [], + "schema": 1558636068339, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553761", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "headingsMap" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "37c5c7d6-e0ce-4c6b-8c89-49d94b6fe159", + "last_modified": 1558636256148 + }, + { + "guid": "/^((\\{5768d1b3-4e2d-4088-bd65-c7a62353ea3a\\})|(\\{65b99c4e-a9bb-4bb9-913d-503fa9bcdc46\\})|(\\{31ebd11b-bb60-403b-94a9-e09a3bf7d64f\\})|(\\{571339cd-9f45-47be-9476-767a62cb6c97\\})|(\\{ed4f9665-1851-4398-ab15-46c5e3ab8fac\\})|(\\{972319b8-8dd8-4ed0-8de2-9bc6582f0560\\})|(\\{4a0d8618-3e21-4bb8-8ae3-d04316b55a1e\\})|(devlopper61@avast\\.com)|(\\{8df3e056-6d4f-42fa-b0ad-40ee9a79d9c4\\})|(\\{e7e68020-07de-4f9f-9aec-6df29e9f64b8\\}))$/", + "prefs": [], + "schema": 1558635731472, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553857", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Easy Screenshot, Youtube Download*" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7f8e7584-6c6c-4579-882a-6f6ed21766dd", + "last_modified": 1558636068330 + }, + { + "guid": "{b19d065e-4460-4714-9208-3f2b38907522}", + "prefs": [], + "schema": 1558537447980, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553521", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "QxSearch" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "60311c2e-044e-4e24-9abe-6ee75d7f5467", + "last_modified": 1558537465968 + }, + { + "guid": "addonfx@horoscope-france.com", + "prefs": [], + "schema": 1558537035045, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553518", + "why": "This add-on violates Mozilla's add-on policies by overriding search setting without user's consent or control and executing remote code.", + "name": "Horoscope France" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ec32be61-2646-4686-9829-7cff21f5d1f8", + "last_modified": 1558537447957 + }, + { + "guid": "{65dc18e1-109f-4039-929b-f8a7a29be090}", + "prefs": [], + "schema": 1558536906311, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553517", + "why": "This add-on violates Mozilla's add-on policies by overriding search setting without user's consent or control.", + "name": "Magnif)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "bf7a8bf0-e60e-4516-9e93-777c19509ef6", + "last_modified": 1558537035028 + }, + { + "guid": "{3fc1db2b-e7db-4512-b24e-1faf4d3a1b4d}", + "prefs": [], + "schema": 1558536030188, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553479", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "quikaxes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "43966df2-e95c-415b-bffc-13814e1d2b11", + "last_modified": 1558536765550 + }, + { + "guid": "{a37a7625-b64e-45f3-8b79-f71634f14438}", + "prefs": [], + "schema": 1558467699805, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553326", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Lift" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7e86024e-5621-4ded-bc16-184f94fa2e29", + "last_modified": 1558528706505 + }, + { + "guid": "/^((\\{3e20d1e2-a7ee-4ce2-ab9c-51c8300a8ff6\\})|(\\{30906bbc-0942-445b-89c8-f74dac0edb8f\\}))$/", + "prefs": [], + "schema": 1558382009200, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1553071", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "Amiri" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9c5f5681-8547-4e65-9c05-5796e483b8e1", + "last_modified": 1558434518612 + }, + { + "guid": "/^((\\{ec19994c-c5a5-46d9-bd4d-0fc417c6f4b8\\})|(\\{a0be7e8d-b0a3-460b-8a52-429c79e49ee2\\})|(\\{1814dd58-4147-4cca-a0a3-c5aa35966d9c\\}))$/", + "prefs": [], + "schema": 1558381075651, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551937", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Inspiring Quotes + Daily Quote Tab + Pug Extension" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "efecef61-549b-4c13-8a52-394c636dd24b", + "last_modified": 1558381320379 + }, + { + "guid": "{dc6176c4-a192-4a92-849f-ad13abe889ad}", + "prefs": [], + "schema": 1558379927394, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551934", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Easy Speedtest" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a5fa8f77-7761-4996-a11d-d8cf723103da", + "last_modified": 1558381015030 + }, + { + "guid": "{ac4be7d1-4db6-4b4c-bf48-e345350bcb59}", + "prefs": [], + "schema": 1558379796996, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552830", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code and exfiltrating user data.", + "name": "Browser type hider" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "41137e55-8a11-4259-a009-42c29daadf17", + "last_modified": 1558379927382 + }, + { + "guid": "{da993d54-9605-42f7-a32f-9f565245070c}", + "prefs": [], + "schema": 1558362750147, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552834", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Fake Adblocker" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2212748f-ad60-497f-af7b-50d20b326e40", + "last_modified": 1558379796981 + }, + { + "guid": "/^((\\{2b3eed60-8f6e-4afc-99f1-38d12f4253da\\})|(\\{3a6f9dac-3a93-4f6f-8b74-9ebc0f501306\\})|(\\{46bba8e9-7a75-4dd6-932e-bdd74141cb87\\})|(\\{4b480ab6-b63a-43f8-b4f4-d312972ab086\\})|(\\{6106687e-ca0c-4d7e-93bc-115929e4d299\\})|(\\{717ce133-3c0a-448e-b6ed-fc7d22b76534\\})|(\\{7224f1ae-c342-4bb5-8441-d324a8951624\\})|(\\{768e9638-2eba-42e4-a13a-4f3f1df391a2\\})|(\\{7b655f43-c871-46d2-8f6d-31989e8ee939\\})|(\\{7e46c692-9488-4671-8c39-7830f92628b0\\})|(\\{83bc6b56-545f-4ba1-a80b-f82d55cc0f68\\})|(\\{970a774e-b7a7-498f-b3f2-d88b14b4dab1\\})|(\\{9d2e8731-3287-46eb-9c19-ece63fe693c7\\})|(\\{a37ccd20-e04f-4949-b055-98ca58639606\\})|(\\{af85f82c-3e8f-4ee5-ab53-b8d3aaac34ec\\})|(\\{b35c6d47-8b07-4d49-89a9-dfe8c10f58f6\\})|(\\{c2485579-368c-4593-a1cd-985b2fa0b990\\})|(\\{c85c16ba-78b4-41b3-9201-f80fa662c52f\\})|(\\{c97e5535-6f2e-4d34-a5a3-0e6e07f7fd13\\})|(\\{ce7db166-9564-482f-91d9-3a450ec3216d\\})|(\\{d64a2c73-ff21-4e3e-998f-ec2dc42ad725\\})|(\\{db6d93c3-67a0-410c-b7bd-f72f267f0cec\\})|(\\{e513775f-359f-47aa-a3d9-eddc946aabe0\\})|(\\{f70258e4-643b-4ec2-9c84-de89009eec61\\})|(\\{f8794e87-82b2-4df4-bce6-db207f62c165\\}))$/", + "prefs": [], + "schema": 1558349836861, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552847", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name while providing unwanted functionality. This is not a legitimate Flash Player add-on.", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0225ae55-626d-42b2-8f48-46ec95ec89f8", + "last_modified": 1558361121954 + }, + { + "guid": "jid1-HfFCNbAsKx6Aow@jetpack", + "prefs": [], + "schema": 1558343683249, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549020", + "why": "Versions up to 2.9.1.0 of this add-on violate Mozilla’s add-on policies by executing remote code through the native messaging host.", + "name": "SConnect" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.9.1.0", + "minVersion": "0" + } + ], + "id": "413065ac-176f-440d-b4a7-0f928f3e330d", + "last_modified": 1558349549359 + }, + { + "guid": "/^((\\{78d3cced-d2a7-46e9-9fea-5be7ed61eea8\\})|(\\{3233777f-a1a7-4ea4-8c2c-fba1a0a68383\\})|(\\{0eb43948-2a3d-4490-b32d-7ca37dd83f07\\})|(\\{64fd625d-2977-46a6-96ca-77f81ebfd54d\\})|(\\{6e138cae-1be3-449e-a964-b3c3060d89b9\\})|(\\{f875c255-8b92-4229-95e1-6d9adaf20dd7\\})|(\\{3c62ef7f-ae8f-4baa-9d2d-27a377480b79\\})|(\\{35a91fe5-c255-498b-9f9f-bec506fdb257\\})|(\\{7d3c52e6-2b7f-4ce8-b28b-032306fe32df\\})|(\\{0ecf6f68-d506-4239-bc69-f77de8f03758\\})|(\\{7290f2b1-3d70-4990-a828-40c775c05f82\\})|(\\{50150580-86bc-460f-ae3a-12e51b9d842e\\})|(\\{a1b46cda-8a83-48e0-b355-7eca4250694f\\})|(\\{614d8f88-b5b4-4897-adc0-0207613f4d4f\\})|(\\{ddc259e9-3738-4b18-a00c-9259dad206ae\\})|(\\{5b2bf836-5322-4161-82dd-fcc8ac6e4247\\})|(\\{97a90c0a-5e3d-47bf-aacc-230e4cb1f2d1\\}))$/", + "prefs": [], + "schema": 1558341490879, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552764", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "Search overriding malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4bda902e-fb36-417b-940f-992ab7a18fde", + "last_modified": 1558343652556 + }, + { + "guid": "{3241efcf-4bfe-4405-ba7e-029d3efb03bf}", + "prefs": [], + "schema": 1558341442463, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552681", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "Neat" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "657d264f-23d2-40f9-bac3-1d7c8b5d8453", + "last_modified": 1558341473815 + }, + { + "guid": "{c4da221b-461a-4ed9-b2d2-6ef1842a94bd}", + "prefs": [], + "schema": 1558341410023, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552639", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "Dimensions" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f48c8fe3-70c4-4d9e-84f4-79aeee9970f0", + "last_modified": 1558341442447 + }, + { + "guid": "{72a6bcef-d0ce-49f5-9773-1b78265174a2}", + "prefs": [], + "schema": 1558341352074, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1552306", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "YtDow" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d4ab989c-1517-493b-9486-f0c4a59a1c95", + "last_modified": 1558341410006 + }, + { + "guid": "{ad6f5b9a-c894-4d15-8c65-4b0f5a29503c}", + "prefs": [], + "schema": 1558341285260, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551964", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "Magnif)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f7135e2e-249f-45d4-bfe3-88b834ddb636", + "last_modified": 1558341352061 + }, + { + "guid": "{ad6f5b9a-c894-4d15-8c65-4b0f5a29503c}", + "prefs": [], + "schema": 1558313782700, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551903", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users consent or control.", + "name": "Top Scroller" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fdc0d028-2915-4130-a19c-c5819b3acc81", + "last_modified": 1558341285245 + }, + { + "guid": "websurf@mizilla.org", + "prefs": [], + "schema": 1557871049351, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1550673", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Websurf" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a2b8447b-ea50-45bb-936a-64d790ebe448", + "last_modified": 1557871228984 + }, + { + "guid": "{d8f707bf-0a35-462f-8e4d-f90205770547}", + "prefs": [], + "schema": 1557870872432, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1550689", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "STPs" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "be42b13c-301b-4304-a1ed-89b4c13d9eb2", + "last_modified": 1557871049337 + }, + { + "guid": "{0b66e692-1991-4b46-89df-c8101925bad1}", + "prefs": [], + "schema": 1557870310029, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551079", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Base6" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a308009d-3320-49af-b0b8-1174f5a25fe6", + "last_modified": 1557870872418 + }, + { + "guid": "/^((\\{198586f6-9fdc-4ce6-8f21-7bdd85eab432\\})|(\\{1cd349e4-1d52-46ec-b648-6da8ba2ef659\\})|(\\{28c26373-1066-4cb5-8e92-1926cb31f83c\\})|(\\{2b99ea1d-9e25-4005-adeb-2fc9fc6700fc\\})|(\\{2be44a2e-f432-4527-a249-f7a6aecc8464\\})|(\\{2eda700f-8674-43fd-842f-73289b6e317f\\})|(\\{3745fc58-1413-4029-aea4-e1aa8a2c0cad\\})|(\\{3c19f6fc-1b86-411c-8d9a-7fdde31600b2\\})|(\\{450f8d34-b065-46a6-bd9f-ee7f614d750b\\})|(\\{498e999a-2d6b-47e7-8da2-97e0f694f6ff\\})|(\\{56862943-b999-45ef-be94-b97211126ba4\\})|(\\{5dc4633d-2c01-4d8d-8980-a90055d0679c\\})|(\\{77101ac4-6fe7-43ed-8362-75ad2a4b3299\\})|(\\{83ae749b-9ab3-41f9-ba8a-f73470399abe\\})|(\\{8ef68e62-a602-477c-95c2-9b861f91e813\\})|(\\{b81c02f0-e563-4794-8fd3-18a65b0f35fe\\})|(\\{c03bf205-6673-4495-abd7-f12556d3d8ce\\})|(\\{d1e8be12-c4e4-481b-9be1-400f54257dfa\\})|(\\{d3f73060-8ca3-4c24-b389-6a896f43f538\\})|(\\{e5e98141-81c0-433d-ade2-4174ea951243\\})|(\\{ec6ff98f-7315-4cfb-88b9-e6a64bb97ef6\\})|(\\{ee765c0e-cf70-426e-ac5d-704c874202af\\})|(\\{f8a4dc88-e967-4c75-acb3-6176ab166bf4\\}))$/", + "prefs": [], + "schema": 1557849229557, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551093", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8436c1bb-43f2-42d6-acec-05145fdbeccf", + "last_modified": 1557870310012 + }, + { + "guid": "{61121092-5257-4607-b16a-12364832f0e4}", + "prefs": [], + "schema": 1557835907235, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551260", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "Page Image Previewer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dbd3690c-2ce4-474b-b13d-97f9ab2c54c5", + "last_modified": 1557835935826 + }, + { + "guid": "/^((\\{2e510835-3d3c-4995-ba75-2eee6ff203c7\\})|(\\{bc72fefd-ab07-40ce-8555-45f9b23ef8c0\\}))$/", + "prefs": [], + "schema": 1557835858186, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551259", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9ced28d2-c4bf-4933-b369-1ded1ca7f6dc", + "last_modified": 1557835907221 + }, + { + "guid": "{4037503e-7401-4ccf-8fc1-af9f8c9fc168}", + "prefs": [], + "schema": 1557835817321, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551243", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "APKCombo" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "abc5acc2-c9ea-4d44-b8ab-3fefb5723194", + "last_modified": 1557835858172 + }, + { + "guid": "{52484281-3051-4c52-9309-83896b989ddf}", + "prefs": [], + "schema": 1557835777799, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551162", + "why": "This add-on violates Mozilla's add-on policy by overriding search settings without user consent or control.", + "name": "Fppl" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "73b95004-eb42-428b-852b-3671edbc3913", + "last_modified": 1557835817308 + }, + { + "guid": "{3555a8e7-6fc1-4ad8-9e35-b09877d94a8c}", + "prefs": [], + "schema": 1557776499103, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1551080", + "why": "This add-on violates Mozilla's add-on policy by overriding search settings without user consent or control and executing remote code.", + "name": "Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a941e00d-4ec8-41db-ac90-75041af68a3b", + "last_modified": 1557835777786 + }, + { + "guid": "/^((\\{da61a3e5-5a98-4c47-ae6c-f4db738f1133\\})|(\\{b0e13c2b-c1cd-426b-bed9-905bf9557fbf\\})|(\\{328c22c5-5f1c-4eb7-95a3-148fd4ad429d\\})|(\\{f6cca5fb-5aa1-471f-88f3-e2ffa87281ef\\})|(\\{d342bf37-554e-41c9-b67b-72769e59b82b\\})|(\\{03ec69b5-3e8e-4bb8-8eda-28f12c54bff8\\})|(\\{a8c876cb-af13-4ad9-9a86-fc3c0722b48c\\})|(\\{56136c32-0159-4368-9d28-c1b8b1515c89\\})|(\\{79bf4660-9729-444b-ae03-6c8005869611\\})|(\\{aa7fdaa5-d888-47e2-b27b-4fa4b3225339\\})|(\\{31e0d180-52b1-4c1d-8f84-7e625715edc4\\})|(\\{f7d20549-e5ee-4045-9e8f-9705bb10c104\\})|(\\{303abacb-760b-43c3-9640-5b456d92db78\\})|(\\{debabd67-2e0a-485e-8213-ac081065a027\\})|(\\{971e739b-c528-41b6-a60c-48fc3cdb52d9\\})|(\\{ffb3a485-2723-4a88-b3ad-8b29773759c4\\})|(\\{b076177a-a5c4-4652-9f6d-953f89f9a81a\\})|(\\{66210cb7-6352-45d5-9d22-ad7a0fb5e247\\})|(\\{8053ad7b-5129-4c74-ade9-8166c38e8636\\})|(\\{1a435c36-133e-4163-ac71-8701a147880c\\})|(\\{8c40c6df-7c9d-4876-bcbe-0621734aba45\\})|(\\{40e1e7d9-ae29-4aec-9465-5e0d49859583\\})|(\\{74eab03b-35cd-4950-b436-7afce3876e58\\})|(\\{95839c11-63a7-4b2b-b3d3-eee9d2c5c42d\\})|(\\{bfaa03c3-744e-48eb-8fb6-4ad61791d4d8\\})|(\\{f123e726-9396-4899-822a-172b8bcb2c5f\\})|(\\{157e255b-2053-4140-b95c-ff003b62bf17\\})|(\\{3e49a17b-b58e-417b-9ebb-a7e8c2317893\\})|(\\{4df1d536-e30f-4344-bee6-6ef2def890c2\\})|(\\{f33ce070-63f4-4d2b-823e-d52fc7a30ba7\\})|(\\{2003e2a5-e848-4fc5-8e7d-3af1efe4f992\\})|(\\{ff2157da-6981-40b6-aa60-d8125e73868e\\})|(\\{d89fa1e5-c9d4-4104-ad8e-00b39e5c6d15\\})|(\\{66e45d14-550f-4489-98c6-8a0caed33375\\})|(\\{86e6d45f-1dfe-4e53-bf52-22bf65b9ae6d\\})|(\\{e71407fe-e1ed-4755-af8f-dd64a952ce1a\\})|(\\{b67b3615-d8fe-4961-a41e-391864afde2d\\})|(\\{5785789b-ccba-44a1-9018-1135b56bd37f\\})|(\\{6dfb93d1-2add-471c-bbbc-b6164b4c1d94\\}))$/", + "prefs": [], + "schema": 1557495790401, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1550581", + "why": "I’ve reviewed the add-on and confirmed that it is collecting ancillary user data, violating our policies.", + "name": "Adobe Flash Players" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fee4b92e-146b-437d-9cc0-95cfc800f0e0", + "last_modified": 1557497630665 + }, + { + "guid": "jid1-NIfFY2CA8fy1tg@jetpack", + "prefs": [], + "schema": 1557437285372, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547789", + "why": "The $rewrite filter functionality allows for remote script injection under certain conditions. Please upgrade to the latest version of Adblock for Firefox to resolve this issue.", + "name": "Adblock for Firefox ($rewrite filter)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.28.0", + "minVersion": "3.19.0" + } + ], + "id": "8ff19ad3-e4e0-40e3-8f02-fd80d18f63b5", + "last_modified": 1557437486195 + }, + { + "guid": "{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}", + "prefs": [], + "schema": 1557437276676, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547789", + "why": "The $rewrite filter functionality allows for remote script injection under certain conditions. Please upgrade to the latest version of Adblock Plus to resolve this issue.", + "name": "Adblock Plus ($rewrite filter)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.5.1", + "minVersion": "3.2" + } + ], + "id": "f0fc8d21-d0ec-4285-82d7-d482dae772bc", + "last_modified": 1557437285359 + }, + { + "guid": "{2b10c1c8-a11f-4bad-fe9c-1c11e82cac42}", + "prefs": [], + "schema": 1557437241208, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547789", + "why": "The $rewrite filter functionality allows for remote script injection under certain conditions. Please upgrade to the latest version of µBlock to resolve this issue.", + "name": "µBlock ($rewrite filter)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "0.9.5.14", + "minVersion": "0.9.5.11" + } + ], + "id": "12a0c69f-e755-428b-97dc-229bccb8a5b0", + "last_modified": 1557437276663 + }, + { + "guid": "/^((\\{4e84c504-10e8-4e75-8885-dcc0c90999b9\\})|(\\{8ce99d6d-8d0d-4420-bd17-c303bd8a763e\\})|(\\{16de314a-56cd-4175-9baf-bbe0b09dfed3\\}))$/", + "prefs": [], + "schema": 1557434135180, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549744", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Secure Privacy + Trustnave + Fastwebnav" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "04a300c2-04fc-401e-a428-c7c887bf2bff", + "last_modified": 1557434278943 + }, + { + "guid": "{5308dcd8-f3c7-4b85-ad66-54a120243594}", + "prefs": [], + "schema": 1557433916783, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1550428", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "F_Feed" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "312e30b0-0b4c-4a43-8f6c-8b8447a20f6a", + "last_modified": 1557434135166 + }, + { + "guid": "/^((\\{c8d0fea0-d7b7-4f6f-b9bc-9df6722d9d18\\})|(\\{bed8e1f2-b00b-44e3-8cf0-5335080d0003\\}))$/", + "prefs": [], + "schema": 1557433212304, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1550435", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Webplus+Fastnav" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b9686c72-1902-4868-88d1-6587fd24a57c", + "last_modified": 1557433916770 + }, + { + "guid": "/^((\\{d389cdfe-843e-44cb-b127-441492e46e63\\})|(\\{1340c760-3f4c-4428-b2c0-88821a84de2b\\})|(\\{38524a16-a73d-4a8f-8111-f9347bb5266c\\}))$/", + "prefs": [], + "schema": 1557258104673, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549740", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Add-ons executing remote code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2feeb46a-6784-4c6e-8c07-e120bec00b14", + "last_modified": 1557394160484 + }, + { + "guid": "/^((adsmin@vietbacsecurity\\.com)|(\\{efdefbd4-5c30-42c3-ad2b-4c49082ec4cd\\})|(\\{63d83b36-a85c-4b51-8f68-8eb6c0ea6922\\})|(\\{4613a1ed-6cb1-410b-a8b1-3f81f73b6e00\\})|(\\{90b1aef7-7a52-4649-b5ca-91b5e81b5eab\\})|(\\{d6e2e76d-edff-416b-8c04-53052ff9fec7\\})|(\\{43af2e0f-b5ce-409b-9ee6-5360785c9b08\\})|(\\{e45fa96d-8b74-4666-86de-3bbfb774a74f\\})|(\\{4f8332b6-6167-4b7f-a1f9-61d8eb89b102\\})|(cpcnbnofbhmpimepokdpmoomejafefhb@chrome-store-foxified-14654081)|(developios89@gmail\\.com)|(\\{d82da356-1fa8-4550-958a-bd2472972314\\})|(\\{1dfbd1c3-a8ca-4eb3-8747-d30bfd20ecd5\\})|(\\{6f9fa22a-128f-4d1b-8ef5-d20a44d24245\\})|(\\{5f6af572-35c1-44d7-9d0f-dffbb62fcafe\\})|(developper@avast\\.com)|(\\{886a6486-37b3-4bcd-891b-fd0e335e7b1a\\})|(\\{886a6486-37b3-4bcd-891b-fd0e355e7b1a\\})|(\\{d1cd26ff-fde7-46a4-85cc-48e3bb7e9e8d\\})|(\\{ae11d5cc-8efb-43a0-89bf-e5a779b4fa40\\})|(\\{aca140ce-8249-4e6e-8e2c-cd5b1c987441\\})|(\\{f68b2ca7-0d2c-44cc-afc8-a606a896c467\\})|(\\{321db3c3-8cfd-49f1-99de-fcdc3485b379\\}))$/", + "prefs": [], + "schema": 1557222463147, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549558", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "More Keyloggers" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b62c9ee1-d66f-4964-906e-2a9b07e3fdc1", + "last_modified": 1557222511299 + }, + { + "guid": "artur.dubovoy@gmail.com", + "prefs": [], + "schema": 1557162612874, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549444", + "why": "This add-on violates Mozilla's policies by executing remote code.", + "name": "Flash Video Downloader" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "16.3.9", + "minVersion": "16.3.5" + } + ], + "id": "d7ca07b4-9c97-4f49-a304-117c874ff073", + "last_modified": 1557162636319 + }, + { + "guid": "{93d460ee-879f-4d8f-8599-a1c69ed59ec2}", + "prefs": [], + "schema": 1556912498785, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1549069", + "why": "This add-on violates Mozilla’s add-on policies by collecting ancillary user data.", + "name": "Browser Security &Adblock" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "95cfb43b-72c3-4fb3-a0f2-fc975aff398d", + "last_modified": 1556977392946 + }, + { + "guid": "{fc4c96b2-4eaa-4221-86a6-392dc1eb919a}", + "prefs": [], + "schema": 1556797012258, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1548536", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "AspectResolver" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3f6acc8a-f46c-42cb-8220-48c2e5885bd3", + "last_modified": 1556808070195 + }, + { + "guid": "/^((premium-enhancer@ext\\.com)|(notif-rm-unlisted@ext\\.com)|(search-updater@ext\\.com)|(updt-lite-unlisted@ext\\.com)|(coldsearch@ext\\.com)|(reader@ext\\.com)|(local@ext\\.com)|(fptool@ext\\.com)|(gflash@ext\\.com)|(advplayer@ext\\.com)|(yfp@ext\\.com)|(ytbenhancer@ext\\.com)|(yoump@ext\\.com)|(floating@ext\\.com)|(ytbhelper@ext\\.com))$/", + "prefs": [], + "schema": 1556792823258, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547311", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "Various fake player/search add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9efe3274-2bd2-44a3-aa7f-92934581470b", + "last_modified": 1556794746654 + }, + { + "guid": "{a38141d9-ef67-4d4b-a9da-e3e4d0b7ba6a}", + "prefs": [], + "schema": 1556787949626, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1548473", + "why": "This add-on violates Mozilla's add-on policies by changing search behavior without users consent or control.", + "name": "ReStyle" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b0ff609b-c98e-4d29-8323-61c3e064ec9c", + "last_modified": 1556791242742 + }, + { + "guid": "{0beedf0b-dc79-48bd-adff-2ed36acdb806}", + "prefs": [], + "schema": 1556787897696, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547930", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "71475499-ca6f-4b71-8bef-2b95cf59ee30", + "last_modified": 1556787931409 + }, + { + "guid": "{c11adb01-56bc-44d6-ac05-6f364e2afe01}", + "prefs": [], + "schema": 1556787838618, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547934", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e6371474-8681-4498-8e89-421f25fd2e12", + "last_modified": 1556787897686 + }, + { + "guid": "{a1f6fa05-26cd-4399-a97a-7996067d04b0}", + "prefs": [], + "schema": 1556739699995, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1548134", + "why": "This add-on violates Mozilla's add-on policies by changing search behavior without users consent or control.", + "name": "TC" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3659d4a2-3121-45cd-b8b6-5b2c96ebc17f", + "last_modified": 1556787838607 + }, + { + "guid": "{c65b18e1-cd3d-4773-a901-15a0753e7d81}", + "prefs": [], + "schema": 1556224830338, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1546994", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "PrincipalInterceptor" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "af62a050-b136-4d56-ab3a-af85a2510bc4", + "last_modified": 1556224874229 + }, + { + "guid": "{4bf110f8-5f50-4a35-b7fa-64228bfa2d0b}", + "prefs": [], + "schema": 1556224790813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547048", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Predicate Property" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5968da82-1d7e-407e-bf93-5d2247ce55c1", + "last_modified": 1556224830329 + }, + { + "guid": "{0bd4e0af-664d-4273-a670-7cb3d0b5a4a5}", + "prefs": [], + "schema": 1556224295744, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1547046", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "ConsumerWatcher" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6a6cc6fb-dce1-49cd-b624-7b44afacf157", + "last_modified": 1556224790803 + }, + { + "guid": "{bbddf452-1a72-4a5d-a833-0416ac7fd76f}", + "prefs": [], + "schema": 1556197615318, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1546989", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "AV Scanner (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "20c25379-aed8-4ab4-9e7f-f2d3f5d948a7", + "last_modified": 1556206274610 + }, + { + "guid": "/^((\\{1601769e-0b0d-4c43-97a7-723ce374996b\\})|(\\{d714118b-5cdd-4829-9299-1066fecc0867\\})|(\\{e8671db6-24be-4744-808c-a63fb744ccca\\}))$/", + "prefs": [], + "schema": 1556133515829, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1545834", + "why": "This add-on violates Mozilla’s add-on policies by overriding search behavior without user consent or control.", + "name": "XPC and Tabs" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1b536121-fd82-4639-bc70-30d7060e42d3", + "last_modified": 1556133806451 + }, + { + "guid": "{3f5f741d-a980-4b58-8552-b1ae328841f4}", + "prefs": [], + "schema": 1556099103820, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1546480", + "why": "This add-on violates Mozilla's add-on policy by intentionally weakening website security and adding fraudulent content to web pages.", + "name": "Google Translate (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ae288e5e-78d5-4b49-8b4d-fa9f25c3baa2", + "last_modified": 1556112119390 + }, + { + "guid": "{3fab603e-3ee1-1222-a859-5f85a3441216}", + "prefs": [], + "schema": 1555527937277, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1545213", + "why": "This add-on violates Mozilla's add-on policy by overriding search behavior without user consent or control.", + "name": "Add security (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "74ad8c8b-a65a-470c-aa2f-ca14e1e8774d", + "last_modified": 1555528639011 + }, + { + "guid": "/^((\\{880cacfe-5793-4346-89ce-fbbd368d394c\\})|(\\{f0780038-50b9-11e9-9c72-4ba2d8f2ec9f\\})|(\\{22ffe411-2b0e-11e9-87f9-c329f1f9c8d2\\})|(\\{cf4bae43-026f-4e7e-a85a-952a7ca697a1\\})|(\\{17052516-09be-11e9-a008-03419f6c8bc6\\})|(\\{333fb3de-18a8-18e8-b6d3-e73213911efb\\})|(\\{aa4abac2-1ffa-12aa-bbdd-9305cb2c1254\\})|(\\{72222e70-2fd6-11e9-956b-27f7787b8d2d\\})|(\\{637212d8-3484-11e9-9812-005056b22b42\\})|(\\{4a222e60-31de-1eca-8476-37565daf6afb\\})|(\\{7fc6d222-48d5-11e9-b586-17e94c73a1b1\\})|(\\{e111c358-121b-13fa-bf23-bb57da32d184\\})|(\\{9674445c-8dff-4580-96b2-99442a7ae9af\\}))$/", + "prefs": [], + "schema": 1555525532852, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1544315", + "why": "This add-on violates Mozilla's add-on policy by executing remote code.", + "name": "Various" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cbd7444f-e62a-4639-b172-845548b6d4a7", + "last_modified": 1555527929174 + }, + { + "guid": "{674fff65-6cd0-488a-9453-fb91fc3d7397}", + "prefs": [], + "schema": 1555406446874, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543924", + "why": "This add-on violates Mozilla’s add-on policies by executing remote code.", + "name": "Assistant" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8aff4cb0-4d5f-4e74-8db7-b04f616c3b60", + "last_modified": 1555503879816 + }, + { + "guid": "{40cd7fd2-a3e4-43f9-9d86-0e0a70376203}", + "prefs": [], + "schema": 1555184501005, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1544268", + "why": "This add-on maliciously injects remote code for execution.", + "name": "Scan Tech" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "881d3476-f18a-4560-b065-cded406783d2", + "last_modified": 1555228377222 + }, + { + "guid": "{8ee8602c-aba6-4e2a-9faa-1724c3f4f9ba}", + "prefs": [], + "schema": 1555102738661, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1544205", + "why": "The add-on is maliciously loading remote code for execution.", + "name": "Service Proccesor" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "94621e2f-28a0-4b18-97c6-5f6203f5912e", + "last_modified": 1555161086175 + }, + { + "guid": "{d8b03707-e39f-4b17-8e56-56354fb14af5}", + "prefs": [], + "schema": 1555100104657, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1544103", + "why": "This add-on maliciously injects scripts, violating our policies.", + "name": "Interruptible Observer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3a921aa8-d44a-4272-be63-0fd102577f59", + "last_modified": 1555100575898 + }, + { + "guid": "{132cb2fd-a6ae-45d2-84cf-b48d591f037d}", + "prefs": [], + "schema": 1555099951278, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543922", + "why": "This add-on injects remote scripts and overrides search behavior without user consent or control. It also masks as an add-on with a different purpose.", + "name": "Player" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "371796e4-387a-4dd0-9ddc-47ba1dd85be7", + "last_modified": 1555100104648 + }, + { + "guid": "H.264.Addon.Test@firefox.com", + "prefs": [], + "schema": 1555099832659, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543919", + "why": "This add-on exfiltrates user data without consent or control and may be misleading the user into believing this is an add-on by Firefox.", + "name": "H.264 Video Codec Fake" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "53ef1aad-7bdb-4f4e-8d46-55d6ec2d78ab", + "last_modified": 1555099951269 + }, + { + "guid": "{608f71eb-5bd6-45d8-bc93-b9e812cf17b7}", + "prefs": [], + "schema": 1555099501294, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543801", + "why": "This add-on maliciously injects remote scripts, violating our policies.", + "name": "Consumer Tech" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ea00841e-8dc2-4e11-9119-7a599e47d800", + "last_modified": 1555099832650 + }, + { + "guid": "/^((\\{8220ccaf-15a4-4f47-a670-a4119a4296a4\\})|(\\{9da72c11-44d7-423c-b19c-c75cd6188c3e\\})|(\\{99d0e4d0-c5ef-4567-b74c-80c5ed21ad99\\}))$/", + "prefs": [], + "schema": 1555011689511, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543848", + "why": "This add-on violates Mozilla's add-on policy by overriding search behavior without user's consent or control.", + "name": "Search overriding add-on" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "03554696-58fe-4b90-89d1-72b72f88f82e", + "last_modified": 1555069748234 + }, + { + "guid": "/^((\\{b37f383f-e60f-4eb1-ac0f-9147e0e8f2f7\\})|(\\{8ad567d2-3fe2-446b-bce9-a3acbe878dba\\}))$/", + "prefs": [], + "schema": 1554997740201, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543673", + "why": "These add-ons are overriding search behavior without user's consent or control.", + "name": "Hash" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "036e2e7d-5403-49be-92cf-b5187ceef7ec", + "last_modified": 1554997910212 + }, + { + "guid": "{be6ab6a9-7004-4c5c-8df9-8d36122d8b14}", + "prefs": [], + "schema": 1554997613727, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543705", + "why": "This add-on injects remote scripts with malicious intent.", + "name": "asin" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6fd3ab94-8e38-47f3-b129-3ca8396d0a22", + "last_modified": 1554997740187 + }, + { + "guid": "/^((\\{61f433aa-45fd-42a9-9c90-c1d7820661d5\\})|(\\{86cd46b6-433a-439c-bff2-722846709f44\\})|(\\{98e126a4-4e70-4300-b893-3b2cca19bc9b\\})|(\\{8f42dc3a-1c46-4fc2-8c7f-dd76a63b1cf7\\})|(\\{a24d3582-2fc2-475c-8440-335736e17c6e\\})|(\\{cf0b5280-cd08-465d-ad7d-70308538f30a\\})|(\\{936f3c79-5dc9-4694-bca8-47932de3357a\\})|(\\{e48d5888-8736-4858-83ba-e816378ffef8\\})|(\\{5305f89c-ceec-4217-8bae-c9c376c7462b\\})|(\\{a2ac47e5-d225-4718-9b57-18a932f87610\\})|(\\{79f60f07-6aee-42cd-9105-c0a52f401316\\}))$/", + "prefs": [], + "schema": 1554981266268, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543664", + "why": "These add-ons exfiltrate user data while masking as a legit add-on.", + "name": "Adobe Flash Player Fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fc38e782-d55b-4fb7-8f9c-374aa18af09c", + "last_modified": 1554997613713 + }, + { + "guid": "/^((\\{0913599d-3094-44a7-8cc2-b8467d5afc7c\\})|(\\{7bee7f1b-d8ad-424d-807d-e69e6634988e\\}))$/", + "prefs": [], + "schema": 1554898779160, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543462", + "why": "This add-on violates Mozilla's policies by exfiltrating user data without their consent or control.", + "name": "Malware exfiltrating user data" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0d86ba71-7baa-4cb3-b3b8-da4ccdfa36b9", + "last_modified": 1554976164809 + }, + { + "guid": "/^((\\{9946bf2f-0aef-4040-bc57-cdae2bde196a\\})|(\\{d511784e-d088-4fce-b77c-14c186f08641\\})|(\\{fe59312a-97bd-4ca7-bce3-b0db95b1e251\\}))$/", + "prefs": [], + "schema": 1554897771015, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543118", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without user consent or control.", + "name": "Invert (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ed01b7e5-73d1-42a6-9fc8-af2d83879854", + "last_modified": 1554898652923 + }, + { + "guid": "{c75432cb-980d-4e64-98c8-d7947b382a2c}", + "prefs": [], + "schema": 1554897109129, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543255", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Concrete Tech" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7fd0eb9c-9f6c-40ea-ba39-645cafb1d5a0", + "last_modified": 1554897390260 + }, + { + "guid": "/^((\\{6a99a9ec-f149-4ad3-b644-15e44290d00c\\})|(\\{cd9d1582-13dc-4ce1-9c83-4aaa31c6bc36\\})|(\\{3414a827-ee54-4331-85eb-736a824bb7e0\\}))$/", + "prefs": [], + "schema": 1554896836686, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543181", + "why": "This add-on violates Mozilla's add-on policies by overriding search behavior without users' consent or control.", + "name": "Drag (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a4f0ac78-ba85-4b5a-9d1f-f3f2c6ae4f7c", + "last_modified": 1554897104624 + }, + { + "guid": "/^((\\{4b6e66db-ee0b-4fc3-abe6-b97cb4798faf\\})|(\\{8aff5f41-86f8-40f1-896d-954eae7fb670\\}))$/", + "prefs": [], + "schema": 1554817097951, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543049", + "why": "Search hijacking", + "name": "Fit" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8a7efe6b-8670-4714-b4b2-08ce5f202ee7", + "last_modified": 1554818136441 + }, + { + "guid": "{81425b21-cc8c-42d0-98e8-69844bcb7404}", + "prefs": [], + "schema": 1554811583185, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543048", + "why": "Remote Script Injection", + "name": "Tech Chip" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dafcc63d-37e0-42e2-b439-59727cf9de48", + "last_modified": 1554811754967 + }, + { + "guid": "{d85aa6ef-639b-43a1-8560-ddeb59935d10}", + "prefs": [], + "schema": 1554803024628, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1543047", + "why": "Remote Script Injection", + "name": "BatchSerialize" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "204b7b05-d8e1-4de3-86f9-fcf95edb40c0", + "last_modified": 1554811583171 + }, + { + "guid": "/^((\\{bf163ed1-e9f9-4c98-ae4b-8391133472d1\\})|(\\{ec283482-2d66-49b2-9dc5-0d03bcbffe65\\})|(\\{0a19856e-4168-4765-a8ab-a3a34fa88ec1\\})|(\\{e2019dd1-4591-42e2-864a-535a99972b1a\\})|(\\{88a65f0c-f952-41f0-8868-f22fa12597b3\\}))$/", + "prefs": [], + "schema": 1554754247858, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540998", + "why": "Exfiltrating user data to a remote site while masking as a different add-on", + "name": "More Flash Player Clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ddd97fae-7040-4758-811f-1dd53116e6ab", + "last_modified": 1554755273775 + }, + { + "guid": "/^((\\{743fc0de-1085-4078-8611-848024989608\\})|(\\{02acc043-f402-4e48-aa15-56ee1364e17c\\})|(\\{1b7892ab-6dbe-49d1-9c71-bbb70458c830\\})|(\\{25b3b5bc-b77a-49d1-ab56-c0e760fe02ff\\})|(\\{2c78aa89-8cdd-4a96-821a-e35628aea2fb\\})|(\\{2f154b99-05c2-4629-b687-f2aa031d9f65\\})|(\\{36df6f78-16c4-42c2-a6b8-9210a2953739\\})|(\\{40ada62f-72a8-46b7-8e50-4153f660ce34\\})|(\\{49f58462-fc24-472c-b85a-4a3dbbf48741\\})|(\\{4a8cb3fd-0400-47b3-a799-9f2964229bfa\\})|(\\{5429f6da-d7fe-4f1b-a85e-6dc721ec0037\\})|(\\{74480b2f-1198-45b3-86b3-ca0778482216\\})|(\\{777f1169-a824-459d-8a2d-ca2ffaf59424\\})|(\\{81e610be-656a-4a71-866d-dd94b5096c60\\})|(\\{81ee3e70-b6e4-44d0-b5c2-94ded26bb5ac\\})|(\\{881c71c1-6800-4e8b-89de-0d14ef67d588\\})|(\\{9b5d7f59-be9c-4f1e-bf0c-31f085c17726\\})|(\\{9eff0ead-25a4-429c-b4b2-280ba3c6f2d9\\})|(\\{ad1b7e87-e260-4aee-a602-ef234743443e\\})|(\\{b54a530a-cacc-4c76-a7c3-feafd4ce0b13\\})|(\\{bd0d7634-770e-4d9f-8309-d264a5fbbfa9\\})|(\\{bdf16cc8-3da6-4317-a1eb-2ab8adce6b66\\})|(\\{bf484a7f-49fd-4681-afa5-8a063d010a14\\})|(\\{c7cf6d86-207b-4231-a96a-bbfdc9fe59aa\\})|(\\{d33f83c2-dbc6-41d2-a8b9-28fdaa96985e\\})|(\\{d71757ee-edc7-44d5-b536-cb0370d7d9f6\\})|(\\{daf86db4-7dd4-47d4-a1d1-7c31f6b9bbe3\\})|(\\{e27060a0-5fb5-4844-b446-d2655d7681fa\\})|(\\{fae0d133-05dd-44e6-88e1-e218ca2b2caf\\})|(\\{fbf69005-55d8-4360-a562-255a8c285fea\\})|(\\{fd6d1b53-89f5-4d91-9234-fb3e1b067c1b\\}))$/", + "prefs": [], + "schema": 1554753973658, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540998", + "why": "Exfiltrating user data to a remote site while masking as a different add-on", + "name": "Adobe Flash fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e12a97c7-2c83-4e1c-a2c3-66a653bc6048", + "last_modified": 1554754247845 + }, + { + "guid": "{ea173fdc-f27a-482a-8a0a-61fd1aa2ee2e}", + "prefs": [], + "schema": 1554744706554, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1541005", + "why": "Masks as a legit add-on and includes a remote script that is against our policies", + "name": "InterpreterInvocation" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8742ec6a-2e51-4e94-bc6a-653dac08521b", + "last_modified": 1554753973644 + }, + { + "guid": "{16768af9-4120-4566-95cf-c4234effa084}", + "prefs": [], + "schema": 1554733900697, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540999", + "why": "This add-on changes the search settings in a way that is not allowed per our policies", + "name": "Unknown search hijacking add-on" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3fb0ed8e-6e5d-489e-8c9d-b6f48705a742", + "last_modified": 1554736392840 + }, + { + "guid": "{35b9640e-ebbb-44b7-85af-d9ec3af3c6a6}", + "prefs": [], + "schema": 1554730607333, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540690", + "why": "Malicious remote script injection", + "name": "Patagonia Bit" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0eb3a151-ca6b-4dbb-81b3-c10635660c84", + "last_modified": 1554733900683 + }, + { + "guid": "{d574e1f8-537d-4b6c-97bb-9f7a138f4d67}", + "prefs": [], + "schema": 1554728269766, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540686", + "why": "Injects remote scripts and masks as a different add-on", + "name": "DistributedConsumer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a158055b-3387-4961-a4a3-a820d9299e15", + "last_modified": 1554730607318 + }, + { + "guid": "one-search@mozzilla.xpi", + "prefs": [], + "schema": 1554666099983, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1542208", + "why": "The add-on violates Mozilla's add-on policies by overriding search behavior without user consent or control.", + "name": "One Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1344c583-9593-412f-a565-c6cc96a07c28", + "last_modified": 1554718843138 + }, + { + "guid": "/^((\\{00b4b65b-79d9-4e92-bc1e-2b926918b91c\\})|(\\{0cb66591-e935-47e4-95c2-3063786f6555\\})|(\\{6cf25884-f86d-4a4e-a924-d95282ce5b71\\})|(\\{22cce9c6-a1de-457f-8938-c981b976b6f4\\})|(\\{89d99d4c-e7c4-4601-91a8-216e597a826b\\})|(\\{998d3ac7-b475-410e-ad3d-2eeb526c1853\\})|(\\{9423e8df-6200-45c0-877a-479c46e91b30\\})|(\\{64937e0b-6e00-4d5f-bf19-190d6614aae2\\})|(\\{91507dc4-c005-4534-80af-d8fbdeac29ed\\})|(\\{a2247e60-7b89-4857-a2fa-0eaee1cad460\\})|(\\{c9c28751-5865-449f-8e45-b3363edf9fb7\\})|(\\{cdfd004f-cddc-4ad7-8e2d-a58457e42b1f\\})|(\\{d3e7f35d-7d9f-4d38-9a2b-1581f6b3e870\\})|(\\{df574ffe-cce0-42db-857b-627cb164a4d4\\})|(\\{e06afe6e-ed52-40f8-82bf-d070a37387fb\\})|(\\{e7e7fb96-cfab-4a5b-85fe-20f621e1bc2e\\})|(\\{e12e5afd-bd1e-43c6-9288-321dc485cb1c\\})|(\\{e92d8545-0396-4808-96de-830c61c0d1b3\\})|(\\{e883b012-1987-4f37-8053-02e59e20c242\\})|(\\{ed3386c6-76c6-4786-a37b-9816d5f2a260\\}))$/", + "prefs": [], + "schema": 1554462951082, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1541662", + "why": "These add-ons violate Mozilla's add-on policies by overriding search preferences without user control or consent.", + "name": "Search overriding malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a1c376fe-20c5-4da3-9126-3fe95b874dce", + "last_modified": 1554463075420 + }, + { + "guid": "/^((\\{6ab41e83-2a91-4c2a-babb-86107a1d1f75\\})|(\\{d84a9d0d-7a31-459e-b45a-2ad111884d1f\\}))$/", + "prefs": [], + "schema": 1554293659259, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1541405", + "why": "This add-on violates Mozilla's add-on policies by overriding search settings without user control or notice.", + "name": "PopWin (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "04b2954e-4f83-4557-968e-2139a277bf1c", + "last_modified": 1554301860877 + }, + { + "guid": "/^((@searchlock-staging-automation)|(@searchlock-automation)|(@searchlock-fx)|(@searchlock-staging)|(jid1-vRJA7N8VwBoiXw@jetpack))$/", + "prefs": [], + "schema": 1554293340413, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540868", + "why": "This add-on violates Mozilla's add-on policies by executing remote code and overriding search preferences.", + "name": "SearchLock" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aacb25e1-71c5-4bee-ad16-e39e732210ba", + "last_modified": 1554293606641 + }, + { + "guid": "{03dfffe0-509f-11e9-aa00-e7e13d49f3de}", + "prefs": [], + "schema": 1554290590697, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540113", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Addon (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9c75fe89-7011-47ad-b213-57f5a81a4c89", + "last_modified": 1554290693618 + }, + { + "guid": "{e555c358-121b-13fa-bf23-bb57da32d184}", + "prefs": [], + "schema": 1554290541557, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540111", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Security (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1809ea7a-8155-4ae7-8c83-ee7c749d30f5", + "last_modified": 1554290590689 + }, + { + "guid": "{a9c33302-4c97-11e9-9a9d-af400df725e1}", + "prefs": [], + "schema": 1554147700324, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539514", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Security (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b4e4e75-cc96-4ca9-aa9f-6a2d2f6cd96a", + "last_modified": 1554290541548 + }, + { + "guid": "{a3f765c3-8dde-4467-ad6e-fd70c3333e50}", + "prefs": [], + "schema": 1554119395186, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538143", + "why": "Remote script injection", + "name": "Angelic Bit" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "93dc42cc-4ff3-460d-a8f2-12f1d947b530", + "last_modified": 1554119427564 + }, + { + "guid": "{91f77263-866e-4acb-a569-f66ac47889f8}", + "prefs": [], + "schema": 1553974898434, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539226", + "why": "Remote script injection", + "name": "Bit Apex" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c528f48a-9b2c-48ca-8b4a-eac442cc0bd0", + "last_modified": 1554119395177 + }, + { + "guid": "{2256fabf-19f1-4e12-9951-5d126dd9e928}", + "prefs": [], + "schema": 1553899022464, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540086", + "why": "Search hijacking", + "name": "Twit" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7c705f73-9d1d-4ee9-ad11-347d18729adb", + "last_modified": 1553900528324 + }, + { + "guid": "/^((\\{00f77164-eca9-4353-916d-8ea493a54c8d\\})|(\\{0716b2a5-8181-45b8-b675-915e38903761\\})|(\\{26124967-7e32-4577-b998-7296c68d3eb9\\})|(\\{273052bc-fc67-4fc1-a6fd-e62acc3ddad1\\})|(\\{4b5f53ac-36ac-4018-80cb-f1106f60ef96\\})|(\\{61065f61-51aa-462c-aca0-f1addbfa202b\\})|(\\{63383006-d617-4a00-9ca7-30a6864782ee\\})|(\\{7629c987-59ea-4e2f-bcde-b55646ecd268\\})|(\\{78e8c8fa-32ce-432b-9a40-b615bff7cd96\\})|(\\{8e9c05df-e0f5-479f-abb9-858650cb471e\\})|(\\{947f1ac0-09f2-4016-a345-dad0d2ee8f57\\})|(\\{9b9f8124-47bc-4699-8557-45573995b0af\\})|(\\{ab159932-d1dd-4d16-9332-8302a01e0ced\\})|(\\{b7340504-f6ba-43cb-8bd6-5ead88d29898\\})|(\\{bcb9265f-20fd-4311-a33f-212c2d08043a\\})|(\\{f8701822-2814-4d5d-af01-cf7fde4fd510\\}))$/", + "prefs": [], + "schema": 1553898687988, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539910", + "why": "Data exfiltration, is not actually a flash player", + "name": "Adobe Flash Player fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "64fc6369-b504-4920-abab-f2cf3cc5424a", + "last_modified": 1553899022456 + }, + { + "guid": "/^((\\{7dd03112-82a0-4c82-9957-117dedaac14a\\})|(\\{59fd3cac-1a4d-4f0f-a129-c241b203eb51\\}))$/", + "prefs": [], + "schema": 1553897736388, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540287", + "why": "Search hijacking", + "name": "Song" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b32b14f5-0024-48fb-a4b6-1496add1dac0", + "last_modified": 1553898687980 + }, + { + "guid": "/^((\\{70c2cef0-6cc6-41b8-ad6b-bbd11182a101\\})|(\\{a0366612-376e-47e3-b5fa-b805c7176088\\}))$/", + "prefs": [], + "schema": 1553810805293, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1540055", + "why": "Search hijacking, masking as legit add-on", + "name": "Pix" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d78262b0-ecfc-475e-9759-f7319451cb43", + "last_modified": 1553847044919 + }, + { + "guid": "/^((\\{10f1b84d-79ca-41d0-97f6-abb53cec0765\\})|(\\{7891c029-0071-4534-b7f0-7288f14ee0ad\\}))$/", + "prefs": [], + "schema": 1553810612956, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538476", + "why": "Remote script injection, search hijacking", + "name": "FX" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "50197dbd-71bc-492f-a0f1-6658ec454df4", + "last_modified": 1553810696456 + }, + { + "guid": "/^((\\{058769c7-784e-47a9-a2c4-dfd81bbf6c8c\\})|(\\{2a58598a-f951-4fb0-af2b-12fb7482bf33\\}))$/", + "prefs": [], + "schema": 1553810234714, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539910", + "why": "Keylogger", + "name": "Fake adobe flash player" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e1355888-e1cd-4d21-9652-c3000662ed88", + "last_modified": 1553810612947 + }, + { + "guid": "/^((\\{54c7e57f-8ef0-48d5-92a0-6e95d193a12c\\})|(\\{32d262da-e3cd-4300-aa0b-c284eb4e17bf\\}))$/", + "prefs": [], + "schema": 1553802101395, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538536", + "why": "Search hijacking, masking as legit add-on", + "name": "TxP" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fa6c7cdb-e477-4478-8dd4-3e1106be6aa3", + "last_modified": 1553810234705 + }, + { + "guid": "/^((\\{36261798-4c2a-4206-89cc-6c28932b2e98\\})|(\\{b2b9bb64-78d5-43ca-b0cf-a9ee8460521b\\}))$/", + "prefs": [], + "schema": 1553616425198, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538234", + "why": "Clone of Feedbro with added remote scripts and search hijacking", + "name": "Feedbro Fake" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e00b2639-8a4f-427a-80d8-7c4937c58f31", + "last_modified": 1553620435398 + }, + { + "guid": "new-tab-search@mozzilla.xpi", + "prefs": [], + "schema": 1553616311575, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538500", + "why": "Search hijacking", + "name": "New Tab Search" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a4dca16a-9fa1-4b55-899c-0f8d5eda1a57", + "last_modified": 1553616386570 + }, + { + "guid": "{a9c33302-4c97-11e9-9a9d-af400df725e3}", + "prefs": [], + "schema": 1553616259023, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538141", + "why": "remote code execution", + "name": "Fake Security add-on" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0c09f067-6e5f-4ee0-9040-08b4297ebe02", + "last_modified": 1553616311567 + }, + { + "guid": "{7ab5c514-4ebe-22e9-a925-9b7c7317c373}", + "prefs": [], + "schema": 1553548654429, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538266", + "why": "remote code injection", + "name": "Eval" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a1f04f09-f4d5-4875-b4b1-a2c772178e8e", + "last_modified": 1553616158755 + }, + { + "guid": "{bc919484-f20e-48e2-a7c8-6642e111abce}", + "prefs": [], + "schema": 1553519202849, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1538962", + "why": "Inserting monetization iframes and masking as a legit add-on. Contains patterns for known malicious add-ons.", + "name": "Pinterest Save Button clone" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7abbecfb-5512-47d1-ba9b-96d6a61b85ee", + "last_modified": 1553548325261 + }, + { + "guid": "/^((\\{157cd8f9-48f0-43a1-9bcf-c4316753e087\\})|(\\{157cd8f9-48f0-43a1-9bcf-c4316753e086\\})|(\\{157cd8f9-48f0-43a1-9bcf-c4316753e088\\}))$/", + "prefs": [], + "schema": 1553186907495, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1537895", + "why": "This add-on violates Mozilla's add-on policies by overriding search settings.", + "name": "SD App (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e263fbec-7155-442f-aa82-cdf218f9e3d7", + "last_modified": 1553193746700 + }, + { + "guid": "/^((\\{1c94bc8a-3ac1-12e1-aae7-0b314772229c\\})|(\\{8a22255c-4737-11e9-a86b-0bb66337cb31\\})|(\\{3fab603e-3ee1-1222-a859-5f85a3441216\\}))$/", + "prefs": [], + "schema": 1553166786114, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535655", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "'Security' add-ons (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6cf1b676-f0b8-4fea-8a5f-64957650dc2e", + "last_modified": 1553172061896 + }, + { + "guid": "{28ac81f1-b04d-448f-94be-1b8cc8fbd58d}", + "prefs": [], + "schema": 1553079961735, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1536513", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "UtilsBridge (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f60b3eec-b8b8-4bd7-8d2b-3f7912c3451f", + "last_modified": 1553080348264 + }, + { + "guid": "/^((\\{9332d73d-7991-46bf-8b67-6db1a21f0167\\})|(\\{b33715d3-eff8-4186-a252-0af5094b8644\\})|(\\{eb7aff78-6145-4a31-a7f5-f3c353ddb312\\})|(\\{6c5cd693-2919-4458-b776-2ac5b6ab1cb0\\})|(\\{daacefee-aaba-4f10-8d4d-059904d8a153\\})|(\\{94d8d504-838c-4392-9971-cd2f6e21ca21\\})|(\\{6574bb31-c971-454f-b08c-a75bfee00855\\})|(\\{1688ecb0-e382-481f-8c70-541d70bdd2e9\\})|(\\{f7b9f777-7b01-4f73-8eb8-f2ad85d4da1c\\})|(\\{598d7ac6-1789-4573-ae6a-5798ed7f6d83\\})|(\\{c0eb4d03-d18e-40bf-b21b-8237ee1bed76\\})|(\\{d0513185-1f20-4045-a232-f3a4252af379\\})|(\\{9ae8269f-eea1-4097-87fd-b7d2f102184d\\})|(\\{5683f95b-2517-4ca7-9d19-83d7f309b62a\\})|(\\{013d3691-0dd6-471b-bf0d-2750d7406a22\\})|(\\{ae73a262-1a27-4d1d-9be7-4b41a84dfd23\\})|(\\{1d92fc5d-5353-401f-8c5f-373b3b6dae67\\})|(\\{e8a81b54-3728-4a9c-8c63-18ef803ef9be\\})|(\\{d604961b-3a3d-4f60-87ae-35977c10b787\\})|(\\{cbe9b620-fac0-407a-b3be-b0a61b319ef8\\})|(\\{1cdb403e-11c7-421b-9c87-0c0d90263626\\})|(\\{f5fa0bfe-a981-48ff-b809-8faa3126f0bc\\})|(\\{7dc6d0d2-b2f0-4334-979d-6ebeff77785a\\})|(\\{13623b47-de82-4226-85f8-d3ae343e619b\\})|(\\{db7b6ea7-2605-44c7-807b-2419d7eec531\\})|(\\{b9298a4a-acca-446d-aa72-d37f5e1576cd\\})|(\\{2e689bc0-735f-445c-bcc7-2cc495f5eb40\\})|(\\{04acd977-4c2b-4162-af33-8c585bea90c5\\})|(\\{2436dde0-3230-4933-9020-c15b3b9e693b\\})|(\\{dcb556aa-ef6e-4778-9f60-c5ae18a72cfb\\})|(\\{5a24385f-ada4-455d-95ad-62cb6256360d\\})|(\\{97f88a13-5b79-4345-a85e-2560d54f577c\\})|(\\{12f4cde8-7d1c-4a9e-9ef7-431f5ecd53a4\\})|(\\{18a93813-7deb-40cf-b3a6-402369e6d817\\})|(\\{9cee5c92-eb1e-4892-86ff-d2d1c627f5b9\\})|(\\{cb1c544e-d444-4c85-8224-64aa26e82230\\})|(\\{1c3b247f-2ef4-4483-93a6-0a3da7bc3548\\})|(\\{1f6913f2-dead-4f96-bf96-0e64affd46ae\\})|(\\{109adc7d-f308-43a5-aa0e-07ccdc5dad2c\\})|(\\{7170e23c-c706-48a7-919f-c1c22548dbfb\\})|(\\{6aa47f05-1f3f-4798-908a-0ed01b2361e0\\})|(\\{33ef0e7b-15ea-4b08-a779-173731ac20b3\\})|(\\{a0361564-9461-4da0-8ec0-7dc6f418f707\\})|(\\{c12631ed-993a-4c2e-9bf0-37867ae40491\\})|(\\{00b79649-3f0e-4b12-a8f0-376a7b2db716\\})|(\\{89096e44-c8b4-4ce5-aad2-f5bac765f608\\})|(\\{6f4eff89-0e32-42bd-a5c1-354adc8417fd\\})|(\\{482c54ae-e080-4596-bf7c-ae972fdff9a3\\})|(\\{04868575-532f-4b43-9325-7e707c109c25\\})|(\\{042c3065-1291-4409-bae5-8d11f3c268e2\\})|(\\{126e7fc4-bf2d-4467-88b1-f3b17bc10da4\\})|(\\{cea21739-b9ce-46c7-ad3e-3607b1ff6538\\})|(\\{06eea1e7-a8be-4794-8cd5-ed12e5f86161\\})|(\\{50993bc5-011c-4322-b522-41e6f3997163\\})|(\\{219a2146-5d9b-472a-8630-4c96a0994bec\\})|(\\{1db94c2f-d957-4b12-a1dc-903bb28f5ff5\\})|(\\{2f7d887c-7d56-41fa-bf9b-eadf6e500427\\})|(\\{2b16f4ab-a2a9-43fd-8fd6-ad6f354b0d28\\})|(\\{034d2b14-29e6-42ad-b2db-2c31286f3fb7\\})|(\\{77058195-5ae1-440c-8f86-c60a96d12ca9\\})|(\\{8082ff2f-2151-4281-8134-1423e5961ca1\\})|(\\{9fa797e8-d7d4-4851-b7e9-76b61ecf046f\\})|(\\{87178fbe-17a5-4d8d-b5ed-48d17179b101\\})|(\\{f010d9e9-0878-4c83-af45-df966cbe8d26\\})|(\\{2aa8c5cd-18fa-4991-b354-d6f459efeca9\\})|(\\{4021839d-3f4a-4866-94fb-9fa809c5245b\\}))$/", + "prefs": [], + "schema": 1553024292304, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535088", + "why": "This add-on violates Mozilla's add-on policies by exfiltration user data and tracking online activities.", + "name": "Search overriding malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "478d4acd-3c01-4dd5-b784-4e06b69d1c05", + "last_modified": 1553079818962 + }, + { + "guid": "{781b89d4-fa53-45a1-bea4-151dd4c8b288}", + "prefs": [], + "schema": 1553013598703, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535280", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Drop Tech (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "81759002-967e-4856-9f55-61d7c30cdb3b", + "last_modified": 1553013656506 + }, + { + "guid": "{3b52063a-0683-4de2-b6e1-6192c78b6ba3}", + "prefs": [], + "schema": 1553013551744, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1536042", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Project Tech (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6a7f932a-3911-4884-8cb9-d282d282c0cc", + "last_modified": 1553013598695 + }, + { + "guid": "{47610aad-982f-4822-93ca-8c27dc96a13b}", + "prefs": [], + "schema": 1552938092086, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534773", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Tech Hand (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "12874e4d-28b5-4e98-8c33-b6cf5eb032bf", + "last_modified": 1553013551736 + }, + { + "guid": "amqp-dwn-all-vd@artur.brown", + "prefs": [], + "schema": 1552916969263, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1536052", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Video Downloader Plus (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a3f5ce2f-d8ef-4dae-9fce-1d7fb69d2b37", + "last_modified": 1552917123606 + }, + { + "guid": "/^((\\{1d758385-fddd-478e-85a0-13eef59f60e5\\})|(\\{1ec3e92a-fd3c-4e16-82e2-56d44bd7bdf4\\})|(\\{3dadda0d-d67c-4452-9475-246c33198192\\})|(\\{4a48f6a8-c9d6-4ae2-8513-a7e9fe785a56\\})|(\\{4d2da172-b160-42b5-9fea-0ede63e0ab52\\})|(\\{5bcd4feb-ce52-4e6f-9da6-eef2a75a4f70\\})|(\\{5eb45d74-0f46-4269-bc0e-8a2a49d64267\\})|(\\{7e8c27c0-b94c-4026-8068-2d152143f073\\})|(\\{9ede19b2-bb97-4d1c-abab-b1d72e7d4c74\\})|(\\{19abb7a0-fb4d-41ff-97d4-65f1680c1348\\})|(\\{25efbdeb-04fa-4998-a9f8-99c1293c7b7f\\})|(\\{0049a401-f02d-4d16-8b5e-5933e5855a4c\\})|(\\{65b91ca5-cd06-42a6-9637-8ecde3a69fd6\\})|(\\{146ec14e-f623-4cb2-88ed-7d3bb8101090\\})|(\\{790d2797-82f3-4bc3-8759-c00d426bbf2f\\})|(\\{865f42b5-e073-4a36-84b1-47d09096b48b\\})|(\\{90055a5b-45a8-45c1-b0a0-979ab2a9064f\\})|(\\{a4f5c163-7b64-46c4-bfd3-348ecc99873a\\})|(\\{a8c40ee7-a376-417b-8022-40909a10181b\\})|(\\{a1031346-14d3-464f-9e50-c30dfd88ad92\\})|(\\{abd16535-2fa8-4bfd-b84e-ed09c9c60e53\\})|(\\{b5ee8c58-b5e5-4ba0-a899-9a54a2f0e386\\})|(\\{b246bb42-577e-4587-adf2-7274b378b0b4\\})|(\\{bb43765b-fe06-4d50-9802-0c6742b220aa\\})|(\\{bf3f628d-9e52-4727-b940-054c65a5a304\\})|(\\{c6bc710d-8cc8-4224-9287-44ecfa452a81\\})|(\\{c232edce-83c9-4184-9782-22df800f65e2\\})|(\\{c5397be4-b756-45b8-a247-339846fada52\\})|(\\{c6675bc5-f7ea-4a11-8252-1152d3783ae3\\})|(\\{cc6a088b-5a84-4e48-8de8-d2f6be3abae7\\})|(\\{e6c12219-f67e-4ea0-a9c3-2c541febeff1\\})|(\\{eb3a7ef7-a4d0-49a4-8b21-2a91c1758100\\})|(\\{ec34588b-86b4-4de3-a3bf-f4d1d8386475\\})|(\\{f4fd8825-648f-4b63-a499-3fd702d42149\\})|(\\{fc4f31f6-c5ed-4afd-8c19-df96e107ce7d\\})|(\\{fe337ef5-bb69-44bf-82a8-ee5c13406165\\})|(\\{ff285a1c-5672-44c3-890e-6c4f25976b83\\}))$/", + "prefs": [], + "schema": 1552908996320, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535421", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "25f18cc5-6ecc-419f-b093-b79e9f261062", + "last_modified": 1552916969252 + }, + { + "guid": "{a4491aab-e273-4bc3-b45e-a7b9b9414a5f}", + "prefs": [], + "schema": 1552695264438, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534792", + "why": "Search takeover not according to policies, masking as a different add-on", + "name": "FFCop" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d2da9c45-59f8-4257-9d7e-07c4fa5de958", + "last_modified": 1552695747900 + }, + { + "guid": "/^((\\{0f6b717d-1625-4434-b770-5ae33eb64b16\\})|(\\{6d092be7-0bad-46af-9489-04e4b3988644\\})|(\\{7f6049d6-e8b0-4c42-8028-204d1458ddb6\\})|(\\{12b75028-c038-40bd-be5b-2809b7d18d78\\})|(\\{46f35a01-faaf-4fab-95e6-7dfc8b6d8b73\\})|(\\{55d2c6f7-62fa-4091-988b-7f4c4b3c1bff\\})|(\\{75aeaeec-d415-404d-84ba-bd70bcc5e70c\\})|(\\{76b8cf24-227d-4e2b-af4c-39ec5b47babf\\})|(\\{77b725cc-5d0e-4b82-88f0-ec6961acd697\\})|(\\{90fc8426-06ba-43ab-8110-7478ff86f531\\})|(\\{90fc8426-06ba-43ab-8110-7478ff86f539\\})|(\\{157cd8f9-48f0-43a1-9bcf-c4316753e084\\})|(\\{157cd8f9-48f0-43a1-9bcf-c4316753e085\\})|(\\{201ec7f7-57b1-48dd-945c-b1ea7489195d\\})|(\\{280fc0f5-6dfb-4a3c-92ae-acb2d5352175\\})|(\\{388f6d65-4a1b-43ac-b791-387882c30599\\})|(\\{0575cabd-38f3-4964-bdc3-0141a2f062e9\\})|(\\{927e4189-4f56-437e-a0d4-5e232612b5c7\\})|(\\{7277d7cf-c598-420b-ab6e-ab066e1e2fdd\\})|(\\{67775ec2-c879-438b-9409-89fba7ffc684\\})|(\\{397386d2-bb76-4b69-8121-86fad15c5216\\})|(\\{bd7f03dc-b362-4744-b118-43ab916205f9\\})|(\\{c133fb29-c967-4aec-953a-4974e8cbdb26\\})|(\\{cc94c8a7-efa3-435c-91fe-ca305f70e39d\\})|(\\{cfd2ff68-6579-4448-8a26-561bdb63877c\\})|(\\{d00f0050-a66c-49fc-9236-1498d4d29f67\\})|(\\{daa287a2-5916-413e-9b61-52c00b5aa061\\})|(\\{dcfac76f-2fd2-4477-9a60-22d167cabcb4\\})|(\\{dd1bbcf4-bff3-4f15-8a2c-3d52ce987f70\\})|(\\{ddb546b5-6490-4af5-8813-8e701bc06e26\\})|(\\{ead6848b-4bd6-4f9a-93bd-b8460c6f6973\\})|(\\{eb8f7a97-ffb0-40f1-9321-5ab1de884f1c\\})|(\\{ec3e8a3d-df39-4f84-ab31-dae369a225e4\\})|(\\{ef986f55-2dc9-4e39-8c87-618cf4fe5e69\\})|(\\{f8b4b601-7917-40c1-94ec-8efbbf125a46\\})|(\\{f8bc456c-0fb4-4d5d-a85f-dfeb25459e76\\})|(\\{f0458469-cc09-407e-a891-be8606553341\\})|(\\{fa73622c-8b41-45b8-9d93-6d66e7633765\\})|(@loveroms)|(loveroms-ash1280@jetpack)|(searchdimension@gmail\\.com))$/", + "prefs": [], + "schema": 1552655172725, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535402", + "why": " This add-on violates Mozilla add-on policies by including abusive search behavior.", + "name": "Add-ons including abusive search behavior" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f9cd41dd-9e52-4506-bb58-a31e189f4ab9", + "last_modified": 1552655392045 + }, + { + "guid": "{b6f5f2d0-1aa3-4e43-b536-6db1b1bf7d1c}", + "prefs": [], + "schema": 1552592498693, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535601", + "why": "This add-on violates Mozilla's add-on policies by exfiltrating user data.", + "name": "FFcanu (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b807d5f-a192-450a-a0b3-98113c4beff1", + "last_modified": 1552655172717 + }, + { + "guid": "{e19fed8c-637a-42e3-b62a-3a6c4040ded8}", + "prefs": [], + "schema": 1552570939014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535345", + "why": "This add-on violates Mozilla's add-policies by executing remote code.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5027a1c1-e050-434f-ba77-56417bc2d7cf", + "last_modified": 1552589019976 + }, + { + "guid": "{fb62e856-f09b-4cbc-ba07-642ab55f6cb4}", + "prefs": [], + "schema": 1552567880022, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534781", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "EncDNA module (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ce66baec-1237-481c-87db-ccc1bcf0359d", + "last_modified": 1552567941331 + }, + { + "guid": "{54fc344c-e8ba-462a-a6d9-9ce1b794ce46}", + "prefs": [], + "schema": 1552567837850, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534817", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4bec4aaf-dd5b-4754-bd01-461fdc7ea5ca", + "last_modified": 1552567880014 + }, + { + "guid": "{7b6def45-d585-431a-a479-5bb2badf2506}", + "prefs": [], + "schema": 1552567781490, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1535055", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "PredicitionR (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "e9227d28-b627-48b8-8392-e9fb5a00d9b6", + "last_modified": 1552567837842 + }, + { + "guid": "{6fb28b6b-abf2-4937-af28-340851faa971}", + "prefs": [], + "schema": 1552567721181, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534769", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "metamedian (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ed853ce8-83e0-42b7-8d93-7f48041d4987", + "last_modified": 1552567781482 + }, + { + "guid": "{ae5b30dd-b29d-4ae6-844b-5d7bfc3d7915}", + "prefs": [], + "schema": 1552567676370, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534807", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "Crypto Valuator (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3cfd9af5-a7d0-49d3-971b-7af5e2eab78f", + "last_modified": 1552567721173 + }, + { + "guid": "web-private@ext.com", + "prefs": [], + "schema": 1552567616148, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534828", + "why": "This add-on violates Mozilla's add-on policies by overriding search preferences.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3d52fe32-71e5-47bd-8eda-d98fa0c489e9", + "last_modified": 1552567676362 + }, + { + "guid": "/^((ads@firefox\\.pl)|(adsfinland@firefox\\.pl)|(adsfrance@firefox\\.pl)|(dodateknowy@o2\\.pl)|(dodateksuper1@firefox\\.pl)|(dodateksuper2@firefox\\.pl)|(dodateksuper3@firefox\\.pl)|(dodateksuper5@firefox\\.pl)|(dodateksuper6@firefox\\.pl)|(dodateksuper@firefox\\.pl)|(test_b@iext\\.pro)|(\\{697be03c-cdd2-430e-b6cf-0f9b5f0556ee\\})|(\\{c9ced03f-a5cf-4dbf-b5ba-67673e442590\\})|(\\{cbe59f66-a23a-45c1-81ac-d0cbedf9ea4e\\})|(\\{dbf0a186-d41c-40ae-8841-e9d8a6b49d8d\\}))$/", + "prefs": [], + "schema": 1552493457658, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534940", + "why": "This add-on violates Mozilla's add-on policies by using a deceiving name and exfiltrating user data.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "67025e3b-860c-4680-949f-ec472cd72fae", + "last_modified": 1552567437766 + }, + { + "guid": "/^((\\{86c18738-11ed-4c16-af92-786aa036c83c\\})|(\\{d0fee16a-f4eb-4dc1-9961-82b913e5943d\\})|(\\{1c4937a1-c678-4607-8665-a16384ee302e\\})|(\\{22caeb02-38a3-415d-b168-64fadccbb4a4\\})|(\\{1c9372e7-5f0e-4541-99cf-dfbf2ab00b01\\})|(\\{9fe66994-8ed1-4317-a20a-1d0544ca062f\\})|(\\{6df222d8-97c7-42bf-9683-1cf8119c1e9e\\})|(\\{4c2dda03-bad0-4160-a8a1-6d089200420e\\})|(\\{7aae7d4f-55b9-42eb-b683-932591265e17\\})|(\\{e6f8ab99-3c96-410c-95d1-267ad48ed3e2\\})|(\\{6d8c5068-d0cb-47a5-af5e-3f23064f4608\\})|(\\{90481f38-d06a-465e-a54c-206bbb1ee9ae\\})|(\\{4b75aeb8-f14a-4ef3-b1ad-09733b40dac3\\})|(\\{3a8ca495-f5ab-4320-b070-4f44266fe3d1\\})|(\\{84f8914f-0dec-48ed-a0fd-4a7712c06793\\})|(\\{aa613fce-603c-41df-bf49-9b09614cebe6\\})|(\\{30314350-199a-4951-9c05-c3537a946492\\})|(\\{a2edce1d-10ab-483d-8c01-5e5fe0c82902\\})|(\\{ec91a3d4-8311-4700-aa15-b3941f21a052\\})|(\\{e9049687-164a-4cf3-be1f-1291cfb0f44a\\})|(\\{2be73925-ebaf-43ca-8b26-bd820887f591\\})|(\\{840eadea-1c68-411f-b4e9-08d9f236385d\\})|(\\{0a89d040-5fb1-46d7-bf81-43b55e83695d\\})|(\\{6a1e76ed-4ac2-4a0c-8beb-43ae63558b36\\})|(\\{1b90c930-e7d7-486a-9085-8b57129489c7\\})|(\\{eab649ca-af76-4de9-95b0-8036e35a66cc\\})|(\\{0628e652-98f4-4e58-9ecb-ad996b061aef\\})|(elfr@geckoaddon\\.org)|(else@geckoaddon\\.org)|(fr_b@iext\\.pro)|(it_b@iext\\.pro)|(sv_b@iext\\.pro)|(no_b1@iext\\.pro)|(fi_b@iext\\.pro)|(au_b@iext\\.pro)|(elfr12@geckoaddon\\.org)|(test@informations\\.to)|(se_pop@informations\\.to)|(it@spongebog\\.funny-ok\\.com)|(it@tsunami\\.funny-ok\\.com)|(fi@spongebog\\.funny-ok\\.com)|(fi@tsunami\\.funny-ok\\.com)|(no@spongebog\\.funny-ok\\.com)|(no@tsunami\\.funny-ok\\.com)|(fr@tsunami\\.funny-ok\\.com)|(fr@spongebog\\.funny-ok\\.com)|(se@tsunami\\.funny-ok\\.com)|(se@spongebog\\.funny-ok\\.com)|(au@spongebog\\.funny-ok\\.com)|(au@tsunami\\.funny-ok\\.com)|(nz@spongebog\\.funny-ok\\.com)|(nz@tsunami\\.funny-ok\\.com)|(gr@spongebog\\.funny-ok\\.com)|(gr@tsunami\\.funny-ok\\.com)|(nz_fnew@tsunami\\.funny-ok\\.com))$/", + "prefs": [], + "schema": 1552320039514, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1534103", + "why": "Stealing cookies, browsing history and other information", + "name": "Rogue Updater add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "b57d9505-21bf-4a24-accb-05ceac50dadc", + "last_modified": 1552323475989 + }, + { + "guid": "{c04d9d7d-1c8c-4eab-a51a-828c47e1b8b7}", + "prefs": [], + "schema": 1552246898392, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1533780", + "why": "This add-on violates Mozilla's add-on policies by executing remote code.", + "name": "asin (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2c739daa-ffee-48d9-a825-e53c8fd2bb3c", + "last_modified": 1552300402314 + }, + { + "guid": "/^((\\{ee2d725e-9726-43ac-8040-60ce9ff2831b\\})|(\\{55417a80-e6f7-4d77-8d73-f59045e5e890\\}))$/", + "prefs": [], + "schema": 1551728497880, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1532269", + "why": "This add-on violates Mozilla's add-on policies by using a deceptive name.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aca80fb4-760e-4cd4-9fec-649fb38b2947", + "last_modified": 1551794995188 + }, + { + "guid": "/^((\\{5084f455-bc8f-483c-b145-91245bcbfd64\\})|(\\{bd69d5d0-4b2f-48cb-bab5-dcf1e0f9c63b\\}))$/", + "prefs": [], + "schema": 1551398716775, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1531416", + "why": "Maliciously collecting form data and cookie modification", + "name": "Adblock/Adobe Flash fakes" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7718be46-8e84-4bc7-a5a9-4c5de18378ee", + "last_modified": 1551399019543 + }, + { + "guid": "/^((\\{6745ccb4-833d-497e-b582-d98a5e790e8c\\})|(\\{cd205ddb-b106-4d2a-a965-5d1c610b5072\\})|(\\{218ec82e-2839-42da-acaa-e527454f4237\\})|(\\{7af25a3d-1caf-49f8-8be9-8ae6065db7c5\\}))$/", + "prefs": [], + "schema": 1551397746059, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530911", + "why": "Search hijacking, remote scripts", + "name": "Emoj1 clones" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "77a32be3-94ce-49c2-b129-fa2562a7f47b", + "last_modified": 1551398716765 + }, + { + "guid": "Youtube-video@Myaddons.com", + "prefs": [], + "schema": 1551397521494, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529887", + "why": "Remote script injection and data exfiltration", + "name": "YouTube Video and MP3 Downloader" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a30f9f2a-aa68-48b7-88cc-8a582405b385", + "last_modified": 1551397746049 + }, + { + "guid": "Youtube-downloader@Myaddons.com", + "prefs": [], + "schema": 1551396963937, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529873", + "why": "Remote script injection, data exfiltration", + "name": "YouTube MP3 Converter" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1d596a69-157f-4743-9465-f86d6452206b", + "last_modified": 1551397521482 + }, + { + "guid": "{ba74c7ee-32b1-11e9-ade5-1f2222a4f325}", + "prefs": [], + "schema": 1551382900634, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529573", + "why": "Remote script injection and user data exfiltration", + "name": "GET Security" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "01825fea-8c5c-4d76-bd06-e1019c188056", + "last_modified": 1551396963926 + }, + { + "guid": "/^((\\{e0686c32-99b4-44d8-972f-88bf08b68f88\\})|(\\{b2225e4c-9d1d-472b-8aeb-5ff203bcff9a\\}))$/", + "prefs": [], + "schema": 1551210091992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530932", + "why": "This add-on is distributed violates Mozilla's add-on policies by being distributed through a fake Firefox update page.", + "name": "Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "37252271-9e38-46a9-b23a-2b6d7048c0db", + "last_modified": 1551250023025 + }, + { + "guid": "{9d7cfde2-39ae-11e9-bde0-02427e2eba50}", + "prefs": [], + "schema": 1551104404768, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1530640", + "why": "This add-on violates Mozilla's add-on policies by including abusive remote functionality, negatively affecting security and performance.", + "name": "Unnamed malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "7bb234b0-cfda-4a23-bf02-9c82fb3500a3", + "last_modified": 1551204284998 + }, + { + "guid": "{4603d01d-ae80-4653-9288-d5ef98b99a17}", + "prefs": [], + "schema": 1551099702949, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529924", + "why": "This add-on violates Mozilla add-on policies by including abusive remote code.", + "name": "IMmailgun (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "32227de6-a7bf-454c-bf44-4478ddd96abe", + "last_modified": 1551099805258 + }, + { + "guid": "{157cd8f9-48f0-43a1-9bcf-c4316753e083}", + "prefs": [], + "schema": 1551037300209, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529926", + "why": "This add-on violates Mozilla add-on policies by including abusive search behavior.", + "name": "SD App (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5b3fe8de-6d05-4d95-a6d2-cd5695f1b0c0", + "last_modified": 1551099426266 + }, + { + "guid": "{5288d05d-253f-4675-be3b-152bf02aa3ec}", + "prefs": [], + "schema": 1550683849233, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529328", + "why": "Remote script injection along with deceptive code to hide the fact", + "name": "Unicode & Emoji Support" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9600b4cd-da02-4947-a4f5-c56c657ba197", + "last_modified": 1550684288501 + }, + { + "guid": "restore.old@youtube.com", + "prefs": [], + "schema": 1550580649249, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1529126", + "why": "This add-ons does remote script injection in a way that attempts to hide the injection. The code being executed runs on all pages which is contrary to what the add-on is described to do.", + "name": "Restore Old Youtube" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "27917953-17fb-4ffc-bcf1-5fc2727174b4", + "last_modified": 1550617105595 + }, + { + "guid": "/^((pohhpifegcbplaijeeonlbkjgocfgjbf@chrome-store-foxified-18573537)|(pohhpifegcbplaijeeonlbkjgocfgjbf@chrome-store-foxified-570932499)|(aackamlchlgmalkmcphbhhcjebbpnfdf@chrome-store-foxified-233164420)|(pohhpifegcbplaijeeonlbkjgocfgjbf@chrome-store-foxified-1808417494)|(nnfhjlgginbfdejaajhbboeemajpjgfp@chrome-store-foxified-699139867)|(dilkapnoekabmkkhhdlpnpfgjcmddlia@chrome-store-foxified-1808417494)|(dilkapnoekabmkkhhdlpnpfgjcmddlia@chrome-store-foxified-1190639722)|(nnfhjlgginbfdejaajhbboeemajpjgfp@chrome-store-foxified-2745518014)|(fibaefnljghpmdibfkhnlaniblfkkndi@chrome-store-foxified-1955364993)|(dilkapnoekabmkkhhdlpnpfgjcmddlia@chrome-store-foxified-1516694386)|(generated-rk4dtanssk56goquir78sz@chrome-store-foxified--1594555229)|(nnfhjlgginbfdejaajhbboeemajpjgfp@chrome-store-foxified-1388315457)|(oaoebpgbkehlcdggaeeohgfpopdhjell@chrome-store-foxified-1823310541)|(fibaefnljghpmdibfkhnlaniblfkkndi@chrome-store-foxified--1031675095)|(ancjheohbkbnkgcmfaldcaepoeeplkgh@chrome-store-foxified-1823310541)|(generated-lwcbpuj27wcknyyl6pkap7@chrome-store-foxified-1823310541)|(generated-bmtr2hbgikv399aj6aeycd@chrome-store-foxified-1823310541)|(generated-8w9odie82h2ugbsrofooj3@chrome-store-foxified-1823310541)|(\\{4170faaa-ee87-4a0e-b57a-1aec49282887\\})|(\\{b66dd4bc-7f5e-41d9-bc43-84c5736d0c87\\})|(\\{507ad1fb-08ae-43ac-a596-fd5b6e7dea9a\\})|(\\{8bf85207-66df-4eb7-b913-ac162498c687\\})|(\\{b063895a-8077-452d-b049-ebc20a39f882\\})|(\\{5776bd90-3d09-43b5-a769-8da373e9820f\\})|(\\{f6bdce44-3d5a-4f88-9a64-e39fcb4f0717\\})|(\\{1c22e687-6a02-440c-a6d5-c1ccaf520e10\\})|(\\{f7be824f-7287-466a-8590-2f48007a223b\\})|(\\{89ffc0b4-97f7-447c-bd6f-9c519b0188bd\\})|(\\{3a67084b-c231-4b4b-a924-ffa741f90921\\})|(\\{fac8b1d0-f321-491d-9234-c40d89b3660d\\})|(\\{a8053a83-8d1a-4f0e-9f88-d31cfe00cf83\\})|(\\{d10fa57e-37d3-43d3-39f8-e6d5b2a7759d\\})|(savetube_downloader@savetube\\.co)|(\\{95a8a08c-53a8-7e1d-5a80-f1a5cd4751bf\\})|(\\{5037bd74-c23b-4bbf-8865-6b5a09e07342\\})|(\\{830c558c-70f0-4b07-95f1-8e06ad34ee2c\\})|(\\{e4d93c37-1299-452f-9b60-adee15ad3802\\})|(googlemaps@search))$/", + "prefs": [], + "schema": 1550502645324, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1528761", + "why": "Malicious add-ons injecting remote scripts and exfiltrating data for monetization purposes. Some of the add-ons are masking themselves as legit add-ons and using various obfuscation tactics to hide their injections.", + "name": "Rogue Youtube downloaders and related add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f2483d7d-1895-4ae6-9901-7321e59062a6", + "last_modified": 1550502978653 + }, + { + "guid": "/^((((generated-e0yf8zkhypow3jjifkqzwo)|(iiinclagpealgnaglbmkdbfbgchjjbpg)|(jaehkpjddfdgiiefcnhahapilbejohhj))@chrome-store-foxified--?\\d+)|(jid1-9ETkKdBARv7Iww@jetpack)|(\\{813fe658-5a29-4dcc-ba6c-3941676e4f19\\}))$/", + "prefs": [], + "schema": 1550254444783, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1528308", + "why": "This add-on violates Mozilla's add-on policies by tracking user behavior and including remote code.", + "name": "BuyHatke Best Price Comparison, Graph, Coupons (and similar)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d6b2dbad-31e9-4984-b649-19036cd38e1d", + "last_modified": 1550265430182 + }, + { + "guid": "/^((odnenajaeicndaeiapagpglohiklndhe@chrome-store-foxified-2174031944)|(akdibckdjeocfbcjaikipkhhbggcbgkg@chrome-store-foxified-699805590)|(\\{bd9f5830-bf8c-4e38-933d-09f85b24d744\\})|(youtube-downloader@addoncrop\\.com)|(dailymotion-downloader@addoncrop\\.com)|(youtube-mp3-iframe-downloader@addoncrop\\.com)|(youtube-downloader-lite@addoncrop\\.com)|(dailymotion-downloader-lite@addoncrop\\.com)|(generated-p5qj07ef5ceqfktp5v8whw@chrome-store-foxified--505607522)|(generated-tuag2j5gwq73xzp8fbw3j8@chrome-store-foxified--505607522)|(generated-uj53d5j612ap5pz11hwgv2@chrome-store-foxified--505607522)|(\\{f51f0244-bfb0-40ce-bf9d-af49f5949e61\\})|(\\{81751484-2dc9-47bf-aec3-b8e556237178\\})|(\\{d89d1e20-57af-4164-82cc-650af45cf0a5\\})|(\\{a5e28713-14c3-4967-befe-2ec253e317cd\\})|(\\{335ac35b-6c16-4304-93f0-64a418e5bf81\\})|(\\{d3fdb429-ef84-40a4-b160-010745ee0098\\})|(\\{d66db057-7d38-4df4-a0ba-63c272be25ee\\})|(\\{ff7f7151-09e3-4e35-9052-b21e23e7e2c1\\})|(\\{1479da02-e759-4a6f-8f62-c08096583806\\})|(\\{42d59cda-c117-459e-b244-1b850c27ccc9\\})|(\\{bec48f33-7869-4158-8cbc-5cf1606f9afa\\})|(\\{08ca656c-4973-46a8-85a9-3d771399c86e\\})|(\\{dc5336c8-5624-4f74-a794-bb15f3760f34\\})|(\\{6a3d97de-1b42-4624-880a-1a68bc917142\\})|(\\{ad965166-3a34-449d-8230-a636fb5cae57\\})|(video-view@vv\\.us)|(video-view@excoe\\.com)|(xeco@rama\\.com)|(ghj@brem\\.com)|(fgtr@rembgr\\.com)|(xero@saltam\\.com)|(sora@remjem\\.com)|(repo@saram\\.com)|(tora@empoytr\\.net)|(saving@ropbart\\.com)|(japa@vbgt\\.com)|(rtya@nop\\.net)|(asan@khazan\\.com)|(xerb@tangot\\.com)|(audiotools@ramdeen\\.com)|(webat@extrolm\\.com)|(media@medplus\\.com)|(audio@audequl\\.com)|(control@medcontrols\\.com)|(sabqo@rimjim\\.com)|(repto@secroa\\.com)|(meters@videobf\\.com)|(betro@diskra\\.com)|(codeca@vxmrop\\.com)|(revoca@feronx\\.com)|(brota@vidbrghta\\.com))$/", + "prefs": [], + "schema": 1550173301925, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1527603", + "why": "This add-on violates Mozilla's add-on policy by including malicious remote code.", + "name": "Various add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d94f7e0f-7088-43c9-a8da-eae102781079", + "last_modified": 1550253583075 + }, + { + "guid": "superzoom@funnerapps.com", + "prefs": [], + "schema": 1549578756953, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1525405", + "why": "This add-on includes abusive functionality including violating the user's security and privacy.", + "name": "SuperZoom (Abusive)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f7c4b329-7a54-48d3-9494-81783fe99d90", + "last_modified": 1549627400713 + }, + { + "guid": "/^((addon@firefox-updater\\.com)|(downloader@youtube-download\\.org)|(downloader2@youtube-download\\.org)|(downloader1@youtube-download\\.org))$/", + "prefs": [], + "schema": 1549369087422, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1525153", + "why": "These add-ons include abusive behavior.", + "name": "\"FF Manual Update (Required)\" and \"Easy Youtube Video Downloader Express\" (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6b72e942-af54-469c-b9f2-9e436ad2c0d1", + "last_modified": 1549373215201 + }, + { + "guid": "/^((\\{0b347362-773f-4527-a006-f96e9db437e5\\})|(\\{9edb10ad-67af-4ad0-af45-efe452479321\\})|(\\{202e2671-6153-450d-bc66-5e67cee3603f\\}))$/", + "prefs": [], + "schema": 1548963700621, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1524357", + "why": "This add-on includes hidden abusive functionality.", + "name": "Video download add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "680a99e9-c972-4a14-9b15-e56eeeed75eb", + "last_modified": 1549037404012 + }, + { + "guid": "/^((\\{a9bc520e-68ab-49c2-a8df-75a0349d54fd\\})|(\\{bfc5d009-c6bd-4526-92ce-a9d27102f7f2\\}))$/", + "prefs": [], + "schema": 1548699141208, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1522959", + "why": "Add-ons that contain abusive functionality.", + "name": "Unnamed (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "97c4ee31-4009-40de-ae02-f1b349c87d01", + "last_modified": 1548699177099 + }, + { + "guid": "{4e47160d-ec83-417c-ab01-cda978378d9e}", + "prefs": [], + "schema": 1548699076839, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1522970", + "why": "The add-on contains abusive functionality.", + "name": "mlflow (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "62c54642-73ab-4641-b5c2-47e4ae29bbc5", + "last_modified": 1548699141199 + }, + { + "guid": "/^((\\{feff5ea4-ed4a-46a3-9331-12fec01d52a9\\})|(\\{8ffc339c-0ca1-46e3-acb3-3bfd889f9a21\\})|(\\{1fe481b5-baad-44e9-b410-082cf0f2acbb\\})|(\\{92c85192-b325-4599-82e2-a110f193eae6\\})|(\\{5bc21266-26f1-469a-bc1a-a49d7b70ecb9\\})|(\\{dc2dd143-f2e7-4f46-a8ff-4dc1a985e889\\})|(\\{c1233bb6-31a9-4c7d-8469-f8f44adee9ba\\})|(\\{d0d48905-1065-43dc-ab96-434d100602ed\\})|(\\{11deae99-2675-4d5e-86cd-7bd55b88daf2\\})|(\\{a7014e8e-eacf-4ba0-9047-c897c4ed3387\\})|(\\{b9c545a5-0ffa-490a-8071-2fee09478cfe\\})|(\\{39e8eebb-4d9e-4a03-93a8-4468fdd7a186\\})|(\\{8ccc00b1-2010-4155-a07c-f4d7c4d6dec2\\})|(\\{a1056511-4919-43b7-a9e5-ac2b770de810\\})|(\\{90a6da19-9165-44c1-819c-e3b53409f9c9\\})|(\\{e3862078-8f9f-4f8e-99dc-55ba558f0619\\})|(\\{d89fcf34-2615-4efc-a267-1e83ab6a19d0\\})|(\\{588151ce-eab4-4acf-83a7-bb5ccaf4d867\\})|(\\{6ab6312d-5fd4-42a9-ab10-08b954e53f9d\\})|(\\{24a6cbde-be68-4b7d-9f1b-d4d5dfd178a3\\})|(\\{55ae1a08-105f-4f7f-9d4e-e448b517db2b\\})|(\\{74fe9d83-df17-4812-bd3f-27b84b0086b7\\})|(\\{21140e51-713a-4bf8-865b-e2ee07282409\\})|(\\{24f39610-2958-4dc8-a73b-75cc9665bffa\\})|(\\{c50a45a5-efa4-44af-8946-6f20e4748d47\\})|(\\{41d0b7e0-0d93-4f67-bed9-da6d7688ad16\\})|(\\{c2bee222-2163-4c0f-89f5-4ac502f06810\\})|(\\{4be4602e-2b20-473f-8f2b-85e8c53d17dc\\})|(\\{dec2e9b5-e787-4fb5-a7bc-5894f80f7367\\})|(\\{179526e1-1824-49f7-afb3-49fdaadaa503\\})|(\\{4f07d826-ca9e-4370-a508-b984f54758de\\})|(\\{d0558df2-714f-4793-9d85-d2d648de4f2e\\})|(\\{daf1a69b-f47b-4936-bd25-5ac21f4e27ec\\}))$/", + "prefs": [], + "schema": 1548099697813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521975", + "why": "Remote script injection and deceptive tactics to hide the fact", + "name": "ext-affiliate add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "87c17ce6-aaef-4d47-a662-588efff34041", + "last_modified": 1548198338831 + }, + { + "guid": "hlper@xero.com", + "prefs": [], + "schema": 1548076840649, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521533", + "why": "This add-on executes abusive remote code.", + "name": "Av Player Helper (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1e2ae4c0-66cd-40bc-9cf6-5ca0ce9548f7", + "last_modified": 1548084072622 + }, + { + "guid": "/^((xtera@soravem\\.net)|(nozl@ssave\\.net))$/", + "prefs": [], + "schema": 1547926889113, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521429", + "why": "This add-on injects abusive remote code.", + "name": "Video Assist (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c69997df-0b61-4de5-a351-b640123a9c3b", + "last_modified": 1548073537411 + }, + { + "guid": "/^((\\{94f608b3-76b6-4b7b-8cef-7360df22a930\\})|(\\{9648b74f-35ea-4218-acf0-ec2191f509f6\\}))$/", + "prefs": [], + "schema": 1547754101798, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1520813", + "why": "Add-ons that leak private user data.", + "name": "Instagram Register and Google Register (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a15e8d7e-0726-4190-8187-c75e2b46d429", + "last_modified": 1547810271416 + }, + { + "guid": "reopen@closedtab.com", + "prefs": [], + "schema": 1547067530457, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1518847", + "why": "This add-on contains unwanted abusive behavior unrelated to the add-ons functionality.", + "name": "Reopen Closed Tabs (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "451d950f-ca89-491b-87e7-45123e4f5ab4", + "last_modified": 1547206877909 + }, + { + "guid": "{43ecded1-f7cb-4bb6-a03d-4bec23b9f22d}", + "prefs": [], + "schema": 1547025691087, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1518580", + "why": "This add-on violates Mozilla's policy and user's security/privacy by loading abusive code from remote.", + "name": "lamme (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "06c6868e-055f-4e7d-aa8f-5ba577f43e85", + "last_modified": 1547027153061 + }, + { + "guid": "youtube_downloader@addon.partners", + "prefs": [], + "schema": 1546890104853, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1518032", + "why": "This add-on contains unwanted abusive functionality.", + "name": "YouTube Download Tool HD (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5a42c5bb-9cb4-4d96-b978-8d9f816322e6", + "last_modified": 1547025691078 + }, + { + "guid": "/^((\\{00cf6ee0-14f3-4e35-a4fd-d2160fe2f05e\\})|(\\{0da583da-e623-41f2-b2d2-0ac61b493171\\})|(\\{105c14a6-8b6f-49ef-b0d6-41bad99ad5e8\\})|(\\{10a15a74-271f-4098-a662-bd827db4f8bc\\})|(\\{13e02b9b-2797-4100-8144-65b73c4145c4\\})|(\\{1eb56568-8a30-42b1-a646-ad9f485f60fe\\})|(\\{1eb8a08c-82a8-4d1d-8b80-f7b5cd4751bf\\})|(\\{2f8220a8-b2a7-4277-ba6b-bdcb6958f669\\})|(\\{33f39a5d-137c-4757-9f9d-e86395c8bf20\\})|(\\{347ca189-9e63-43d2-8a2f-5d5141598bdc\\})|(\\{396056fc-1697-4954-b535-06de8d62fe1b\\})|(\\{3d530918-dbe8-442c-8faf-1f4ca7ca8ab9\\})|(\\{3e283c2e-cde3-4baa-8076-226ca8fb90ef\\})|(\\{591468f8-ebbd-497a-92f1-fa0a1206adb4\\})|(\\{5f6c3eb8-aa32-489a-bb01-b12b23d2001a\\})|(\\{6cbb397a-d865-42b2-8454-25a75b710dff\\})|(\\{7ae2bde0-f7ea-4bf3-a055-06953f9fcf31\\})|(\\{7b402578-ddec-4eee-9c8b-98e4e8db0059\\})|(\\{7fb00cf7-40d3-4415-a0c8-a48d3fbe847f\\})|(\\{87a8a08c-82a8-4d1d-8b80-f7b5cd4751bf\\})|(\\{888220a8-b2a7-4277-ba6b-bdcb6958f669\\})|(\\{8b1dd8f3-224b-4975-bda2-cb2dd184d4d8\\})|(\\{8bcdc9cc-f6be-4203-ae43-a9d281f0bcdb\\})|(\\{8cda9ce6-7893-4f47-ac70-a65215cec288\\})|(\\{8dc9b946-0bb9-4264-9c76-fd9ff1e159a2\\})|(\\{942e0fec-19f2-4ebc-8a74-009da7fa625d\\})|(\\{b2a720a8-b2a7-4277-aa6b-bdeb6958f669\\})|(\\{bdcf953b-d2aa-4e7a-8176-aeb1e95a0caf\\})|(\\{cae82615-f7be-4aff-875d-33da1bc93923\\})|(\\{d2aa953b-bdcf-4f7a-8476-ddb1e9500caf\\})|(\\{da0fa57e-17d3-40d3-99f8-e9d5b2a7759d\\})|(\\{da1237ca-e35d-4653-b2b5-d98043f33781\\})|(\\{e164563a-1512-4b81-99ff-95f2644c4075\\})|(\\{e2a720a8-b3a7-1277-aa2b-bdeb2958f669\\})|(\\{e6a90490-6ef7-407d-863a-7dd120f6f7dc\\})|(\\{f15cfa53-fa9b-43cf-84e8-16ece6695922\\})|(\\{f722c845-2d8b-4a0a-b518-4f39af703e79\\})|(\\{ff1c4e62-7c11-4ea7-b734-3462417ceeb5\\})|(\\{ffa0a57e-17d2-41d3-96f8-e8d5b2a0759d\\}))$/", + "prefs": [], + "schema": 1546378806655, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1517154", + "why": "Executing remote code containing coin mining and other undisclosed monetization", + "name": "Various remote iframe add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "53c5fb08-1001-471e-87ce-31185a84bcbc", + "last_modified": 1546439268437 + }, + { + "guid": "{02267dc4-36f2-4c22-8103-9e26477b48ef}", + "prefs": [], + "schema": 1545922885656, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1516539", + "why": "Google Search hijacking and affiliate injection", + "name": "Markdown" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "6dd73da4-cb2f-4eb8-8850-890e80c8d57b", + "last_modified": 1545926512914 + }, + { + "guid": "{d064563a-1542-4b8b-99ff-95f1644c4075}", + "prefs": [], + "schema": 1545921144932, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1516488", + "why": "Obfuscated remote script injection stealing data", + "name": "PDF Print and Save" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "875bc1c6-257e-438a-8624-3bfe963747b0", + "last_modified": 1545922824252 + }, + { + "guid": "/^((\\{83768008-e10c-48c0-b303-5a0f1de763a1\\})|(\\{430b0612-bfad-463b-8783-cf2e32801513\\})|(\\{519adb83-4abb-4a66-8e94-243754b8adce\\})|(\\{228a707d-55c1-465b-a759-a2129eb6602e\\}))$/", + "prefs": [], + "schema": 1545853297809, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1516131", + "why": "Remote script injection and data exfiltration", + "name": "Various malicious remote script injection add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c0cb1a85-c6c6-453e-b126-0e6e26ceaf88", + "last_modified": 1545870108716 + }, + { + "guid": "/^((\\{4c4ceb83-f3f1-ad73-bfe0-259a371ed872\\})|(\\{a941b5ab-8894-41e1-a2ca-c5a6e2769c5f\\})|(\\{56488007-bd74-4702-9b6d-aee8f6cc05ea\\})|(\\{9eebac07-ac86-4be7-928f-e1015f858eee\\})|(\\{5a993517-5be7-480e-a86c-b8e8109fa774\\})|(\\{309ad78e-efff-43cf-910c-76361c536b20\\})|(\\{cefcf45b-dfec-4072-9ffc-317094c69c28\\})|(\\{5b04980b-25e9-4bc6-b6ea-02c58d86cc5e\\})|(\\{0021a844-360f-480e-ac53-47391b7b17b4\\})|(\\{2bed9f51-62a8-4471-b23c-827e6727c794\\})|(\\{7d2130d3-d724-4f58-b6b7-8537a9e09d4c\\})|(\\{ccd3847a-e5ec-4d28-bf98-340230dcbd4d\\})|(\\{83716b9b-6e6e-4471-af76-2d846f5804f3\\})|(\\{5154c03a-4bfc-4b13-86a9-0581a7d8c26d\\})|(\\{24f51c5c-e3f5-4667-bd6c-0be4f6ef5cc2\\})|(\\{73554774-4390-4b00-a5b9-84e8e06d6f3c\\})|(\\{c70cfd12-6dc3-4021-97f2-68057b3b759b\\})|(\\{ef5fe17b-eb6a-4e5e-9c18-9d423525bbbd\\})|(\\{461eb9b4-953c-4412-998e-9452a7cb42e0\\})|(\\{966b00fe-40b0-4d4b-8fde-6deca31c577b\\})|(\\{dab908ac-e1b0-4d7e-bc2e-86a15f37621f\\})|(\\{01a067d3-7bfa-44ac-8da7-2474a0114a7e\\})|(\\{6126261f-d025-4254-a1db-068a48113b11\\})|(\\{6c80453f-05ec-4243-bb71-e1aac5e59cae\\})|(\\{f94ec34b-5590-4518-8546-c1c3a94a5731\\})|(\\{5d4c049e-7433-485a-ac62-dd6e41af1a73\\})|(\\{507f643d-6db8-47fe-af9c-7a7b85a86d83\\})|(\\{5c56eeb4-f97c-4b0d-a72f-8e639fbaf295\\})|(\\{2ef98f55-1e26-40d3-a113-a004618a772e\\})|(\\{77d58874-d516-4b00-b68a-2d987ef83ec5\\})|(\\{7a0755d3-3ba2-4b19-98ce-efcdc36423fc\\})|(\\{47ee3ba1-8974-4f71-b8a4-8033d8c2155f\\})|(\\{a477f774-bc36-4cc8-85bd-99f6b04ea255\\})|(\\{1a2e41e3-4343-4a00-90cd-ce77ac77a8af\\})|(\\{7b180e9a-afd6-4693-94a1-c7b5ed9b46fa\\})|(\\{51f76862-f222-414d-8724-6063f61bbabf\\})|(\\{d47a0c63-ac4c-48ce-8fc7-c5abc81d7f75\\})|(\\{b8adf653-f262-413c-b955-100213b105ad\\})|(\\{ccedf35b-dfd6-417a-80de-fb432948861d\\})|(\\{70e29b0e-7cd8-40df-b560-cf6eb066350d\\})|(\\{9926f8ad-b4c3-4122-a033-1b8a5db416db\\})|(\\{62eefb1c-a2d8-40ba-ab94-9fc2f2d31b2f\\})|(\\{17f14919-00bd-44a4-8c14-78ab9728038f\\})|(\\{20e36a3e-672c-4448-9efb-5750cbffe90c\\})|(\\{6070c95f-6460-4ffd-9846-2bbd7238697f\\})|(\\{1edb8a4e-f105-4623-9e19-e40fb082b132\\})|(\\{223a1503-772d-45eb-8cb8-e2e49563703d\\})|(\\{59e0f01c-1f70-445c-a572-7be5d85549bd\\})|(\\{8ec160b7-1089-4944-a999-a1d6afa71c5d\\})|(\\{d2d111d6-0ea1-4880-ae7b-2e82dff3a719\\})|(\\{cfacacd6-191c-46c4-b78c-8a48289b2829\\})|(\\{1155e72f-2b21-433f-ba9a-5af6ed40c8ee\\})|(\\{583910bd-759f-40f6-b96a-1d678d65650f\\}))$/", + "prefs": [], + "schema": 1545162093238, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1514865", + "why": "Remote script injection and data exfiltration", + "name": "Various add-ons using .cool domains" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "53168513-103a-4ea0-a48f-bc291354cc9f", + "last_modified": 1545232187960 + }, + { + "guid": "{56a1e8d2-3ced-4919-aca5-ddd58e0f31ef}", + "prefs": [], + "schema": 1544470901949, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1491312", + "why": "The add-on introduces unwanted functionality for users.", + "name": "Web Guard (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1dc366d6-c774-4eca-af19-4f9495c2c55e", + "last_modified": 1544544484935 + }, + { + "guid": "/^((\\{a99e680b-4349-42a5-b292-79b349bf4f3d\\})|(\\{f09a2393-1e6d-4ae4-a020-4772e94040ae\\})|(\\{c9ed9184-179f-485f-adb8-8bd8e9b7cee6\\})|(\\{085e53da-25a2-4162-906e-6c158ec977ac\\})|(\\{bd6960ba-7c06-493b-8cc4-0964a9968df5\\})|(\\{6eeec42e-a844-4bfd-a380-cfbfc988bd78\\})|(\\{3bbfb999-1c82-422e-b7a8-9e04649c7c51\\})|(\\{bfd229b6-089d-49e8-a09c-9ad652f056f6\\})|(\\{ab23eb77-1c96-4e20-b381-14dec82ee9b8\\})|(\\{ebcce9f0-6210-4cf3-a521-5c273924f5ba\\})|(\\{574aba9d-0573-4614-aec8-276fbc85741e\\})|(\\{12e75094-10b0-497b-92af-5405c053c73b\\})|(\\{99508271-f8c0-4ca9-a5f8-ee61e4bd6e86\\})|(\\{831beefc-cd8c-4bd5-a581-bba13d374973\\})|(\\{c8fe42db-b7e2-49e6-98c4-14ac369473a4\\})|(\\{f8927cca-e6cb-4faf-941d-928f84eb937f\\})|(\\{17e9f867-9402-4b19-8686-f0c2b02d378f\\})|(\\{f12ac367-199b-4cad-8e5a-0a7a1135cad0\\})|(\\{487003ce-5253-4eab-bf76-684f26365168\\})|(\\{487003ce-5213-2ecb-bf16-684f25365161\\}))$/", + "prefs": [], + "schema": 1543088493623, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1509864", + "why": "Add-ons that track users and load remote code, while pretending to provide cursor customization features.", + "name": "Various cursor and update add-ons (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a8d942b3-779d-4391-a39c-58c746c13b70", + "last_modified": 1543241996691 + }, + { + "guid": "{97f19f1f-dbb0-4e50-8b46-8091318617bc}", + "prefs": [], + "schema": 1542229276053, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1507191", + "why": "Fraudulent Adobe Reader add-on", + "name": "Adobe Reader (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "03120522-ee87-4cf8-891a-acfb248536ff", + "last_modified": 1542272674851 + }, + { + "guid": "/^((video-downloader@vd\\.io)|(image-search-reverse@an\\.br)|(YouTube\\.Downloader@2\\.8)|(eMoji@ems-al\\.io))$/", + "prefs": [], + "schema": 1542023230755, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1506560", + "why": "Add-ons that contain malicious copies of third-party libraries.", + "name": "Malware containing unwanted behavior" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cd079abe-8e8d-476f-a550-63f75ac09fe8", + "last_modified": 1542025588071 + }, + { + "guid": "/^({b384b75c-c978-4c4d-b3cf-62a82d8f8f12})|({b471eba0-dc87-495e-bb4f-dc02c8b1dc39})|({36f623de-750c-4498-a5d3-ac720e6bfea3})$/", + "prefs": [], + "schema": 1541360505662, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504619", + "why": "Add-ons that contain unwanted behavior.", + "name": "Google Translate (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aa5eefa7-716a-45a6-870b-4697b023d894", + "last_modified": 1541435973146 + }, + { + "guid": "{80869932-37ba-4dd4-8dfe-2ef30a2067cc}", + "prefs": [], + "schema": 1538941301306, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1497161", + "why": "Malicious page redirection", + "name": "Iridium (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "dd5b0fa4-48fd-4bf6-943d-34de125bf502", + "last_modified": 1538996335645 + }, + { + "guid": "admin@vietbacsecurity.com", + "prefs": [], + "schema": 1537309741764, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1491716", + "why": "Logging and sending keystrokes to a remote server.", + "name": "Vietnamese Input Method (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "89d714f6-9f35-4107-b8af-a16777f66337", + "last_modified": 1537309752952 + }, + { + "guid": "Safe@vietbacsecurity.com", + "prefs": [], + "schema": 1537309684266, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1491717", + "why": "Logging and sending keystrokes to a remote server.", + "name": "SafeKids (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c651780e-c185-4d6c-b509-d34673c158a3", + "last_modified": 1537309741758 + }, + { + "guid": "/^((\\{c9226c62-9948-4038-b247-2b95a921135b\\})|(\\{5de34d4f-b891-4575-b54b-54c53b4e6418\\})|(\\{9f7ac3be-8f1c-47c6-8ebe-655b29eb7f21\\})|(\\{bb33ccaf-e279-4253-8946-cfae19a35aa4\\}))$/", + "prefs": [], + "schema": 1537305338753, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1491298", + "why": "These add-ons inject remote malicious scripts on Google websites.", + "name": "Dll and similar (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "54b3e69a-40ae-4be5-b7cf-cf51c526dcfb", + "last_modified": 1537306138745 + }, + { + "guid": "updater-pro-unlisted@mozilla.com", + "prefs": [], + "schema": 1537305285414, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1491306", + "why": "Redirects search queries.", + "name": "Updater Pro (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3108c151-9f25-4eca-8d80-a2fbb2d9bd07", + "last_modified": 1537305338747 + }, + { + "guid": "/^((\\{686fc9c5-c339-43db-b93a-5181a217f9a6\\})|(\\{eb4b28c8-7f2d-4327-a00c-40de4299ba44\\})|(\\{58d735b4-9d6c-4e37-b146-7b9f7e79e318\\})|(\\{ff608c10-2abc-415c-9fe8-0fdd8e988de8\\})|(\\{5a8145e2-6cbb-4509-a268-f3121429656c\\})|(\\{6d451f29-1d6b-4c34-a510-c1234488b0a3\\})|(\\{de71f09a-3342-48c5-95c1-4b0f17567554\\})|(\\{df106b04-984e-4e27-97b6-3f3150e98a9e\\})|(\\{70DE470A-4DC0-11E6-A074-0C08D310C1A8\\})|(\\{4dcde019-2a1b-499b-a5cd-322828e1279b\\})|(\\{1ec3563f-1567-49a6-bb5c-75d52334b01c\\})|(\\{c140c82e-98e6-49fd-ae17-0627e6f7c5e1\\})|(\\{2581c1f6-5ad9-48d4-8008-4c37dcea1984\\})|(\\{a2bcc6f7-14f7-4083-b4b0-c335edc68612\\})|(\\{4c726bb6-a2af-44ed-b498-794cfd8d8838\\})|(\\{fa6c39a6-cd11-477b-966d-f388f0ba4203\\})|(\\{26c7bd04-18d3-47f5-aeec-bb54e562acf2\\})|(\\{7a961c90-2071-4f94-9d9a-d4e3bbf247c0\\})|(\\{a0481ea2-03f0-4e56-a0e1-030908ecb43e\\})|(\\{c98fb54e-d25f-43f4-bd72-dfaa736391e2\\})|(\\{da57263d-adfc-4768-91f7-b3b076c20d63\\})|(\\{3abb352c-8735-4fb6-9fd6-8117aea3d705\\})|(contactus@unzipper\\.com)|(\\{a1499769-6978-4647-ac0f-78da4652716d\\})|(\\{581D0A4C-1013-11E7-938B-FCD2A0406E17\\})|(\\{68feffe4-bfd8-4fc3-8320-8178a3b7aa67\\})|(\\{823489ae-1bf8-4403-acdd-ea1bdc6431da\\})|(\\{4c0d11c3-ee81-4f73-a63c-da23d8388abd\\})|(\\{dc7d2ecc-9cc3-40d7-93ed-ef6f3219bd6f\\})|(\\{21f29077-6271-46fc-8a79-abaeedb2002b\\})|(\\{55d15d4d-da76-44ab-95a3-639315be5ef8\\})|(\\{edfbec6b-8432-4856-930d-feb334fb69c1\\})|(\\{f81a3bf7-d626-48cf-bd24-64e111ddc580\\})|(\\{4407ab94-60ae-4526-b1ab-2521ffd285c7\\})|(\\{4aa2ba11-f87b-4950-8250-cd977252e556\\})|(\\{646b0c4d-4c6f-429d-9b09-37101b36ed1c\\})|(\\{1b2d76f1-4906-42d2-9643-0ce928505dab\\})|(\\{1869f89d-5f15-4c0d-b993-2fa8f09694fb\\})|(\\{7e4edd36-e3a6-4ddb-9e98-22b4e9eb4721\\})|(\\{e9c9ad8c-84ba-43f2-9ae2-c1448694a2a0\\})|(\\{6b2bb4f0-78ea-47c2-a03a-f4bf8f916eda\\})|(\\{539e1692-5841-4ac6-b0cd-40db15c34738\\}))$/", + "prefs": [], + "schema": 1536183366865, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1488578", + "why": "These add-ons take away user control by redirecting search.", + "name": "Tightrope search add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "81eb67a5-3fdb-448c-aadd-5f4d3b7cf281", + "last_modified": 1536186868443 + }, + { + "guid": "/^((\\{f01a138a-c051-4bc7-a90a-21151ce05755\\})|(\\{50f78250-63ce-4191-b7c3-e0efc6309b64\\})|(\\{3d2b2ff4-126b-4874-a57e-ed7dac670230\\})|(\\{e7c1abd4-ec8e-4519-8f3a-7bd763b8a353\\})|(\\{4d40bf75-fbe2-45f6-a119-b191c2dd33b0\\})|(\\{08df7ff2-dee0-453c-b85e-f3369add18ef\\}))$/", + "prefs": [], + "schema": 1535990752587, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1488248", + "why": "Add-ons that inject malicious remote code.", + "name": "Various Malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "67f72634-e170-4860-a5a3-133f160ebc32", + "last_modified": 1535992146430 + }, + { + "guid": "/^((\\{1cfaec8b-a1cb-4fc5-b139-897a22a71390\\})|(\\{2ed89659-09c1-4280-9dd7-1daf69272a86\\})|(\\{5c82f5cc-31f8-4316-bb7d-45a5c05227e6\\})|(\\{6a98a401-378c-4eac-b93c-da1036a00c6c\\})|(\\{6d83ebde-6396-483c-b078-57c9d445abfa\\})|(\\{07efb887-b09f-4028-8f7f-c0036d0485ea\\})|(\\{36f4882f-ff0b-4865-8674-ef02a937f7da\\})|(\\{61dea9e9-922d-4218-acdd-cfef0fdf85e7\\})|(\\{261be583-9695-48e0-bd93-a4feafaa18e6\\})|(\\{401ae092-6c5c-4771-9a87-a6827be80224\\})|(\\{534b7a84-9fc6-4d7c-9d67-e3365d2ae088\\})|(\\{552a949f-6d0e-402d-903d-1550075541ba\\})|(\\{579b8de8-c461-4301-ab09-695579f9b7c7\\})|(\\{754d3be3-7337-488e-a5bb-86487e495495\\})|(\\{2775f69b-75e4-46cb-a5aa-f819624bd9a6\\})|(\\{41290ec4-b3f0-45ad-b8f3-7bcbca01ed0d\\})|(\\{0159131f-d76f-4365-81cd-d6831549b90a\\})|(\\{01527332-1170-4f20-a65b-376e25438f3d\\})|(\\{760e6ff0-798d-4291-9d5f-12f48ef7658b\\})|(\\{7e31c21c-156a-4783-b1ce-df0274a89c75\\})|(\\{8e247308-a68a-4280-b0e2-a14c2f15180a\\})|(\\{b6d36fe8-eca1-4d85-859e-a4cc74debfed\\})|(\\{bab0e844-2979-407f-9264-c87ebe279e72\\})|(\\{d00f78fe-ee73-4589-b120-5723b9a64aa0\\})|(\\{d59a7294-6c08-4ad5-ba6d-a3bc41851de5\\})|(\\{d145aa5b-6e66-40cb-8a08-d55a53fc7058\\})|(\\{d79962e3-4511-4c44-8a40-aed6d32a53b1\\})|(\\{e3e2a47e-7295-426f-8517-e72c31da3f23\\})|(\\{e6348f01-841d-419f-8298-93d6adb0b022\\})|(\\{eb6f8a22-d96e-4727-9167-be68c7d0a7e9\\})|(\\{fdd72dfe-e10b-468b-8508-4de34f4e95e3\\}))$/", + "prefs": [], + "schema": 1535830899087, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487472", + "why": "Several add-ons that change forcefully override search settings.", + "name": "Various malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "43f11241-88e3-4139-9f02-ac39489a241f", + "last_modified": 1535990735167 + }, + { + "guid": "/^((\\{35253b0b-8109-437f-b8fa-d7e690d3bde1\\})|(\\{0c8d774c-0447-11e7-a3b1-1b43e3911f03\\})|(\\{c11f85de-0bf8-11e7-9dcd-83433cae2e8e\\})|(\\{f9f072c8-5357-11e7-bb4c-c37ea2335fb4\\})|(\\{b6d09408-a35e-11e7-bc48-f3e9438e081e\\}))$/", + "prefs": [], + "schema": 1535658090284, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1486754", + "why": "Add-ons that execute remote malicious code.", + "name": "Several malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "56bd2f99-57eb-4904-840a-23ca155d93ad", + "last_modified": 1535701073599 + }, + { + "guid": "/^((fireAnalytics\\.download@mozilla\\.com)|(fireabsorb@mozilla\\.com)|(fireaccent@mozilla\\.com)|(fireaccept@mozilla\\.com)|(fireads@mozilla\\.com)|(firealerts@mozilla\\.com)|(fireapi@mozilla\\.com)|(fireapp@mozilla\\.com)|(fireattribution@mozilla\\.com)|(fireauthenticator@mozilla\\.com)|(firecalendar@mozilla\\.com)|(firemail@mozilla\\.com)|(firemarketplace@mozilla\\.com)|(firequestions@mozilla\\.com)|(firescript@mozilla\\.com)|(firesheets@mozilla\\.com)|(firespam@mozilla\\.com)|(firesuite@mozilla\\.com)|(\\{3b6dfc8f-e8ed-4b4c-b616-bdc8c526ac1d\\})|(\\{834f87db-0ff7-4518-89a0-0167a963a869\\})|(\\{4921fe4d-fbe6-4806-8eed-346d7aff7c75\\})|(\\{07809949-bd7d-40a6-a17b-19807448f77d\\})|(\\{68968617-cc8b-4c25-9c38-34646cdbe43e\\})|(\\{b8b2c0e1-f85d-4acd-aeb1-b6308a473874\\})|(\\{bc0b3499-f772-468e-9de6-b4aaf65d2bbb\\}))$/", + "prefs": [], + "schema": 1535555549913, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1486636", + "why": "Add-ons that hijack search settings.", + "name": "Various malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "fcd12629-43df-4751-9654-7cc008f8f7c0", + "last_modified": 1535555562143 + }, + { + "guid": "/^((\\{25211004-63e4-4a94-9c71-bdfeabb72bfe\\})|(\\{cbf23b92-ea55-4ca9-a5ae-f4197e286bc8\\})|(\\{7ac0550e-19cb-4d22-be12-b0b352144b33\\})|(Mada111@mozilla\\.com)|(\\{c71709a9-af59-4958-a587-646c8c314c16\\})|(\\{6ac3f3b4-18db-4f69-a210-7babefd94b1e\\})|(addon@fastsearch\\.me)|(\\{53d152fa-0ae0-47f1-97bf-c97ca3051562\\})|(\\{f9071611-24ee-472b-b106-f5e2f40bbe54\\})|(\\{972920f1-3bfd-4e99-b605-8688a94c3c85\\})|(\\{985afe98-fa74-4932-8026-4bdc880552ac\\})|(\\{d96a82f5-5d3e-46ed-945f-7c62c20b7644\\})|(\\{3a036dc5-c13b-499a-a62d-e18aab59d485\\})|(\\{49574957-56c6-4477-87f1-1ac7fa1b2299\\})|(\\{097006e8-9a95-4f7c-9c2f-59f20c61771c\\})|(\\{8619885d-0380-467a-b3fe-92a115299c32\\})|(\\{aa0587d6-4760-4abe-b3a1-2a5958f46775\\})|(\\{bdada7ae-cf89-46cf-b1fe-f3681f596278\\})|(\\{649bead3-df51-4023-8090-02ceb2f7095a\\})|(\\{097c3142-0b68-416a-9919-9dd576aedc17\\})|(\\{bc3cced8-51f0-4519-89ee-56706b67ea4b\\})|(\\{796da6e3-01c0-4c63-96dd-1737710b2ff6\\}))$/", + "prefs": [], + "schema": 1535485297866, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487083", + "why": "Add-ons that hijack search settings and contain other unwanted features.", + "name": "Vairous malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "016676cc-c381-4c01-adcf-2d46f48142d0", + "last_modified": 1535550828514 + }, + { + "guid": "/^((Timemetric@tmetric)|(image-fastpicker@eight04.blogspot\\.com)|(textMarkertool@underFlyingBirches\\.org)|(youpanel@jetpack)|({0ff32ce0-dee9-4e7e-9260-65e58373e21d})|({4ca00873-7e8d-4ada-b460-96cad0eb8fa9})|({6b427f73-2ee1-4256-b69d-7dc253ebe030})|({6f13489d-b274-45b6-80fa-e9daa140e1a4})|({40a9d23b-09ef-4c82-ae1d-7fc5c067e987})|({205c2185-ebe4-4106-92ab-0ffa7c4efcbb})|({256ec7b0-57b4-416d-91c1-2bfdf01b2438})|({568db771-c718-4587-bcd0-e3728ee53550})|({5782a0f1-de26-42e5-a5b3-dae9ec05221b})|({9077390b-89a9-41ad-998f-ab973e37f26f})|({8e7269ac-a171-4d9f-9c0a-c504848fd52f})|({3e6586e2-7410-4f10-bba0-914abfc3a0b4})|({b3f06312-93c7-4a4f-a78b-f5defc185d8f})|({c1aee371-4401-4bab-937a-ceb15c2323c1})|({c579191c-6bb8-4795-adca-d1bf180b512d})|({d0aa0ad2-15ed-4415-8ef5-723f303c2a67})|({d8157e0c-bf39-42eb-a0c3-051ff9724a8c})|({e2a4966f-919d-4afc-a94f-5bd6e0606711})|({ee97f92d-1bfe-4e9d-816c-0dfcd63a6206}))$/", + "prefs": [], + "schema": 1535356061028, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1485145", + "why": "Add-ons that run remote malicious code from websites that trick the user into installing the add-on.", + "name": "Malware running remote malicious code" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a0d44ee3-9492-47d7-ac1c-35f520e819ae", + "last_modified": 1535393877555 + }, + { + "guid": "/^((fastplayer@fastsearch\\.me)|(ff-search-flash-unlisted@mozilla\\.com)|(inspiratiooo-unlisted@mozilla\\.com)|(lite-search-ff-unlisted@mozilla\\.com)|(mysearchprotect-unlisted@mozilla\\.com)|(pdfconverter-unlisted@mozilla\\.com)|(plugin-search-ff-unlisted@mozilla\\.com)|(pro-search-ff-unlisted@mozilla\\.com)|(pro-search-unlisted@mozilla\\.com)|(searchincognito-unlisted@mozilla\\.com)|(socopoco-search@mozilla\\.com)|(socopoco-unlisted@mozilla\\.com)|(\\{08ea1e08-e237-42e7-ad60-811398c21d58\\})|(\\{0a56e2a0-a374-48b6-9afc-976680fab110\\})|(\\{193b040d-2a00-4406-b9ae-e0d345b53201\\})|(\\{1ffa2e79-7cd4-4fbf-8034-20bcb3463d20\\})|(\\{528cbbe2-3cde-4331-9344-e348cb310783\\})|(\\{6f7c2a42-515a-4797-b615-eaa9d78e8c80\\})|(\\{be2a3fba-7ea2-48b9-bbae-dffa7ae45ef8\\})|(\\{c0231a6b-c8c8-4453-abc9-c4a999a863bd\\}))$/", + "prefs": [], + "schema": 1535139689975, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1483854", + "why": "Add-ons overwriting search changes without consent and remote script injection", + "name": "\"Flash Updater\" and search redirectors" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "46779b5a-2369-4007-bff0-857a657626ba", + "last_modified": 1535153064735 + }, + { + "guid": "/^(({aeac6f90-5e17-46fe-8e81-9007264b907d})|({6ee25421-1bd5-4f0c-9924-79eb29a8889d})|({b317fa11-c23d-45b9-9fd8-9df41a094525})|({16ac3e8f-507a-4e04-966b-0247a196c0b4}))$/", + "prefs": [], + "schema": 1534946831027, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1485609", + "why": "Add-ons that take away user control by changing search settings.", + "name": "Search hijacking malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ab029019-0e93-450a-8c11-ac31556c2a77", + "last_modified": 1535020847820 + }, + { + "guid": "@testpilot-addon", + "prefs": [], + "schema": 1534876689555, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1485083", + "why": "Older versions of the TestPilot add-on cause stability issues in Firefox.", + "name": "Testpilot (old, broken versions)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.0.8-dev-259fe19", + "minVersion": "0" + } + ], + "id": "ee2d12a4-ea1d-4f3d-9df1-4303e8993f18", + "last_modified": 1534946810180 + }, + { + "guid": "/^((@svuznnqyxinw)|(myprivacytools@besttools\\.com)|(powertools@penprivacy\\.com)|(privacypro@mybestprivacy\\.com)|(realsecure@top10\\.com)|(rlbvpdfrlbgx@scoutee\\.net)|(vfjkurlfijwz@scoutee\\.net))$/", + "prefs": [], + "schema": 1534382102271, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1482322", + "why": "Add-ons that change the default search engine, taking away user control.", + "name": "Search hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "df852b6a-28be-4b10-9285-869f4761f111", + "last_modified": 1534382538298 + }, + { + "guid": "/^(({1a3fb414-0945-405c-a62a-9fe5e1a50c69})|({1a45f6aa-d80a-4317-84d2-0ce43671b08a})|({2d52a462-8bec-4708-9cd1-894b682bdc78})|({3f841cfc-de5a-421f-8bd7-2bf1d943b02a})|({5c7601bf-522b-47e5-b0f0-ea0e706af443})|({7ebe580f-71c9-4ef8-8073-f38deaeb9dfb})|({8b2188fd-1daf-4851-b387-28d964014353})|({8cee42ac-f1fe-40ae-aed6-24e3b76b2f77})|({8d13c4a9-5e8c-47a6-b583-681c83164ac9})|({9b1d775a-1877-45c9-ad48-d6fcfa4fff39})|({9efdbe5f-6e51-4a35-a41b-71dc939e6221})|({23f63efb-156e-440b-a96c-118bebc21057})|({026dfc8c-ecc8-41ba-b45f-70ffbd5cc672})|({34aa433c-27e9-4c87-a662-9f82f99eb9af})|({36f34d69-f22f-47c3-b4cd-4f37b7676107})|({39bd8607-0af4-4d6b-bd69-9a63c1825d3c})|({48c6ad6d-297c-4074-8fef-ca5f07683859})|({54aa688d-9504-481d-ba75-cfee421b98e0})|({59f59748-e6a8-4b41-87b5-9baadd75ddef})|({61d99407-1231-4edc-acc8-ab96cbbcf151})|({68ca8e3a-397a-4135-a3af-b6e4068a1eae})|({71beafd6-779b-4b7d-a78b-18a107277b59})|({83ed90f8-b07e-4c45-ba6b-ba2fe12cebb6})|({231dfb44-98e0-4bc4-b6ee-1dac4a836b08})|({273f0bce-33f4-45f6-ae03-df67df3864c2})|({392f4252-c731-4715-9f8d-d5815f766abb})|({484ec5d0-4cfd-4d96-88d0-a349bfc33780})|({569dbf47-cc10-41c4-8fd5-5f6cf4a833c7})|({578cad7a-57d5-404d-8dda-4d30de33b0c2})|({986b2c3f-e335-4b39-b3ad-46caf809d3aa})|({1091c11f-5983-410e-a715-0968754cff54})|({2330eb8a-e3fe-4b2e-9f17-9ddbfb96e6f5})|({5920b042-0af1-4658-97c1-602315d3b93d})|({6331a47f-8aae-490c-a9ad-eae786b4349f})|({6698b988-c3ef-4e1f-8740-08d52719eab5})|({30516f71-88d4-489b-a27f-d00a63ad459f})|({12089699-5570-4bf6-890f-07e7f674aa6e})|({84887738-92bf-4903-a5e8-695fd078c657})|({8562e48e-3723-412a-9ebd-b33d3d3b29dd})|({6e449795-c545-41be-92c0-5d467c147389})|({1e369c7c-6b61-436e-8978-4640687670d6})|({a03d427a-bd2e-42b6-828f-a57f38fac7b5})|({a77fc9b9-6ebb-418d-b0b6-86311c191158})|({a368025b-9828-43a1-8a5c-f6fab61c9be9})|({b1908b02-410d-4778-8856-7e259fbf471d})|({b9425ace-c2e9-4ec4-b564-4062546f4eca})|({b9845b5d-70c9-419c-a9a5-98ea8ee5cc01})|({ba99fee7-9806-4e32-8257-a33ffc3b8539})|({bdf8767d-ae4c-4d45-8f95-0ba29b910600})|({c6c4a718-cf91-4648-aa9b-170d66163cf2})|({ca0f2988-e1a8-4e83-afde-0dca56a17d5f})|({cac5db09-979b-40e3-8c8e-d96397b0eecb})|({d3b5280b-f8d8-4669-bdf6-91f23ae58042})|({d73d2f6a-ea24-4b1b-8c76-563fce9f786d})|({d77fed37-85c0-4b94-89bb-0d2849472b8d})|({d371abec-84bb-481b-acbf-235639451127})|({de47a3b4-dad1-4f4a-bdd6-8666586e29e8})|({ded6afad-2aaa-446b-b6bd-b12a8a61c945})|({e0c3a1ca-8e21-4d1b-b53b-ea115cf59172})|({e6bbf496-6489-4b48-8e5a-799aad4aa742})|({e63b262a-f9b8-4496-9c4b-9d3cbd6aea90})|({e73c1b5d-20f7-4d86-ad16-9de3c27718e2})|({eb01dc49-688f-4a21-aa8d-49bd88a8f319})|({edc9816b-60b4-493c-a090-01125e0b8018})|({effa2f97-0f07-44c8-99cb-32ac760a0621})|({f6e6fd9b-b89f-4e8d-9257-01405bc139a6})|({ff87977a-fefb-4a9d-b703-4b73dce8853d})|({ffea9e62-e516-4238-88a7-d6f9346f4955}))$/", + "prefs": [], + "schema": 1534335096640, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1483191", + "why": "Add-ons that change the default search engine, taking away user control.", + "name": "Search hijacking add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d9892a76-b22e-40bd-8073-89b0f8110ec7", + "last_modified": 1534336165428 + }, + { + "guid": "/^((Timemetric@tmetric)|(textMarkertool@underFlyingBirches\\.org)|(youpanel@jetpack)|({6f13489d-b274-45b6-80fa-e9daa140e1a4})|({568db771-c718-4587-bcd0-e3728ee53550})|({829827cd-03be-4fed-af96-dd5997806fb4})|({9077390b-89a9-41ad-998f-ab973e37f26f})|({8e7269ac-a171-4d9f-9c0a-c504848fd52f})|({aaaffe20-3306-4c64-9fe5-66986ebb248e})|({bf153de7-cdf2-4554-af46-29dabfb2aa2d})|({c579191c-6bb8-4795-adca-d1bf180b512d})|({e2a4966f-919d-4afc-a94f-5bd6e0606711})|({ee97f92d-1bfe-4e9d-816c-0dfcd63a6206}))$/", + "prefs": [], + "schema": 1534275699570, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1483206", + "why": "Add-ons that execute malicious remote code", + "name": "Various malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2734325e-143b-4962-98bf-4b18c77407e2", + "last_modified": 1534334500118 + }, + { + "guid": "{5834f62d-6164-4cdd-a0a3-c00c66ec9d13}", + "prefs": [], + "schema": 1532704368947, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1479002", + "why": "This add-on violates our security and user-choice/no surprises policies.", + "name": "Youtube Dark Mode (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d0a401cb-0c70-4784-8288-b06a88b2ae8a", + "last_modified": 1532705151926 + }, + { + "guid": "/^((@asdfjhsdfuhw)|(@asdfsdfwe)|(@asdieieuss)|(@dghfghfgh)|(@difherk)|(@dsfgtftgjhrdf4)|(@fidfueir)|(@fsgergsdqtyy)|(@hjconsnfes)|(@isdifvdkf)|(@iweruewir)|(@oiboijdjfj)|(@safesearchavs)|(@safesearchavsext)|(@safesearchincognito)|(@safesearchscoutee)|(@sdfykhhhfg)|(@sdiosuff)|(@sdklsajd)|(@sduixcjksd)|(@sicognitores)|(@simtabtest)|(@sodiasudi)|(@test13)|(@test131)|(@test131ver)|(@test132)|(@test13s)|(@testmptys)|(\\{ac4e5b0c-13c4-4bfd-a0c3-1e73c81e8bac\\})|(\\{e78785c3-ec49-44d2-8aac-9ec7293f4a8f\\})|(general@filecheckerapp\\.com)|(general@safesearch\\.net))$/", + "prefs": [], + "schema": 1532703832328, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1475330", + "why": "These Add-ons violate our data collection, no surprises and user-choice policies.", + "name": "Safesearch (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "d664412d-ed08-4892-b247-b007a70856ff", + "last_modified": 1532704364007 + }, + { + "guid": "{dd3d7613-0246-469d-bc65-2a3cc1668adc}", + "prefs": [], + "schema": 1532684052432, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1478731", + "why": "This add-on violates data practices outlined in the review policy.", + "name": "BlockSite" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "4.0.3", + "minVersion": "0" + } + ], + "id": "e04f98b5-4480-43a3-881d-e509e4e28cdc", + "last_modified": 1532684085999 + }, + { + "guid": "{bee8b1f2-823a-424c-959c-f8f76c8b2306}", + "prefs": [], + "schema": 1532547689407, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1478731", + "why": "This add-on violates data practices outlined in the review policy.", + "name": "Popup blocker for FireFox" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "4.0.7.3", + "minVersion": "0" + } + ], + "id": "f0713a5e-7208-484e-b3a0-4e6dc6a195be", + "last_modified": 1532684052426 + }, + { + "guid": "/^((\\{39bd8607-0af4-4d6b-bd69-9a63c1825d3c\\})|(\\{273f0bce-33f4-45f6-ae03-df67df3864c2\\})|(\\{a77fc9b9-6ebb-418d-b0b6-86311c191158\\})|(\\{c6c4a718-cf91-4648-aa9b-170d66163cf2\\})|(\\{d371abec-84bb-481b-acbf-235639451127\\})|(\\{e63b262a-f9b8-4496-9c4b-9d3cbd6aea90\\}))$/", + "prefs": [], + "schema": 1532386339902, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1477950", + "why": "Add-ons that contain malicious functionality like search engine redirect.", + "name": "Smash (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c37c7c24-e738-4d06-888c-108b4d63b428", + "last_modified": 1532424286908 + }, + { + "guid": "/^((\\{ac296b47-7c03-486f-a1d6-c48b24419749\\})|(\\{1cab8ccf-deff-4743-925d-a47cbd0a6b56\\})|(\\{5da81d3d-5db1-432a-affc-4a2fe9a70749\\})|(\\{071b9878-a7d3-4ae3-8ef0-2eaee1923403\\})|(\\{261476ea-bd0e-477c-abd7-33cdf626f81f\\})|(\\{224e66d0-6b11-4c4b-9bcf-41180889898a\\})|(\\{1e90cf52-c67c-4bd9-80c3-a2bf521fc981\\})|(\\{09c4799c-00f1-439e-9e60-3827c589b372\\})|(\\{d3d2095a-9faa-466f-82ae-3114179b34d6\\})|(\\{70389ea5-7e4d-4515-835c-fbd047f229dd\\})|(\\{2e8083a5-cd88-4aaa-bb8b-e54e9753f280\\})|(\\{fbf2480b-5c19-478e-bfd0-192ad9f84dc9\\})|(\\{6c7dc694-89f8-477e-88d5-c55af4d6a846\\})|(\\{915c12c6-901a-490d-9bfc-20f00d1ad31d\\})|(\\{d3a4aa3e-f74c-4382-876d-825f592f2976\\})|(\\{0ad91ec1-f7c4-4a39-9244-3310e9fdd169\\})|(\\{9c17aa27-63c5-470a-a678-dc899ab67ed3\\})|(\\{c65efef2-9988-48db-9e0a-9ff8164182b6\\})|(\\{d54c5d25-2d51-446d-8d14-18d859e3e89a\\})|(\\{e458f1f1-a331-4486-b157-81cba19f0993\\})|(\\{d2de7e1f-6e51-41d6-ba8a-937f8a5c92ff\\})|(\\{2b08a649-9bea-4dd4-91c8-f53a84d38e19\\})|(\\{312dd57e-a590-4e19-9b26-90e308cfb103\\})|(\\{82ce595a-f9b6-4db8-9c97-b1f1c933418b\\})|(\\{0a2e64f0-ea5a-4fff-902d-530732308d8e\\})|(\\{5fbdc975-17ab-4b4e-90d7-9a64fd832a08\\})|(\\{28820707-54d8-41f0-93e9-a36ffb2a1da6\\})|(\\{64a2aed1-5dcf-4f2b-aad6-9717d23779ec\\})|(\\{ee54794f-cd16-4f7d-a7dd-515a36086f60\\})|(\\{4d381160-b2d5-4718-9a05-fc54d4b307e7\\})|(\\{60393e0e-f039-4b80-bad4-10189053c2ab\\})|(\\{0997b7b2-52d7-4d14-9aa6-d820b2e26310\\})|(\\{8214cbd6-d008-4d16-9381-3ef1e1415665\\})|(\\{6dec3d8d-0527-49a3-8f12-b05f2a8b95b2\\})|(\\{0c0d8d8f-3ae0-4c98-81ac-06453a316d16\\})|(\\{84d5ef02-a283-484a-80da-7087836c74aa\\})|(\\{24413756-2c44-47c5-8bbf-160cb37776d8\\})|(\\{cf6ac458-06e8-45d0-9cbf-ec7fc0eb1710\\})|(\\{263a5792-933a-4de1-820a-d04198e17120\\})|(\\{b5fd7f37-190d-4c0a-b8dd-8b4850c986ac\\})|(\\{cb5ef07b-c2e7-47a6-be81-2ceff8df4dd5\\})|(\\{311b20bc-b498-493c-a5e1-22ec32b0e83c\\})|(\\{b308aead-8bc1-4f37-9324-834b49903df7\\})|(\\{3a26e767-b781-4e21-aaf8-ac813d9edc9f\\}))$/", + "prefs": [], + "schema": 1532361925873, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1476553", + "why": "Third-party websites try to trick users into installing add-ons that inject remote scripts.", + "name": "Various malicious add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "52842139-3d11-41ac-9d7f-8e51122a3141", + "last_modified": 1532372344457 + }, + { + "guid": "{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}", + "prefs": [], + "schema": 1530711142817, + "blockID": "i1900", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1472948", + "why": "This add-on violates data practices outlined in the review policy.", + "name": "Stylish" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.1.1", + "minVersion": "3.0.0" + } + ], + "id": "c635229f-7aa0-44c5-914f-80c590949071", + "last_modified": 1530716488758 + }, + { + "guid": "/^(contactus@unzipper.com|{72dcff4e-48ce-41d8-a807-823adadbe0c9}|{dc7d2ecc-9cc3-40d7-93ed-ef6f3219bd6f}|{994db3d3-ccfe-449a-81e4-f95e2da76843}|{25aef460-43d5-4bd0-aa3d-0a46a41400e6}|{178e750c-ae27-4868-a229-04951dac57f7})$/", + "prefs": [], + "schema": 1528400492025, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1460331", + "why": "Add-ons change search settings against our policies, affecting core Firefox features. Add-on is also reportedly installed without user consent.", + "name": "SearchWeb" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5afea853-d029-43f3-a387-64ce9980742a", + "last_modified": 1528408770328 + }, + { + "guid": "{38363d75-6591-4e8b-bf01-0270623d1b6c}", + "prefs": [], + "schema": 1526326889114, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1461625", + "why": "This add-on contains abusive functionality.", + "name": "Photobucket Hotlink Fix" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0f0764d5-a290-428b-a5b2-3767e1d72c71", + "last_modified": 1526381862851 + }, + { + "guid": "@vkmad", + "prefs": [], + "schema": 1526154098016, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1461410", + "why": "This add-on includes malicious functionality.", + "name": "VK Universal Downloader (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cbfa5303-c1bf-49c8-87d8-259738a20064", + "last_modified": 1526322954850 + }, + { + "guid": "/((@extcorp\\.[a-z]+)|(@brcorporation\\.com)|(@brmodcorp\\.com)|(@teset\\.com)|(@modext\\.tech)|(@ext?mod\\.net)|(@browcorporation\\.org)|(@omegacorporation\\.org)|(@browmodule\\.com)|(@corpext\\.net)|({6b50ddac-f5e0-4d9e-945b-e4165bfea5d6})|({fab6484f-b8a7-4ba9-a041-0f948518b80c})|({b797035a-7f29-4ff5-bd19-77f1b5e464b1})|({0f612416-5c5a-4ec8-b482-eb546af9cac4}))$/", + "prefs": [], + "schema": 1525290095999, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458330", + "why": "These are malicious add-ons that inject remote scripts and use deceptive names.", + "name": "\"Table\" add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3ab9f100-e253-4080-b3e5-652f842ddb7a", + "last_modified": 1525377099954 + }, + { + "guid": "/^({b99ae7b1-aabb-4674-ba8f-14ed32d04e76})|({dfa77d38-f67b-4c41-80d5-96470d804d09})$/", + "prefs": [], + "schema": 1524146566650, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1455291", + "why": "These add-ons claim to be the flash plugin.", + "name": "Flash Plugin (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "96b137e6-8cb5-44d6-9a34-4a4a76fb5e38", + "last_modified": 1524147337556 + }, + { + "guid": "/^({6ecb9f49-90f0-43a1-8f8a-e809ea4f732b})|(@googledashboard)|(@smashdashboard)|(@smash_tv)|(@smash_mov)|(@smashmovs)|(@smashtvs)|(@FirefoxUpdate)|({92b9e511-ac81-4d47-9b8f-f92dc872447e})|({3c841114-da8c-44ea-8303-78264edfe60b})|({116a0754-20eb-4fe5-bd35-575867a0b89e})|({6e6ff0fd-4ae4-49ae-ac0c-e2527e12359b})|({f992ac88-79d3-4960-870e-92c342ed3491})|({6ecb9f49-90f0-43a1-8f8a-e809ea4f732b})|({a512297e-4d3a-468c-bd1a-f77bd093f925})|({08c28c16-9fb6-4b32-9868-db37c1668f94})|({b4ab1a1d-e137-4c59-94d5-4f509358a81d})|({feedf4f8-08c1-451f-a717-f08233a64ec9})$/", + "prefs": [], + "schema": 1524139371832, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1454691", + "why": "This malware prevents itself from getting uninstalled ", + "name": "Malware" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "feb2d0d7-1b76-4dba-bf84-42873a92af5f", + "last_modified": 1524141477640 + }, + { + "guid": "{872f20ea-196e-4d11-8835-1cc4c877b1b8}", + "prefs": [], + "schema": 1523734896380, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1454413", + "why": "Extension claims to be Flash Player", + "name": "Flash Player (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "1e5f5cb2-346c-422a-9aaa-29d8760949d2", + "last_modified": 1523897202689 + }, + { + "guid": "adbeaver@adbeaver.org", + "prefs": [], + "schema": 1521630548030, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1445031", + "why": "This add-on generates numerous errors when loading Facebook, caused by ad injection included in it. Users who want to continue using this add-on can enable it in the Add-ons Manager.", + "name": "AdBeaver" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "baf7f735-d6b6-410a-8cc8-25c60f7c57e2", + "last_modified": 1522103097333 + }, + { + "guid": "{44685ba6-68b3-4895-879e-4efa29dfb578}", + "prefs": [], + "schema": 1521565140013, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447042", + "why": "This add-on impersonates a Flash tool and runs remote code on users' systems.", + "name": "FF Flash Manager" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "547037f2-97ae-435a-863c-efd7532668cd", + "last_modified": 1521630548023 + }, + { + "guid": "/^(addon@fasterweb\\.com|\\{5f398d3f-25db-47f5-b422-aa2364ff6c0b\\}|addon@fasterp\\.com|addon@calculator)$/", + "prefs": [], + "schema": 1520338910918, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1443478", + "why": "These are malicious add-ons that use deceptive names and run remote scripts.", + "name": "FasterWeb add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f58729ec-f93c-41d9-870d-dd9c9fd811b6", + "last_modified": 1520358450708 + }, + { + "guid": "{42baa93e-0cff-4289-b79e-6ae88df668c4}", + "prefs": [], + "schema": 1520336325565, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1443196", + "why": "The add-on claims to be \"Adobe Shockwave Flash Player\"", + "name": "Adobe Shockwave Flash Player (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0cd723fe-d33d-43a0-b84f-7a3cad253212", + "last_modified": 1520338780397 + }, + { + "guid": "{f3c31b34-862c-4bc8-a98f-910cc6314a86}", + "prefs": [], + "schema": 1519242096699, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1440736", + "why": "This is a malicious add-on that is masked as an official Adobe Updater and runs malicious code.", + "name": "Adobe Updater (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "adfd98ef-cebc-406b-b1e0-61bd4c71e4b1", + "last_modified": 1519409417397 + }, + { + "guid": "/^(\\{fd0c36fa-6a29-4246-810b-0bb4800019cb\\}|\\{b9c1e5bf-6585-4766-93fc-26313ac59999\\}|\\{3de25fff-25e8-40e9-9ad9-fdb3b38bb2f4\\})$/", + "prefs": [], + "schema": 1519069296530, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1439432", + "why": "These are malicious add-ons that are masked as an official Adobe Updater and run malicious code.", + "name": "Adobe Updater" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4e28ba5c-af62-4e53-a7a1-d33334571cf8", + "last_modified": 1519078890592 + }, + { + "guid": "/^(\\{01c9a4a4-06dd-426b-9500-2ea6fe841b88\\}|{5e024309-042c-4b9d-a634-5d92cf9c7514\\}|{f4262989-6de0-4604-918f-663b85fad605\\}|{e341ed12-a703-47fe-b8dd-5948c38070e4\\}|{cd89045b-2e06-46bb-9e34-48e8799e5ef2\\}|{ac296b47-7c03-486f-a1d6-c48b24419749\\}|{5da81d3d-5db1-432a-affc-4a2fe9a70749\\}|{df09f268-3c92-49db-8c31-6a25a6643896\\}|{81ac42f3-3d17-4cff-85af-8b7f89c8826b\\}|{09c8fa16-4eec-4f78-b19d-9b24b1b57e1e\\}|{71639610-9cc3-47e0-86ed-d5b99eaa41d5\\}|{83d38ac3-121b-4f28-bf9c-1220bd3c643b\\}|{7f8bc48d-1c7c-41a0-8534-54adc079338f\\})$/", + "prefs": [], + "schema": 1518550894975, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1438028", + "why": "These are malicious add-ons that inject remote scripts into popular websites.", + "name": "Page Marker add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "cc5848e8-23d5-4655-b45c-dc239839b74e", + "last_modified": 1518640450735 + }, + { + "guid": "/^(https|youtube)@vietbacsecurity\\.com$/", + "prefs": [], + "schema": 1517909997354, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1435974", + "why": "These add-ons contain malicious functionality, violating the users privacy and security.", + "name": "HTTPS and Youtube downloader (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "646e2384-f894-41bf-b7fc-8879e0095109", + "last_modified": 1517910100624 + }, + { + "guid": "{ed352072-ddf0-4cb4-9cb6-d8aa3741c2de}", + "prefs": [], + "schema": 1517514097126, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1434893", + "why": "This is a malicious add-on that injects remote scripts into popular pages while pretending to do something else.", + "name": "Image previewer" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2104a522-bb2f-4b04-ad0d-b0c571644552", + "last_modified": 1517577111194 + }, + { + "guid": "/^({be5d0c88-571b-4d01-a27a-cc2d2b75868c})|({3908d078-e1db-40bf-9567-5845aa77b833})|({5b620343-cd69-49b8-a7ba-f9d499ee5d3d})|({6eee2d17-f932-4a43-a254-9e2223be8f32})|({e05ba06a-6d6a-4c51-b8fc-60b461ffecaf})|({a5808da1-5b4f-42f2-b030-161fd11a36f7})|({d355bee9-07f0-47d3-8de6-59b8eecba57b})|({a1f8e136-bce5-4fd3-9ed1-f260703a5582})$/", + "prefs": [], + "schema": 1517260691761, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.\n", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "70f37cc7-9f8a-4d0f-a881-f0c56934fa75", + "last_modified": 1517260722621 + }, + { + "guid": "/^({d78d27f4-9716-4f13-a8b6-842c455d6a46})|({bd5ba448-b096-4bd0-9582-eb7a5c9c0948})|({0b24cf69-02b8-407d-83db-e7af04fc1f3e})|({e08d85c5-4c0f-4ce3-9194-760187ce93ba})|({1c7d6d9e-325a-4260-8213-82d51277fc31})|({8a0699a0-09c3-4cf1-b38d-fec25441650c})|({1e68848a-2bb7-425c-81a2-524ab93763eb})$/", + "prefs": [], + "schema": 1517168490224, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "805ee80e-0929-4c92-93ed-062b98053f28", + "last_modified": 1517260691755 + }, + { + "guid": "/^({abec23c3-478f-4a5b-8a38-68ccd500ec42}|{a83c1cbb-7a41-41e7-a2ae-58efcb4dc2e4}|{62237447-e365-487e-8fc3-64ddf37bdaed}|{b12cfdc7-3c69-43cb-a3fb-38981b68a087}|{1a927d5b-42e7-4407-828a-fdc441d0daae}|{dd1cb0ec-be2a-432b-9c90-d64c824ac371}|{82c8ced2-e08c-4d6c-a12b-3e8227d7fc2a}|{87c552f9-7dbb-421b-8deb-571d4a2d7a21})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "c92f2a05-73eb-454e-9583-f6d2382d8bca", + "last_modified": 1516829074251 + }, + { + "guid": "/^({618baeb9-e694-4c7b-9328-69f35b6a8839}|{b91fcda4-88b0-4a10-9015-9365e5340563}|{04150f98-2d7c-4ae2-8979-f5baa198a577}|{4b1050c6-9139-4126-9331-30a836e75db9}|{1e6f5a54-2c4f-4597-aa9e-3e278c617d38}|{e73854da-9503-423b-ab27-fafea2fbf443}|{a2427e23-d349-4b25-b5b8-46960b218079}|{f92c1155-97b3-40f4-9d5b-7efa897524bb}|{c8e14311-4b2d-4eb0-9a6b-062c6912f50e}|{45621564-b408-4c29-8515-4cf1f26e4bc3}|{27380afd-f42a-4c25-b57d-b9012e0d5d48})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "2d4fe65b-6c02-4461-baa8-dda52e688cf6", + "last_modified": 1516829040469 + }, + { + "guid": "/^({4dac7c77-e117-4cae-a9f0-6bd89e9e26ab}|{cc689da4-203f-4a0c-a7a6-a00a5abe74c5}|{0eb4672d-58a6-4230-b74c-50ca3716c4b0}|{06a71249-ef35-4f61-b2c8-85c3c6ee5617}|{5280684d-f769-43c9-8eaa-fb04f7de9199}|{c2341a34-a3a0-4234-90cf-74df1db0aa49}|{85e31e7e-3e3a-42d3-9b7b-0a2ff1818b33}|{b5a35d05-fa28-41b5-ae22-db1665f93f6b}|{1bd8ba17-b3ed-412e-88db-35bc4d8771d7}|{a18087bb-4980-4349-898c-ca1b7a0e59cd}|{488e190b-d1f6-4de8-bffb-0c90cc805b62})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9a3fd797-0ab8-4286-9a1b-2b6c97f9075b", + "last_modified": 1516829006347 + }, + { + "guid": "/^({f6df4ef7-14bd-43b5-90c9-7bd02943789c}|{ccb7b5d6-a567-40a2-9686-a097a8b583dd}|{9b8df895-fcdd-452a-8c46-da5be345b5bc}|{5cf77367-b141-4ba4-ac2a-5b2ca3728e81}|{ada56fe6-f6df-4517-9ed0-b301686a34cc}|{95c7ae97-c87e-4827-a2b7-7b9934d7d642}|{e7b978ae-ffc2-4998-a99d-0f4e2f24da82}|{115a8321-4414-4f4c-aee6-9f812121b446}|{bf153de7-cdf2-4554-af46-29dabfb2aa2d}|{179710ba-0561-4551-8e8d-1809422cb09f}|{9d592fd5-e655-461a-9b28-9eba85d4c97f})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "aae78cd5-6b26-472e-ab2d-db4105911250", + "last_modified": 1516828973824 + }, + { + "guid": "/^({30972e0a-f613-4c46-8c87-2e59878e7180}|{0599211f-6314-4bf9-854b-84cb18da97f8}|{4414af84-1e1f-449b-ac85-b79f812eb69b}|{2a8bec00-0ab0-4b4d-bd3d-4f59eada8fd8}|{bea8866f-01f8-49e9-92cd-61e96c05d288}|{046258c9-75c5-429d-8d5b-386cfbadc39d}|{c5d359ff-ae01-4f67-a4f7-bf234b5afd6e}|{fdc0601f-1fbb-40a5-84e1-8bbe96b22502}|{85349ea6-2b5d-496a-9379-d4be82c2c13d}|{640c40e5-a881-4d16-a4d0-6aa788399dd2}|{d42328e1-9749-46ba-b35c-cce85ddd4ace})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "750aa293-3742-46b5-8761-51536afecaef", + "last_modified": 1516828938683 + }, + { + "guid": "/^({d03b6b0f-4d44-4666-a6d6-f16ad9483593}|{767d394a-aa77-40c9-9365-c1916b4a2f84}|{a0ce2605-b5fc-4265-aa65-863354e85058}|{b7f366fa-6c66-46bf-8df2-797c5e52859f}|{4ad16913-e5cb-4292-974c-d557ef5ec5bb}|{3c3ef2a3-0440-4e77-9e3c-1ca8d48f895c}|{543f7503-3620-4f41-8f9e-c258fdff07e9}|{98363f8b-d070-47b6-acc6-65b80acac4f3}|{5af74f5a-652b-4b83-a2a9-f3d21c3c0010}|{484e0ba4-a20b-4404-bb1b-b93473782ae0}|{b99847d6-c932-4b52-9650-af83c9dae649})$/", + "prefs": [], + "schema": 1516828883529, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "a29aed6f-6546-4fa2-8131-df5c9a5427af", + "last_modified": 1516828911059 + }, + { + "guid": "/^({2bb68b03-b528-4133-9fc4-4980fbb4e449}|{231e58ac-0f3c-460b-bb08-0e589360bec7}|{a506c5af-0f95-4107-86f8-3de05e2794c9}|{8886a262-1c25-490b-b797-2e750dd9f36b}|{65072bef-041f-492e-8a51-acca2aaeac70}|{6fa41039-572b-44a4-acd4-01fdaebf608d}|{87ba49bd-daba-4071-aedf-4f32a7e63dbe}|{95d58338-ba6a-40c8-93fd-05a34731dc0e}|{4cbef3f0-4205-4165-8871-2844f9737602}|{1855d130-4893-4c79-b4aa-cbdf6fee86d3}|{87dcb9bf-3a3e-4b93-9c85-ba750a55831a})$/", + "prefs": [], + "schema": 1516822896448, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are malicious add-ons that automatically close the Add-ons Manager.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5c092b0d-7205-43a1-aa75-b7a42372fb52", + "last_modified": 1516828883523 + }, + { + "guid": "/^({fce89242-66d3-4946-9ed0-e66078f172fc})|({0c4df994-4f4a-4646-ae5d-8936be8a4188})|({6cee30bc-a27c-43ea-ac72-302862db62b2})|({e08ebf0b-431d-4ed1-88bb-02e5db8b9443})$/", + "prefs": [], + "schema": 1516650096284, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1432560", + "why": "These are malicious add-ons that make it hard for the user to be removed.", + "name": "FF AntiVir Monitoring" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9dfeee42-e6a8-49e0-8979-0648f7368239", + "last_modified": 1516744119329 + }, + { + "guid": "/^(\\{1490068c-d8b7-4bd2-9621-a648942b312c\\})|(\\{d47ebc8a-c1ea-4a42-9ca3-f723fff034bd\\})|(\\{83d6f65c-7fc0-47d0-9864-a488bfcaa376\\})|(\\{e804fa4c-08e0-4dae-a237-8680074eba07\\})|(\\{ea618d26-780e-4f0f-91fd-2a6911064204\\})|(\\{ce93dcc7-f911-4098-8238-7f023dcdfd0d\\})|(\\{7eaf96aa-d4e7-41b0-9f12-775c2ac7f7c0\\})|(\\{b019c485-2a48-4f5b-be13-a7af94bc1a3e\\})|(\\{9b8a3057-8bf4-4a9e-b94b-867e4e71a50c\\})|(\\{eb3ebb14-6ced-4f60-9800-85c3de3680a4\\})|(\\{01f409a5-d617-47be-a574-d54325fe05d1\\})$/", + "prefs": [], + "schema": 1516394914836, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are a set of malicious add-ons that block the add-ons manager tab from opening so they can't be uninstalled.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5bf72f70-a611-4845-af3f-d4dabe8862b6", + "last_modified": 1516394982586 + }, + { + "guid": "/^(\\{ac06c6b2-3fd6-45ee-9237-6235aa347215\\})|(\\{d461cc1b-8a36-4ff0-b330-1824c148f326\\})|(\\{d1ab5ebd-9505-481d-a6cd-6b9db8d65977\\})|(\\{07953f60-447e-4f53-a5ef-ed060487f616\\})|(\\{2d3c5a5a-8e6f-4762-8aff-b24953fe1cc9\\})|(\\{f82b3ad5-e590-4286-891f-05adf5028d2f\\})|(\\{f96245ad-3bb0-46c5-8ca9-2917d69aa6ca\\})|(\\{2f53e091-4b16-4b60-9cae-69d0c55b2e78\\})|(\\{18868c3a-a209-41a6-855d-f99f782d1606\\})|(\\{47352fbf-80d9-4b70-9398-fb7bffa3da53\\})$/", + "prefs": [], + "schema": 1516311993443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1431748", + "why": "These are a set of malicious add-ons that block the add-ons manager tab from opening so they can't be uninstalled.", + "name": "FF Tool" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "4ca8206f-bc2a-4428-9439-7f3142dc08db", + "last_modified": 1516394914828 + }, + { + "guid": "{5b0f6d3c-10fd-414c-a135-dffd26d7de0f}", + "prefs": [], + "schema": 1516131689499, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1430577", + "why": "This is a malicious add-on that executes remote scripts, redirects popular search URLs and tracks users.", + "name": "P Birthday" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "8088b39a-3e6d-4a17-a22f-3f95c0464bd6", + "last_modified": 1516303320468 + }, + { + "guid": "{1490068c-d8b7-4bd2-9621-a648942b312c}", + "prefs": [], + "schema": 1515267698296, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1428754", + "why": "This add-on is using a deceptive name and performing unwanted actions on users' systems.", + "name": "FF Safe Helper" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "674b6e19-f087-4706-a91d-1e723ed6f79e", + "last_modified": 1515433728497 + }, + { + "guid": "{dfa727cb-0246-4c5a-843a-e4a8592cc7b9}", + "prefs": [], + "schema": 1514922095288, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1426582", + "why": "Version 2.0.0 shipped with a hidden coin miner, which degrades performance in users who have it enabled. Version 1.2.3 currently available on AMO is not affected.", + "name": "Open With Adobe PDF Reader 2.0.0" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.0.0", + "minVersion": "2.0.0" + } + ], + "id": "455772a3-8360-4f5a-9a5f-a45b904d0b51", + "last_modified": 1515007270887 + }, + { + "guid": "{d03b6b0f-4d44-4666-a6d6-f16ad9483593}", + "prefs": [], + "schema": 1513366896461, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1425581", + "why": "This is a malicious add-on posing as a legitimate update.", + "name": "FF Guard Tool (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "10d9ce89-b8d4-4b53-b3d7-ecd192681f4e", + "last_modified": 1513376470395 + }, + { + "guid": "{7e907a15-0a4c-4ff4-b64f-5eeb8f841349}", + "prefs": [], + "schema": 1510083698490, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1411885", + "why": "This is a malicious add-on posing as a legitimate update.", + "name": "Manual Update" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "f7569261-f575-4719-8202-552b20d013b0", + "last_modified": 1510168860382 + }, + { + "guid": "{3602008d-8195-4860-965a-d01ac4f9ca96}", + "prefs": [], + "schema": 1509120801051, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1411885", + "why": "This is a malicious add-on posing as a legitimate antivirus.\n", + "name": "Manual Antivirus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "28c805a9-e692-4ef8-b3ae-14e085c19ecd", + "last_modified": 1509120934909 + }, + { + "guid": "{87010166-e3d0-4db5-a394-0517917201df}", + "prefs": [], + "schema": 1509120801051, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1411885", + "why": "This is a malicious add-on posing as a legitimate antivirus.\n", + "name": "Manual Antivirus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "84dd8a02-c879-4477-8ea7-bf2f225b0940", + "last_modified": 1509120881470 + }, + { + "guid": "{8ab60777-e899-475d-9a4f-5f2ee02c7ea4}", + "prefs": [], + "schema": 1509120801051, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1411885", + "why": "This is a malicious add-on posing as a legitimate antivirus.\n", + "name": "Manual Antivirus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "ccebab59-7190-4258-8faa-a0b752dd5301", + "last_modified": 1509120831329 + }, + { + "guid": "{368eb817-31b4-4be9-a761-b67598faf9fa}", + "prefs": [], + "schema": 1509046897080, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1411885", + "why": "This is a malicious add-on posing as a legitimate antivirus.", + "name": "Manual Antivirus" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "9abc7502-bd6f-40d7-b035-abe721345360", + "last_modified": 1509120801043 + }, + { + "guid": "@68eba425-7a05-4d62-82b1-1d6d5a51716b", + "prefs": [], + "schema": 1505072496256, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1398905", + "why": "Misleads users into thinking this is a security and privacy tool (also distributed on a site that makes it look like an official Mozilla product).", + "name": "SearchAssist Incognito" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "595e0e53-b76b-4188-a160-66f29c636094", + "last_modified": 1505211411253 + }, + { + "guid": "@H99KV4DO-UCCF-9PFO-9ZLK-8RRP4FVOKD9O", + "prefs": [], + "schema": 1502483549048, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1340877", + "why": "This is a malicious add-on that is being installed silently.", + "name": "FF Adr (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "5df16afc-c804-43c9-9de5-f1835403e5fb", + "last_modified": 1502483601731 + }, + { + "guid": "@DA3566E2-F709-11E5-8E87-A604BC8E7F8B", + "prefs": [], + "schema": 1502480491460, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1340877", + "why": "This is a malicious add-on that is being installed silently into users' systems.", + "name": "SimilarWeb (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "0a47a2f7-f07c-489b-bd39-88122a2dfe6a", + "last_modified": 1502483549043 + }, + { + "guid": "/^({95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}|{E3605470-291B-44EB-8648-745EE356599A}|{95E5E0AD-65F9-4FFC-A2A2-0008DCF6ED25}|{FF20459C-DA6E-41A7-80BC-8F4FEFD9C575}|{6E727987-C8EA-44DA-8749-310C0FBE3C3E}|{12E8A6C2-B125-479F-AB3C-13B8757C7F04}|{EB6628CF-0675-4DAE-95CE-EFFA23169743})$/", + "prefs": [], + "schema": 1494022576295, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1362585", + "why": "All of these add-ons have been identified as malware, and are being installed in Firefox globally, most likely via a malicious application installer.", + "name": "Malicious globally-installed add-ons" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "3fd71895-7fc6-4f3f-aa22-1cbb0c5fd922", + "last_modified": 1494024191520 + }, + { + "guid": "@safesearchscoutee", + "prefs": [], + "schema": 1494013289942, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1362553", + "why": "This add-on intercepts queries sent to search engines and replaces them with its own, without user consent.", + "name": "SafeSearch Incognito (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0" + } + ], + "id": "edad04eb-ea16-42f3-a4a7-20dded33cc37", + "last_modified": 1494022568654 + }, + { + "guid": "msktbird@mcafee.com", + "prefs": [], + "schema": 1493150718059, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1354912", + "why": "These versions of this add-on are known to cause frequent crashes in Thunderbird.", + "name": "McAfee Anti-Spam Thunderbird Extension 2.0 and lower" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.0", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{3550f703-e582-4d05-9a08-453d09bdfdc6}", + "maxVersion": "*", + "minVersion": "0" + } + ] + } + ], + "id": "9e86d1ff-727a-45e3-9fb6-17f32666daf2", + "last_modified": 1493332747360 + }, + { + "guid": "/^(\\{11112503-5e91-4299-bf4b-f8c07811aa50\\})|(\\{501815af-725e-45be-b0f2-8f36f5617afc\\})$/", + "prefs": [], + "schema": 1491421290217, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1354045", + "why": "This add-on steals user credentials for popular websites from Facebook.", + "name": "Flash Player Updater (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c142360c-4f93-467e-9717-b638aa085d95", + "last_modified": 1491472107658 + }, + { + "guid": "fr@fbt.ovh", + "prefs": [], + "schema": 1490898754477, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1351689", + "why": "Scam add-on that silently steals user credentials of popular websites", + "name": "Adobe Flash Player (Malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0f8344d0-8211-49a1-81be-c0084b3da9b1", + "last_modified": 1490898787752 + }, + { + "guid": "{95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}", + "prefs": [], + "schema": 1487179851382, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1338690", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that is silently installed in users' systems.", + "name": "youtube adblock (malware)" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "04b25e3d-a725-493e-be07-cbd74fb37ea7", + "last_modified": 1487288975999 + }, + { + "guid": "ext@alibonus.com", + "prefs": [], + "schema": 1485297431051, + "blockID": "i1524", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1333471", + "who": "All Firefox users who have these versions installed.", + "why": "Versions 1.20.9 and lower of this add-on contain critical security issues.", + "name": "Alibonus 1.20.9 and lower", + "created": "2017-01-24T22:45:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.20.9", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a015d5a4-9184-95db-0c74-9262af2332fa", + "last_modified": 1485301116629 + }, + { + "guid": "/^(ciscowebexstart1@cisco\\.com|ciscowebexstart_test@cisco\\.com|ciscowebexstart@cisco\\.com|ciscowebexgpc@cisco\\.com)$/", + "prefs": [], + "schema": 1485212610474, + "blockID": "i1522", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1333225", + "who": "All Firefox users who have any Cisco WebEx add-ons installed.", + "why": "A critical security vulnerability has been discovered in Cisco WebEx add-ons that enable malicious websites to execute code on the user's system.", + "name": "Cisco WebEx add-ons", + "created": "2017-01-23T22:55:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0.1", + "minVersion": "1.0.0", + "targetApplication": [] + } + ], + "id": "30368779-1d3b-490a-0a34-253085af7754", + "last_modified": 1485215014902 + }, + { + "guid": "googlotim@gmail.com", + "prefs": [], + "schema": 1483389810787, + "blockID": "i1492", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1328594", + "who": "All users who have Savogram version 1.3.2 installed. Version 1.3.1 doesn't have this problem and can be installed from the <a href=\"https://addons.mozilla.org/addon/savogram/\">add-on page</a>. Note that this is an older version, so affected users won't be automatically updated to it. New versions should correct this problem if they become available.", + "why": "Version 1.3.2 of this add-on loads remote code and performs DOM injection in an unsafe manner.", + "name": "Savogram 1.3.2", + "created": "2017-01-05T19:58:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.3.2", + "minVersion": "1.3.2", + "targetApplication": [] + } + ], + "id": "0756ed76-7bc7-ec1e-aba5-3a9fac2107ba", + "last_modified": 1483646608603 + }, + { + "guid": "support@update-firefox.com", + "prefs": [], + "schema": 1483387107003, + "blockID": "i21", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=629717", + "who": "All users of the add-on in all Mozilla applications.", + "why": "This add-on is adware/spyware masquerading as a Firefox update mechanism.", + "name": "Browser Update (spyware)", + "created": "2011-01-31T16:23:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dfb06be8-3594-28e4-d163-17e27119f15d", + "last_modified": 1483389809169 + }, + { + "guid": "{2224e955-00e9-4613-a844-ce69fccaae91}", + "prefs": [], + "schema": 1483387107003, + "blockID": "i7", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=512406", + "who": "All users of Internet Saving Optimizer for all Mozilla applications.", + "why": "This add-on causes a high volume of Firefox crashes and is considered malware.", + "name": "Internet Saving Optimizer (extension)", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b9efb796-97c2-6434-d28f-acc83436f8e5", + "last_modified": 1483389809147 + }, + { + "guid": "supportaccessplugin@gmail.com", + "prefs": [], + "schema": 1483387107003, + "blockID": "i43", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=693673", + "who": "All users with Firefox Access Plugin installed", + "why": "This add-on is spyware that reports all visited websites to a third party with no user value.", + "name": "Firefox Access Plugin (spyware)", + "created": "2011-10-11T11:24:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1ed230a4-e174-262a-55ab-0c33f93a2529", + "last_modified": 1483389809124 + }, + { + "guid": "{8CE11043-9A15-4207-A565-0C94C42D590D}", + "prefs": [], + "schema": 1483387107003, + "blockID": "i10", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=541302", + "who": "All users of this add-on in all Mozilla applications.", + "why": "This add-on secretly hijacks all search results in most major search engines and masks as a security add-on.", + "name": "Internal security options editor (malware)", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e2e0ac09-6d68-75f5-2424-140f51904876", + "last_modified": 1483389809102 + }, + { + "guid": "admin@youtubespeedup.com", + "prefs": [], + "schema": 1483387107003, + "blockID": "i48", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=714221", + "who": "All users with any version of Youtube Speed UP! installed on any Mozilla product.", + "why": "This add-on hijacks your Facebook account.", + "name": "Youtube Speed UP! (malware)", + "created": "2011-12-29T19:48:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a93922c4-8a8a-5230-8f76-76fecb0653b6", + "last_modified": 1483389809057 + }, + { + "guid": "pink@rosaplugin.info", + "prefs": [], + "schema": 1482945809444, + "blockID": "i84", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=743484", + "who": "All Firefox users who have this add-on installed", + "why": "Add-on acts like malware and performs user actions on Facebook without their consent.", + "name": "Facebook Rosa (malware)", + "created": "2012-04-09T10:13:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "66ad8de9-311d-076c-7356-87fde6d30d8f", + "last_modified": 1482945810971 + }, + { + "guid": "videoplugin@player.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i90", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=752483", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware disguised as a Flash Player update. It can hijack Google searches and Facebook accounts.", + "name": "FlashPlayer 11 (malware)", + "created": "2012-05-07T08:58:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d25943f1-39ef-b9ec-ab77-baeef3498365", + "last_modified": 1482945810949 + }, + { + "guid": "youtb3@youtb3.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i60", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=723753", + "who": "All Firefox users who have this extension installed.", + "why": "Malicious extension installed under false pretenses.", + "name": "Video extension (malware)", + "created": "2012-02-02T16:38:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cae3093f-a7b3-5352-a264-01dbfbf347ce", + "last_modified": 1482945810927 + }, + { + "guid": "{8f42fb8b-b6f6-45de-81c0-d6d39f54f971}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i82", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=743012", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on maliciously manipulates Facebook and is installed under false pretenses.", + "name": "Face Plus (malware)", + "created": "2012-04-09T10:04:28Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "09319ab3-55e7-fec1-44e0-84067d014b9b", + "last_modified": 1482945810904 + }, + { + "guid": "{95ff02bc-ffc6-45f0-a5c8-619b8226a9de}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i105", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=763065", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that inserts scripts into Facebook and hijacks the user's session.\r\n", + "name": "Eklenti Dünyası (malware)", + "created": "2012-06-08T14:34:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "afbbc08d-2414-f51e-fdb8-74c0a2d90323", + "last_modified": 1482945810858 + }, + { + "guid": "{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i77", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=738419", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware that is installed under false pretenses as an Adobe plugin.", + "name": "Adobe Flash (malware)", + "created": "2012-03-22T14:39:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "81753a93-382d-5f9d-a4ca-8a21b679ebb1", + "last_modified": 1482945810835 + }, + { + "guid": "youtube@youtube3.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i57", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=722823", + "who": "All Firefox users that have installed this add-on.", + "why": "Malware installed on false pretenses.", + "name": "Divx 2012 Plugin (malware)", + "created": "2012-01-31T13:54:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4a93a0eb-a513-7272-6199-bc4d6228ff50", + "last_modified": 1482945810811 + }, + { + "guid": "{392e123b-b691-4a5e-b52f-c4c1027e749c}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i109", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=769781", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on pretends to be developed by Facebook and injects scripts that manipulate users' Facebook accounts.", + "name": "Zaman Tuneline Hayır! (malware)", + "created": "2012-06-29T13:20:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b9a805aa-cae7-58d6-5a53-2af4442e4cf6", + "last_modified": 1482945810788 + }, + { + "guid": "msntoolbar@msn.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i18", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=599971", + "who": "Users of Bing Bar 6.0 and older for all versions of Firefox.", + "why": "This add-on has security issues and was blocked at Microsoft's request. For more information, please see <a href=\"http://support.microsoft.com/kb/2430460\">this article</a>.", + "name": "Bing Bar", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "6.*", + "minVersion": " 0", + "targetApplication": [] + } + ], + "id": "9b2f2039-b997-8993-d6dc-d881bc1ca7a1", + "last_modified": 1482945810764 + }, + { + "guid": "yasd@youasdr3.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i104", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=763065", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that inserts scripts into Facebook and hijacks the user's session.\r\n", + "name": "Play Now (malware)", + "created": "2012-06-08T14:33:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8a352dff-d09d-1e78-7feb-45dec7ace5a5", + "last_modified": 1482945810740 + }, + { + "guid": "fdm_ffext@freedownloadmanager.org", + "prefs": [], + "schema": 1482945809444, + "blockID": "i2", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=408445", + "who": "Users of Firefox 3 and later with versions 1.0 through 1.3.1 of Free Download Manager", + "why": "This add-on causes a high volume of crashes.", + "name": "Free Download Manager", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.3.1", + "minVersion": "1.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "3.0a1" + } + ] + } + ], + "id": "fc46f8e7-0489-b90f-a373-d93109479ca5", + "last_modified": 1482945810393 + }, + { + "guid": "flash@adobe.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i56", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=722526", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on poses as an Adobe Flash update and injects malicious scripts into web pages. It hides itself in the Add-ons Manager.", + "name": "Adobe Flash Update (malware)", + "created": "2012-01-30T15:41:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "696db959-fb0b-8aa4-928e-65f157cdd77a", + "last_modified": 1482945810371 + }, + { + "guid": "youtubeer@youtuber.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i66", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=726787", + "who": "All Firefox users who have installed this add-on.", + "why": "Add-on behaves maliciously, and is installed under false pretenses.", + "name": "Plug VDS (malware)", + "created": "2012-02-13T15:44:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0878ce4e-b476-ffa3-0e06-21a65b7917a1", + "last_modified": 1482945810348 + }, + { + "guid": "flash@adobee.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i83", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=743497", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware installed under false pretenses.", + "name": "FlashPlayer 11 (malware)", + "created": "2012-04-09T10:08:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "09bb4661-331c-f7ba-865b-9e085dc437af", + "last_modified": 1482945810259 + }, + { + "guid": "youtube@2youtube.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i71", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=730399", + "who": "All Firefox users who have installed this add-on.", + "why": "Extension is malware, installed under false pretenses.", + "name": "YouTube extension (malware)", + "created": "2012-02-27T10:23:23Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5d389c1f-b3a0-b06f-6ffb-d1e8aa055e3c", + "last_modified": 1482945810236 + }, + { + "guid": "webmaster@buzzzzvideos.info", + "prefs": [], + "schema": 1482945809444, + "blockID": "i58", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=722844", + "who": "All Firefox users who have installed this add-on.", + "why": "Malware add-on that is installed under false pretenses.", + "name": "Buzz Video (malware)", + "created": "2012-01-31T14:51:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f7aab105-e2c2-42f5-d9be-280eb9c0c8f7", + "last_modified": 1482945810213 + }, + { + "guid": "play5@vide04flash.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i92", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=755443", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on impersonates a Flash Player update (poorly), and inserts malicious scripts into Facebook.", + "name": "Lastest Flash PLayer (malware)", + "created": "2012-05-15T13:27:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7190860e-fc1f-cd9f-5d25-778e1e9043b2", + "last_modified": 1482945810191 + }, + { + "guid": "support3_en@adobe122.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i97", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=759164", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware disguised as the Flash Player plugin.", + "name": "FlashPlayer 11 (malware)", + "created": "2012-05-28T13:42:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "decf93a1-2bb0-148c-a1a6-10b3757b554b", + "last_modified": 1482945810168 + }, + { + "guid": "a1g0a9g219d@a1.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i73", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=736275", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware disguised as Flash Player. It steals user cookies and sends them to a remote location.", + "name": "Flash Player (malware)", + "created": "2012-03-15T15:03:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6dd66b43-897d-874a-2227-54e240b8520f", + "last_modified": 1482945810146 + }, + { + "guid": "ghostviewer@youtube2.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i59", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=723683", + "who": "All Firefox users who have installed this add-on.", + "why": "Malicious add-on that automatically posts to Facebook.", + "name": "Ghost Viewer (malware)", + "created": "2012-02-02T16:32:15Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "06dfe833-8c3d-90ee-3aa8-37c3c28f7c56", + "last_modified": 1482945810123 + }, + { + "guid": "kdrgun@gmail.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i103", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=763065", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that inserts scripts into Facebook and hijacks the user's session.", + "name": "Timeline Kapat (malware)", + "created": "2012-06-08T14:32:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a9a46ab2-2f56-1046-201c-5faa3435e248", + "last_modified": 1482945810078 + }, + { + "guid": "youtube2@youtube2.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i67", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=728476", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware, installed under false pretenses.", + "name": "Youtube Online (malware)", + "created": "2012-02-18T09:10:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "14650ece-295b-a667-f9bc-a3d973e2228c", + "last_modified": 1482945810055 + }, + { + "guid": "admin@youtubeplayer.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i51", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=717165", + "who": "All Firefox users with this extension installed.", + "why": "This add-on is malware, doing nothing more than inserting advertisements into websites through iframes.", + "name": "Youtube player (malware)", + "created": "2012-01-18T14:34:55Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "16b2ce94-88db-0d79-33fc-a93070ceb509", + "last_modified": 1482945809957 + }, + { + "guid": "youtubeee@youtuber3.com", + "prefs": [], + "schema": 1482945809444, + "blockID": "i96", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=758503", + "who": "All Firefox users who have installed this add-on.", + "why": "This is a malicious add-on that is disguised as a DivX plugin.", + "name": "Divx 2012 Plugins (malware)", + "created": "2012-05-25T09:26:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f01be9cb-5cf2-774a-a4d7-e210a24db5b9", + "last_modified": 1482945809912 + }, + { + "guid": "{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i17", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=599971", + "who": "Users of version 2.2 of this add-on in all versions of Firefox.", + "why": "This add-on has security issues and was blocked at Microsoft's request. For more information, please see <a href=\"http://support.microsoft.com/kb/2430460\">this article</a>.", + "name": "Default Manager (Microsoft)", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.2", + "minVersion": "2.2", + "targetApplication": [] + } + ], + "id": "38be28ac-2e30-37fa-4332-852a55fafb43", + "last_modified": 1482945809886 + }, + { + "guid": "{68b8676b-99a5-46d1-b390-22411d8bcd61}", + "prefs": [], + "schema": 1482945809444, + "blockID": "i93", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=755635", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that post content on Facebook accounts and steals user data.", + "name": "Zaman Tünelini Kaldır! (malware)", + "created": "2012-05-16T10:44:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "733aff15-9b1f-ec04-288f-b78a55165a1c", + "last_modified": 1482945809863 + }, + { + "guid": "applebeegifts@mozilla.doslash.org", + "prefs": [], + "schema": 1482945809444, + "blockID": "i54", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=721562", + "who": "All Firefox users that install this add-on.", + "why": "Add-on is malware installed under false pretenses.", + "name": "Applebees Gift Card (malware)", + "created": "2012-01-26T16:17:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1372c8ab-5452-745a-461a-aa78e3e12c4b", + "last_modified": 1482945809840 + }, + { + "guid": "activity@facebook.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i65", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=726803", + "who": "All Firefox users who have installed this add-on.", + "why": "Add-on behaves maliciously and poses as an official Facebook add-on.", + "name": "Facebook extension (malware)", + "created": "2012-02-13T15:41:02Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "79ad1c9b-0828-7823-4574-dd1cdd46c3d6", + "last_modified": 1482945809437 + }, + { + "guid": "jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack", + "prefs": [], + "schema": 1482945112982, + "blockID": "i62", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=724650", + "who": "All Firefox users who have installed this add-on.", + "why": "Add-on is installed under false pretenses and delivers malware.", + "name": "YouTube extension (malware)", + "created": "2012-02-06T14:46:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5ae1e642-b53c-54c0-19e7-5562cfdac3a3", + "last_modified": 1482945809415 + }, + { + "guid": "{B7082FAA-CB62-4872-9106-E42DD88EDE45}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i25", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=637542", + "who": "Users of McAfee SiteAdvisor below version 3.3.1 for Firefox 4.\r\n\r\nUsers of McAfee SiteAdvisor 3.3.1 and below for Firefox 5 and higher.", + "why": "This add-on causes a high volume of crashes and is incompatible with certain versions of Firefox.", + "name": "McAfee SiteAdvisor", + "created": "2011-03-14T15:53:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.3.0.*", + "minVersion": "0.1", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "3.7a1" + } + ] + } + ], + "id": "c950501b-1f08-2ab2-d817-7c664c0d16fe", + "last_modified": 1482945809393 + }, + { + "guid": "{B7082FAA-CB62-4872-9106-E42DD88EDE45}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i38", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=660111", + "who": "Users of McAfee SiteAdvisor below version 3.3.1 for Firefox 4.\r\n\r\nUsers of McAfee SiteAdvisor 3.3.1 and below for Firefox 5 and higher.", + "why": "This add-on causes a high volume of crashes and is incompatible with certain versions of Firefox.", + "name": "McAfee SiteAdvisor", + "created": "2011-05-27T13:55:02Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "3.3.1", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "5.0a1" + } + ] + } + ], + "id": "f11de388-4511-8d06-1414-95d3b2b122c5", + "last_modified": 1482945809371 + }, + { + "guid": "youtube@youtube7.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i55", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=721646", + "who": "All Firefox users with this add-on installed.", + "why": "This is malware posing as video software.", + "name": "Plugin Video (malware)", + "created": "2012-01-27T09:39:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "08ceedf5-c7c1-f54f-db0c-02f01f0e319a", + "last_modified": 1482945809304 + }, + { + "guid": "crossriderapp3924@crossrider.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i76", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=738282", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on compromises Facebook privacy and security and spams friends lists without user intervention.", + "name": "Fblixx (malware)", + "created": "2012-03-22T10:38:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "39d0a019-62fb-837b-1f1f-6831e56442b5", + "last_modified": 1482945809279 + }, + { + "guid": "{45147e67-4020-47e2-8f7a-55464fb535aa}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i86", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=748993", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on injects scripts into Facebook and performs malicious activity.", + "name": "Mukemmel Face+", + "created": "2012-04-25T16:33:21Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "960443f9-cf48-0b71-1ff2-b8c34a3411ea", + "last_modified": 1482945809255 + }, + { + "guid": "{4B3803EA-5230-4DC3-A7FC-33638F3D3542}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i4", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=441649", + "who": "Users of Firefox 3 and later with version 1.2 of Crawler Toolbar", + "why": "This add-on causes a high volume of crashes.", + "name": "Crawler Toolbar", + "created": "2008-07-08T10:23:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.2", + "minVersion": "1.2", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "3.0a1" + } + ] + } + ], + "id": "a9818d53-3a6a-8673-04dd-2a16f5644215", + "last_modified": 1482945809232 + }, + { + "guid": "flashupdate@adobe.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i68", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=722526", + "who": "All Firefox users who have this add-on installed.", + "why": "Add-on is malware, installed under false pretenses.", + "name": "Flash Update (malware)", + "created": "2012-02-21T13:55:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1ba5b46e-790d-5af2-9580-a5f1e6e65522", + "last_modified": 1482945809208 + }, + { + "guid": "plugin@youtubeplayer.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i127", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=783356", + "who": "All users who have this add-on installed.", + "why": "This add-on tries to pass as a YouTube player and runs malicious scripts on webpages.", + "name": "Youtube Facebook Player (malware)", + "created": "2012-08-16T13:03:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "17a8bece-e2df-a55d-8a72-95faff028b83", + "last_modified": 1482945809185 + }, + { + "guid": "GifBlock@facebook.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i79", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=739482", + "who": "All Firefox users who have installed this extension.", + "why": "This extension is malicious and is installed under false pretenses.", + "name": "Facebook Essentials (malware)", + "created": "2012-03-27T10:53:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "728451e8-1273-d887-37e9-5712b1cc3bff", + "last_modified": 1482945809162 + }, + { + "guid": "ff-ext@youtube", + "prefs": [], + "schema": 1482945112982, + "blockID": "i52", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=719296", + "who": "All Firefox users that have this add-on installed.", + "why": "This add-on poses as a YouTube player while posting spam into Facebook account.", + "name": "Youtube player (malware)", + "created": "2012-01-19T08:26:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cd2dd72a-dd52-6752-a0cd-a4b312fd0b65", + "last_modified": 1482945809138 + }, + { + "guid": "ShopperReports@ShopperReports.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i22", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=630191", + "who": "Users of Shopper Reports version 3.1.22.0 in Firefox 4 and later.", + "why": "This add-on causes a high volume of Firefox crashes.", + "name": "Shopper Reports", + "created": "2011-02-09T17:03:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.1.22.0", + "minVersion": "3.1.22.0", + "targetApplication": [] + } + ], + "id": "f26b049c-d856-750f-f050-996e6bec7cbb", + "last_modified": 1482945809115 + }, + { + "guid": "{27182e60-b5f3-411c-b545-b44205977502}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i16", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=599971", + "who": "Users of version 1.0 of this add-on in all versions of Firefox.", + "why": "This add-on has security issues and was blocked at Microsoft's request. For more information, please see <a href=\"http://support.microsoft.com/kb/2430460\">this article</a>.", + "name": "Search Helper Extension (Microsoft)", + "created": "2011-03-31T16:28:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0", + "minVersion": "1.0", + "targetApplication": [] + } + ], + "id": "2655f230-11f3-fe4c-7c3d-757d37d5f9a5", + "last_modified": 1482945809092 + }, + { + "guid": "{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i46", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=712369", + "who": "Users of all versions of Nectar Search Toolbar in Firefox 9.", + "why": "This add-on causes crashes and other stability issues in Firefox.", + "name": "Nectar Search Toolbar", + "created": "2011-12-20T11:38:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0.1", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "9.0", + "minVersion": "9.0a1" + } + ] + } + ], + "id": "b660dabd-0dc0-a55c-4b86-416080b345d9", + "last_modified": 1482945809069 + }, + { + "guid": "support@daemon-tools.cc", + "prefs": [], + "schema": 1482945112982, + "blockID": "i5", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=459850", + "who": "Users of Daemon Tools Toolbar version 1.0.0.5 and older for all Mozilla applications.", + "why": "This add-on causes a high volume of crashes.", + "name": "Daemon Tools Toolbar", + "created": "2009-02-13T18:39:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0.0.5", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8cabafd3-576a-b487-31c8-ab59e0349a0e", + "last_modified": 1482945809045 + }, + { + "guid": "{a3a5c777-f583-4fef-9380-ab4add1bc2a8}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i53", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=719605", + "who": "All users of Firefox with this add-on installed.", + "why": "This add-on is being offered as an online movie viewer, when it reality it only inserts scripts and ads into known sites.", + "name": "Peliculas-FLV (malware)", + "created": "2012-01-19T15:58:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "2.0.3", + "minVersion": "2.0.3", + "targetApplication": [] + } + ], + "id": "07bc0962-60da-087b-c3ab-f2a6ab84d81c", + "last_modified": 1482945809021 + }, + { + "guid": "royal@facebook.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i64", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=725777", + "who": "All Firefox users who have installed this add-on.", + "why": "Malicious add-on posing as a Facebook tool.", + "name": "Facebook ! (malware)", + "created": "2012-02-09T13:24:23Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dd1d2623-0d15-c93e-8fbd-ba07b0299a44", + "last_modified": 1482945808997 + }, + { + "guid": "{28bfb930-7620-11e1-b0c4-0800200c9a66}", + "prefs": [], + "schema": 1482945112982, + "blockID": "i108", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=766852", + "who": "All Firefox user who have this add-on installed.", + "why": "This is malware disguised as an Adobe product. It spams Facebook pages.", + "name": "Aplicativo (malware)", + "created": "2012-06-21T09:24:11Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "908dc4fb-ebc9-cea1-438f-55e4507ba834", + "last_modified": 1482945808973 + }, + { + "guid": "socialnetworktools@mozilla.doslash.org", + "prefs": [], + "schema": 1482945112982, + "blockID": "i78", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=739441", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on hijacks the Facebook UI and adds scripts to track users.", + "name": "Social Network Tools (malware)", + "created": "2012-03-26T16:46:55Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1064cd25-3b87-64bb-b0a6-2518ad281574", + "last_modified": 1482945808950 + }, + { + "guid": "youtubeeing@youtuberie.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i98", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=759663", + "who": "All Firefox users who have installed this add-on.", + "why": "This add-on is malware disguised as a Youtube add-on.", + "name": "Youtube Video Player (malware)", + "created": "2012-05-30T09:30:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3484f860-56e1-28e8-5a70-cdcd5ab9d6ee", + "last_modified": 1482945808927 + }, + { + "guid": "pfzPXmnzQRXX6@2iABkVe.com", + "prefs": [], + "schema": 1482945112982, + "blockID": "i99", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=759950", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware disguised as a Flash Player update.", + "name": "Flash Player (malware)", + "created": "2012-05-30T17:10:18Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "29cc4abc-4f52-01f1-eb0b-cad84ba4db13", + "last_modified": 1482945808881 + }, + { + "guid": "/^(pdftoword@addingapps.com|jid0-EYTXLS0GyfQME5irGbnD4HksnbQ@jetpack|jid1-ZjJ7t75BAcbGCX@jetpack)$/", + "prefs": [], + "schema": 1482341309012, + "blockID": "i1425", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1325060", + "who": "All users who have any of the affected versions installed.", + "why": "A security vulnerability was discovered in old versions of the Add-ons SDK, which is exposed by certain old versions of add-ons. In the case of some add-ons that haven't been updated for a long time, all versions are being blocked.", + "name": "Various vulnerable add-on versions", + "created": "2016-12-21T17:23:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "150e639f-c832-63d0-a775-59313b2e1bf9", + "last_modified": 1482343886365 + }, + { + "guid": "{cc8f597b-0765-404e-a575-82aefbd81daf}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i380", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=866332", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts and performs unwanted actions on behalf of the user.", + "name": "Update My Browser (malware)", + "created": "2013-06-19T13:03:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4950d7aa-c602-15f5-a7a2-d844182d5cbd", + "last_modified": 1480349217152 + }, + { + "guid": "extension@FastFreeConverter.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i470", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:38:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "649dd933-debf-69b7-020f-496c2c9f99c8", + "last_modified": 1480349217071 + }, + { + "guid": "59D317DB041748fdB89B47E6F96058F3@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i694", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1053540", + "who": "All Firefox users who have this add-ons installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This is a suspicious add-on that appears to be installed without user consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "JsInjectExtension", + "created": "2014-08-21T13:46:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "75692bd4-18e5-a9be-7ec3-9327e159ef68", + "last_modified": 1480349217005 + }, + { + "guid": "/^({bfec236d-e122-4102-864f-f5f19d897f5e}|{3f842035-47f4-4f10-846b-6199b07f09b8}|{92ed4bbd-83f2-4c70-bb4e-f8d3716143fe})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i527", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949566", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and uses multiple IDs.", + "name": "KeyBar add-on", + "created": "2013-12-20T14:13:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6d68dd97-7965-0a84-8ca7-435aac3c8040", + "last_modified": 1480349216927 + }, + { + "guid": "support@vide1flash2.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i246", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=830159", + "who": "All Firefox users who have this add-on installed.", + "why": "This is an add-on that poses as the Adobe Flash Player and runs malicious code in the user's system.", + "name": "Lastest Adobe Flash Player (malware)", + "created": "2013-01-14T09:17:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2004fba1-74bf-a072-2a59-6e0ba827b541", + "last_modified": 1480349216871 + }, + { + "guid": "extension21804@extension21804.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i312", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835665", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, bypassing our third party install opt-in screen. Users who wish to continue using this extension can enable it in the Add-ons Manager.", + "name": "Coupon Companion", + "created": "2013-03-06T14:14:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b2cf1256-dadd-6501-1f4e-25902d408692", + "last_modified": 1480349216827 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i602", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:18:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.8.*", + "minVersion": "3.15.8", + "targetApplication": [] + } + ], + "id": "b2b4236d-5d4d-82b2-99cd-00ff688badf1", + "last_modified": 1480349216765 + }, + { + "guid": "{FE1DEEEA-DB6D-44b8-83F0-34FC0F9D1052}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i364", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=867670", + "who": "All Firefox users who have this add-on installed. Users who want to enable the add-on again can do so in the Add-ons Manager.", + "why": "This add-on is side-installed with other software, and blocks setting reversions attempted by users who want to recover their settings after they are hijacked by other add-ons.", + "name": "IB Updater", + "created": "2013-06-10T16:14:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a59b967c-66ca-7ad9-2dc6-d0ad37ded5fd", + "last_modified": 1480349216652 + }, + { + "guid": "vpyekkifgv@vpyekkifgv.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i352", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=872211", + "who": "All Firefox users who have this add-on installed.", + "why": "Uses a deceptive name and injects ads into pages without user consent.", + "name": "SQLlite Addon (malware)", + "created": "2013-05-14T13:42:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8fd981ab-7ee0-e367-d804-0efe29d63178", + "last_modified": 1480349216614 + }, + { + "guid": "thefoxonlybetter@quicksaver", + "prefs": [], + "schema": 1480349193877, + "blockID": "i702", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1053469", + "who": "All Firefox users who have any of these versions of the add-on installed.", + "why": "Certain versions of The Fox, Only Better weren't developed by the original developer, and are likely malicious in nature. This violates the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> for reusing an already existent ID.", + "name": "The Fox, Only Better (malicious versions)", + "created": "2014-08-27T10:05:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "1.10", + "targetApplication": [] + } + ], + "id": "60e54f6a-1b10-f889-837f-60a76a98fccc", + "last_modified": 1480349216512 + }, + { + "guid": "{f0e59437-6148-4a98-b0a6-60d557ef57f4}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i304", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=845975", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">installation guidelines</a> and is dropped silently into user's profiles.", + "name": "WhiteSmoke B", + "created": "2013-02-27T13:10:18Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0469e643-1a90-f9be-4aad-b347469adcbe", + "last_modified": 1480349216402 + }, + { + "guid": "xz123@ya456.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i486", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=939254", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware and is installed silently in violation of the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BetterSurf (malware)", + "created": "2013-11-15T13:34:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b9825a25-a96c-407e-e656-46a7948e5745", + "last_modified": 1480349215808 + }, + { + "guid": "{C7AE725D-FA5C-4027-BB4C-787EF9F8248A}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i424", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=860641", + "who": "Users of Firefox 23 or later who have RelevantKnowledge 1.0.0.2 or lower.", + "why": "Old versions of this add-on are causing startup crashes in Firefox 23, currently on the Beta channel. RelevantKnowledge users on Firefox 23 and above should update to version 1.0.0.3 of the add-on.", + "name": "RelevantKnowledge 1.0.0.2 and lower", + "created": "2013-07-01T10:45:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0.0.2", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "23.0a1" + } + ] + } + ], + "id": "c888d167-7970-4b3f-240f-2d8e6f14ded4", + "last_modified": 1480349215779 + }, + { + "guid": "superlrcs@svenyor.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i545", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949596", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.", + "why": "This add-on is in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, using multiple add-on IDs and potentially doing other unwanted activities.", + "name": "SuperLyrics", + "created": "2014-01-30T11:52:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "002cd4fa-4c2b-e28b-9220-4a520f4d9ec6", + "last_modified": 1480349215672 + }, + { + "guid": "mbrsepone@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i479", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=937331", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Mozilla Lightweight Pack (malware)", + "created": "2013-11-11T15:42:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0549645e-5f50-5089-1f24-6e7d3bfab8e0", + "last_modified": 1480349215645 + }, + { + "guid": "mbroctone@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i476", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=936590", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks the users' Facebook account.", + "name": "Mozilla Storage Service (malware)", + "created": "2013-11-08T15:32:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "92198396-8756-8d09-7f18-a68d29894f71", + "last_modified": 1480349215504 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i616", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:24:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.28.*", + "minVersion": "3.15.28", + "targetApplication": [] + } + ], + "id": "f11b485f-320e-233c-958b-a63377024fad", + "last_modified": 1480349215479 + }, + { + "guid": "/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i494", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=776404", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on changes search settings without user interaction, and fails to reset them after it is removed. It also uses multiple add-on IDs for no apparent reason. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "uTorrent and related", + "created": "2013-12-02T14:52:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "485210d0-8e69-3436-536f-5d1deeea4167", + "last_modified": 1480349215454 + }, + { + "guid": "{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i162", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=799266", + "who": "All Firefox users who have installed any of these add-ons.", + "why": "This block covers a number of malicious add-ons that deceive users, using names like \"Mozilla Safe Browsing\" and \"Translate This!\", and claiming they are developed by \"Mozilla Corp.\". They hijack searches and redirects users to pages they didn't intend to go to.\r\n\r\nNote: this block won't be active until bug 799266 is fixed.", + "name": "Mozilla Safe Browsing and others (Medfos malware)", + "created": "2012-10-11T12:25:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "07566aa3-4ff9-ac4f-9de9-71c77454b4da", + "last_modified": 1480349215428 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i614", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:23:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.26.*", + "minVersion": "3.15.26", + "targetApplication": [] + } + ], + "id": "ede541f3-1748-7b33-9bd6-80e2f948e14f", + "last_modified": 1480349215399 + }, + { + "guid": "/^({976cd962-e0ca-4337-aea7-d93fae63a79c}|{525ba996-1ce4-4677-91c5-9fc4ead2d245}|{91659dab-9117-42d1-a09f-13ec28037717}|{c1211069-1163-4ba8-b8b3-32fc724766be})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i522", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947485", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by being silently installed and using multiple add-on IDs.", + "name": "appbario7", + "created": "2013-12-20T13:15:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "580aed26-dc3b-eef8-fa66-a0a402447b7b", + "last_modified": 1480349215360 + }, + { + "guid": "jid0-O6MIff3eO5dIGf5Tcv8RsJDKxrs@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i552", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974041", + "who": "All Firefox users who have this extension installed.", + "why": "This extension is malware that attempts to make it impossible for a second extension and itself to be disabled, and also forces the new tab page to have a specific URL.", + "name": "Extension_Protected (malware)", + "created": "2014-02-19T15:26:37Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e53063b4-5702-5b66-c860-d368cba4ccb6", + "last_modified": 1480349215327 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i604", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:18:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.11.*", + "minVersion": "3.15.10", + "targetApplication": [] + } + ], + "id": "b910f779-f36e-70e1-b17a-8afb75988c03", + "last_modified": 1480349215302 + }, + { + "guid": "brasilescapefive@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i483", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=938473", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Facebook Video Pack (malware)", + "created": "2013-11-14T09:37:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "85ee7840-f262-ad30-eb91-74b3248fd13d", + "last_modified": 1480349215276 + }, + { + "guid": "brasilescapeeight@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i482", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=938476", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Mozilla Security Pack (malware)", + "created": "2013-11-14T09:36:55Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "457a5722-be90-5a9f-5fa0-4c753e9f324c", + "last_modified": 1480349215249 + }, + { + "guid": "happylyrics@hpyproductions.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i370", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=881815", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into Firefox without the users' consent, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Happy Lyrics", + "created": "2013-06-11T15:42:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "730e616d-94a7-df0c-d31a-98b7875d60c2", + "last_modified": 1480349215225 + }, + { + "guid": "search-snacks@search-snacks.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i872", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1082733", + "who": "All users who have this add-on installed. Users who wish to continue using the add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Search Snacks", + "created": "2015-03-04T14:37:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7567b06f-98fb-9400-8007-5d0357c345d9", + "last_modified": 1480349215198 + }, + { + "os": "WINNT", + "guid": "{ABDE892B-13A8-4d1b-88E6-365A6E755758}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i107", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=764210", + "who": "All Firefox users on Windows who have the RealPlayer Browser Record extension installed.", + "why": "The RealPlayer Browser Record extension is causing significant problems on Flash video sites like YouTube. This block automatically disables the add-on, but users can re-enable it from the Add-ons Manager if necessary.\r\n\r\nThis block shouldn't disable any other RealPlayer plugins, so watching RealPlayer content on the web should be unaffected.\r\n\r\nIf you still have problems playing videos on YouTube or elsewhere, please visit our <a href=\"http://support.mozilla.com/\">support site</a> for help.", + "name": "RealPlayer Browser Record Plugin", + "created": "2012-06-14T13:54:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "15.0.5", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e3b89e55-b35f-8694-6f0e-f856e57a191d", + "last_modified": 1480349215173 + }, + { + "guid": "amo-validator-bypass@example.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1058", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1227605", + "who": "All users who install this add-on.", + "why": "This add-on is a proof of concept of a malicious add-on that bypasses the code validator.", + "name": " AMO Validator Bypass", + "created": "2015-11-24T09:03:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "86e38e3e-a729-b5a2-20a8-4738b376eea6", + "last_modified": 1480349214743 + }, + { + "guid": "6lIy@T.edu", + "prefs": [], + "schema": 1480349193877, + "blockID": "i852", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128269", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "unIsaless", + "created": "2015-02-09T15:30:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "39798bc2-9c75-f172-148b-13f3ca1dde9b", + "last_modified": 1480349214613 + }, + { + "guid": "{a7f2cb14-0472-42a1-915a-8adca2280a2c}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i686", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1033809", + "who": "All users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-on Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "HomeTab", + "created": "2014-08-06T16:35:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "33a8f403-b2c8-cadf-e1ba-40b39edeaf18", + "last_modified": 1480349214537 + }, + { + "guid": "{CA8C84C6-3918-41b1-BE77-049B2BDD887C}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i862", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1131230", + "who": "All users who have this add-on installed. Users who wish to continue using the add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Ebay Shopping Assistant by Spigot", + "created": "2015-02-26T12:51:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9a9d6da2-90a1-5b71-8b24-96492d57dfd1", + "last_modified": 1480349214479 + }, + { + "guid": "update@firefox.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i374", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=781088", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that hijacks Facebook accounts.", + "name": "Premium Update (malware)", + "created": "2013-06-18T13:58:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "bb388413-60ea-c9d6-9a3b-c90df950c319", + "last_modified": 1480349214427 + }, + { + "guid": "sqlmoz@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i350", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=871610", + "who": "All Firefox users who have this extension installed.", + "why": "This extension is malware posing as Mozilla software. It hijacks Facebook accounts and spams other Facebook users.", + "name": "Mozilla Service Pack (malware)", + "created": "2013-05-13T09:43:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "715082e8-7a30-b27b-51aa-186c38e078f6", + "last_modified": 1480349214360 + }, + { + "guid": "mozillahmpg@mozilla.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i140", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=791867", + "who": "All Firefox users who have installed this add-on.", + "why": "This is a malicious add-on that tries to monetize on its users by embedding unauthorized affiliate codes on shopping websites, and sometimes redirecting users to alternate sites that could be malicious in nature.", + "name": "Google YouTube HD Player (malware)", + "created": "2012-09-17T16:04:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "98150e2e-cb45-1fee-8458-28d3602ec2ec", + "last_modified": 1480349214216 + }, + { + "guid": "astrovia@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i489", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=942699", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Facebook Security Service (malware)", + "created": "2013-11-25T12:40:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6f365ff4-e48f-8a06-d19d-55e19fba81f4", + "last_modified": 1480349214157 + }, + { + "guid": "{bbea93c6-64a3-4a5a-854a-9cc61c8d309e}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1126", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1251940", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that disables various security checks in Firefox.", + "name": "Tab Extension (malware)", + "created": "2016-02-29T21:58:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5acb9dcc-59d4-46d1-2a11-1194c4948239", + "last_modified": 1480349214066 + }, + { + "guid": "ffxtlbr@iminent.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i628", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=866943", + "who": "All Firefox users who have any of these add-ons installed. Users who wish to continue using them can enable them in the Add-ons Manager.", + "why": "These add-ons have been silently installed repeatedly, and change settings without user consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Iminent Minibar", + "created": "2014-06-26T15:47:15Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4387ad94-8500-d74d-68e3-20564a9aac9e", + "last_modified": 1480349214036 + }, + { + "guid": "{28387537-e3f9-4ed7-860c-11e69af4a8a0}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i40", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=665775", + "who": "Users of MediaBar versions 4.3.1.00 and below in all versions of Firefox.", + "why": "This add-on causes a high volume of crashes and is incompatible with certain versions of Firefox.", + "name": "MediaBar (2)", + "created": "2011-07-19T10:19:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "4.3.1.00", + "minVersion": "0.1", + "targetApplication": [] + } + ], + "id": "ff95664b-93e4-aa73-ac20-5ffb7c87d8b7", + "last_modified": 1480349214002 + }, + { + "guid": "{41e5ef7a-171d-4ab5-8351-951c65a29908}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i784", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "HelpSiteExpert", + "created": "2014-11-14T14:37:56Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0c05a0bb-30b4-979e-33a7-9f3955eba17d", + "last_modified": 1480349213962 + }, + { + "guid": "/^({2d7886a0-85bb-4bf2-b684-ba92b4b21d23}|{2fab2e94-d6f9-42de-8839-3510cef6424b}|{c02397f7-75b0-446e-a8fa-6ef70cfbf12b}|{8b337819-d1e8-48d3-8178-168ae8c99c36}|firefox@neurowise.info|firefox@allgenius.info)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i762", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1082599", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "These add-ons are silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SaveSense, neurowise, allgenius", + "created": "2014-10-17T16:58:10Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c5439f55-ace5-ad73-1270-017c0ba7b2ce", + "last_modified": 1480349213913 + }, + { + "guid": "{462be121-2b54-4218-bf00-b9bf8135b23f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i226", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812303", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently side-installed by other software, and doesn't do much more than changing the users' settings, without reverting them on removal.", + "name": "WhiteSmoke", + "created": "2012-11-29T16:27:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "994c6084-e864-0e4e-ac91-455083ee46c7", + "last_modified": 1480349213879 + }, + { + "guid": "firefox@browsefox.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i546", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=936244", + "who": "All Firefox users who have this add-on installed. If you want to continue using it, it can be enabled in the Add-ons Manager.", + "why": "This add-on is silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BrowseFox", + "created": "2014-01-30T12:26:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "407d8c84-8939-cd28-b284-9b680e529bf6", + "last_modified": 1480349213853 + }, + { + "guid": "{6926c7f7-6006-42d1-b046-eba1b3010315}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i382", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844956", + "who": "All Firefox users who have this add-on installed. Those who wish to continue using it can enable it again in the Add-ons Manager.", + "why": "This add-on is silently installed, bypassing the Firefox opt-in screen and violating our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "appbario7", + "created": "2013-06-25T12:05:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2367bd94-2bdd-c615-de89-023ba071a443", + "last_modified": 1480349213825 + }, + { + "guid": "05dd836e-2cbd-4204-9ff3-2f8a8665967d@a8876730-fb0c-4057-a2fc-f9c09d438e81.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i468", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935135", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be part of a <a href=\"https://www.drwebhk.com/en/virus_techinfo/Trojan.DownLoader9.50268.html\">Trojan software</a> package.", + "name": "Trojan.DownLoader9.50268 (malware)", + "created": "2013-11-07T14:43:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2fd53d9b-7096-f1fb-fbcb-2b40a6193894", + "last_modified": 1480349213774 + }, + { + "guid": "jid1-0xtMKhXFEs4jIg@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i586", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1011286", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware installed without user consent.", + "name": "ep (malware)", + "created": "2014-06-03T15:50:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "50ca2179-83ab-1817-163d-39ed2a9fbd28", + "last_modified": 1480349213717 + }, + { + "guid": "/^({16e193c8-1706-40bf-b6f3-91403a9a22be}|{284fed43-2e13-4afe-8aeb-50827d510e20}|{5e3cc5d8-ed11-4bed-bc47-35b4c4bc1033}|{7429e64a-1fd4-4112-a186-2b5630816b91}|{8c9980d7-0f09-4459-9197-99b3e559660c}|{8f1d9545-0bb9-4583-bb3c-5e1ac1e2920c})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i517", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947509", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by silently installing the add-on, and using multiple add-on IDs.", + "name": "Re-markit", + "created": "2013-12-20T12:54:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e88a28ab-5569-f06d-b0e2-15c51bb2a4b7", + "last_modified": 1480349213344 + }, + { + "guid": "safebrowse@safebrowse.co", + "prefs": [], + "schema": 1480349193877, + "blockID": "i782", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1097696", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on loads scripts with malicious code that appears intended to steal usernames, passwords, and other private information.", + "name": "SafeBrowse", + "created": "2014-11-12T14:20:44Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "edd81c91-383b-f041-d8f6-d0b9a90230bd", + "last_modified": 1480349213319 + }, + { + "guid": "{af95cc15-3b9b-45ae-8d9b-98d08eda3111}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i492", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=945126", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Facebook (malware)", + "created": "2013-12-02T12:45:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7064e9e2-fba4-7b57-86d7-6f4afbf6f560", + "last_modified": 1480349213294 + }, + { + "guid": "{84a93d51-b7a9-431e-8ff8-d60e5d7f5df1}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i744", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080817", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on appears to be silently installed into users' systems, and changes settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Spigot Shopping Assistant", + "created": "2014-10-17T15:47:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dbc7ef8b-2c48-5dae-73a0-f87288c669f0", + "last_modified": 1480349213264 + }, + { + "guid": "{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i918", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1170633", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-02T09:58:16Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "7f2a68f3-aa8a-ae41-1e48-d1f8f63d53c7", + "last_modified": 1480349213231 + }, + { + "guid": "831778-poidjao88DASfsAnindsd@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i972", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1190962", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Video patch (malware)", + "created": "2015-08-04T15:18:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "39471221-6926-e11b-175a-b28424d49bf6", + "last_modified": 1480349213194 + }, + { + "guid": "lbmsrvfvxcblvpane@lpaezhjez.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i342", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=863385", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>. It also appears to install itself both locally and globally, producing a confusing uninstall experience.", + "name": "RapidFinda", + "created": "2013-05-06T16:18:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "98fb4536-07a4-d03a-f7c5-945acecc8203", + "last_modified": 1480349213128 + }, + { + "guid": "{babb9931-ad56-444c-b935-38bffe18ad26}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i499", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=946086", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Facebook Credits (malware)", + "created": "2013-12-04T15:22:02Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "be1d19fa-1662-322a-13e6-5fa5474f33a7", + "last_modified": 1480349213100 + }, + { + "guid": "{18d5a8fe-5428-485b-968f-b97b05a92b54}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i802", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080839", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and is considered malware, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astromenda Search Addon", + "created": "2014-12-15T10:52:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "bc846147-cdc1-141f-5846-b705c48bd6ed", + "last_modified": 1480349213074 + }, + { + "guid": "{b6ef1336-69bb-45b6-8cba-e578fc0e4433}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i780", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Power-SW", + "created": "2014-11-12T14:00:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3b080157-2900-d071-60fe-52b0aa376cf0", + "last_modified": 1480349213024 + }, + { + "guid": "info@wxdownloadmanager.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i196", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806451", + "who": "All Firefox users who have these add-ons installed.", + "why": "These are malicious add-ons that are distributed with a trojan and negatively affect web browsing.", + "name": "Codec (malware)", + "created": "2012-11-05T09:24:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b62597d0-d2cb-d597-7358-5143a1d13658", + "last_modified": 1480349212999 + }, + { + "os": "WINNT", + "guid": "{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i111", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=771802", + "who": "All Firefox users on Windows who have the RealPlayer Browser Record extension installed.", + "why": "The RealPlayer Browser Record extension is causing significant problems on Flash video sites like YouTube. This block automatically disables the add-on, but users can re-enable it from the Add-ons Manager if necessary.\r\n\r\nThis block shouldn't disable any other RealPlayer plugins, so watching RealPlayer content on the web should be unaffected.\r\n\r\nIf you still have problems playing videos on YouTube or elsewhere, please visit our <a href=\"http://support.mozilla.com/\">support site</a> for help.", + "name": "RealPlayer Browser Record Plugin", + "created": "2012-07-10T15:28:16Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "15.0.5", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d3f96257-7635-555f-ef48-34d426322992", + "last_modified": 1480349212971 + }, + { + "guid": "l@AdLJ7uz.net", + "prefs": [ + "browser.startup.homepage" + ], + "schema": 1480349193877, + "blockID": "i728", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes user settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>", + "name": "GGoSavee", + "created": "2014-10-16T16:34:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e6bfa340-7d8a-1627-5cdf-40c0c4982e9d", + "last_modified": 1480349212911 + }, + { + "guid": "{6b2a75c8-6e2e-4267-b955-43e25b54e575}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i698", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1052611", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BrowserShield", + "created": "2014-08-21T15:46:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "492e4e43-f89f-da58-9c09-d99528ee9ca9", + "last_modified": 1480349212871 + }, + { + "guid": "/^({65f9f6b7-2dae-46fc-bfaf-f88e4af1beca}|{9ed31f84-c8b3-4926-b950-dff74047ff79}|{0134af61-7a0c-4649-aeca-90d776060cb3}|{02edb56b-9b33-435b-b7df-b2843273a694}|{da51d4f6-3e7e-4ef8-b400-9198e0874606}|{b24577db-155e-4077-bb37-3fdd3c302bb5})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i525", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949566", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and using multiple IDs.", + "name": "KeyBar add-on", + "created": "2013-12-20T14:11:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "78562d79-9a64-c259-fb63-ce24e29bb141", + "last_modified": 1480349212839 + }, + { + "guid": "adsremoval@adsremoval.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i560", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=962793", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes various user settings, in violation of the <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Ad Removal", + "created": "2014-02-27T09:57:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4d150ad4-dc22-9790-07a9-36e0a23f857f", + "last_modified": 1480349212798 + }, + { + "guid": "firefoxaddon@youtubeenhancer.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i445", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=911966", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that imitates a popular video downloader extension, and attempts to hijack Facebook accounts. The add-on available in the add-ons site is safe to use.", + "name": "YouTube Enhancer Plus (malware)", + "created": "2013-09-04T16:53:37Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "208.7.0", + "minVersion": "208.7.0", + "targetApplication": [] + } + ], + "id": "41d75d3f-a57e-d5ad-b95b-22f5fa010b4e", + "last_modified": 1480349212747 + }, + { + "guid": "{336D0C35-8A85-403a-B9D2-65C292C39087}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i224", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812292", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is side-installed with other software, and blocks setting reversions attempted by users who want to recover their settings after they are hijacked by other add-ons.", + "name": "IB Updater", + "created": "2012-11-29T16:22:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c87666e6-ec9a-2f1e-ad03-a722d2fa2a25", + "last_modified": 1480349212655 + }, + { + "guid": "G4Ce4@w.net", + "prefs": [ + "browser.startup.homepage" + ], + "schema": 1480349193877, + "blockID": "i718", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems and changes settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "YoutUbeAdBlaocke", + "created": "2014-10-02T12:21:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3e1e9322-93e9-4ce1-41f5-46ad4ef1471b", + "last_modified": 1480349212277 + }, + { + "guid": "extension@Fast_Free_Converter.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i533", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949597", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by silently installing it.", + "name": "FastFreeConverter", + "created": "2013-12-20T15:04:18Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "726f5645-c0bf-66dc-a97a-d072b46e63e7", + "last_modified": 1480349212247 + }, + { + "guid": "@stopad", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1266", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1298780", + "who": "Users who have version 0.0.4 and earlier of the add-on installed.", + "why": "Stop Ads sends each visited url to a third party server which is not necessary for the add-on to work or disclosed in a privacy policy or user opt-in. Versions 0.0.4 and earlier are affected.", + "name": "Stop Ads Addon", + "created": "2016-08-30T12:24:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "0.0.4", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3d1893dd-2092-d1f7-03f3-9629b7d7139e", + "last_modified": 1480349212214 + }, + { + "guid": "703db0db-5fe9-44b6-9f53-c6a91a0ad5bd@7314bc82-969e-4d2a-921b-e5edd0b02cf1.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i519", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947509", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by silently installing the add-on, and using multiple add-on IDs.", + "name": "Re-markit", + "created": "2013-12-20T12:57:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a27d0f9f-7708-3d5f-82e1-e3f29e6098a0", + "last_modified": 1480349212183 + }, + { + "guid": "{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i348", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=867359", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>. It also fails to revert settings changes on removal.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "mywebsearch", + "created": "2013-05-08T15:55:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "372cf3df-0810-85d8-b5d7-faffff309a11", + "last_modified": 1480349212102 + }, + { + "guid": "{a6e67e6f-8615-4fe0-a599-34a73fc3fba5}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i346", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=867333", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>. Also, it doesn't reset its settings changes on uninstall.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Startnow", + "created": "2013-05-06T17:06:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1caf911c-ff2f-b0f6-0d32-29ef74be81bb", + "last_modified": 1480349212077 + }, + { + "guid": "9518042e-7ad6-4dac-b377-056e28d00c8f@f1cc0a13-4df1-4d66-938f-088db8838882.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i308", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=846455", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed, bypassing our third-party opt-in screen, in violation of our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Solid Savings", + "created": "2013-02-28T13:48:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "df25ee07-74d4-ccd9-dbbe-7eb053015144", + "last_modified": 1480349212020 + }, + { + "guid": "jufa098j-LKooapd9jasJ9jliJsd@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1000", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1201163", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Secure Video (malware)", + "created": "2015-09-07T14:00:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c3a98025-0f4e-3bb4-b475-97329e7b1426", + "last_modified": 1480349211979 + }, + { + "guid": "{46eddf51-a4f6-4476-8d6c-31c5187b2a2f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i750", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963788", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Slick Savings", + "created": "2014-10-17T16:17:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9b4ef650-e1ad-d55f-c420-4f26dbb4139c", + "last_modified": 1480349211953 + }, + { + "guid": "{DAC3F861-B30D-40dd-9166-F4E75327FAC7}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i924", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1173154", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-09T15:28:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "be57998b-9e4d-1040-e6bb-ed9de056338d", + "last_modified": 1480349211896 + }, + { + "guid": "JMLv@njMaHh.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i790", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1103516", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "YouttubeAdBlocke", + "created": "2014-11-24T14:14:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "070d5747-137d-8500-8713-cfc6437558a3", + "last_modified": 1480349211841 + }, + { + "guid": "istart_ffnt@gmail.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i888", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1152553", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-on Manager.", + "why": "This add-on appears to be malware, being silently installed and hijacking user settings, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Istart", + "created": "2015-04-10T16:27:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "32fad759-38d9-dad9-2295-e44cc6887040", + "last_modified": 1480349211785 + }, + { + "guid": "gystqfr@ylgga.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i449", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=912742", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. It is installed bypassing the Firefox opt-in screen, and manipulates settings without reverting them on removal. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Define Ext", + "created": "2013-09-13T16:19:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8fe8f509-c530-777b-dccf-d10d58ae78cf", + "last_modified": 1480349211748 + }, + { + "guid": "e9d197d59f2f45f382b1aa5c14d82@8706aaed9b904554b5cb7984e9.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i844", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128324", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and attempts to change user settings like the home page and default search, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Sense", + "created": "2015-02-06T15:01:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f8f8695c-a356-a1d6-9291-502b377c63c2", + "last_modified": 1480349211713 + }, + { + "guid": "{184AA5E6-741D-464a-820E-94B3ABC2F3B4}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i968", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1164243", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that poses as a Java extension.", + "name": "Java String Helper (malware)", + "created": "2015-08-04T09:41:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fac1d2cb-eed7-fcef-5d5a-43c556371bd7", + "last_modified": 1480349211687 + }, + { + "guid": "7d51fb17-b199-4d8f-894e-decaff4fc36a@a298838b-7f50-4c7c-9277-df6abbd42a0c.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i455", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=919792", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that hijacks Facebook accounts and posts spam to the users' friends.", + "name": "Video Console (malware)", + "created": "2013-09-25T10:28:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dd4d2e17-4ce6-36b0-3035-93e9cc5846d4", + "last_modified": 1480349211660 + }, + { + "guid": "prositez@prz.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i764", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "ProfSitez", + "created": "2014-10-29T16:43:15Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "684ad4fd-2cbd-ce2a-34cd-bc66b20ac8af", + "last_modified": 1480349211628 + }, + { + "guid": "{25D77636-38B1-1260-887C-2D4AFA92D6A4}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i536", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=959279", + "who": "All Firefox users who have this extension installed.", + "why": "This is a malicious extension that is installed alongside a trojan. It hijacks searches on selected sites.", + "name": "Microsoft DirectInput Object (malware)", + "created": "2014-01-13T10:36:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cd174588-940e-f5b3-12ea-896c957bd4b3", + "last_modified": 1480349211555 + }, + { + "guid": "{badea1ae-72ed-4f6a-8c37-4db9a4ac7bc9}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i543", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963809", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.", + "why": "This add-on is apparently malware that is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Address Bar Search", + "created": "2014-01-28T14:28:18Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8c1dd68e-7df6-0c37-2f41-107745a7be54", + "last_modified": 1480349211119 + }, + { + "guid": "{d87d56b2-1379-49f4-b081-af2850c79d8e}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i726", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080835", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Website Xplorer Lite", + "created": "2014-10-13T16:01:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7b0895b4-dd4f-1c91-f4e3-31afdbdf3178", + "last_modified": 1480349211007 + }, + { + "guid": "OKitSpace@OKitSpace.es", + "prefs": [], + "schema": 1480349193877, + "blockID": "i469", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:35:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6a11aa68-0dae-5524-cc96-a5053a31c466", + "last_modified": 1480349210982 + }, + { + "guid": "{c96d1ae6-c4cf-4984-b110-f5f561b33b5a}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i808", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Better Web", + "created": "2014-12-19T09:36:52Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0413d46b-8205-d9e0-65df-4caa3e6355c4", + "last_modified": 1480349210956 + }, + { + "guid": "thefoxonlybetter@quicksaver", + "prefs": [], + "schema": 1480349193877, + "blockID": "i706", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1053469", + "who": "All Firefox users who have any of these versions of the add-on installed.", + "why": "Certain versions of The Fox, Only Better weren't developed by the original developer, and are likely malicious in nature. This violates the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> for reusing an already existent ID.", + "name": "The Fox, Only Better (malicious versions)", + "created": "2014-08-27T14:50:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "1.6.160", + "minVersion": "1.6.160", + "targetApplication": [] + } + ], + "id": "bb2b2114-f8e7-511d-04dc-abc8366712cc", + "last_modified": 1480349210859 + }, + { + "guid": "CortonExt@ext.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i336", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=864551", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is reported to be installed without user consent, with a non-descriptive name, and ties a number of browser features to Amazon URLs, probably monetizing on affiliate codes.", + "name": "CortonExt", + "created": "2013-04-22T16:10:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a5bdd05d-eb4c-ce34-9909-a677b4322384", + "last_modified": 1480349210805 + }, + { + "guid": "1chtw@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i430", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=901770", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that uses a deceptive name and hijacks social networks.", + "name": " Mozilla Service Pack (malware)", + "created": "2013-08-05T16:42:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "bf1e31c7-ba50-1075-29ae-47368ac1d6de", + "last_modified": 1480349210773 + }, + { + "guid": "lrcsTube@hansanddeta.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i344", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=866944", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "LyricsTube", + "created": "2013-05-06T16:44:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "424b9f39-d028-b1fb-d011-d8ffbbd20fe9", + "last_modified": 1480349210718 + }, + { + "guid": "{341f4dac-1966-47ff-aacf-0ce175f1498a}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i356", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=868129", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "MyFreeGames", + "created": "2013-05-23T14:45:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "560e08b1-3471-ad34-8ca9-463f5ca5328c", + "last_modified": 1480349210665 + }, + { + "guid": "/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i518", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947509", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by silently installing the add-on, and using multiple add-on IDs.", + "name": "Re-markit", + "created": "2013-12-20T12:56:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "145b0f22-501e-39eb-371e-ec8342a5add9", + "last_modified": 1480349210606 + }, + { + "guid": "{72b98dbc-939a-4e0e-b5a9-9fdbf75963ef}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i772", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SitezExpert", + "created": "2014-10-31T16:15:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "386cb2c9-e674-ce2e-345f-d30a785f90c5", + "last_modified": 1480349210536 + }, + { + "guid": "hha8771ui3-Fo9j9h7aH98jsdfa8sda@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i970", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1190963", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Video fix (malware)", + "created": "2015-08-04T15:15:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3ca577d8-3685-4ba9-363b-5b2d8d8dd608", + "last_modified": 1480349210477 + }, + { + "guid": "{7e8a1050-cf67-4575-92df-dcc60e7d952d}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i478", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935796", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "SweetPacks", + "created": "2013-11-08T15:42:28Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1519eb45-fcaa-b531-490d-fe366490ed45", + "last_modified": 1480349210416 + }, + { + "guid": "/^({66b103a7-d772-4fcd-ace4-16f79a9056e0}|{6926c7f7-6006-42d1-b046-eba1b3010315}|{72cabc40-64b2-46ed-8648-26d831761150}|{73ee2cf2-7b76-4c49-b659-c3d8cf30825d}|{ca6446a5-73d5-4c35-8aa1-c71dc1024a18}|{5373a31d-9410-45e2-b299-4f61428f0be4})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i521", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947485", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by being silently installed and using multiple add-on IDs.", + "name": "appbario7", + "created": "2013-12-20T13:14:29Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "983cb7fe-e0b4-6a2e-f174-d2670876b2cd", + "last_modified": 1480349210351 + }, + { + "guid": "{dd6b651f-dfb9-4142-b0bd-09912ad22674}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i400", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835678", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This group of add-ons is silently installed, bypassing our install opt-in screen. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Searchqu", + "created": "2013-06-25T15:16:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "975d2126-f727-f5b9-ca01-b83345b80c56", + "last_modified": 1480349210301 + }, + { + "guid": "25p@9eAkaLq.net", + "prefs": [ + "browser.startup.homepage" + ], + "schema": 1480349193877, + "blockID": "i730", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes user settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>\r\n", + "name": "YYOutoubeAdBlocke", + "created": "2014-10-16T16:35:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5a0c5818-693f-43ae-f85a-c6928d9c2cc4", + "last_modified": 1480349210275 + }, + { + "guid": "tmbepff@trendmicro.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1222", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1275245", + "who": "All users of this add-on. If you wish to continue using it, you can enable it in the Add-ons Manager.", + "why": "Add-on is causing a high-frequency crash in Firefox", + "name": "Trend Micro BEP 9.1.0.1035 and lower", + "created": "2016-05-24T12:10:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "9.1.0.1035", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8045c799-486a-927c-b972-b9da1c2dab2f", + "last_modified": 1480349209818 + }, + { + "guid": "pricepeep@getpricepeep.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i220", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=811433", + "who": "All Firefox users who have Pricepeed below 2.1.0.20 installed.", + "why": "Versions older than 2.1.0.20 of the PricePeep add-on were silently side-installed with other software, injecting advertisements in Firefox. Versions 2.1.0.20 and above don't have the install problems are not blocked.", + "name": "PricePeep", + "created": "2012-11-29T16:18:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.1.0.19.99", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "227b9a8d-c18d-239c-135e-d79e614fe392", + "last_modified": 1480349209794 + }, + { + "guid": "hoverst@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i498", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=946029", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Adobe Flash Player (malware)", + "created": "2013-12-04T15:17:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2b25ba3e-45db-0e6c-965a-3acda1a44117", + "last_modified": 1480349209745 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i606", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:20:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.13.*", + "minVersion": "3.15.13", + "targetApplication": [] + } + ], + "id": "c3d88e22-386a-da3b-8aba-3cb526e08053", + "last_modified": 1480349209713 + }, + { + "guid": "advance@windowsclient.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i508", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=950773", + "who": "All Firefox users who have this add-on installed.", + "why": "This is not the <a href=\"https://addons.mozilla.org/addon/microsoft-net-framework-assist/\">Microsoft .NET Framework Assistant</a> created and distributed by Microsoft. It is a malicious extension that is distributed under the same name to trick users into installing it, and turns users into a botnet that conducts SQL injection attacks on visited websites.", + "name": "Microsoft .NET Framework Assistant (malware)", + "created": "2013-12-16T10:15:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e5d30a74-732e-c3fa-f13b-097ee28d4b27", + "last_modified": 1480349209674 + }, + { + "guid": "{5eeb83d0-96ea-4249-942c-beead6847053}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i756", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080846", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SmarterPower", + "created": "2014-10-17T16:30:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e101dbc0-190c-f6d8-e168-0c1380581cc9", + "last_modified": 1480349209625 + }, + { + "guid": "/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i523", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947481", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted.", + "name": "SweetPacks", + "created": "2013-12-20T13:42:15Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a3a6bc8e-46a1-b3d5-1b20-58b90ba099c3", + "last_modified": 1480349209559 + }, + { + "guid": "{e0352044-1439-48ba-99b6-b05ed1a4d2de}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i710", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Site Counselor", + "created": "2014-09-30T15:28:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b8fedf07-dcaf-f0e3-b42b-32db75c4c304", + "last_modified": 1480349209491 + }, + { + "guid": "addlyrics@addlyrics.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i426", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=891605", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Add Lyrics", + "created": "2013-07-09T15:25:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "81678e9e-ebf0-47d6-e409-085c25e67c7e", + "last_modified": 1480349209383 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i610", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:21:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.22.*", + "minVersion": "3.15.22", + "targetApplication": [] + } + ], + "id": "935dfec3-d017-5660-db5b-94ae7cea6e5f", + "last_modified": 1480349209128 + }, + { + "guid": "info@allpremiumplay.info", + "prefs": [], + "schema": 1480349193877, + "blockID": "i163", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806451", + "who": "All Firefox users who have these add-ons installed.", + "why": "These are malicious add-ons that are distributed with a trojan and negatively affect web browsing.", + "name": "Codec-C (malware)", + "created": "2012-10-29T16:40:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6afbf9b8-ae3a-6a48-0f6c-7a3e065ec043", + "last_modified": 1480349209076 + }, + { + "guid": "now.msn.com@services.mozilla.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i490", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=926378", + "who": "All Firefox users who have this add-on installed.", + "why": "As part of their ongoing work to fine-tune their editorial mix, msnNOW has decided that msnNOW will stop publishing on Dec. 3, 2013. Rather than having a single home for trending content, they will continue integrating that material throughout all MSN channels. A big thank you to everyone who followed msnNOW stories using the Firefox sidebar", + "name": "MSNNow (discontinued social provider)", + "created": "2013-11-27T08:06:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "de7d699d-016d-d973-5e39-52568de6ffde", + "last_modified": 1480349209021 + }, + { + "guid": "fftoolbar2014@etech.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i858", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1131078", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and changes users' settings, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "FF Toolbar", + "created": "2015-02-11T15:32:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6877bf40-9e45-7017-4dac-14d09e7f0ef6", + "last_modified": 1480349208988 + }, + { + "guid": "mbrnovone@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i477", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=936249", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Mozilla Security Service (malware)", + "created": "2013-11-08T15:35:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "758c2503-766d-a2f5-4c58-7cea93acfe05", + "last_modified": 1480349208962 + }, + { + "guid": "{739df940-c5ee-4bab-9d7e-270894ae687a}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i530", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949558", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted.", + "name": "WhiteSmoke New", + "created": "2013-12-20T14:49:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f8097aa6-3009-6dfc-59df-353ba6b1142b", + "last_modified": 1480349208933 + }, + { + "guid": "firefoxaddon@youtubeenhancer.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i636", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1033120", + "who": "All Firefox users who have this version of the add-on installed.", + "why": "Version 199.7.0 of the YoutubeEnhancer extension isn't developed by the original developer, and is likely malicious in nature. It violates the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> for reusing an already existent ID.", + "name": "YoutubeEnhancer - Firefox", + "created": "2014-07-08T15:58:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "199.7.0", + "minVersion": "199.7.0", + "targetApplication": [] + } + ], + "id": "204a074b-da87-2784-f15b-43a9ea9a6b36", + "last_modified": 1480349208851 + }, + { + "guid": "extacylife@a.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i505", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947741", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that hijacks users' Facebook accounts.", + "name": "Facebook Haber (malware)", + "created": "2013-12-09T15:08:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5acadb8d-d3be-e0e0-4656-9107f9de0ea9", + "last_modified": 1480349208823 + }, + { + "guid": "{746505DC-0E21-4667-97F8-72EA6BCF5EEF}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i842", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128325", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Shopper-Pro", + "created": "2015-02-06T14:45:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5f19c5fb-1c78-cbd6-8a03-1678efb54cbc", + "last_modified": 1480349208766 + }, + { + "guid": "{51c77233-c0ad-4220-8388-47c11c18b355}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i580", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1004132", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, it can be enabled in the Add-ons Manager.", + "why": "This add-on is silently installed into Firefox, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Browser Utility", + "created": "2014-04-30T13:55:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "0.1.9999999", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "daa2c60a-5009-2c65-a432-161d50bef481", + "last_modified": 1480349208691 + }, + { + "guid": "{a2bfe612-4cf5-48ea-907c-f3fb25bc9d6b}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i712", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Website Xplorer", + "created": "2014-09-30T15:28:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "13450534-93d7-f2a2-7f0a-e4e3948c4dc1", + "last_modified": 1480349208027 + }, + { + "guid": "ScorpionSaver@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i539", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963826", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.", + "why": "This add-on is being silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>", + "name": "ScorpionSaver", + "created": "2014-01-28T14:00:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3499c968-6e8b-37f1-5f6e-2384807c2a6d", + "last_modified": 1480349207972 + }, + { + "guid": "{D19CA586-DD6C-4a0a-96F8-14644F340D60}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i42", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=690184", + "who": "Users of McAfee ScriptScan versions 14.4.0 and below for all versions of Firefox and SeaMonkey.", + "why": "This add-on causes a high volume of crashes.", + "name": "McAfee ScriptScan", + "created": "2011-10-03T09:38:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "14.4.0", + "minVersion": "0.1", + "targetApplication": [] + } + ], + "id": "1d35ac9d-49df-23cf-51f5-f3c228ad0dc9", + "last_modified": 1480349207877 + }, + { + "guid": "gjhrjenrengoe@jfdnkwelfwkm.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1042", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1212174", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that takes over Facebook accounts.", + "name": "Avant Player (malware)", + "created": "2015-10-07T13:12:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d6453893-becc-7617-2050-0db284e0e0db", + "last_modified": 1480349207840 + }, + { + "guid": "{33e0daa6-3af3-d8b5-6752-10e949c61516}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i282", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835683", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on violates our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">add-on guidelines</a>, bypassing the third party opt-in screen.", + "name": "Complitly", + "created": "2013-02-15T12:19:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.1.999", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1f94bc8d-9d5f-c8f5-45c0-ad1f6e147c71", + "last_modified": 1480349207789 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i608", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:20:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.20.*", + "minVersion": "3.15.18", + "targetApplication": [] + } + ], + "id": "e7d50ff2-5948-d571-6711-37908ccb863f", + "last_modified": 1480349207761 + }, + { + "guid": "{63eb5ed4-e1b3-47ec-a253-f8462f205350}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i786", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "FF-Plugin", + "created": "2014-11-18T12:33:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ca4558a2-8ce4-3ca0-3d29-63019f680c8c", + "last_modified": 1480349207705 + }, + { + "guid": "jid1-4vUehhSALFNqCw@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i634", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1033002", + "who": "All Firefox users who have this version of the add-on installed.", + "why": "Version 99.7 of the YouTube Plus Plus extension isn't developed by the original developer, and is likely malicious in nature. It violates the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> for reusing an already existent ID.", + "name": "YouTube Plus Plus 99.7", + "created": "2014-07-04T14:13:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "99.7", + "minVersion": "99.7", + "targetApplication": [] + } + ], + "id": "a6d017cb-e33f-2239-4e42-ab4e7cfb19fe", + "last_modified": 1480349207680 + }, + { + "guid": "{aab02ab1-33cf-4dfa-8a9f-f4e60e976d27}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i820", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems without their consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Incredible Web", + "created": "2015-01-13T09:27:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "847ecc6e-1bc1-f7ff-e1d5-a76e6b8447d2", + "last_modified": 1480349207654 + }, + { + "guid": "torntv@torntv.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i320", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=845610", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, bypassing our third party install opt-in screen. Users who wish to continue using this extension can enable it in the Add-ons Manager.", + "name": "TornTV", + "created": "2013-03-20T16:35:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cdd492b8-8101-74a9-5760-52ff709fd445", + "last_modified": 1480349207608 + }, + { + "guid": "crossriderapp12555@crossrider.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i674", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=877836", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "The add-on is silently installed, in violation of our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "JollyWallet", + "created": "2014-07-22T16:26:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d4467e20-0f71-f0e0-8cd6-40c82b6c7379", + "last_modified": 1480349207561 + }, + { + "guid": "344141-fasf9jas08hasoiesj9ia8ws@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1038", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1211169", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Video Plugin (malware)", + "created": "2015-10-05T16:42:09Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f7986b7b-9b5a-d372-8147-8b4bd6f5a29b", + "last_modified": 1480349207485 + }, + { + "guid": "meOYKQEbBBjH5Ml91z0p9Aosgus8P55bjTa4KPfl@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i998", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1201164", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Smooth Player (malware)", + "created": "2015-09-07T13:54:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "30c3e511-9e8d-15ee-0867-d61047e56515", + "last_modified": 1480349207370 + }, + { + "guid": "{8dc5c42e-9204-2a64-8b97-fa94ff8a241f}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i770", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1088726", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and is considered malware, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astrmenda Search", + "created": "2014-10-30T14:52:52Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8a9c7702-0349-70d6-e64e-3a666ab084c6", + "last_modified": 1480349207320 + }, + { + "guid": "savingsslider@mybrowserbar.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i752", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963788", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Slick Savings", + "created": "2014-10-17T16:18:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9b1faf30-5725-7847-d993-b5cdaabc9829", + "last_modified": 1480349207290 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i612", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:23:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.24.*", + "minVersion": "3.15.24", + "targetApplication": [] + } + ], + "id": "e0ff9df4-60e4-dbd0-8018-57f395e6610a", + "last_modified": 1480349206818 + }, + { + "guid": "{1e4ea5fc-09e5-4f45-a43b-c048304899fc}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i812", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Great Finder", + "created": "2015-01-06T13:22:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1ea40b9f-2423-a2fd-a5e9-4ec1df2715f4", + "last_modified": 1480349206784 + }, + { + "guid": "crossriderapp8812@crossrider.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i314", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835665", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, bypassing our third party install opt-in screen. Users who wish to continue using this extension can enable it in the Add-ons Manager.", + "name": "Coupon Companion", + "created": "2013-03-06T14:14:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "06c07e28-0a34-e5ee-e724-491a2f6ce586", + "last_modified": 1480349206708 + }, + { + "guid": "/^(ffxtlbr@mixidj\\.com|{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}|{67097627-fd8e-4f6b-af4b-ecb65e50112e}|{f6f0f973-a4a3-48cf-9a7a-b7a69c30d71a}|{a3d0e35f-f1da-4ccb-ae77-e9d27777e68d}|{1122b43d-30ee-403f-9bfa-3cc99b0caddd})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i540", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963819", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on has been repeatedly blocked before and keeps showing up with new add-on IDs, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "MixiDJ (malware)", + "created": "2014-01-28T14:07:56Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4c03ddda-bb3f-f097-0a7b-b7b77b050584", + "last_modified": 1480349206678 + }, + { + "guid": "hansin@topvest.id", + "prefs": [], + "schema": 1480349193877, + "blockID": "i836", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1130406", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Inside News (malware)", + "created": "2015-02-06T14:17:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0945a657-f28d-a02c-01b2-5115b3f90d7a", + "last_modified": 1480349206628 + }, + { + "guid": "lfind@nijadsoft.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i358", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=874131", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Lyrics Finder", + "created": "2013-05-24T14:09:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2307f11c-6216-0dbf-a464-b2921055ce2b", + "last_modified": 1480349206603 + }, + { + "guid": "plugin@getwebcake.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i484", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=938264", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> and is broadly considered to be malware. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "WebCake", + "created": "2013-11-14T09:55:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2865addd-da1c-20c4-742f-6a2270da2e78", + "last_modified": 1480349206578 + }, + { + "guid": "{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i354", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=837838", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n\r\nUsers who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Mixi DJ", + "created": "2013-05-23T14:31:21Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "03a745c3-0ee7-e262-ba31-62d4f78ddb62", + "last_modified": 1480349206525 + }, + { + "guid": "/^({7316e43a-3ebd-4bb4-95c1-9caf6756c97f}|{0cc09160-108c-4759-bab1-5c12c216e005}|{ef03e721-f564-4333-a331-d4062cee6f2b}|{465fcfbb-47a4-4866-a5d5-d12f9a77da00}|{7557724b-30a9-42a4-98eb-77fcb0fd1be3}|{b7c7d4b0-7a84-4b73-a7ef-48ef59a52c3b})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i520", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947485", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by being silently installed and using multiple add-on IDs.", + "name": "appbario7", + "created": "2013-12-20T13:11:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e3901c48-9c06-fecb-87d3-efffd9940c22", + "last_modified": 1480349206491 + }, + { + "guid": "{354dbb0a-71d5-4e9f-9c02-6c88b9d387ba}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i538", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=964081", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Show Mask ON (malware)", + "created": "2014-01-27T10:13:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "aad90253-8921-b5df-3658-45a70d75f3d7", + "last_modified": 1480349206465 + }, + { + "guid": "{8E9E3331-D360-4f87-8803-52DE43566502}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i461", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=906071", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This is a companion add-on for the SweetPacks Toolbar which is <a href=\"https://addons.mozilla.org/firefox/blocked/i392\">blocked</a> due to guideline violations.", + "name": "Updater By SweetPacks", + "created": "2013-10-17T16:10:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ae8cca6e-4258-545f-9a69-3d908264a701", + "last_modified": 1480349206437 + }, + { + "guid": "info@bflix.info", + "prefs": [], + "schema": 1480349193877, + "blockID": "i172", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806802", + "who": "All Firefox users who have this add-on installed.", + "why": "These are malicious add-ons that are distributed with a trojan and negatively affect web browsing.", + "name": "Bflix (malware)", + "created": "2012-10-30T13:39:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7a9062f4-218d-51d2-9b8c-b282e6eada4f", + "last_modified": 1480349206384 + }, + { + "guid": "{dff137ae-1ffd-11e3-8277-b8ac6f996f26}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i450", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=917861", + "who": "All Firefox users who have this add-on installed.", + "why": "This is add-on is malware that silently redirects popular search queries to a third party.", + "name": "Addons Engine (malware)", + "created": "2013-09-18T16:19:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8e583fe4-1c09-9bea-2473-faecf3260685", + "last_modified": 1480349206312 + }, + { + "guid": "12x3q@3244516.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i493", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=939254", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware and is installed silently in violation of the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BetterSurf (malware)", + "created": "2013-12-02T12:49:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "af2a9e74-3753-9ff1-d899-5d1e79ed3dce", + "last_modified": 1480349206286 + }, + { + "guid": "{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i626", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1027886", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is probably silently installed, and is causing significant stability issues for users, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "V-Bates", + "created": "2014-06-19T15:16:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9b9ccabe-8f9a-e3d1-a689-1aefba1f33b6", + "last_modified": 1480349206261 + }, + { + "guid": "{c5e48979-bd7f-4cf7-9b73-2482a67a4f37}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i736", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080842", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "ClearThink", + "created": "2014-10-17T15:22:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6e8b3e4f-2f59-cde3-e6d2-5bc6e216c506", + "last_modified": 1480349206231 + }, + { + "guid": "{41339ee8-61ed-489d-b049-01e41fd5d7e0}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i810", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "FireWeb", + "created": "2014-12-23T10:32:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a35f2ca6-aec4-c01d-170e-650258ebcd2c", + "last_modified": 1480349206165 + }, + { + "guid": "{845cab51-d8d2-472f-8bd9-2b44642d97c2}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i460", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=927456", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and handles users' settings, violating some of the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Vafmusic9", + "created": "2013-10-17T15:50:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8538ccb4-3b71-9858-3f6d-c0fff7af58b0", + "last_modified": 1480349205746 + }, + { + "guid": "SpecialSavings@SpecialSavings.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i676", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=881511", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This is add-on is generally considered to be unwanted and is probably silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SpecialSavings", + "created": "2014-07-22T16:31:56Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5e921810-fc3a-0729-6749-47e38ad10a22", + "last_modified": 1480349205688 + }, + { + "guid": "afurladvisor@anchorfree.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i434", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844945", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on bypasses the external install opt in screen in Firefox, violating the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Hotspot Shield Helper", + "created": "2013-08-09T11:26:11Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "083585eb-d7e7-e228-5fbf-bf35c52044e4", + "last_modified": 1480349205645 + }, + { + "guid": "info@thebflix.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i174", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806802", + "who": "All Firefox users who have these add-ons installed.", + "why": "These are malicious add-ons that are distributed with a trojan and negatively affect web browsing.", + "name": "Bflix (malware)", + "created": "2012-10-30T13:40:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "811a61d4-9435-133e-6262-fb72486c36b0", + "last_modified": 1480349205526 + }, + { + "guid": "{EEE6C361-6118-11DC-9C72-001320C79847}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i392", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=881447", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on changes search settings without user interaction, and fails to reset them after it is removed. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SweetPacks Toolbar", + "created": "2013-06-25T12:38:45Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c1dc6607-4c0a-4031-9f14-70ef1ae1edcb", + "last_modified": 1480349205455 + }, + { + "guid": "/^(4cb61367-efbf-4aa1-8e3a-7f776c9d5763@cdece6e9-b2ef-40a9-b178-291da9870c59\\.com|0efc9c38-1ec7-49ed-8915-53a48b6b7600@e7f17679-2a42-4659-83c5-7ba961fdf75a\\.com|6be3335b-ef79-4b0b-a0ba-b87afbc6f4ad@6bbb4d2e-e33e-4fa5-9b37-934f4fb50182\\.com)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i531", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949672", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and using multiple IDs.", + "name": "Feven", + "created": "2013-12-20T15:01:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "46aa79a9-d329-f713-d4f2-07d31fe7071e", + "last_modified": 1480349205287 + }, + { + "guid": "afext@anchorfree.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i466", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=933988", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't respect user choice, violating the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Hotspot Shield Extension", + "created": "2013-11-07T13:32:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8176f879-bd73-5468-e908-2d7cfc115ac2", + "last_modified": 1480349205108 + }, + { + "guid": "{FCE04E1F-9378-4f39-96F6-5689A9159E45}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i920", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1173154", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-09T15:26:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "eb191ff0-20f4-6e04-4344-d880af4faf51", + "last_modified": 1480349204978 + }, + { + "guid": "{9CE11043-9A15-4207-A565-0C94C42D590D}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i503", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947384", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that uses a deceptive name to stay in users' systems.", + "name": "XUL Cache (malware)", + "created": "2013-12-06T11:58:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dcdae267-8d3a-5671-dff2-f960febbbb20", + "last_modified": 1480349204951 + }, + { + "guid": "{0153E448-190B-4987-BDE1-F256CADA672F}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i914", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1170633", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-02T09:56:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "2bfe0d89-e458-9d0e-f944-ddeaf8c4db6c", + "last_modified": 1480349204871 + }, + { + "guid": "{77beece6-3997-403a-92fa-0055bfcf88e5}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i452", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=916966", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, manipulating settings without reverting them on removal. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Entrusted11", + "created": "2013-09-18T16:34:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d348f91f-caeb-a803-dfd9-fd5d285aa0fa", + "last_modified": 1480349204844 + }, + { + "guid": "dealcabby@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i222", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=811435", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently side-installed with other software, injecting advertisements in Firefox.", + "name": "DealCabby", + "created": "2012-11-29T16:20:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6585f0bd-4f66-71e8-c565-d9762c5c084a", + "last_modified": 1480349204818 + }, + { + "guid": "{3c9a72a0-b849-40f3-8c84-219109c27554}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i510", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=951301", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks users' Facebook accounts.", + "name": "Facebook Haber (malware)", + "created": "2013-12-17T14:27:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7cfa3d0b-0ab2-5e3a-8143-1031c180e32f", + "last_modified": 1480349204778 + }, + { + "guid": "{2aab351c-ad56-444c-b935-38bffe18ad26}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i500", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=946087", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Adobe Photo (malware)", + "created": "2013-12-04T15:29:44Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f7a76d34-ddcd-155e-9fae-5967bd796041", + "last_modified": 1480349204716 + }, + { + "guid": "{0A92F062-6AC6-8180-5881-B6E0C0DC2CC5}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i864", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1131220", + "who": "All users who have this add-on installed. Users who wish to continue using the add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems and makes unwanted settings changes, in violation of our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BlockAndSurf", + "created": "2015-02-26T12:56:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "acb16d1c-6274-93a3-7c1c-7ed36ede64a9", + "last_modified": 1480349204612 + }, + { + "guid": "trackerbird@bustany.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i986", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1189264", + "who": "All Thunderbird users who have this version of the add-on installed on Thunderbird 38.0a2 and above.", + "why": "This add-on is causing consistent crashes on Thunderbird 38.0a2 and above.", + "name": "trackerbird 1.2.6", + "created": "2015-08-17T15:56:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.2.6", + "minVersion": "1.2.6", + "targetApplication": [ + { + "guid": "{3550f703-e582-4d05-9a08-453d09bdfdc6}", + "maxVersion": "*", + "minVersion": "38.0a2" + } + ] + } + ], + "id": "bb1c699e-8790-4528-0b6d-4f83b7a3152d", + "last_modified": 1480349204041 + }, + { + "guid": "{0134af61-7a0c-4649-aeca-90d776060cb3}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i448", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=912746", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our Add-on Guidelines. It manipulates settings without reverting them on removal. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "KeyBar add-on", + "created": "2013-09-13T16:15:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cf428416-4974-8bb4-7928-c0cb2cfe7957", + "last_modified": 1480349203968 + }, + { + "guid": "/^(firefox@vebergreat\\.net|EFGLQA@78ETGYN-0W7FN789T87\\.COM)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i564", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974104", + "who": "All Firefox users who have these add-ons installed. If you wish to continue using these add-ons, you can enable them in the Add-ons Manager.", + "why": "These add-ons are silently installed by the Free Driver Scout installer, in violation of our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "veberGreat and vis (Free Driver Scout bundle)", + "created": "2014-03-05T13:02:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "487538f1-698e-147e-6395-986759ceed7e", + "last_modified": 1480349203902 + }, + { + "guid": "69ffxtbr@PackageTracer_69.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i882", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1153001", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on appears to be malware, hijacking user's settings, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "PackageTracer", + "created": "2015-04-10T16:18:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0d37b4e0-3c60-fdad-dd8c-59baff6eae87", + "last_modified": 1480349203836 + }, + { + "guid": "{a9bb9fa0-4122-4c75-bd9a-bc27db3f9155}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i404", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835678", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This group of add-ons is silently installed, bypassing our install opt-in screen. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Searchqu", + "created": "2013-06-25T15:16:43Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fb7a1dc7-16a0-4f70-8289-4df494e0d0fa", + "last_modified": 1480349203633 + }, + { + "guid": "P2@D.edu", + "prefs": [], + "schema": 1480349193877, + "blockID": "i850", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128269", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "unIsaless", + "created": "2015-02-09T15:29:21Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "49536a29-fc7e-9fd0-f415-e15ac090fa56", + "last_modified": 1480349203605 + }, + { + "guid": "linksicle@linksicle.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i472", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:38:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9b5b15b3-6da7-cb7c-3c44-30b4fe079d52", + "last_modified": 1480349203581 + }, + { + "guid": "{377e5d4d-77e5-476a-8716-7e70a9272da0}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i398", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835678", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This group of add-ons is silently installed, bypassing our install opt-in screen. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Searchqu", + "created": "2013-06-25T15:15:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ea94df32-2a85-23da-43f7-3fc5714530ec", + "last_modified": 1480349203519 + }, + { + "guid": "{4933189D-C7F7-4C6E-834B-A29F087BFD23}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i437", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=900695", + "who": "All Firefox users.", + "why": "This add-on is widely reported to be malware.", + "name": "Win32.SMSWebalta (malware)", + "created": "2013-08-09T15:14:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cbef1357-d6bc-c8d3-7a82-44af6b1c390f", + "last_modified": 1480349203486 + }, + { + "guid": "{ADFA33FD-16F5-4355-8504-DF4D664CFE10}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i306", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844972", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed, bypassing our third-party opt-in screen, in violation of our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>. It's also possible that it changes user settings without their consent.", + "name": "Nation Toolbar", + "created": "2013-02-28T12:56:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "017fd151-37ca-4646-4763-1d303fb918fa", + "last_modified": 1480349203460 + }, + { + "guid": "detgdp@gmail.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i884", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1152614", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware, hijacking user settings, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Security Protection (malware)", + "created": "2015-04-10T16:21:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1b5cc88e-499d-2a47-d793-982d4c05e6ee", + "last_modified": 1480349203433 + }, + { + "guid": "/^(67314b39-24e6-4f05-99f3-3f88c7cddd17@6c5fa560-13a3-4d42-8e90-53d9930111f9\\.com|ffxtlbr@visualbee\\.com|{7aeae561-714b-45f6-ace3-4a8aed6e227b}|{7093ee04-f2e4-4637-a667-0f730797b3a0}|{53c4024f-5a2e-4f2a-b33e-e8784d730938})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i514", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947473", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by using multiple add-on IDs and making unwanted settings changes.", + "name": "VisualBee Toolbar", + "created": "2013-12-20T12:25:50Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5f91eee1-7303-3f97-dfe6-1e897a156c7f", + "last_modified": 1480349203408 + }, + { + "guid": "FXqG@xeeR.net", + "prefs": [ + "browser.startup.homepage" + ], + "schema": 1480349193877, + "blockID": "i720", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems and changes settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "GoSSave", + "created": "2014-10-02T12:23:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8ebbc061-a4ff-b75b-ec42-eb17c42a2956", + "last_modified": 1480349203341 + }, + { + "guid": "kallow@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i495", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=945426", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Facebook Security Service (malware)", + "created": "2013-12-02T15:09:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1a2c37a9-e7cc-2d03-2043-098d36b8aca2", + "last_modified": 1480349203247 + }, + { + "guid": "008abed2-b43a-46c9-9a5b-a771c87b82da@1ad61d53-2bdc-4484-a26b-b888ecae1906.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i528", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949565", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by being silently installed.", + "name": "weDownload Manager Pro", + "created": "2013-12-20T14:40:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "da46065f-1c68-78f7-80fc-8ae07b5df68d", + "last_modified": 1480349203131 + }, + { + "guid": "{25dd52dc-89a8-469d-9e8f-8d483095d1e8}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i714", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Web Counselor", + "created": "2014-10-01T15:36:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e46c31ad-0ab3-e48a-47aa-9fa91b675fda", + "last_modified": 1480349203066 + }, + { + "guid": "{B1FC07E1-E05B-4567-8891-E63FBE545BA8}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i926", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1173154", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-09T15:28:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "09868783-261a-ac24-059d-fc772218c1ba", + "last_modified": 1480349202708 + }, + { + "guid": "/^(torntv@torntv\\.com|trtv3@trtv\\.com|torntv2@torntv\\.com|e2fd07a6-e282-4f2e-8965-85565fcb6384@b69158e6-3c3b-476c-9d98-ae5838c5b707\\.com)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i529", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949559", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by being silently installed.", + "name": "TornTV", + "created": "2013-12-20T14:46:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "040e5ec2-ea34-816a-f99f-93296ce845e8", + "last_modified": 1480349202677 + }, + { + "guid": "249911bc-d1bd-4d66-8c17-df533609e6d8@c76f3de9-939e-4922-b73c-5d7a3139375d.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i532", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949672", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and using multiple IDs.", + "name": "Feven", + "created": "2013-12-20T15:02:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d32b850d-82d5-b63d-087c-fb2041b2c232", + "last_modified": 1480349202631 + }, + { + "guid": "{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i394", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=881447", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This is a companion add-on for the SweetPacks Toolbar which <a href=\"https://addons.mozilla.org/en-US/firefox/blocked/i392\">is blocked</a> due to guideline violations.", + "name": "Updater By SweetPacks", + "created": "2013-06-25T12:40:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "851c2b8e-ea19-3a63-eac5-f931a8da5d6e", + "last_modified": 1480349202341 + }, + { + "guid": "g@uzcERQ6ko.net", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i776", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes user settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>", + "name": "GoSave", + "created": "2014-10-31T16:23:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ee1e1a44-b51b-9f12-819d-64c3e515a147", + "last_modified": 1480349202307 + }, + { + "guid": "ffxtlbr@incredibar.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i318", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812264", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, bypassing our third party install opt-in screen. Users who wish to continue using this extension can enable it in the Add-ons Manager.", + "name": "IncrediBar", + "created": "2013-03-20T14:40:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9e84b07c-84d5-c932-85f2-589713d7e380", + "last_modified": 1480349202280 + }, + { + "guid": "M1uwW0@47z8gRpK8sULXXLivB.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i870", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1131159", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that goes by the the name \"Flash Player 11\".", + "name": "Flash Player 11 (malware)", + "created": "2015-03-04T14:34:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "71d961b2-37d1-d393-76f5-3afeef57e749", + "last_modified": 1480349202252 + }, + { + "guid": "4zffxtbr-bs@VideoDownloadConverter_4z.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i507", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949266", + "who": "All Firefox users who have this add-on installed.", + "why": "Certain versions of this add-on contains an executable that is flagged by multiple tools as malware. Newer versions no longer use it.", + "name": "VideoDownloadConverter", + "created": "2013-12-12T15:37:23Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "5.75.3.25126", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0a0f106a-ecc6-c537-1818-b36934943e91", + "last_modified": 1480349202156 + }, + { + "guid": "contato@facefollow.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i509", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=950846", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on spams users' Facebook accounts.", + "name": "Face follow", + "created": "2013-12-16T16:15:15Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "56f15747-af8c-342c-6877-a41eeacded84", + "last_modified": 1480349202067 + }, + { + "guid": "/^({83a8ce1b-683c-4784-b86d-9eb601b59f38}|{ef1feedd-d8da-4930-96f1-0a1a598375c6}|{79ff1aae-701f-4ca5-aea3-74b3eac6f01b}|{8a184644-a171-4b05-bc9a-28d75ffc9505}|{bc09c55d-0375-4dcc-836e-0e3c8addfbda}|{cef81415-2059-4dd5-9829-1aef3cf27f4f})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i526", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949566", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and uses multiple IDs.", + "name": "KeyBar add-on", + "created": "2013-12-20T14:12:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9dfa4e92-bbf2-66d1-59a9-51402d1d226c", + "last_modified": 1480349202010 + }, + { + "guid": "{f2548724-373f-45fe-be6a-3a85e87b7711}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i768", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1088726", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and is considered malware, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astro New Tab", + "created": "2014-10-30T14:52:09Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8510e9e2-c7d8-90d0-a2ff-eb09293acc6e", + "last_modified": 1480349201854 + }, + { + "guid": "KSqOiTeSJEDZtTGuvc18PdPmYodROmYzfpoyiCr2@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1032", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1211172", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Video Player (malware)", + "created": "2015-10-05T16:22:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3d9188ac-235f-773a-52a2-261b3ea9c03c", + "last_modified": 1480349201504 + }, + { + "guid": "{849ded12-59e9-4dae-8f86-918b70d213dc}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i708", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1047102", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes homepage and search settings without the user's consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astromenda New Tab", + "created": "2014-09-02T16:29:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a319bfee-464f-1c33-61ad-738c52842fbd", + "last_modified": 1480349201453 + }, + { + "guid": "grjkntbhr@hgergerherg.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1018", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1208196", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "GreenPlayer (malware)", + "created": "2015-09-24T16:04:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "9c47d940-bdd9-729f-e32e-1774d87f24b5", + "last_modified": 1480349201425 + }, + { + "guid": "{EEF73632-A085-4fd3-A778-ECD82C8CB297}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i165", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806451", + "who": "All Firefox users who have these add-ons installed.", + "why": "These are malicious add-ons that are distributed with a trojan and negatively affect web browsing.", + "name": "Codec-M (malware)", + "created": "2012-10-29T16:41:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e5ecd02a-20ee-749b-d5cf-3d74d1173a1f", + "last_modified": 1480349201262 + }, + { + "guid": "firefox-extension@mozilla.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i688", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1049533", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hides itself under the name Java_plugin, among others.", + "name": "FinFisher (malware)", + "created": "2014-08-06T17:13:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "98aca74a-69c7-9960-cccc-096a4a4adc6c", + "last_modified": 1480349201235 + }, + { + "guid": "{e44a1809-4d10-4ab8-b343-3326b64c7cdd}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i451", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=916966", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, manipulating settings without reverting them on removal. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Entrusted", + "created": "2013-09-18T16:33:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ad5f53ed-7a43-cb1f-cbd7-41808fac1791", + "last_modified": 1480349201128 + }, + { + "guid": "{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i620", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024752", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is probably silently installed, and is causing significant stability issues for users, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "V-Bates", + "created": "2014-06-12T15:27:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2d8833db-01a7-a758-080f-19e47abc54cb", + "last_modified": 1480349201096 + }, + { + "guid": "{1FD91A9C-410C-4090-BBCC-55D3450EF433}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i338", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844979", + "who": "All Firefox users who have this add-on installed.", + "why": "This extension overrides search settings, and monitors any further changes done to them so that they can be reverted. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">add-on guidelines</a>.", + "name": "DataMngr (malware)", + "created": "2013-04-24T11:30:28Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "2e35995f-bec6-aa2b-3372-346d3325f72e", + "last_modified": 1480349201059 + }, + { + "guid": "9598582LLKmjasieijkaslesae@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i996", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1201165", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that takes over Facebook accounts.", + "name": "Secure Player (malware)", + "created": "2015-09-07T13:50:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "52f9c6e7-f7d5-f52e-cc35-eb99ef8b4b6a", + "last_modified": 1480349201029 + }, + { + "guid": "{bf7380fa-e3b4-4db2-af3e-9d8783a45bfc}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i406", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=776404", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on changes search settings without user interaction, and fails to reset them after it is removed. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "uTorrentBar", + "created": "2013-06-27T10:46:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3bcefc4b-110c-f3b8-17ad-f9fc97c1120a", + "last_modified": 1480349201000 + }, + { + "guid": "{424b0d11-e7fe-4a04-b7df-8f2c77f58aaf}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i800", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080839", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and is considered malware, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astromenda NT", + "created": "2014-12-15T10:51:56Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "07bdf6aa-cfc8-ed21-6b36-6f90af02b169", + "last_modified": 1480349200939 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i618", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.\r\n", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.\r\n", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:25:04Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.31.*", + "minVersion": "3.15.31", + "targetApplication": [] + } + ], + "id": "825feb43-d6c2-7911-4189-6f589f612c34", + "last_modified": 1480349200911 + }, + { + "guid": "{167d9323-f7cc-48f5-948a-6f012831a69f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i262", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812303", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently side-installed by other software, and doesn't do much more than changing the users' settings, without reverting them on removal.", + "name": "WhiteSmoke (malware)", + "created": "2013-01-29T13:33:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a8f249fe-3db8-64b8-da89-7b584337a7af", + "last_modified": 1480349200885 + }, + { + "guid": "/^({988919ff-0cd8-4d0c-bc7e-60d55a49eb64}|{494b9726-9084-415c-a499-68c07e187244}|{55b95864-3251-45e9-bb30-1a82589aaff1}|{eef3855c-fc2d-41e6-8d91-d368f51b3055}|{90a1b331-c2b4-4933-9f63-ba7b84d60d58}|{d2cf9842-af95-48cd-b873-bfbb48cd7f5e})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i541", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963819", + "who": "All Firefox users who have this add-on installed", + "why": "This add-on has been repeatedly blocked before and keeps showing up with new add-on IDs, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "MixiDJ (malware)", + "created": "2014-01-28T14:09:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "36196aed-9d0d-ebee-adf1-d1f7fadbc48f", + "last_modified": 1480349200819 + }, + { + "guid": "{29b136c9-938d-4d3d-8df8-d649d9b74d02}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i598", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1011322", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed, in violation with our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Mega Browse", + "created": "2014-06-12T13:21:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "63b1c965-27c3-cd06-1b76-8721add39edf", + "last_modified": 1480349200775 + }, + { + "guid": "{6e7f6f9f-8ce6-4611-add2-05f0f7049ee6}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i868", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1086574", + "who": "All users who have this add-on installed. Users who wish to continue using the add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Word Proser", + "created": "2015-02-26T14:58:59Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f54797da-cdcd-351a-c95e-874b64b0d226", + "last_modified": 1480349200690 + }, + { + "guid": "{02edb56b-9b33-435b-b7df-b2843273a694}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i438", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=896581", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. It is installed bypassing the Firefox opt-in screen, and manipulates settings without reverting them on removal. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "KeyBar Toolbar", + "created": "2013-08-09T15:27:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "896710d2-5a65-e9b0-845b-05aa72c2bd51", + "last_modified": 1480349200338 + }, + { + "guid": "{1cdbda58-45f8-4d91-b566-8edce18f8d0a}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i724", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080835", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Website Counselor Pro", + "created": "2014-10-13T16:00:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "7b70bd36-d2f7-26fa-9038-8b8dd132cd81", + "last_modified": 1480349200288 + }, + { + "guid": "{b12785f5-d8d0-4530-a3ea-5c4263b85bef}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i988", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1161573", + "who": "All users who have this add-on installed. Those who wish continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on overrides user's preferences without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Hero Fighter Community Toolbar", + "created": "2015-08-17T16:04:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3e6d73f2-e8e3-af69-866e-30d3977b09e4", + "last_modified": 1480349200171 + }, + { + "guid": "{c2d64ff7-0ab8-4263-89c9-ea3b0f8f050c}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i39", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=665775", + "who": "Users of MediaBar versions 4.3.1.00 and below in all versions of Firefox.", + "why": "This add-on causes a high volume of crashes and is incompatible with certain versions of Firefox.", + "name": "MediaBar", + "created": "2011-07-19T10:18:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "4.3.1.00", + "minVersion": "0.1", + "targetApplication": [] + } + ], + "id": "e928a115-9d8e-86a4-e2c7-de39627bd9bf", + "last_modified": 1480349200047 + }, + { + "guid": "{9edd0ea8-2819-47c2-8320-b007d5996f8a}", + "prefs": [ + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i684", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1033857", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is believed to be silently installed in Firefox, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "webget", + "created": "2014-08-06T13:33:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d38561f5-370f-14be-1443-a74dad29b1f3", + "last_modified": 1480349199962 + }, + { + "guid": "/^({ad9a41d2-9a49-4fa6-a79e-71a0785364c8})|(ffxtlbr@mysearchdial\\.com)$/", + "prefs": [ + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i670", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1036740", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on has been repeatedly been silently installed into users' systems, and is known for changing the default search without user consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "MySearchDial", + "created": "2014-07-18T15:47:35Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a04075e6-5df2-2e1f-85a6-3a0171247349", + "last_modified": 1480349199927 + }, + { + "guid": "/^({1f43c8af-e9e4-4e5a-b77a-f51c7a916324}|{3a3bd700-322e-440a-8a6a-37243d5c7f92}|{6a5b9fc2-733a-4964-a96a-958dd3f3878e}|{7b5d6334-8bc7-4bca-a13e-ff218d5a3f17}|{b87bca5b-2b5d-4ae8-ad53-997aa2e238d4}|{bf8e032b-150f-4656-8f2d-6b5c4a646e0d})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1136", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1251940", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hides itself from view and disables various security features in Firefox.", + "name": "Watcher (malware)", + "created": "2016-03-04T17:56:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a2d0378f-ebe4-678c-62d8-2e4c6a613c17", + "last_modified": 1480349199818 + }, + { + "guid": "liiros@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i814", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1119657", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems without their consent and performs unwanted operations.", + "name": "One Tab (malware)", + "created": "2015-01-09T12:49:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "387c054d-cc9f-7ebd-c814-b4c1fbcb2880", + "last_modified": 1480349199791 + }, + { + "guid": "{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i916", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1170633", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-02T09:57:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "94fba774-c4e6-046a-bc7d-ede787a9d0fe", + "last_modified": 1480349199738 + }, + { + "guid": "{b64982b1-d112-42b5-b1e4-d3867c4533f8}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i167", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=805973", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is a frequent cause for browser crashes and other problems.", + "name": "Browser Manager", + "created": "2012-10-29T17:17:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "00bbe501-2d27-7a1c-c344-6eea1c707473", + "last_modified": 1480349199673 + }, + { + "guid": "{58bd07eb-0ee0-4df0-8121-dc9b693373df}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i286", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=842206", + "who": "All Firefox users who have this extension installed.", + "why": "This extension is malicious and is installed under false pretenses, causing problems for many Firefox users. Note that this is not the same <a href=\"https://addons.mozilla.org/firefox/addon/browserprotect/\">BrowserProtect extension</a> that is listed on our add-ons site. That one is safe to use.", + "name": "Browser Protect / bProtector (malware)", + "created": "2013-02-18T10:54:28Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b40a60d3-b9eb-09eb-bb02-d50b27aaac9f", + "last_modified": 1480349199619 + }, + { + "guid": "trtv3@trtv.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i465", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=845610", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, bypassing our third party install opt-in screen. Users who wish to continue using this extension can enable it in the Add-ons Manager.", + "name": "TornTV", + "created": "2013-11-01T15:21:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3d4d8a33-2eff-2556-c699-9be0841a8cd4", + "last_modified": 1480349199560 + }, + { + "guid": "{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i439", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=902569", + "who": "All Firefox users who have this add-on installed.", + "why": "This is another instance of the <a href=\"https://addons.mozilla.org/en-US/firefox/blocked/i354\">previously blocked</a> Mixi DJ add-on, which doesn't follow our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. If you wish to continue using it, it can be enabled in the Add-ons Manager.", + "name": "Mixi DJ V45", + "created": "2013-08-09T16:08:18Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e81c31fc-265e-61b9-d4c1-0e2f31f1652e", + "last_modified": 1480349199478 + }, + { + "guid": "/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i624", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947482", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is known to change user settings without their consent, is distributed under multiple add-on IDs, and is also correlated with reports of tab functions being broken in Firefox, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.\r\n", + "name": "WiseConvert", + "created": "2014-06-18T13:50:38Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ed57d7a6-5996-c7da-8e07-1ad125183e84", + "last_modified": 1480349199446 + }, + { + "guid": "{f894a29a-f065-40c3-bb19-da6057778493}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i742", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080817", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on appears to be silently installed into users' systems, and changes settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Spigot Shopping Assistant", + "created": "2014-10-17T15:46:59Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "39d8334e-4b7c-4336-2d90-e6aa2d783967", + "last_modified": 1480349199083 + }, + { + "guid": "plugin@analytic-s.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i467", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935797", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on bypasses the external install opt in screen in Firefox, violating the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Analytics", + "created": "2013-11-07T14:08:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ffbed3f3-e5c9-bc6c-7530-f68f47b7efd6", + "last_modified": 1480349199026 + }, + { + "guid": "{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i678", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=895668", + "who": "All Firefox users who have this add-on installed. If you wish to continue using it, you can enable it in the Add-ons Manager.", + "why": "This add-on is generally silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "DataMngr", + "created": "2014-07-23T14:12:23Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "151021fc-ce4e-a734-e075-4ece19610f64", + "last_modified": 1480349198947 + }, + { + "guid": "HxLVJK1ioigz9WEWo8QgCs3evE7uW6LEExAniBGG@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1036", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1211170", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Mega Player (malware)", + "created": "2015-10-05T16:37:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "32e34b41-a73c-72d4-c96c-136917ad1d4d", + "last_modified": 1480349198894 + }, + { + "guid": "{6af08a71-380e-42dd-9312-0111d2bc0630}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i822", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1126353", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on appears to be malware, hiding itself in the Add-ons Manager, and keeping track of certain user actions.", + "name": "{6af08a71-380e-42dd-9312-0111d2bc0630} (malware)", + "created": "2015-01-27T09:50:40Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "96d0c12b-a6cf-4539-c1cf-a1c75c14ff24", + "last_modified": 1480349198826 + }, + { + "guid": "colmer@yopmail.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i550", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=968445", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook accounts.", + "name": "Video Plugin Facebook (malware)", + "created": "2014-02-06T15:49:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c394d10b-384e-cbd0-f357-9c521715c373", + "last_modified": 1480349198744 + }, + { + "guid": "fplayer@adobe.flash", + "prefs": [], + "schema": 1480349193877, + "blockID": "i444", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=909433", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware disguised as the Flash Player plugin.", + "name": "Flash Player (malware)", + "created": "2013-08-26T14:49:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c6557989-1b59-72a9-da25-b816c4a4c723", + "last_modified": 1480349198667 + }, + { + "guid": "{6E19037A-12E3-4295-8915-ED48BC341614}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i24", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=615518", + "who": "Users of RelevantKnowledge version 1.3.328.4 and older in Firefox 4 and later.", + "why": "This add-on causes a high volume of Firefox crashes.", + "name": "comScore RelevantKnowledge", + "created": "2011-03-02T17:42:56Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.3.328.4", + "minVersion": "0.1", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "3.7a1pre" + } + ] + } + ], + "id": "7c189c5e-f95b-0aef-e9e3-8e879336503b", + "last_modified": 1480349198606 + }, + { + "guid": "crossriderapp4926@crossrider.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i91", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=754648", + "who": "All Firefox users who have installed this add-on.", + "why": "Versions of this add-on prior to 0.81.44 automatically post message to users' walls and hide them from their view. Version 0.81.44 corrects this.", + "name": "Remove My Timeline (malware)", + "created": "2012-05-14T14:16:43Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "0.81.43", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5ee3e72e-96fb-c150-fc50-dd581e960963", + "last_modified": 1480349198547 + }, + { + "guid": "/^(93abedcf-8e3a-4d02-b761-d1441e437c09@243f129d-aee2-42c2-bcd1-48858e1c22fd\\.com|9acfc440-ac2d-417a-a64c-f6f14653b712@09f9a966-9258-4b12-af32-da29bdcc28c5\\.com|58ad0086-1cfb-48bb-8ad2-33a8905572bc@5715d2be-69b9-4930-8f7e-64bdeb961cfd\\.com)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i544", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=949596", + "who": "All Firefox users who have this add-on installed. If you wish to continue using it, it can be enabled in the Add-ons Manager.", + "why": "This add-on is in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, using multiple add-on IDs and potentially doing other unwanted activities.", + "name": "SuperLyrics", + "created": "2014-01-30T11:51:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d8d25967-9814-3b65-0787-a0525c16e11e", + "last_modified": 1480349198510 + }, + { + "guid": "wHO@W9.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i980", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1192468", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "BestSavEFOrYoU (malware)", + "created": "2015-08-11T11:20:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4beb917f-68f2-1f91-beed-dff6d83006f8", + "last_modified": 1480349198483 + }, + { + "guid": "frhegnejkgner@grhjgewfewf.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1040", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1212451", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Async Codec (malware)", + "created": "2015-10-07T13:03:37Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fb6ab4ce-5517-bd68-2cf7-a93a109a528a", + "last_modified": 1480349198458 + }, + { + "guid": "firefox@luckyleap.net", + "prefs": [], + "schema": 1480349193877, + "blockID": "i471", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:38:33Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3a9e04c7-5e64-6297-8442-2816915aad77", + "last_modified": 1480349198433 + }, + { + "guid": "lugcla21@gmail.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i432", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=902072", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on includes malicious code that spams users' Facebook accounts with unwanted messages.", + "name": "FB Color Changer (malware)", + "created": "2013-08-06T13:16:22Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b6943f35-9429-1f8e-bf8e-fe37979fe183", + "last_modified": 1480349198372 + }, + { + "guid": "{99079a25-328f-4bd4-be04-00955acaa0a7}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i402", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=835678", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This group of add-ons is silently installed, bypassing our install opt-in screen. This violates our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Searchqu", + "created": "2013-06-25T15:16:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "16008331-8b47-57c8-a6f7-989914d1cb8a", + "last_modified": 1480349198341 + }, + { + "guid": "{81b13b5d-fba1-49fd-9a6b-189483ac548a}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i473", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:38:43Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "76debc7b-b875-6da4-4342-1243cbe437f6", + "last_modified": 1480349198317 + }, + { + "guid": "{e935dd68-f90d-46a6-b89e-c4657534b353}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i732", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Sites Pro", + "created": "2014-10-16T16:38:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "97fdc235-ac1a-9f20-1b4a-17c2f0d89ad1", + "last_modified": 1480349198260 + }, + { + "guid": "{32da2f20-827d-40aa-a3b4-2fc4a294352e}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i748", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963787", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Start Page", + "created": "2014-10-17T16:02:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6c980c8e-4a3c-7912-4a3a-80add457575a", + "last_modified": 1480349198223 + }, + { + "guid": "chinaescapeone@facebook.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i431", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=901770", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that uses a deceptive name and hijacks social networks.", + "name": "F-Secure Security Pack (malware)", + "created": "2013-08-05T16:43:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fbd89a9d-9c98-8481-e4cf-93e327ca8be1", + "last_modified": 1480349198192 + }, + { + "guid": "{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i716", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Website Counselor", + "created": "2014-10-02T12:12:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "debcd28c-884b-ca42-d983-6fabf91034dd", + "last_modified": 1480349198148 + }, + { + "guid": "{906000a4-88d9-4d52-b209-7a772970d91f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i474", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=935779", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is part of a malicious Firefox installer bundle.", + "name": "Installer bundle (malware)", + "created": "2013-11-07T15:38:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "326d05b9-ace7-67c6-b094-aad926c185a5", + "last_modified": 1480349197744 + }, + { + "guid": "{AB2CE124-6272-4b12-94A9-7303C7397BD1}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i20", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=627278", + "who": "Users of Skype extension versions below 5.2.0.7165 for all versions of Firefox.", + "why": "This add-on causes a high volume of Firefox crashes and introduces severe performance issues. Please <a href=\"http://www.skype.com/intl/en/get-skype/on-your-computer/click-and-call/\">update to the latest version</a>. For more information, please <a href=\"http://blog.mozilla.com/addons/2011/01/20/blocking-the-skype-toolbar-in-firefox/\">read our announcement</a>.", + "name": "Skype extension", + "created": "2011-01-20T18:39:25Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "5.2.0.7164", + "minVersion": "0.1", + "targetApplication": [] + } + ], + "id": "60e16015-1803-197a-3241-484aa961d18f", + "last_modified": 1480349197667 + }, + { + "guid": "f6682b47-e12f-400b-9bc0-43b3ccae69d1@39d6f481-b198-4349-9ebe-9a93a86f9267.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i682", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1043017", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is being silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "enformation", + "created": "2014-08-04T16:07:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a7ae65cd-0869-67e8-02f8-6d22c56a83d4", + "last_modified": 1480349197636 + }, + { + "guid": "rally_toolbar_ff@bulletmedia.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i537", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=950267", + "who": "All Firefox users who have this extension installed. If you want to continue using it, you can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by silently installing it.", + "name": "Rally Toolbar", + "created": "2014-01-23T15:51:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4ac6eb63-b51a-3296-5b02-bae77f424032", + "last_modified": 1480349197604 + }, + { + "guid": "x77IjS@xU.net", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i774", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1076771", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed and changes user settings without consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>\r\n", + "name": "YoutubeAdBlocke", + "created": "2014-10-31T16:22:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "4771da14-bcf2-19b1-3d71-bc61a1c7d457", + "last_modified": 1480349197578 + }, + { + "guid": "{49c53dce-afa0-49a1-a08b-2eb8e8444128}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i441", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844985", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed, violating our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "ytbyclick", + "created": "2013-08-09T16:58:50Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5f08d720-58c2-6acb-78ad-7af45c82c90b", + "last_modified": 1480349197550 + }, + { + "guid": "{bb7b7a60-f574-47c2-8a0b-4c56f2da9802}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i754", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080850", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "AdvanceElite", + "created": "2014-10-17T16:27:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f222ceb2-9b69-89d1-8dce-042d8131a12e", + "last_modified": 1480349197500 + }, + { + "guid": "/^((34qEOefiyYtRJT@IM5Munavn\\.com)|(Mro5Fm1Qgrmq7B@ByrE69VQfZvZdeg\\.com)|(KtoY3KGxrCe5ie@yITPUzbBtsHWeCdPmGe\\.com)|(9NgIdLK5Dq4ZMwmRo6zk@FNt2GCCLGyUuOD\\.com)|(NNux7bWWW@RBWyXdnl6VGls3WAwi\\.com)|(E3wI2n@PEHTuuNVu\\.com)|(2d3VuWrG6JHBXbQdbr@3BmSnQL\\.com))$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i324", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=841791", + "who": "All users who have this add-on installed.", + "why": "This extension is malware, installed pretending to be the Flash Player plugin.", + "name": "Flash Player (malware)", + "created": "2013-03-22T14:48:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5be3a399-af3e-644e-369d-628273b3fdc2", + "last_modified": 1480349197432 + }, + { + "guid": "{8f894ed3-0bf2-498e-a103-27ef6e88899f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i792", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "ExtraW", + "created": "2014-11-26T13:49:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "bebc9e15-59a1-581d-0163-329d7414edff", + "last_modified": 1480349197368 + }, + { + "guid": "profsites@pr.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i734", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.\r\n", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "ProfSites", + "created": "2014-10-16T16:39:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "0d6d84d7-0b3f-c5ab-57cc-6b66b0775a23", + "last_modified": 1480349197341 + }, + { + "guid": "{872b5b88-9db5-4310-bdd0-ac189557e5f5}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i497", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=945530", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making settings changes that can't be easily reverted.", + "name": "DVDVideoSoft Menu", + "created": "2013-12-03T16:08:09Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e8da89c4-c585-77e4-9872-591d20723a7e", + "last_modified": 1480349197240 + }, + { + "guid": "123456789@offeringmedia.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i664", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1036757", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that attempts to hide itself by impersonating the Adobe Flash plugin.", + "name": "Taringa MP3 / Adobe Flash", + "created": "2014-07-10T15:41:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6d0a7dda-d92a-c8e2-21be-c92b0a88ac8d", + "last_modified": 1480349197208 + }, + { + "guid": "{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i487", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=940681", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Facebook 2013 (malware)", + "created": "2013-11-19T14:59:45Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5867c409-b342-121e-3c3b-426e2f0ba1d4", + "last_modified": 1480349197109 + }, + { + "guid": "/^({4e988b08-8c51-45c1-8d74-73e0c8724579}|{93ec97bf-fe43-4bca-a735-5c5d6a0a40c4}|{aed63b38-7428-4003-a052-ca6834d8bad3}|{0b5130a9-cc50-4ced-99d5-cda8cc12ae48}|{C4CFC0DE-134F-4466-B2A2-FF7C59A8BFAD})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i524", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947481", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted.", + "name": "SweetPacks", + "created": "2013-12-20T13:43:21Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1a3a26a2-cdaa-e5ba-f6ac-47b98ae2cc26", + "last_modified": 1480349197082 + }, + { + "guid": "{87b5a11e-3b54-42d2-9102-0a7cb1f79ebf}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i838", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128327", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Cyti Web (malware)", + "created": "2015-02-06T14:29:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1ba0e57c-4c0c-4eb6-26e7-c2016769c343", + "last_modified": 1480349196965 + }, + { + "guid": "/^({bf67a47c-ea97-4caf-a5e3-feeba5331231}|{24a0cfe1-f479-4b19-b627-a96bf1ea3a56})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i542", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963819", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on has been repeatedly blocked before and keeps showing up with new add-on IDs, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "MixiDJ (malware)", + "created": "2014-01-28T14:10:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fc442b64-1b5d-bebb-c486-f431b154f3db", + "last_modified": 1480349196622 + }, + { + "guid": "/^({ebd898f8-fcf6-4694-bc3b-eabc7271eeb1}|{46008e0d-47ac-4daa-a02a-5eb69044431a}|{213c8ed6-1d78-4d8f-8729-25006aa86a76}|{fa23121f-ee7c-4bd8-8c06-123d087282c5}|{19803860-b306-423c-bbb5-f60a7d82cde5})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i622", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947482", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is known to change user settings without their consent, is distributed under multiple add-on IDs, and is also correlated with reports of tab functions being broken in Firefox, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "WiseConvert", + "created": "2014-06-18T13:48:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "ffd184fa-aa8f-8a75-ff00-ce285dec5b22", + "last_modified": 1480349196597 + }, + { + "guid": "/^({fa95f577-07cb-4470-ac90-e843f5f83c52}|ffxtlbr@speedial\\.com)$/", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i696", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1031115", + "who": "All Firefox users who have any of these add-ons installed. Users who wish to continue using these add-ons can enable them in the Add-ons Manager.", + "why": "These add-ons are silently installed and change homepage and search defaults without user consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. They are also distributed under more than one add-on ID.", + "name": "Speedial", + "created": "2014-08-21T13:55:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "130c7419-f727-a2fb-3891-627bc69a43bb", + "last_modified": 1480349196565 + }, + { + "guid": "pennerdu@faceobooks.ws", + "prefs": [], + "schema": 1480349193877, + "blockID": "i442", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=904050", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that hijacks Facebook accounts.", + "name": "Console Video (malware)", + "created": "2013-08-13T14:00:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "fb83e48e-a780-9d06-132c-9ecc65b43674", + "last_modified": 1480349196541 + }, + { + "guid": "{E90FA778-C2B7-41D0-9FA9-3FEC1CA54D66}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i446", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=788838", + "who": "All Firefox users who have this add-on installed. The add-on can be enabled again in the Add-ons Manager.", + "why": "This add-on is installed silently, in violation of our <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "YouTube to MP3 Converter", + "created": "2013-09-06T15:59:29Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "83eb6337-a3b6-84e4-e76c-ee9200b80796", + "last_modified": 1480349196471 + }, + { + "guid": "{ad7ce998-a77b-4062-9ffb-1d0b7cb23183}", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i804", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1080839", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and is considered malware, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Astromenda Search Addon", + "created": "2014-12-15T10:53:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "633f9999-c81e-bd7a-e756-de7d34feb39d", + "last_modified": 1480349196438 + }, + { + "guid": "dodatek@flash2.pl", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1279", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312748", + "who": "Any user with version 1.3 or newer of this add-on installed.", + "why": "This add-on claims to be a flash plugin and it does some work on youtube, but it also steals your facebook and adfly credentials and sends them to a remote server.", + "name": "Aktualizacja Flash WORK addon", + "created": "2016-10-27T15:52:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "1.3", + "targetApplication": [] + } + ], + "id": "2dab5211-f9ec-a1bf-c617-6f94f28b5ee1", + "last_modified": 1480349196331 + }, + { + "guid": "{2d069a16-fca1-4e81-81ea-5d5086dcbd0c}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i440", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=903647", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is installed silently and doesn't follow many other of the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. If you want to continue using this add-on, you can enable it in the Add-ons Manager.", + "name": "GlitterFun", + "created": "2013-08-09T16:26:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e3f77f3c-b1d6-3b29-730a-846007b9cb16", + "last_modified": 1480349196294 + }, + { + "guid": "xivars@aol.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i501", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=946420", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious Firefox extension that uses a deceptive name and hijacks users' Facebook accounts.", + "name": "Video Plugin Facebook (malware)", + "created": "2013-12-04T15:34:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3303d201-7006-3c0d-5fd5-45503e2e690c", + "last_modified": 1480349196247 + }, + { + "guid": "2bbadf1f-a5af-499f-9642-9942fcdb7c76@f05a14cc-8842-4eee-be17-744677a917ed.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i700", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1052599", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is widely considered malware and is apparently installed silently into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "PIX Image Viewer", + "created": "2014-08-21T16:15:16Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1b72889b-90e6-ea58-4fe8-d48257df7d8b", + "last_modified": 1480349196212 + }, + { + "guid": "toolbar@ask.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i600", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1024719", + "who": "All Firefox users who have these versions of the Ask Toolbar installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "Certain old versions of the Ask Toolbar are causing problems to users when trying to open new tabs. Using more recent versions of the Ask Toolbar should also fix this problem.", + "name": "Ask Toolbar (old Avira Security Toolbar bundle)", + "created": "2014-06-12T14:16:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "3.15.5.*", + "minVersion": "3.15.5", + "targetApplication": [] + } + ], + "id": "51c4ab3b-9ad3-c5c3-98c8-a220025fc5a3", + "last_modified": 1480349196158 + }, + { + "guid": "{729c9605-0626-4792-9584-4cbe65b243e6}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i788", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Browser Ext Assistance", + "created": "2014-11-20T10:07:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3c588238-2501-6a53-65ea-5c8ff0f3e51d", + "last_modified": 1480349196123 + }, + { + "guid": "webbooster@iminent.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i630", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=866943", + "who": "All Firefox users who have any of these add-ons installed. Users who wish to continue using them can enable them in the Add-ons Manager.", + "why": "These add-ons have been silently installed repeatedly, and change settings without user consent, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Iminent Minibar", + "created": "2014-06-26T15:49:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d894ea79-8215-7a0c-b0e9-be328c3afceb", + "last_modified": 1480349196032 + }, + { + "guid": "firefox@bandoo.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i23", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=629634", + "who": "Users of Bandoo version 5.0 for Firefox 3.6 and later.", + "why": "This add-on causes a high volume of Firefox crashes.", + "name": "Bandoo", + "created": "2011-03-01T23:30:23Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "5.0", + "minVersion": "5.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "3.7a1pre" + } + ] + } + ], + "id": "bd487cf4-3f6a-f956-a6e9-842ac8deeac5", + "last_modified": 1480349195915 + }, + { + "guid": "5nc3QHFgcb@r06Ws9gvNNVRfH.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i372", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=875752", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware pretending to be the Flash Player plugin.", + "name": "Flash Player 11 (malware)", + "created": "2013-06-18T13:23:40Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "dc71fcf5-fae4-5a5f-6455-ca7bbe4202db", + "last_modified": 1480349195887 + }, + { + "guid": "/^(7tG@zEb\\.net|ru@gfK0J\\.edu)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i854", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=952255", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "youtubeadblocker (malware)", + "created": "2015-02-09T15:41:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "cfe42207-67a9-9b88-f80c-994e6bdd0c55", + "last_modified": 1480349195851 + }, + { + "guid": "{a7aae4f0-bc2e-a0dd-fb8d-68ce32c9261f}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i378", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=865090", + "who": "All Firefox users who have installed this add-on.", + "why": "This extension is malware that hijacks Facebook accounts for malicious purposes.", + "name": "Myanmar Extension for Facebook (malware)", + "created": "2013-06-18T15:58:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "30ecd9b9-4023-d9ef-812d-f1a75bb189b0", + "last_modified": 1480349195823 + }, + { + "guid": "crossriderapp5060@crossrider.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i228", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=810016", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently side-installed by other software, and it overrides user preferences and inserts advertisements in web content.", + "name": "Savings Sidekick", + "created": "2012-11-29T16:31:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "a37f76ac-7b77-b5a3-bac8-addaacf34bae", + "last_modified": 1480349195769 + }, + { + "guid": "/^(saamazon@mybrowserbar\\.com)|(saebay@mybrowserbar\\.com)$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i672", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1011337", + "who": "All Firefox users who have these add-ons installed. Users wishing to continue using these add-ons can enable them in the Add-ons Manager.", + "why": "These add-ons are being silently installed, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Spigot Shopping Assistant", + "created": "2014-07-22T15:13:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e072a461-ee5a-c83d-8d4e-5686eb585a15", + "last_modified": 1480349195347 + }, + { + "guid": "{b99c8534-7800-48fa-bd71-519a46cdc7e1}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i596", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1011325", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed, in violation with our <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "BrowseMark", + "created": "2014-06-12T13:19:59Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f411bb0f-7c82-9061-4a80-cabc8ff45beb", + "last_modified": 1480349195319 + }, + { + "guid": "/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i856", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1130323", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "AdvanceElite (malware)", + "created": "2015-02-09T15:51:11Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e3d52650-d3e2-4cef-71f7-e6188f56fe4d", + "last_modified": 1480349195286 + }, + { + "guid": "{458fb825-2370-4973-bf66-9d7142141847}", + "prefs": [ + "app.update.auto", + "app.update.enabled", + "app.update.interval", + "app.update.url" + ], + "schema": 1480349193877, + "blockID": "i1024", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1209588", + "who": "All users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on hides itself in the Add-ons Manager, interrupts the Firefox update process, and reportedly causes other problems to users, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Web Shield", + "created": "2015-09-29T09:25:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "32c5baa7-d547-eaab-302d-b873c83bfe2d", + "last_modified": 1480349195258 + }, + { + "guid": "{f2456568-e603-43db-8838-ffa7c4a685c7}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i778", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Sup-SW", + "created": "2014-11-07T13:53:13Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "93568fa2-0cb7-4e1d-e893-d7261e81547c", + "last_modified": 1480349195215 + }, + { + "guid": "{77BEC163-D389-42c1-91A4-C758846296A5}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i566", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=964594", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-on Manager.", + "why": "This add-on is silently installed into Firefox, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "V-Bates", + "created": "2014-03-05T13:20:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "080edbac-25d6-e608-abdd-feb1ce7a9a77", + "last_modified": 1480349195185 + }, + { + "guid": "helper@vidscrab.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1077", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1231010", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using the add-on can enable it in the Add-ons Manager.", + "why": "This add-on injects remote scripts and injects unwanted content into web pages.", + "name": "YouTube Video Downloader (from AddonCrop)", + "created": "2016-01-14T14:32:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "36b2e1e0-5fda-bde3-db55-dfcbe24dfd04", + "last_modified": 1480349195157 + }, + { + "guid": "jid1-XLjasWL55iEE1Q@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i578", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1002037", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious add-on that presents itself as \"Flash Player\" but is really injecting unwanted content into Facebook pages.", + "name": "Flash Player (malware)", + "created": "2014-04-28T16:25:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "1e75b2f0-02fc-77a4-ad2f-52a4caff1a71", + "last_modified": 1480349195058 + }, + { + "guid": "{a3a5c777-f583-4fef-9380-ab4add1bc2a8}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i142", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=792132", + "who": "Todos los usuarios de Firefox que instalaron la versión 4.2 del complemento Cuevana Stream.\r\n\r\nAll Firefox users who have installed version 4.2 of the Cuevana Stream add-on.", + "why": "<strong>Español</strong>\r\nUna versión maliciosa del complemento Cuevana Stream (4.2) fue colocada en el sitio Cuevana y distribuida a muchos usuarios del sitio. Esta versión recopila información de formularios web y los envía a una dirección remota con fines maliciosos. Se le recomienda a todos los usuarios que instalaron esta versión que cambien sus contraseñas inmediatamente, y que se actualicen a la nueva versión segura, que es la 4.3.\r\n\r\n<strong>English</strong>\r\nA malicious version of the Cuevana Stream add-on (4.2) was uploaded to the Cuevana website and distributed to many of its users. This version takes form data and sends it to a remote location with malicious intent. It is recommended that all users who installed this version to update their passwords immediately, and update to the new safe version, version 4.3.\r\n\r\n", + "name": "Cuevana Stream (malicious version)", + "created": "2012-09-18T13:37:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "4.2", + "minVersion": "4.2", + "targetApplication": [] + } + ], + "id": "91e551b9-7e94-60e2-f1bd-52f25844ab16", + "last_modified": 1480349195007 + }, + { + "guid": "{34712C68-7391-4c47-94F3-8F88D49AD632}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i922", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1173154", + "who": "All Firefox users who have this add-on installed in Firefox 39 and above.\r\n", + "why": "Certain versions of this extension are causing startup crashes in Firefox 39 and above.\r\n", + "name": "RealPlayer Browser Record Plugin", + "created": "2015-06-09T15:27:31Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "39.0a1" + } + ] + } + ], + "id": "dd350efb-34ac-2bb5-5afd-eed722dbb916", + "last_modified": 1480349194976 + }, + { + "guid": "PDVDZDW52397720@XDDWJXW57740856.com", + "prefs": [ + "browser.startup.homepage", + "browser.search.defaultenginename" + ], + "schema": 1480349193877, + "blockID": "i846", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128320", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and attempts to change user settings like the home page and default search, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Ge-Force", + "created": "2015-02-06T15:03:39Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "c33e950c-c977-ed89-c86a-3be8c4be1967", + "last_modified": 1480349194949 + }, + { + "guid": "discoverypro@discoverypro.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i582", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1004231", + "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enabled it in the Add-ons Manager.", + "why": "This add-on is silently installed by the CNET installer for MP3 Rocket and probably other software packages. This is in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Website Discovery Pro", + "created": "2014-04-30T16:10:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "34eab242-6fbc-a459-a89e-0dc1a0b8355d", + "last_modified": 1480349194878 + }, + { + "guid": "jid1-bKSXgRwy1UQeRA@jetpack", + "prefs": [], + "schema": 1480349193877, + "blockID": "i680", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=979856", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into user's systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Trusted Shopper", + "created": "2014-08-01T16:34:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f701b790-b266-c69d-0fba-f2d189cb0f34", + "last_modified": 1480349194851 + }, + { + "guid": "bcVX5@nQm9l.org", + "prefs": [], + "schema": 1480349193877, + "blockID": "i848", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128266", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "boomdeal", + "created": "2015-02-09T15:21:17Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f8d6d4e1-b9e6-07f5-2b49-192106a45d82", + "last_modified": 1480349194799 + }, + { + "guid": "aytac@abc.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i504", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947341", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that hijacks users' Facebook accounts.", + "name": "Facebook Haber (malware)", + "created": "2013-12-06T12:07:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "bfaf8298-dd69-165c-e1ed-ad55584abd18", + "last_modified": 1480349194724 + }, + { + "guid": "Adobe@flash.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i136", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=790100", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware posing as a legitimate Adobe product.", + "name": "Adobe Flash (malware)", + "created": "2012-09-10T16:09:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "47ac744e-3176-5cb6-1d02-b460e0c7ada0", + "last_modified": 1480349194647 + }, + { + "guid": "{515b2424-5911-40bd-8a2c-bdb20286d8f5}", + "prefs": [], + "schema": 1480349193877, + "blockID": "i491", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=940753", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted.", + "name": "Connect DLC", + "created": "2013-11-29T14:52:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "6d658443-b34a-67ad-934e-cbf7cd407460", + "last_modified": 1480349194580 + }, + { + "guid": "/^({3f3cddf8-f74d-430c-bd19-d2c9147aed3d}|{515b2424-5911-40bd-8a2c-bdb20286d8f5}|{17464f93-137e-4646-a0c6-0dc13faf0113}|{d1b5aad5-d1ae-4b20-88b1-feeaeb4c1ebc}|{aad50c91-b136-49d9-8b30-0e8d3ead63d0})$/", + "prefs": [], + "schema": 1480349193877, + "blockID": "i516", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947478", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by making changes that can't be easily reverted and being distributed under multiple add-on IDs.", + "name": "Connect DLC", + "created": "2013-12-20T12:38:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "96f8e157-8b8b-8e2e-76cd-6850599b4370", + "last_modified": 1480349194521 + }, + { + "guid": "wxtui502n2xce9j@no14", + "prefs": [], + "schema": 1480349193877, + "blockID": "i1012", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1206157", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that takes over Facebook accounts.", + "name": "Video fix (malware)", + "created": "2015-09-21T13:04:09Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "246798ac-25fa-f4a4-258c-a71f9f6ae091", + "last_modified": 1480349194463 + }, + { + "guid": "flashX@adobe.com", + "prefs": [], + "schema": 1480349193877, + "blockID": "i168", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=807052", + "who": "All Firefox users who have this add-on installed.", + "why": "This is an exploit proof-of-concept created for a conference presentation, which will probably be copied and modified for malicious purposes. \r\n", + "name": "Zombie Browser Pack", + "created": "2012-10-30T12:07:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d7c69812-801c-8d8e-12cb-c5171bdc48a1", + "last_modified": 1480349194428 + }, + { + "guid": "{4889ddce-7a83-45e6-afc9-1e4f1149fff4}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i840", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1128327", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed and performs unwanted actions, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Cyti Web (malware)", + "created": "2015-02-06T14:30:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "be600f35-0633-29f3-c571-819e19d85db9", + "last_modified": 1480349193867 + }, + { + "guid": "{55dce8ba-9dec-4013-937e-adbf9317d990", + "prefs": [], + "schema": 1480343836083, + "blockID": "i690", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1048647", + "who": "All Firefox users. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is being silently installed in users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Deal Keeper", + "created": "2014-08-12T16:23:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "512b0d40-a10a-5ddc-963b-b9c487eb1422", + "last_modified": 1480349193833 + }, + { + "guid": "/^new@kuot\\.pro|{13ec6687-0b15-4f01-a5a0-7a891c18e4ee}|rebeccahoppkins(ty(tr)?)?@gmail\\.com|{501815af-725e-45be-b0f2-8f36f5617afc}|{9bdb5f1f-b1e1-4a75-be31-bdcaace20a99}|{e9d93e1d-792f-4f95-b738-7adb0e853b7b}|dojadewaskurwa@gmail\\.com$/", + "prefs": [], + "schema": 1480343836083, + "blockID": "i1414", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312748", + "who": "All users who have this add-on installed.", + "why": "This add-on claims to be a flash plugin and it does some work on youtube, but it also steals your facebook and adfly credentials and sends them to a remote server.", + "name": "Aktualizacja dodatku Flash (malware)", + "created": "2016-10-28T18:06:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "5cebc983-bc88-d5f8-6807-bd1cbfcd82fd", + "last_modified": 1480349193798 + }, + { + "guid": "{58d2a791-6199-482f-a9aa-9b725ec61362}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i746", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=963787", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Start Page", + "created": "2014-10-17T16:01:53Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "8ebbc7d0-635c-b74a-de9f-16eb5837b36a", + "last_modified": 1480349193730 + }, + { + "guid": "{94cd2cc3-083f-49ba-a218-4cda4b4829fd}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i590", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1013678", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is silently installed into users' profiles, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Value Apps", + "created": "2014-06-03T16:12:50Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "556b8d4d-d6c2-199d-9f33-8eccca07e8e7", + "last_modified": 1480349193649 + }, + { + "guid": "contentarget@maildrop.cc", + "prefs": [], + "schema": 1480343836083, + "blockID": "i818", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1119971", + "who": "All Firefox users who have this add-on installed.", + "why": "This is a malicious extension that hijacks Facebook accounts.", + "name": "Astro Play (malware)", + "created": "2015-01-12T09:29:19Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "440e9923-027a-6089-e036-2f78937dc193", + "last_modified": 1480349193622 + }, + { + "guid": "noOpus@outlook.com", + "prefs": [], + "schema": 1480343836083, + "blockID": "i816", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1119659", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems without their consent and performs unwanted operations.", + "name": "Full Screen (malware)", + "created": "2015-01-09T12:52:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b64d7cef-8b6c-2575-16bc-732fca7db377", + "last_modified": 1480349193537 + }, + { + "guid": "{c95a4e8e-816d-4655-8c79-d736da1adb6d}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i433", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=844945", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on bypasses the external install opt in screen in Firefox, violating the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "name": "Hotspot Shield", + "created": "2013-08-09T11:25:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b3168278-a8ae-4882-7f26-355bc362bed0", + "last_modified": 1480349193510 + }, + { + "guid": "{9802047e-5a84-4da3-b103-c55995d147d1}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i722", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1073810", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is silently installed into users' systems. It uses very unsafe practices to load its code, and leaks information of all web browsing activity. These are all violations of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "Web Finder Pro", + "created": "2014-10-07T12:58:14Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "50097c29-26b1-bf45-ffe1-83da217eb127", + "last_modified": 1480349193482 + }, + { + "guid": "/^({bf9194c2-b86d-4ebc-9b53-1c08b6ff779e}|{61a83e16-7198-49c6-8874-3e4e8faeb4f3}|{f0af464e-5167-45cf-9cf0-66b396d1918c}|{5d9968c3-101c-4944-ba71-72d77393322d}|{01e86e69-a2f8-48a0-b068-83869bdba3d0})$/", + "prefs": [], + "schema": 1480343836083, + "blockID": "i515", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=947473", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using it can enable it in the Add-ons Manager.", + "why": "The installer that includes this add-on violates the <a href=\"https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/Add-on_guidelines\">Add-on Guidelines</a> by using multiple add-on IDs and making unwanted settings changes.", + "name": "VisualBee Toolbar", + "created": "2013-12-20T12:26:49Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "029fa6f9-2351-40b7-5443-9a66e057f199", + "last_modified": 1480349193449 + }, + { + "guid": "/^({d50bfa5f-291d-48a8-909c-5f1a77b31948}|{d54bc985-6e7b-46cd-ad72-a4a266ad879e}|{d89e5de3-5543-4363-b320-a98cf150f86a}|{f3465017-6f51-4980-84a5-7bee2f961eba}|{fae25f38-ff55-46ea-888f-03b49aaf8812})$/", + "prefs": [], + "schema": 1480343836083, + "blockID": "i1137", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1251940", + "who": "All users who have this add-on installed.", + "why": "This is a malicious add-on that hides itself from view and disables various security features in Firefox.", + "name": "Watcher (malware)", + "created": "2016-03-04T17:56:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "252e18d0-85bc-7bb3-6197-5f126424c9b3", + "last_modified": 1480349193419 + }, + { + "guid": "ffxtlbr@claro.com", + "prefs": [], + "schema": 1480343836083, + "blockID": "i218", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=816762", + "who": "All Firefox users who have installed this add-on.", + "why": "The Claro Toolbar is side-installed with other software, unexpectedly changing users' settings and then making it impossible for these settings to be reverted by users.", + "name": "Claro Toolbar", + "created": "2012-11-29T16:07:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "e017a3b2-9b37-b8a0-21b0-bc412ae8a7f4", + "last_modified": 1480349193385 + }, + { + "guid": "/^(j003-lqgrmgpcekslhg|SupraSavings|j003-dkqonnnthqjnkq|j003-kaggrpmirxjpzh)@jetpack$/", + "prefs": [], + "schema": 1480343836083, + "blockID": "i692", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1048656", + "who": "All Firefox users who have this add-on installed. Users who wish to continue using this add-on can enable it in the Add-ons Manager.", + "why": "This add-on is being silently installed in users' systems, in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>.", + "name": "SupraSavings", + "created": "2014-08-12T16:27:06Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "b0d30256-4581-1489-c241-d2e85b6c38f4", + "last_modified": 1480349193295 + }, + { + "guid": "helperbar@helperbar.com", + "prefs": [], + "schema": 1480343836083, + "blockID": "i258", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=817786", + "who": "All Firefox users who have this add-on installed. This only applies to version 1.0 of Snap.do. Version 1.1 fixed all the issues for which this block was created.", + "why": "This extension violates a number of our <a href=\"https://developer.mozilla.org/en-US/docs/Addons/Add-on_guidelines\">Add-on Guidelines</a>, particularly on installation and settings handling. It also causes some stability problems in Firefox due to the way the toolbar is handled.\r\n\r\nUsers who wish to keep the add-on enabled can enable it again in the Add-ons Manager.", + "name": "Snap.do", + "created": "2013-01-28T13:52:26Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "f1ede5b8-7757-5ec5-d8ed-1a01889154aa", + "last_modified": 1480349193254 + }, + { + "guid": "/^((support2_en@adobe14\\.com)|(XN4Xgjw7n4@yUWgc\\.com)|(C7yFVpIP@WeolS3acxgS\\.com)|(Kbeu4h0z@yNb7QAz7jrYKiiTQ3\\.com)|(aWQzX@a6z4gWdPu8FF\\.com)|(CBSoqAJLYpCbjTP90@JoV0VMywCjsm75Y0toAd\\.com)|(zZ2jWZ1H22Jb5NdELHS@o0jQVWZkY1gx1\\.com))$/", + "prefs": [], + "schema": 1480343836083, + "blockID": "i326", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=841791", + "who": "All users who have this add-on installed.", + "why": "This extension is malware, installed pretending to be the Flash Player plugin.", + "name": "Flash Player (malware)", + "created": "2013-03-22T14:49:08Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "3142020b-8af9-1bac-60c5-ce5ad0ff3d42", + "last_modified": 1480349193166 + }, + { + "guid": "newmoz@facebook.com", + "prefs": [], + "schema": 1480343836083, + "blockID": "i576", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=997986", + "who": "All Firefox users who have this add-on installed.", + "why": "This add-on is malware that hijacks Facebook user accounts and sends spam on the user's behalf.", + "name": "Facebook Service Pack (malware)", + "created": "2014-04-22T14:34:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "d85798d3-9b87-5dd9-ace2-64914b93df77", + "last_modified": 1480349193114 + }, + { + "guid": "{0F827075-B026-42F3-885D-98981EE7B1AE}", + "prefs": [], + "schema": 1480343836083, + "blockID": "i334", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=862272", + "who": "All Firefox users who have this extension installed.", + "why": "This extension is malicious and is installed under false pretenses, causing problems for many Firefox users. Note that this is not the same <a href=\"https://addons.mozilla.org/firefox/addon/browserprotect/\">BrowserProtect extension</a> that is listed on our add-ons site. That one is safe to use.", + "name": "Browser Protect / bProtector (malware)", + "created": "2013-04-16T13:25:01Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 3, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [] + } + ], + "id": "aad4545f-8f9d-dd53-2aa8-e8945cad6185", + "last_modified": 1480349192987 + } + ], + "timestamp": 1604940558744 +} diff --git a/services/settings/dumps/blocklists/gfx.json b/services/settings/dumps/blocklists/gfx.json new file mode 100644 index 0000000000..5d2f0de672 --- /dev/null +++ b/services/settings/dumps/blocklists/gfx.json @@ -0,0 +1,1509 @@ +{ + "data": [ + { + "os": "Linux", + "schema": 1692576006114, + "vendor": "0x1002", + "details": { + "bug": "1831598", + "name": "AMD R600 Linux Webrender" + }, + "devices": [ + "0x9400", + "0x9401", + "0x9402", + "0x9403", + "0x9405", + "0x940a", + "0x940b", + "0x940f", + "0x94c0", + "0x94c1", + "0x94c3", + "0x94c4", + "0x94c5", + "0x94c6", + "0x94c7", + "0x94c8", + "0x94c9", + "0x94cb", + "0x94cc", + "0x94cd", + "0x9580", + "0x9581", + "0x9583", + "0x9586", + "0x9587", + "0x9588", + "0x9589", + "0x958a", + "0x958b", + "0x958c", + "0x958d", + "0x958e", + "0x958f", + "0x9500", + "0x9501", + "0x9504", + "0x9505", + "0x9506", + "0x9507", + "0x9508", + "0x9509", + "0x950f", + "0x9511", + "0x9515", + "0x9517", + "0x9519", + "0x95c0", + "0x95c2", + "0x95c4", + "0x95c5", + "0x95c6", + "0x95c7", + "0x95c9", + "0x95cc", + "0x95cd", + "0x95ce", + "0x95cf", + "0x9590", + "0x9591", + "0x9593", + "0x9595", + "0x9596", + "0x9597", + "0x9598", + "0x9599", + "0x959b", + "0x9610", + "0x9611", + "0x9612", + "0x9613", + "0x9614", + "0x9615", + "0x9616", + "0x9710", + "0x9711", + "0x9712", + "0x9713", + "0x9714", + "0x9715" + ], + "enabled": true, + "feature": "WEBRENDER", + "driverVendor": "", + "versionRange": { + "maxVersion": "", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "80366b54-4a61-4aa9-af07-15c43abe4684", + "last_modified": 1692730580117 + }, + { + "os": "Linux", + "schema": 1680017089172, + "vendor": "0x10de", + "details": { + "bug": "1824778", + "name": "NVIDIA driver stability issues" + }, + "devices": [], + "enabled": true, + "feature": "DMABUF", + "hardware": "", + "driverVendor": "nvidia/unknown", + "versionRange": { + "maxVersion": "111.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "78cc082a-221b-4353-8b07-57db1d03c871", + "last_modified": 1680018554787 + }, + { + "os": "Linux", + "schema": 1680017296058, + "vendor": "0x10de", + "details": { + "bug": "1824778", + "name": "NVIDIA driver stability issues" + }, + "devices": [], + "enabled": true, + "feature": "THREADSAFE_GL", + "hardware": "", + "driverVendor": "nvidia/unknown", + "versionRange": { + "maxVersion": "111.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "57d7fc9f-3e12-4b79-b28e-84abda7496af", + "last_modified": 1680018554776 + }, + { + "os": "Linux", + "schema": 1677861301308, + "vendor": "0x10de", + "details": { + "bug": "1788573", + "name": "NVIDIA driver stability issues" + }, + "devices": [], + "enabled": true, + "feature": "DMABUF", + "hardware": "", + "driverVendor": "non-mesa/all", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "530.30.2.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "5193a59e-7502-4462-8a56-5d918f8983eb", + "last_modified": 1677879347585 + }, + { + "os": "Linux", + "schema": 1677861725427, + "vendor": "0x10de", + "details": { + "bug": "1788573", + "name": "NVIDIA driver stability issues" + }, + "devices": [], + "enabled": true, + "feature": "THREADSAFE_GL", + "hardware": "", + "driverVendor": "non-mesa/all", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "530.30.2.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "382db832-b5ac-4d3d-831a-f7409ae82ce2", + "last_modified": 1677879347580 + }, + { + "os": "All", + "schema": 1677776405692, + "vendor": "0x1002", + "details": { + "bug": "1817269", + "name": "AMD video overlay bugs" + }, + "devices": [], + "enabled": true, + "feature": "VIDEO_OVERLAY", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "6fb52585-39e1-4ba0-8e35-9e2246b57a15", + "last_modified": 1677776905806 + }, + { + "os": "All", + "schema": 1677776209251, + "vendor": "0x10de", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1817617", + "name": "Block video overlay on Nvidia too" + }, + "devices": [], + "enabled": true, + "feature": "VIDEO_OVERLAY", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "0dee1a85-cdaa-4bf5-9300-cadfd4e2d1c6", + "last_modified": 1677776905802 + }, + { + "os": "Linux", + "schema": 1677775532727, + "vendor": "0x10de", + "details": { + "bug": "1817336", + "name": "Nouveau multithread race" + }, + "devices": [], + "enabled": true, + "feature": "ACCELERATED_CANVAS2D", + "hardware": "", + "driverVendor": "mesa/nouveau", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "b2a88f5c-4470-4441-9ffd-a5386a528d8b", + "last_modified": 1677776209179 + }, + { + "os": "Linux", + "schema": 1677775524274, + "vendor": "0x10de", + "details": { + "bug": "1777849", + "name": "GLX race caused by accelerated canvas with WebGL2 on Nvidia" + }, + "devices": [], + "enabled": true, + "feature": "ACCELERATED_CANVAS2D", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_OS_VERSION", + "windowProtocol": "x11/all", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "c0927c75-f1b0-4efc-9183-5861809858f0", + "last_modified": 1677776209175 + }, + { + "os": "Linux", + "schema": 1677775515298, + "vendor": "0x8086", + "details": { + "bug": "1777849", + "name": "GLX race caused by accelerated canvas with WebGL2 on Intel" + }, + "devices": [], + "enabled": true, + "feature": "ACCELERATED_CANVAS2D", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_OS_VERSION", + "windowProtocol": "x11/all", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "28ebdbf7-d638-481c-bb91-99c97ca4a5b2", + "last_modified": 1677776209169 + }, + { + "os": "Linux", + "schema": 1677775501360, + "vendor": "0x1002", + "details": { + "bug": "1777849", + "name": "GLX race caused by accelerated canvas with WebGL2" + }, + "devices": [], + "enabled": true, + "feature": "ACCELERATED_CANVAS2D", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "110.*", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_OS_VERSION", + "windowProtocol": "x11/all", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "d8694183-9ab5-4f3a-a3e5-9da7b82c56da", + "last_modified": 1677776209165 + }, + { + "os": "Android", + "schema": 1643797519778, + "vendor": "Vivante Corporation", + "details": { + "bug": "1719327", + "name": "Vivante startup crashes" + }, + "devices": [ + "Vivante GC7000UL" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "driverVendor": "", + "versionRange": { + "maxVersion": "97", + "minVersion": "" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "bbd241d5-e3b2-44eb-bb4d-69ae07f12a0e", + "last_modified": 1643818378440 + }, + { + "os": "All", + "schema": 1623194747864, + "vendor": "0x8086", + "details": { + "bug": "1715382", + "name": "Block D2D to avoid bug 1712047" + }, + "devices": [ + "0x0046", + "0x0042" + ], + "enabled": true, + "feature": "DIRECT2D", + "hardware": "gen5", + "driverVendor": "", + "versionRange": { + "maxVersion": "89.0", + "minVersion": "89.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "windowProtocol": "", + "driverVersionMax": "", + "desktopEnvironment": "", + "driverVersionComparator": "", + "id": "53585ac9-ab16-4201-a075-64730df7c752", + "last_modified": 1623195938349 + }, + { + "os": "All", + "schema": 1605987696433, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678942", + "name": "sandybridge from WebRender in 83 on Windows" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "driverVersionComparator": "", + "id": "6359e124-61ae-466f-9066-8f58f783b1ba", + "last_modified": 1606146402211 + }, + { + "os": "Darwin 20", + "schema": 1605878544498, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678448", + "name": "sandybridge from WebRender in 83 on macOS" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "driverVersionComparator": "", + "id": "f138fcc2-3477-4b1e-a0d3-97c7a99246cf", + "last_modified": 1605895309599 + }, + { + "os": "Darwin 19", + "schema": 1605885821224, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678448", + "name": "sandybridge from WebRender in 83 on macOS" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "id": "29815508-ba95-474b-ab55-e1669ece31bb", + "last_modified": 1605895309594 + }, + { + "os": "Darwin 18", + "schema": 1605887703589, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678448", + "name": "sandybridge from WebRender in 83 on macOS" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "driverVersionComparator": "", + "id": "84ab1546-f779-42cb-8692-4426d38a06ec", + "last_modified": 1605895309589 + }, + { + "os": "Darwin 17", + "schema": 1605887758025, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678448", + "name": "sandybridge from WebRender in 83 on macOS" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "driverVersionComparator": "", + "id": "bec686f1-428d-4979-931a-211974cd7f33", + "last_modified": 1605895309584 + }, + { + "os": "Darwin 16", + "schema": 1605887815692, + "vendor": "0x8086", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678448", + "name": "sandybridge from WebRender in 83 on macOS" + }, + "devices": [ + "0x0102", + "0x0106", + "0x010a", + "0x0112", + "0x0116", + "0x0122", + "0x0126" + ], + "enabled": true, + "feature": "WEBRENDER", + "hardware": "", + "versionRange": { + "maxVersion": "83.*", + "minVersion": "83.0" + }, + "driverVersion": "", + "featureStatus": "BLOCKED_DEVICE", + "driverVersionMax": "", + "driverVersionComparator": "", + "id": "fb5e482f-a0b2-434f-a667-d01b1d7fdf77", + "last_modified": 1605895309578 + }, + { + "os": "Darwin 11", + "schema": 1480349134676, + "vendor": "0x10de", + "blockID": "g200", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:35:32Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "ae36a32e-5f4f-2f98-804f-22b0de5e7bfa", + "last_modified": 1480349135384 + }, + { + "os": "WINNT 6.0", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1217", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 6.0", + "created": "2016-05-23T15:43:58Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "a3fe9700-b9e6-1c31-f94b-030f72f4aa07", + "last_modified": 1480349135360 + }, + { + "os": "All", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1124", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1170143", + "who": ".", + "why": ".", + "name": "Intel Driver 8.15.10.2086", + "created": "2016-02-22T23:40:14Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.2086", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "851c8fcb-5763-bf89-813d-0f98cdd6f8c1", + "last_modified": 1480349135337 + }, + { + "os": "Darwin 12", + "schema": 1480349134676, + "vendor": "0x10de", + "blockID": "g202", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:37:07Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "a825cb5d-36f6-54b0-31b3-6668aae49cdd", + "last_modified": 1480349135314 + }, + { + "os": "Darwin 12", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g208", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:42:34Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "a8e8aa1a-8e70-af51-065d-81ed31d23221", + "last_modified": 1480349135291 + }, + { + "os": "All", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1073", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T16:01:46Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1994", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "13f75dff-c65f-eab1-4be5-864a79503ad5", + "last_modified": 1480349135267 + }, + { + "os": "WINNT 10.0", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1221", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 10.0", + "created": "2016-05-23T15:47:17Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "65ad7851-4af6-2a10-1d47-72943e63dcf0", + "last_modified": 1480349135245 + }, + { + "os": "WINNT 5.2", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1216", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 5.2", + "created": "2016-05-23T15:32:22Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "18968a19-7317-cc56-f9fc-f568f40abca4", + "last_modified": 1480349135215 + }, + { + "os": "WINNT 5.1", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1215", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 5.1", + "created": "2016-05-23T15:31:32Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "5c141970-5076-0af4-152f-efb3642a7b14", + "last_modified": 1480349135171 + }, + { + "os": "Darwin 12", + "schema": 1480349134676, + "vendor": "0x1002", + "blockID": "g234", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=815437", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing (AMD)", + "created": "2012-11-30T12:50:57Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "3310f8b8-1789-1b13-9326-5531943514ae", + "last_modified": 1480349135145 + }, + { + "os": "Darwin 10", + "schema": 1480349134676, + "vendor": "0x1002", + "blockID": "g230", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=815437", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing (AMD)", + "created": "2012-11-30T12:48:55Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "4769433c-51e9-a0f9-eef5-45829ac54243", + "last_modified": 1480349135120 + }, + { + "os": "WINNT 6.1", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g1218", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 6.1", + "created": "2016-05-23T15:44:43Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "7a3ea538-8519-2845-53bf-6a4be0ff9e6b", + "last_modified": 1480349135096 + }, + { + "os": "Darwin 10", + "schema": 1480349134676, + "vendor": "0x10de", + "blockID": "g198", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:34:32Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "c9551a39-62fb-f652-8032-cbe9d2ce419c", + "last_modified": 1480349135072 + }, + { + "os": "WINNT 5.1", + "schema": 1480349134676, + "vendor": "0x8086", + "blockID": "g511", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=951422", + "who": "All Firefox users.", + "why": "Stability.", + "name": "Intel driver < 6.14.10.5216 for DIRECT3D_9_LAYERS on XP", + "created": "2013-12-18T14:29:36Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "driverVersion": "6.14.10.5218", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN", + "id": "37ffcc44-0d03-681e-69bf-89c38111fcdd", + "last_modified": 1480349135048 + }, + { + "os": "WINNT 6.1", + "schema": 1480343737148, + "vendor": "0x10de", + "blockID": "g36", + "details": { + "bug": "", + "who": "", + "why": "", + "name": "<BlocklistGfx: None: WINNT 6.1 : 0x10de : 0x0a6c>", + "created": "2011-03-01T12:13:42Z" + }, + "devices": [ + "0x0a6c" + ], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "driverVersion": "8.17.12.5896", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "6ac4f440-9661-ee18-248f-f3b7b6eb8deb", + "last_modified": 1480349134662 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1069", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T15:58:51Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1855", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "9928c898-4941-555d-e502-54a90e9e5371", + "last_modified": 1480349134638 + }, + { + "os": "WINNT 10.0", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g974", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1189266", + "who": "All users who have these cards on Windows 10.", + "why": "Crashes on Windows 10.", + "name": "ATI cards on Windows 10", + "created": "2015-08-11T10:30:37Z" + }, + "devices": [ + "0x6920", + "0x6921", + "0x6928", + "0x6929", + "0x692b", + "0x692f", + "0x6930", + "0x6938", + "0x6939", + "0x6900", + "0x6901", + "0x6902", + "0x6903", + "0x6907", + "0x7300", + "0x9870", + "0x9874", + "0x9875", + "0x9876", + "0x9877" + ], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "15.201.1151.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN", + "id": "ec9cf554-49de-e5ea-bbdc-54f84d290605", + "last_modified": 1480349134615 + }, + { + "os": "WINNT 6.1", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g280", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=840161", + "who": "All Firefox user who use this driver.", + "why": "Crashes.", + "name": "AMD Radeon HD 6290/6300/6310/6320 for Direct3D 9", + "created": "2013-02-13T14:52:05Z" + }, + "devices": [ + "0x9802", + "0x9803", + "0x9803", + "0x9804", + "0x9805", + "0x9806", + "0x9807" + ], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "featureStatus": "BLOCKED_DEVICE", + "id": "9b7a25d9-075e-e5e9-678e-af1292db7463", + "last_modified": 1480349134582 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g984", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1193802", + "who": "All users using these driver versions.", + "why": "Major graphics artifacts.", + "name": "Intel graphics block", + "created": "2015-08-14T16:21:45Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "8.15.10.2413", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "f1830d7b-c807-afda-1b97-f41474157165", + "last_modified": 1480349134550 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1070", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T15:59:29Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1872", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "0f7b2114-976c-db1c-2d9a-02d47d1968e1", + "last_modified": 1480349134528 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x1022", + "blockID": "g148", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=793869", + "who": "All Firefox users who have these drivers installed.", + "why": "Some features in these drivers are causing frequent crashes in Firefox.", + "name": "ATI/AMD driver 8.982.0.0 (Direct3D)", + "created": "2012-09-25T14:31:35Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "driverVersion": "8.982.0.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "85925d60-d04e-2cbd-cdee-de70ea6be4f4", + "last_modified": 1480349134505 + }, + { + "os": "WINNT 6.2", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g192", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806991", + "who": "All Firefox users.", + "why": "Crashes.", + "name": "AMD DIRECT2D on Windows 8, [0x1002]", + "created": "2012-11-02T16:33:13Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "9.10.8.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "5e34680b-5279-4ffc-7382-b1bc661b3094", + "last_modified": 1480349134483 + }, + { + "os": "WINNT 6.1", + "schema": 1480343737148, + "vendor": "0x10de", + "blockID": "g35", + "details": { + "bug": "", + "who": "", + "why": "", + "name": "<BlocklistGfx: None: WINNT 6.1 : 0x10de : 0x0a6c>", + "created": "2011-03-01T12:13:40Z" + }, + "devices": [ + "0x0a6c" + ], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "8.17.12.5896", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "00a6b9d2-285f-83f0-0a1f-ef0205a60067", + "last_modified": 1480349134459 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1071", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T16:00:05Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1883", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "d4297784-dbac-906c-16b7-1a8792e868de", + "last_modified": 1480349134436 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g150", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=793869", + "who": "All Firefox users who have these drivers installed.", + "why": "Some features in these drivers are causing frequent crashes in Firefox.", + "name": "ATI/AMD driver 8.982.0.0 (Direct3D)", + "created": "2012-09-25T14:33:17Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "driverVersion": "8.982.0.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "6a3c05bb-f971-d3d4-2a02-184e6970ba87", + "last_modified": 1480349134413 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g144", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=792480", + "who": "All Firefox users who have these drivers installed.", + "why": "Some features in these drivers are causing frequent crashes in Firefox.", + "name": "ATI/AMD driver 8.982.0.0", + "created": "2012-09-24T08:23:25Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "8.982.0.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "35e25a7c-d77d-6b43-fa5e-39b7e9c8a481", + "last_modified": 1480349134391 + }, + { + "os": "WINNT 5.1", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1251", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1283601", + "who": ".", + "why": ".", + "name": "Intel driver < 6.14.10.5216 for DIRECT3D_9_LAYERS on XP", + "created": "2016-07-22T12:30:30Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_ANGLE", + "versionRange": { + "maxVersion": "49.9" + }, + "driverVersion": "6.14.10.5218", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN", + "id": "ea27a4d2-ed88-0190-db12-473820b340b2", + "last_modified": 1480349134369 + }, + { + "os": "Darwin 11", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g206", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:39:32Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "91cc2cec-11dd-0f77-ffad-f57e69b3f441", + "last_modified": 1480349134345 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1068", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T15:58:01Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1851", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "b7452893-25cc-497a-626c-aacee78683c6", + "last_modified": 1480349134323 + }, + { + "os": "WINNT 6.2", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1219", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 6.2", + "created": "2016-05-23T15:45:21Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "73e4e12d-f27a-1113-b0e9-c05cb4dd78d2", + "last_modified": 1480349134301 + }, + { + "os": "WINNT 5.1", + "schema": 1480343737148, + "vendor": "0x10de", + "blockID": "g37", + "details": { + "bug": "", + "who": "", + "why": "", + "name": "<BlocklistGfx: None: WINNT 5.1 : 0x10de : >", + "created": "2011-03-14T14:11:51Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT3D_9_LAYERS", + "driverVersion": "7.0.0.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "GREATER_THAN_OR_EQUAL", + "id": "fb507393-460e-685e-91de-1ce3b18ddbc0", + "last_modified": 1480349134278 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x1022", + "blockID": "g146", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=792480", + "who": "All Firefox users who have these drivers installed.", + "why": "Some features in these drivers are causing frequent crashes in Firefox.", + "name": "ATI/AMD driver 8.982.0.0", + "created": "2012-09-24T08:23:33Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "8.982.0.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "e6984930-7969-3cbc-fbaa-0f350afe6f14", + "last_modified": 1480349134255 + }, + { + "os": "WINNT 8.1", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g992", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1189266", + "who": "Users who have these cards installed.", + "why": "Crashing.", + "name": "ATI cards on Windows 10", + "created": "2015-08-20T09:56:09Z" + }, + "devices": [ + "0x6920", + "0x6921", + "0x6928", + "0x6929", + "0x692b", + "0x692f", + "0x6930", + "0x6938", + "0x6939", + "0x6900", + "0x6901", + "0x6902", + "0x6903", + "0x6907", + "0x7300", + "0x9870", + "0x9874", + "0x9875", + "0x9876", + "0x9877" + ], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "15.201.1151.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN", + "id": "77b27d80-1ebe-04de-2db9-bb197acbac32", + "last_modified": 1480349134232 + }, + { + "os": "Darwin 11", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g232", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing (AMD)", + "created": "2012-11-30T12:49:56Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "8f8e9025-a9ec-5f1e-ae9f-aee28176bdf7", + "last_modified": 1480349134210 + }, + { + "os": "WINNT 6.3", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1220", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1266220", + "who": ".", + "why": ".", + "name": "10.18.10.3947 winnt 6.3", + "created": "2016-05-23T15:46:34Z" + }, + "devices": [], + "enabled": true, + "feature": "HARDWARE_VIDEO_DECODING", + "driverVersion": "10.18.10.3947", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "1f91fdd6-8b89-d948-d05a-86491c705ce2", + "last_modified": 1480349134187 + }, + { + "os": "Darwin 10", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g204", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=809550", + "who": "All Firefox users.", + "why": "Security problems.", + "name": "Mac OS X WebGL anti-aliasing", + "created": "2012-11-12T10:38:32Z" + }, + "devices": [], + "enabled": true, + "feature": "WEBGL_MSAA", + "featureStatus": "BLOCKED_DEVICE", + "id": "33866148-1d41-2d4c-27b8-5f147467686b", + "last_modified": 1480349134160 + }, + { + "os": "WINNT 6.1", + "schema": 1480343737148, + "vendor": "0x1002", + "blockID": "g278", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=840161", + "who": "All Firefox users using these drivers.", + "why": "Crashes", + "name": "AMD Radeon HD 6290/6300/6310/6320 for Direct2D", + "created": "2013-02-13T14:50:33Z" + }, + "devices": [ + "0x9802", + "0x9803", + "0x9803", + "0x9804", + "0x9805", + "0x9806", + "0x9807" + ], + "enabled": true, + "feature": "DIRECT2D", + "featureStatus": "BLOCKED_DEVICE", + "id": "bccdbab1-c335-6f2e-6d0a-d563ccb2f1f4", + "last_modified": 1480349134138 + }, + { + "os": "All", + "schema": 1480343737148, + "vendor": "0x8086", + "blockID": "g1072", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1207993", + "who": ".", + "why": ".", + "name": "Crash in igd10umd32.dll@0x18f35", + "created": "2015-12-21T16:00:50Z" + }, + "devices": [ + "0x2a42", + "0x2e22", + "0x2e12", + "0x2e32", + "0x0046" + ], + "enabled": true, + "driverVersion": "8.15.10.1892", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "EQUAL", + "id": "d780a857-b80d-0049-981e-1a8d3ebe485e", + "last_modified": 1480349134115 + }, + { + "os": "WINNT 6.2", + "schema": 1480343737148, + "vendor": "0x1022", + "blockID": "g194", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=806991", + "who": "All Firefox users.", + "why": "Crashes.", + "name": "AMD DIRECT2D on Windows 8, [0x1022]", + "created": "2012-11-02T16:35:37Z" + }, + "devices": [], + "enabled": true, + "feature": "DIRECT2D", + "driverVersion": "9.10.8.0", + "featureStatus": "BLOCKED_DRIVER_VERSION", + "driverVersionComparator": "LESS_THAN_OR_EQUAL", + "id": "0afc5fe9-1f81-7fde-2424-0cb4d933c173", + "last_modified": 1480349134090 + } + ], + "timestamp": 1692730580117 +} diff --git a/services/settings/dumps/blocklists/moz.build b/services/settings/dumps/blocklists/moz.build new file mode 100644 index 0000000000..8ace1d0541 --- /dev/null +++ b/services/settings/dumps/blocklists/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Blocklist Implementation") + +# The addons blocklist is also in mobile/android/installer/package-manifest.in +FINAL_TARGET_FILES.defaults.settings.blocklists += [ + "addons-bloomfilters.json", + "gfx.json", +] +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + FINAL_TARGET_FILES.defaults.settings.blocklists += ["plugins.json"] + +FINAL_TARGET_FILES.defaults.settings.blocklists["addons-bloomfilters"] += [ + "addons-bloomfilters/addons-mlbf.bin", + "addons-bloomfilters/addons-mlbf.bin.meta.json", +] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DIST_SUBDIR = "browser" diff --git a/services/settings/dumps/blocklists/plugins.json b/services/settings/dumps/blocklists/plugins.json new file mode 100644 index 0000000000..d9aff97acb --- /dev/null +++ b/services/settings/dumps/blocklists/plugins.json @@ -0,0 +1,3515 @@ +{ + "data": [ + { + "os": "Linux", + "schema": 1603099489080, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671688", + "why": "Old versions of the Flash Player plugin have known vulnerabilities. All users are strongly recommended to check for updates <a href=\"https://get.adobe.com/flashplayer/\">on Adobe's Flash page</a>.", + "name": "Flash Player Plugin on Linux 32.0.0.443 and older (click-to-play)" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/flashplayer/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "32.0.0.443", + "minVersion": "0", + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libflashplayer\\.so", + "id": "49b843cc-a8fc-4ede-be0c-a0da56d0214f", + "last_modified": 1603126502200 + }, + { + "schema": 1603050119276, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1671688", + "why": "Old versions of the Flash Player plugin have known vulnerabilities. All users are strongly recommended to check for updates <a href=\"https://get.adobe.com/flashplayer/\">on Adobe's Flash page</a>.", + "name": "Flash Player Plugin 32.0.0.443 and older (click-to-play)" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/flashplayer/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "32.0.0.443", + "minVersion": "0", + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(NPSWF32.*\\.dll)|(NPSWF64.*\\.dll)|(Flash\\ Player\\.plugin)", + "id": "832dc9ff-3314-4df2-abcf-7bd65a645371", + "last_modified": 1603126502196 + }, + { + "schema": 1524072853593, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1454720", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 8 update 76 to 170 (click-to-play), Windows" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 8 U(7[6-9]|[8-9]\\d|1([0-6]\\d|70))(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "33147281-45b2-487e-9fea-f66c6517252d", + "last_modified": 1524073129197 + }, + { + "schema": 1524072853593, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1454720", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 8 update 76 to 170 (click-to-play), Linux" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 11\\.(7[6-9]|[8-9]\\d|1([0-6]\\d|70))(\\.\\d+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "ab59635e-2e93-423a-9d57-871dde8ae675", + "last_modified": 1524073076276 + }, + { + "schema": 1524068554047, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1454720", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 8 update 170 and lower (click-to-play), Mac OS X" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 8 Update 170", + "minVersion": "Java 8 Update", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "f24ffd6f-e02b-4cf4-91d9-d54cd793e4bf", + "last_modified": 1524072853584 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1330483", + "who": "All users who have these versions of the plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates <a href=\"https://get.adobe.com/reader/\">on Adobe's Reader page</a>.", + "name": "Adobe Reader XI 11.0.18 and lower" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "11.0.18", + "minVersion": "11.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "59c31ade-88d6-4b22-8601-5316f82e3977", + "last_modified": 1519390923381 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1330483", + "who": "All users who have these versions of the plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates <a href=\"https://get.adobe.com/reader/\">on Adobe's Reader page</a>.", + "name": "Adobe Reader 15.006.30244 and lower" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "15.006.30244", + "minVersion": "15.006", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "3f136e56-4c93-4619-8c0d-d86258c1065d", + "last_modified": 1519390923357 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1330483", + "who": "All users who have these versions of the plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates <a href=\"https://get.adobe.com/reader/\">on Adobe's Reader page</a>.", + "name": "Adobe Reader 15.020.20042 and lower" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "15.020.20042", + "minVersion": "15.020", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "43b45ad8-a373-42c1-89c6-64e2746885e5", + "last_modified": 1519390923333 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1347194", + "why": "Old versions of this plugin have critical security vulnerabilities. You can update your plugin on <a href=\"https://get.adobe.com/shockwave/\">Adobe's website</a>.", + "name": "Adobe Shockwave for Director, version 12.2.7.197" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "12.2.7.197", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "np32dsw_1227197\\.dll", + "id": "97647cd8-03c5-416c-b9d3-cd5ef87ab39f", + "last_modified": 1519390923310 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381926", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 7 update 97 to 150 (click-to-play), Mac OS X" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 150", + "minVersion": "Java 7 Update 97", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "d70fdf87-0441-479c-833f-2213b769eb40", + "last_modified": 1519390923286 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381926", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 7 update 97 to 150 (click-to-play), Windows" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 7 U(97|98|99|1([0-4][0-9]|50))(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "427f5ec6-d1a7-4725-ac29-d5c5e51de537", + "last_modified": 1519390923263 + }, + { + "schema": 1519390914958, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381926", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on <a href=\"https://java.com/\">java.com</a>.", + "name": "Java Plugin 7 update 97 to 150 (click-to-play), Linux" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 10\\.(97|98|99|1([0-4]\\d|50))(\\.\\d+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "fdc40de3-95ab-41a5-94cf-9b400221a713", + "last_modified": 1519390923239 + }, + { + "schema": 1519390914958, + "blockID": "p416", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 6 updates 42 to 45 (click-to-play), Mac OS X", + "created": "2013-06-28T12:44:58Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 6 Update 45", + "minVersion": "Java 6 Update 42", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "02a5865d-f6ea-a330-674e-7dea7390680f", + "last_modified": 1519390921640 + }, + { + "schema": 1519390914958, + "blockID": "p119", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=780717", + "who": "All Firefox users who have installed the Java plugin, JRE versions below 1.6.0_33 or between 1.7.0 and 1.7.0_4.", + "why": "Outdated versions of the Java plugin are vulnerable to an actively exploited security issue. All users are strongly encouraged to <a href=\"http://java.com/inc/BrowserRedirect1.jsp\">update their Java plugin</a>. For more information, please <a href=\"http://blog.mozilla.org/addons/2012/08/14/new-java-blocklist/\">read our blog post</a> or <a href=\"http://www.oracle.com/technetwork/topics/security/javacpujun2012-1515912.html\">Oracle's Advisory</a>.", + "name": "Java Plugin", + "created": "2012-08-14T09:27:32Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.(6\\.0_(\\d|[0-2]\\d?|3[0-2])|7\\.0(_0?([1-4]))?)([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "d1aa9366-d40b-a68d-b3ed-4b36e4939442", + "last_modified": 1519390921616 + }, + { + "schema": 1519390914958, + "blockID": "p328", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=853629", + "who": "All Firefox users who have these versions of the plugin installed.", + "why": "Old versions of this plugin are potentially insecure and unstable. All affected users should visit the <a href=\"http://www.mozilla.com/plugincheck/\">plugin check</a> page to look for updates for their Silverlight plugin.", + "name": "Silverlight for Mac OS X between 5.1 and 5.1.20124.* (click-to-play)", + "created": "2013-03-26T09:35:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "5.1.20124.9999", + "minVersion": "5.1", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "19.0a1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.16a1" + }, + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0.4" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "Silverlight\\.plugin", + "id": "dd81b232-09cb-e31d-ed8b-c5cc6ea28dd0", + "last_modified": 1519390921592 + }, + { + "schema": 1519390914958, + "blockID": "p210", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812896", + "who": "All Firefox users who have the Java 7 plugin, updates 7 and below.", + "why": "The Java 7 Runtime Environment, update version 7, has a <a href=\"http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin 1.7u7 (Linux)", + "created": "2012-11-22T09:31:33Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.7\\.0(_0?7)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "8e562dba-7ae7-fa85-2c31-479c4e661056", + "last_modified": 1519390921568 + }, + { + "schema": 1519390914958, + "blockID": "p214", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812896", + "who": "All Firefox users who have the Java 7 plugin, updates 7 and below.", + "why": "The Java 7 Runtime Environment, update version 7 and below, has a <a href=\"http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin 1.7u7 (Windows)", + "created": "2012-11-22T09:34:13Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U7(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "npjp2\\.dll", + "id": "524ff62f-9564-a2b2-2585-88b88ea4c2f9", + "last_modified": 1519390921544 + }, + { + "schema": 1519390914958, + "blockID": "p414", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 6 updates 42 to 45 (click-to-play), Windows", + "created": "2013-06-28T12:43:43Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 6 U4[2-5](\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "e36516a6-8bb3-09cc-02e3-72c67046b42e", + "last_modified": 1519390921520 + }, + { + "schema": 1519390914958, + "blockID": "p184", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 7 update 11 and lower (click-to-play), Linux", + "created": "2012-10-30T14:39:15Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.7\\.0(_0?([0-9]|(1[0-1]))?)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "06621403-39a8-3867-ba6e-406dc20c88af", + "last_modified": 1519390921496 + }, + { + "schema": 1519390914958, + "blockID": "p412", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 6 updates 42 to 45 (click-to-play), Linux", + "created": "2013-06-28T12:42:20Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.6\\.0_4[2-5]([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "8f652c63-cda4-1640-0b0c-23025e336b20", + "last_modified": 1519390921472 + }, + { + "schema": 1519390914958, + "blockID": "p29", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=421993", + "who": "Users of all versions of Yahoo Application State Plugin for Firefox 3 and later.\r\n\r\nUsers of all versions of Yahoo Application State Plugin for SeaMonkey 1.0.0.5 and later.", + "why": "This plugin causes a high volume of Firefox and SeaMonkey crashes.", + "name": "Yahoo Application State Plugin (SeaMonkey)", + "created": "2011-03-31T16:28:26Z" + }, + "enabled": true, + "matchName": "^Yahoo Application State Plugin$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "1.0.0.5" + } + ] + } + ], + "matchFilename": "npYState.dll", + "matchDescription": "^Yahoo Application State Plugin$", + "id": "8f52a562-5438-731b-5c64-7f76009f1489", + "last_modified": 1519390921448 + }, + { + "schema": 1519390914958, + "blockID": "p138", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=794247", + "who": "All Firefox users who have the Java 7 plugin, update 6 and below.", + "why": "The Java 7 Runtime Environment, update versions 6 and below, has a <a href=\"https://blog.mozilla.org/security/2012/08/28/protecting-users-against-java-security-vulnerability/\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin", + "created": "2012-09-13T14:49:52Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "Java 7 Update 06", + "minVersion": "Java 7 Update 01", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "5e898a46-8ea9-2fab-5dfe-a43e51641d81", + "last_modified": 1519390921424 + }, + { + "schema": 1519390914958, + "blockID": "p457", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=927273", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 25 to 44 (click-to-play), Linux", + "created": "2013-10-16T16:28:58Z" + }, + "enabled": true, + "matchName": "Java(\\(TM\\))? Plug-in ((1\\.7\\.0_(2[5-9]|3\\d|4[0-4]))|(10\\.4[0-4](\\.[0-9]+)?))([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "5ca97332-f4b7-81f5-d441-6acb1834f332", + "last_modified": 1519390921400 + }, + { + "schema": 1519390914958, + "blockID": "p294", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 12 to 15 (click-to-play), Windows", + "created": "2013-02-25T12:33:48Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U1[2-5](\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "ab8aff96-ead4-615d-be44-bcb7c31699f0", + "last_modified": 1519390921376 + }, + { + "schema": 1519390914958, + "blockID": "p186", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 6 updates 31 through 38 (click-to-play), Windows", + "created": "2012-10-30T14:45:39Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 6 U3[1-8](\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "8488cdb0-1f00-1349-abfc-f50e85c9163b", + "last_modified": 1519390921351 + }, + { + "os": "Darwin", + "schema": 1519390914958, + "blockID": "p242", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=821422", + "who": "All Firefox (18 and above) users on Mac OS X who have installed a version of Flip4Mac older than 2.4.4.", + "why": "Old versions of the Flip4Mac WMV plugin are causing significant stability problems in Firefox 18 and above. Users are encouraged update to the latest version of the plugin, available at <a href=\"http://www.telestream.net/flip4mac/overview.htm\">the Flip4Mac site</a>.", + "name": "Flip4Mac WMV Plugin", + "created": "2012-12-21T13:32:36Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.4.3.999", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "18.0a1" + } + ] + } + ], + "matchDescription": "Flip4Mac", + "id": "cef6f402-bdf8-0ba6-66d8-8ac42c72d4be", + "last_modified": 1519390921327 + }, + { + "schema": 1519390914958, + "blockID": "p26", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=421993", + "who": "Users of all versions of Yahoo Application State Plugin for Firefox 3 and later.\r\n\r\nUsers of all versions of Yahoo Application State Plugin for SeaMonkey 1.0.0.5 and later.", + "why": "This plugin causes a high volume of Firefox and SeaMonkey crashes.", + "name": "Yahoo Application State Plugin", + "created": "2011-03-31T16:28:26Z" + }, + "enabled": true, + "matchName": "^Yahoo Application State Plugin$", + "versionRange": [ + { + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "3.0a1" + } + ] + } + ], + "matchFilename": "npYState.dll", + "matchDescription": "^Yahoo Application State Plugin$", + "id": "6a1b6dfe-f463-3061-e8f8-6e896ccf2a8a", + "last_modified": 1519390921303 + }, + { + "schema": 1519390914958, + "blockID": "p459", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=927273", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 25 to 44 (click-to-play), Mac OS X", + "created": "2013-10-16T16:29:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 44", + "minVersion": "Java 7 Update 25", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "5b0bbf0e-dbae-7896-3c31-c6cb7a74e6fa", + "last_modified": 1519390921278 + }, + { + "schema": 1519390914958, + "blockID": "p188", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 6 updates 38 and lower (click-to-play), Mac OS X", + "created": "2012-10-30T14:48:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 6 Update 38", + "minVersion": "Java 6 Update 0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "61944828-8178-71ab-e9c6-d846b5f45d96", + "last_modified": 1519390921253 + }, + { + "schema": 1519390914958, + "blockID": "p292", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 12 to 15 (click-to-play), Mac OS X", + "created": "2013-02-25T12:32:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 15", + "minVersion": "Java 7 Update 12", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "9a09fe22-c3d8-57e6-4e99-ecd06fb03081", + "last_modified": 1519390921228 + }, + { + "schema": 1519390914958, + "blockID": "p125", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=780717", + "who": "All Firefox users who have installed the Java plugin, JRE versions below 1.6.0_33 or between 1.7.0 and 1.7.0_4.", + "why": "Outdated versions of the Java plugin are vulnerable to an actively exploited security issue. All users are strongly encouraged to <a href=\"http://java.com/inc/BrowserRedirect1.jsp\">update their Java plugin</a>. For more information, please <a href=\"http://blog.mozilla.org/addons/2012/08/14/new-java-blocklist/\">read our blog post</a> or <a href=\"http://www.oracle.com/technetwork/topics/security/javacpujun2012-1515912.html\">Oracle's Advisory</a>.", + "name": "Java Plugin", + "created": "2012-08-14T09:31:17Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE ((6( U(\\d|([0-2]\\d)|3[0-2]))?)|(7(\\sU[0-4])?))(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "npjp2\\.dll", + "id": "7538347d-ca2e-09b8-c5d8-0a9d140f8c87", + "last_modified": 1519390921203 + }, + { + "schema": 1519390914958, + "blockID": "p134", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=794247", + "who": "All Firefox users who have the Java 7 plugin, update 6 and below.", + "why": "The Java 7 Runtime Environment, update versions 6 and below, has a <a href=\"https://blog.mozilla.org/security/2012/08/28/protecting-users-against-java-security-vulnerability/\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin", + "created": "2012-08-31T15:22:51Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U[5-6](\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "npjp2\\.dll", + "id": "1f519b98-a4b6-2a98-b1ff-b642f7b4d2bb", + "last_modified": 1519390921179 + }, + { + "schema": 1519390914958, + "blockID": "p123", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=780717", + "who": "All Firefox users who have installed the Java plugin, JRE versions below 1.6.0_33 or between 1.7.0 and 1.7.0_4.", + "why": "Outdated versions of the Java plugin are vulnerable to an actively exploited security issue. All users are strongly encouraged to <a href=\"http://java.com/inc/BrowserRedirect1.jsp\">update their Java plugin</a>. For more information, please <a href=\"http://blog.mozilla.org/addons/2012/08/14/new-java-blocklist/\">read our blog post</a> or <a href=\"http://www.oracle.com/technetwork/topics/security/javacpujun2012-1515912.html\">Oracle's Advisory</a>.", + "name": "Java Plugin", + "created": "2012-08-14T09:29:42Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "14.2.0", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "JavaPlugin2_NPAPI\\.plugin", + "id": "46bf36a7-1934-38f8-1fcc-c9c4bcc8343e", + "last_modified": 1519390921152 + }, + { + "schema": 1519390914958, + "blockID": "p190", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 6 updates 31 through 38 (click-to-play), Linux", + "created": "2012-10-30T14:55:37Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.6\\.0_3[1-8]([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "9f9eb0ae-6495-aaa6-041a-d802cdb8e134", + "last_modified": 1519390921128 + }, + { + "schema": 1519390914958, + "blockID": "p418", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 16 to 24 (click-to-play), Linux", + "created": "2013-06-28T12:46:18Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.7\\.0_(1[6-9]|2[0-4])([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "ee5c1584-0170-8702-5f99-e0325b4a91a8", + "last_modified": 1519390921103 + }, + { + "schema": 1519390914958, + "blockID": "p182", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 7 update 11 and lower (click-to-play), Windows", + "created": "2012-10-30T14:33:04Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U([0-9]|(1[0-1]))(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "385d9911-f8bc-83e0-8cd2-94c98087938c", + "last_modified": 1519390919419 + }, + { + "schema": 1519390914958, + "blockID": "p422", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 16 to 24 (click-to-play), Mac OS X", + "created": "2013-06-28T12:48:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 24", + "minVersion": "Java 7 Update 16", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "709960a7-d268-0c70-d6ad-17bbed0cd1c4", + "last_modified": 1519390919396 + }, + { + "schema": 1519390914958, + "blockID": "p212", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=812896", + "who": "All Firefox users who have the Java 7 plugin, updates 7 and below.", + "why": "The Java 7 Runtime Environment, update version 7, has a <a href=\"http://www.oracle.com/technetwork/topics/security/javacpuoct2012-1515924.html\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin 1.7u7 (Mac OS X)", + "created": "2012-11-22T09:33:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "Java 7 Update 07", + "minVersion": "Java 7 Update 07", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "5ec1cd0f-d478-e6e0-989e-c91694b2a411", + "last_modified": 1519390919373 + }, + { + "schema": 1519390914958, + "blockID": "p302", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.\r\n", + "name": "Java Plugin 6 updates 39 to 41 (click-to-play), Linux", + "created": "2013-02-25T12:37:17Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.6\\.0_(39|40|41)([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "caec8103-dec9-8018-eb3d-9cf21cbf68a6", + "last_modified": 1519390919350 + }, + { + "schema": 1519390914958, + "blockID": "p132", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=794247", + "who": "All Firefox users who have the Java 7 plugin, updates 6 and below.", + "why": "The Java 7 Runtime Environment, update versions 6 and below, has a <a href=\"https://blog.mozilla.org/security/2012/08/28/protecting-users-against-java-security-vulnerability/\">serious security vulnerability</a> that is fixed in the latest update. All Firefox users are strongly encouraged to update as soon as possible. This can be done on the <a href=\"http://www.mozilla.com/plugincheck/\">Plugin Check</a> page.", + "name": "Java Plugin", + "created": "2012-08-31T15:21:34Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.7\\.0(_0?([5-6]))?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.14.*", + "minVersion": "0.1" + } + ] + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "572d0485-3388-7c2a-a062-b062e42c8710", + "last_modified": 1519390919327 + }, + { + "schema": 1519390914958, + "blockID": "p296", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 12 to 15 (click-to-play), Linux", + "created": "2013-02-25T12:34:37Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Plug-in 1\\.7\\.0_1[2-5]([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "5b7aaf34-48a3-dfa5-1db8-550faef41501", + "last_modified": 1519390919304 + }, + { + "schema": 1519390914958, + "blockID": "p298", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.\r\n", + "name": "Java Plugin 6 updates 39 to 41 (click-to-play), Mac OS X", + "created": "2013-02-25T12:35:34Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 6 Update 41", + "minVersion": "Java 6 Update 39", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "cfd76877-e79d-8e8c-c6a7-6b596fd344e8", + "last_modified": 1519390919281 + }, + { + "schema": 1519390914958, + "blockID": "p458", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=927273", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 25 to 44 (click-to-play), Windows", + "created": "2013-10-16T16:29:18Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U(2[5-9]|3\\d|4[0-4])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "d199d513-3c49-b53c-9447-33c8021c0df2", + "last_modified": 1519390919257 + }, + { + "schema": 1519390914958, + "blockID": "p420", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=885362", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 16 to 24 (click-to-play), Windows", + "created": "2013-06-28T12:47:32Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 7 U(1[6-9]|2[0-4])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "5942b7ae-bcdc-e329-9f17-f7a795c9ec83", + "last_modified": 1519390919234 + }, + { + "schema": 1519390914958, + "blockID": "p254", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=830410", + "who": "All Firefox users on Mac OS X who are using the affected versions of the PDF Browser Plugin.", + "why": "The PDF Browser Plugin is causing frequent crashes on Firefox 18 and above. All users are recommended to keep the plugin disabled and update it if a new version becomes available.", + "name": "PDF Browser Plugin 2.4.2 and below", + "created": "2013-01-15T11:54:24Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "2.4.2", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "18.0a1" + } + ] + } + ], + "matchFilename": "PDF Browser Plugin\\.plugin", + "id": "2dd1b53a-52db-2d0f-392a-410d5aade9d6", + "last_modified": 1519390919211 + }, + { + "schema": 1519390914958, + "blockID": "p34", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=634639", + "who": "Users of Java 2 Plugin versions 1.5_00 to 1.6_99 in Firefox 3.6 and later.", + "why": "These versions of the Java plugin are no longer supported by Oracle and cause a high volume of Firefox crashes.", + "name": "Java Plugin", + "created": "2011-02-17T17:20:54Z" + }, + "enabled": true, + "versionRange": [ + { + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "3.6a1pre" + } + ] + } + ], + "matchFilename": "[Nn][Pp][Jj][Pp][Ii]1[56]0_[0-9]+\\.[Dd][Ll][Ll]", + "id": "91618232-c650-5dc6-00fe-293c0baedc34", + "last_modified": 1519390919188 + }, + { + "schema": 1519390914958, + "blockID": "p180", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have these versions of the plugin installed in Firefox 17 and above.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 7 update 10 and lower (click-to-play), Mac OS X", + "created": "2012-10-30T14:29:51Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 10", + "minVersion": "Java 7 Update 0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "12f38c07-c1e1-2464-2eab-d454cf471eab", + "last_modified": 1519390919165 + }, + { + "schema": 1519390914958, + "blockID": "p32", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=539282", + "who": "All users of the Viewpoint plugin for Firefox 3 and later.", + "why": "This plugin causes a high volume of Firefox crashes.", + "name": "Viewpoint", + "created": "2011-03-31T16:28:26Z" + }, + "enabled": true, + "versionRange": [ + { + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "3.0" + } + ] + } + ], + "matchFilename": "npViewpoint.dll", + "id": "1206e79a-4817-16e9-0f5e-7762a8d19216", + "last_modified": 1519390919142 + }, + { + "schema": 1519390914958, + "blockID": "p94", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=754723", + "who": "All Firefox users who have a version of the Flash Player Plugin older than 10.2.159.1.", + "why": "Old versions of the Flash Player plugin are targeted by known security vulnerabilities, and cause frequent Firefox crashes. We strongly recommend all users to visit <a href=\"http://get.adobe.com/flashplayer/\">the Flash Player homepage</a> to download and install the latest available version of this plugin.\r\n\r\nThis is not an ordinary block. It's just an upgrade notification that will be shown to all users who have these old versions installed, asking them to download the latest version. If they choose to ignore the notification, the plugin will continue to work normally.", + "name": "Flash Player Plugin", + "created": "2012-05-23T09:15:23Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/flashplayer/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "10.2.159.1", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0.1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "2.13.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "Flash\\ Player\\.plugin", + "id": "2a741cac-32d7-a67e-0bf7-b5b53a0ff22b", + "last_modified": 1519390919119 + }, + { + "schema": 1519390914958, + "blockID": "p300", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=843373", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 6 updates 39 to 41 (click-to-play), Windows", + "created": "2013-02-25T12:36:25Z" + }, + "enabled": true, + "matchName": "Java\\(TM\\) Platform SE 6 U(39|40|41)(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "17.0" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.14" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "d64e08b6-66b2-f534-53d8-1cbcbb139f2a", + "last_modified": 1519390919095 + }, + { + "schema": 1519390914958, + "blockID": "p152", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=797378", + "who": "All Firefox users who have this plugin installed.", + "why": "This plugin is outdated and is potentially insecure. Affected users should go to <a href=\"http://www.mozilla.com/plugincheck/\">the plugin check page</a> and update to the latest version.", + "name": "Silverlight 4.1.10328.0 and lower", + "created": "2012-10-05T10:34:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "4.1.10328.0", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npctrl\\.dll", + "id": "abdcbe90-5575-6b4b-12de-bbabd93ecf57", + "last_modified": 1519390919072 + }, + { + "schema": 1519390914958, + "blockID": "p904", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 44 and lower (click-to-play), Mac OS X", + "created": "2015-05-19T09:01:42Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 8 Update 44", + "minVersion": "Java 8", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "47f6217d-0aa6-1e39-a9c9-cc64d57bb91f", + "last_modified": 1519390919049 + }, + { + "schema": 1519390914958, + "blockID": "p1061", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 81 to 90 (click-to-play), Windows", + "created": "2015-12-02T12:39:41Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 7 U(8[1-9]|90)(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "69196ada-69bd-6454-eea8-6f8b2037368c", + "last_modified": 1519390919026 + }, + { + "schema": 1519390914958, + "blockID": "p1120", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1241237", + "who": "All users who have these versions of the Silverlight plugin installed.", + "why": "Old versions of the Silverlight plugin are known to have critical security vulnerabilities. You can get the latest version of Silverlight <a href=\"http://www.microsoft.com/getsilverlight/get-started/install/default.aspx\">here</a>.", + "name": "Silverlight plugin 5.1.41105.0 and lower (click to play)", + "created": "2016-02-03T09:42:43Z" + }, + "enabled": true, + "infoURL": "https://www.microsoft.com/getsilverlight", + "versionRange": [ + { + "severity": 0, + "maxVersion": "5.1.41105.0", + "minVersion": "5.1.20125", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(Silverlight\\.plugin|npctrl\\.dll)", + "id": "f0bfeb8c-04ee-d5ef-6670-b28c0e0d0f58", + "last_modified": 1519390919002 + }, + { + "schema": 1519390914958, + "blockID": "p102", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=695927", + "who": "All Firefox users who have this plugin installed.", + "why": "This plugin was discontinued years ago and only worked up to Firefox version 1.5. Because plugins aren't marked as incompatible, there are still many users who have it installed and have recently been experiencing crashes related to it.", + "name": "Mozilla ActiveX Plug-in", + "created": "2012-06-07T17:43:57Z" + }, + "enabled": true, + "versionRange": [ + { + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "npmozax\\.dll", + "id": "2e001e2f-483b-7595-8810-3672e0346950", + "last_modified": 1519390918979 + }, + { + "os": "Darwin", + "schema": 1519390914958, + "blockID": "p252", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=826002", + "who": "All Firefox users on Mac OS X who have installed the Adobe Reader XI plugin. Users are recommended to update when a new version of Adobe Reader becomes available, or to downgrade to the latest version of Adobe Reader 10.", + "why": "The Adobe Reader XI plugin is causing frequent crashes on Firefox for Mac OS X. The impact and this block are currently limited to versions 11.0.0 and 11.0.01.", + "name": "Adobe Reader XI for Mac OS X", + "created": "2013-01-15T10:56:30Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "11.0.01", + "minVersion": "11.0.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "AdobePDFViewerNPAPI\\.plugin", + "id": "821ddb3d-bba4-6479-28b6-9974383fa8c9", + "last_modified": 1519390918955 + }, + { + "schema": 1519390914958, + "blockID": "p1004", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974012", + "who": "All users who have these versions of the plugin installed", + "why": "These versions of the Unity Web Player plugin have known vulnerabilities that can put users at risk.", + "name": "Unity Web Player 5.0 to 5.0.3f1 (click-to-play), Mac OS", + "created": "2015-09-09T14:11:57Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "Unity Web Player\\.plugin", + "matchDescription": "^($|Unity Web Player version 5.0(\\.([0-2]|3f1))?[^0-9.])", + "id": "5a0b69cb-a516-e7a2-ea3b-b4fafcc13c6b", + "last_modified": 1519390918932 + }, + { + "schema": 1519390914958, + "blockID": "p248", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829054", + "who": "All users who have Firefox 18 or above installed, and the Sibelius Scorch plugin.", + "why": "The Sibelius Scorch plugin is not compatible with Firefox 18 and above, and is crashing whenever loaded on affected versions of Firefox. ", + "name": "Sibelius Scorch plugin", + "created": "2013-01-14T09:26:47Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "6.2.0b88", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "Scorch\\.plugin", + "id": "28b5baa1-6490-38e2-395f-53fc84aa12df", + "last_modified": 1519390918909 + }, + { + "schema": 1519390914958, + "blockID": "p1151", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1264874", + "who": "All users who have the QuickTime Plugin for Windows installed.", + "why": "The QuickTime Plugin for Windows has been <a href=\"https://support.apple.com/en-us/HT205771\">discontinued by Apple</a> and has known critical security vulnerabilities. All users are strongly encouraged to remove it or keep it disabled.", + "name": "QuickTime Plugin for Windows", + "created": "2016-04-18T17:41:00Z" + }, + "enabled": true, + "infoURL": "https://support.apple.com/en-us/HT205771", + "versionRange": [ + { + "severity": 0, + "maxVersion": "*", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 2 + } + ], + "matchFilename": "npqtplugin\\.dll", + "id": "bb8db302-9579-42d2-ff75-c61500f6a020", + "last_modified": 1519390918887 + }, + { + "schema": 1519390914958, + "blockID": "p28", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=512406", + "who": "All users of Internet Saving Optimizer for all Mozilla applications.", + "why": "This plugin causes a high volume of Firefox crashes and is considered malware.", + "name": "Internet Saving Optimizer (plugin)", + "created": "2011-03-31T16:28:26Z" + }, + "enabled": true, + "versionRange": [ + { + "maxVersion": "*", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "NPFFAddOn.dll", + "id": "c85a4a9c-d61c-130f-d525-ea36becac235", + "last_modified": 1519390918863 + }, + { + "schema": 1519390914958, + "blockID": "p456", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=470936", + "who": "All Firefox users who are using old versions of the VLC player plugin.", + "why": "Old versions of the VLC player are known to cause stability problems in Firefox. All users are recommended to update to the latest version of their VLC software.", + "name": "VLC Player plugin 2.0.5 and lower (click-to-play)", + "created": "2013-09-30T14:35:07Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "2.0.5", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npvlc\\.dll", + "id": "0cfaaefe-88a4-dda4-96bc-110e402ca9d5", + "last_modified": 1519390917137 + }, + { + "schema": 1519390914958, + "blockID": "p1144", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 64 to 76 (click-to-play), Windows", + "created": "2016-03-31T16:18:15Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 8 U(6[4-9]|7[0-6])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "b3c55844-79d2-66dd-0d44-82fb4533307e", + "last_modified": 1519390917115 + }, + { + "schema": 1519390914958, + "blockID": "p408", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=837377", + "who": "All Firefox users who have affected versions of the QuickTime plugin installed.", + "why": "Old versions of the QuickTime plugin are known to be insecure and cause stability problems. All users are recommended to update to the latest version available. You can check for updates in the <a href=\"http://www.mozilla.org/plugincheck/\">plugin check page</a>.", + "name": "QuickTime Plugin 7.6.5 and lower (click-to-play), Mac OS X", + "created": "2013-06-28T09:52:37Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "7.6.5", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "QuickTime Plugin\\.plugin", + "id": "4f39cd0f-976c-1f71-2d9c-2abe1b4706d9", + "last_modified": 1519390917091 + }, + { + "schema": 1519390914958, + "blockID": "p1059", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 81 to 90 (click-to-play), Mac OS X", + "created": "2015-12-02T12:37:58Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 90", + "minVersion": "Java 7 Update 81", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "799c4f10-d75d-30a0-5570-56df6be628e0", + "last_modified": 1519390917067 + }, + { + "schema": 1519390914958, + "blockID": "p558", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974012", + "who": "All Firefox users who have these versions of the plugin installed.", + "why": "Current versions of the Unity Web Player plugin have known vulnerabilities that can put users at risk.", + "name": "Unity Web Player 4.6.6f1 and lower (click-to-play), Mac OS", + "created": "2014-02-25T08:29:51Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "Unity Web Player\\.plugin", + "matchDescription": "^($|Unity Web Player version ([0-3]|(4\\.([0-5]|6(\\.([0-5]|6f1)))?[^0-9.])))", + "id": "bbf83e70-65f5-75af-b841-7cebcf3e38c1", + "last_modified": 1519390917044 + }, + { + "schema": 1519390914958, + "blockID": "p1145", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 91 to 97 (click-to-play), Linux", + "created": "2016-03-31T16:18:53Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 10\\.(9[1-7])(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "d4e75c8c-9c32-6093-12cc-1de0fd8f9e87", + "last_modified": 1519390917020 + }, + { + "schema": 1519390914958, + "blockID": "p33", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=558584", + "who": "Users of Java Deployment Toolkit versions 6.0.200.0 and older in all versions of Firefox.", + "why": "This plugin has publicly-known security vulnerabilities. For more information, please <a href=\"http://java.com/en/download/help/firefox_addons.xml\">visit the vendor page</a>.", + "name": "Java Deployment Toolkit", + "created": "2010-04-16T17:52:32Z" + }, + "enabled": true, + "matchName": "[0-6]\\.0\\.[01]\\d{2}\\.\\d+", + "versionRange": [ + { + "severity": 1, + "maxVersion": "", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "npdeploytk.dll", + "id": "b12b4282-d327-06d6-6e62-4788ba2c4b2f", + "last_modified": 1519390916997 + }, + { + "schema": 1519390914958, + "blockID": "p1060", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 46 to 64 (click-to-play), Mac OS X", + "created": "2015-12-02T12:38:53Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 8 Update 64", + "minVersion": "Java 8 Update 46", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "aee0ad26-018f-392b-91f7-6a8aaf332774", + "last_modified": 1519390916973 + }, + { + "os": "Darwin", + "schema": 1519390914958, + "blockID": "p89", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=750387", + "who": "Firefox users on Mac OS X who have installed the Adobe Acrobat plugin.", + "why": "The Adobe Acrobat plugin doesn't work on Mac OS X in default (64-bit) mode, showing a blank page when users click on links to PDF files in Firefox. It also causes a significant number of crashes in 32-bit mode.\r\n\r\nThere's more information on <a href=\"http://blog.mozilla.org/addons/2012/05/04/adobe-reader-blocked-mac/\">this blog post</a>.", + "name": "Adobe Acrobat NPAPI Plug-in", + "created": "2012-05-04T11:29:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "10.1.3", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "AdobePDFViewerNPAPI\\.plugin", + "id": "473f4d9c-e38e-a348-26d1-de7b842893d9", + "last_modified": 1519390916947 + }, + { + "schema": 1519390914958, + "blockID": "p129", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=782672", + "who": "All Firefox users on Mac OS X who have old versions of the Silverlight plugin.", + "why": "Old versions of the Silverlight plugin for Mac OS X are causing serious stability problems in Firefox. Affected users can visit <a href=\"https://www.microsoft.com/getsilverlight/Get-Started/Install/Default.aspx\">the official Silverlight page</a> and get the latest version of the Silverlight plugin to correct this problem.", + "name": "Silverlight for Mac OS X", + "created": "2012-08-22T10:37:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "5.0.99999", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "Silverlight\\.plugin", + "id": "ab861311-9c24-5f55-0d3a-f0072868357c", + "last_modified": 1519390916923 + }, + { + "schema": 1519390914958, + "blockID": "p1142", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 64 to 76 (click-to-play), Mac OS X", + "created": "2016-03-31T16:16:46Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 8 Update 76", + "minVersion": "Java 8 Update 64", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "646ebafe-dd54-e45f-9569-c245ef72259d", + "last_modified": 1519390916899 + }, + { + "schema": 1519390914958, + "blockID": "p250", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=828982", + "who": "All Firefox users on Windows who have installed the Foxit Reader Plugin, versions 2.2.1.530 and below.", + "why": "The Foxit Reader plugin is vulnerable to a <a href=\"http://secunia.com/advisories/51733/\">critical security bug</a> that can compromise a user's system by visiting a malicious site.", + "name": "Foxit Reader Plugin 2.2.1.530 and below", + "created": "2013-01-14T13:07:03Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "2.2.1.530", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 2 + } + ], + "matchFilename": "npFoxitReaderPlugin\\.dll", + "id": "c6299be0-ab77-8bbd-bcaf-c886cec4e799", + "last_modified": 1519390916873 + }, + { + "schema": 1519390914958, + "blockID": "p85", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=741592", + "who": "All Firefox users who have installed the Java plugin, JRE versions below 1.6.0_31 or between 1.7.0 and 1.7.0_2.", + "why": "Outdated versions of the Java plugin are vulnerable to an actively exploited security issue. All Mac OS X users are strongly encouraged to update their Java plugin through Software Update, or disable it if no alternatives are available. For more information, please <a href=\"http://blog.mozilla.org/addons/2012/04/30/java-block-complete-for-mac-os-x\">read our blog post</a> or <a href=\"http://www.oracle.com/technetwork/topics/security/javacpufeb2012-366318.html\">Oracle's Advisory</a>.\r\n\r\n", + "name": "Java Plugin", + "created": "2012-04-16T13:58:43Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "13.6.0", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "JavaPlugin2_NPAPI\\.plugin", + "id": "4df1324d-8236-b7ae-db55-4fb1b7db2754", + "last_modified": 1519390916849 + }, + { + "schema": 1519390914958, + "blockID": "p1250", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286972", + "who": "All users who have this plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Adobe Reader (Continuous) 15.016.20045", + "created": "2016-07-21T22:22:11Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader", + "versionRange": [ + { + "severity": 0, + "maxVersion": "15.016.20045", + "minVersion": "15.016.20045", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "b9f6998a-7a45-7d83-7ee6-897e6b193e42", + "last_modified": 1519390916826 + }, + { + "schema": 1519390914958, + "blockID": "p1053", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1222130", + "who": "All users who have these versions of the Real Player plugin. Affected users can visit our <a href=\"http://www.mozilla.org/plugincheck/\">plugin check page</a> and check for updates.", + "why": "Old versions of the Real Player plugin have serious security problems that could lead to system compromise.", + "name": "Real Player for Windows, 17.0.10.7 and lower, click-to-play", + "created": "2015-11-12T09:07:04Z" + }, + "enabled": true, + "infoURL": "https://real.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "17.0.10.7", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "nprpplugin\\.dll", + "id": "2a6a7300-fac0-6518-c44a-35b3c4c714c3", + "last_modified": 1519390916801 + }, + { + "schema": 1519390914958, + "blockID": "p1141", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 91 to 97 (click-to-play), Mac OS X", + "created": "2016-03-31T16:16:02Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 97", + "minVersion": "Java 7 Update 91", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "2a432d6e-9545-66be-9f93-aefe9793f450", + "last_modified": 1519390916777 + }, + { + "schema": 1519390914958, + "blockID": "p158", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=850745", + "who": "All Firefox users who have this plugin installed.", + "why": "This plugin is outdated and is potentially insecure. Affected users should go to <a href=\"http://www.mozilla.com/plugincheck/\">the plugin check page</a> and update to the latest version.", + "name": "Adobe Reader 10.0 to 10.1.5.*", + "created": "2012-10-05T10:40:20Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "10.1.5.9999", + "minVersion": "10.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "nppdf32\\.dll", + "id": "107c9a8c-2ef8-da26-75c3-bc26f8ae90fa", + "last_modified": 1519390916752 + }, + { + "schema": 1519390914958, + "blockID": "p960", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 45 (click-to-play), Windows", + "created": "2015-07-15T10:50:25Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 8 U45(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "990fa997-1d4a-98ee-32de-a7471e81db42", + "last_modified": 1519390916727 + }, + { + "schema": 1519390914958, + "blockID": "p113", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=778686", + "who": "All Firefox users who have this plugin installed.", + "why": "Version 1.0.0.0 of the Ubisoft Uplay plugin has a security vulnerability that can be exploited by malicious websites to gain control of the user's system.", + "name": "Ubisoft Uplay", + "created": "2012-07-30T12:11:59Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.0.0.0", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "npuplaypc\\.dll", + "id": "ea3b767d-933b-3f54-f447-09bd2bfbc6e0", + "last_modified": 1519390916703 + }, + { + "schema": 1519390914958, + "blockID": "p366", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=879128", + "who": "All users who have Firefox 18 or above installed, and the Sibelius Scorch plugin.", + "why": "The Sibelius Scorch plugin is not compatible with Firefox 18 and above, and is crashing whenever loaded on affected versions of Firefox. ", + "name": "Sibelius Scorch plugin", + "created": "2013-06-11T13:20:32Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "6.2.0", + "minVersion": "6.2.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "Scorch\\.plugin", + "id": "43ec430a-75ec-6853-f62b-1a54489fea5f", + "last_modified": 1519390916680 + }, + { + "schema": 1519390914958, + "blockID": "p964", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 45 (click-to-play), Linux", + "created": "2015-07-15T10:51:57Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 11\\.45(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "c2431673-3478-f1e1-5555-28e2d5377adc", + "last_modified": 1519390916656 + }, + { + "schema": 1519390914958, + "blockID": "p1002", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974012", + "who": "All users who have these versions of the plugin installed.", + "why": "These versions of the Unity Web Player plugin have known vulnerabilities that can put users at risk.", + "name": "Unity Web Player 5.0 to 5.0.3f1 (click-to-play), Windows", + "created": "2015-09-09T14:07:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "5.0.3f1", + "minVersion": "5.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npUnity3D32\\.dll", + "id": "48e45eea-fd32-2304-2776-fe75211a69e5", + "last_modified": 1519390916633 + }, + { + "schema": 1519390914958, + "blockID": "p902", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 45 to 78 (click-to-play), Mac OS X", + "created": "2015-05-19T09:00:27Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 78", + "minVersion": "Java 7 Update 45", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "72de1aa2-6afe-2f5b-a93e-baa4c8c4578d", + "last_modified": 1519390916609 + }, + { + "schema": 1519390914958, + "blockID": "p154", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=850744", + "who": "All Firefox users who have this plugin installed.", + "why": "This plugin is outdated and is potentially insecure. Affected users should go to <a href=\"http://www.mozilla.com/plugincheck/\">the plugin check page</a> and update to the latest version.", + "name": "Silverlight 5.0 to 5.1.20124.*", + "created": "2012-10-05T10:35:55Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "5.1.20124.9999", + "minVersion": "5.0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npctrl\\.dll", + "id": "64b50946-53f8-182d-a239-bd585b0e0b0e", + "last_modified": 1519390916585 + }, + { + "schema": 1519390914958, + "blockID": "p1247", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286972", + "who": "All users who have this plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Adobe Reader (Classic) 15.006.30174", + "created": "2016-07-21T16:21:52Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader", + "versionRange": [ + { + "severity": 0, + "maxVersion": "15.006.30174", + "minVersion": "15.006.30174", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "eba24802-0c93-b8d4-ca2d-c9c194c7462b", + "last_modified": 1519390916562 + }, + { + "schema": 1519242099615, + "blockID": "p31", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=449062", + "who": "All users of MyWebSearch for all Mozilla applications.", + "why": "This plugin causes a high volume of Firefox crashes.", + "name": "MyWebSearch", + "created": "2011-03-31T16:28:26Z" + }, + "enabled": true, + "versionRange": [ + { + "maxVersion": "*", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "NPMySrch.dll", + "id": "2b2b85e9-4f64-9894-e3fa-3c05ead892b3", + "last_modified": 1519390914951 + }, + { + "schema": 1519242099615, + "blockID": "p572", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=992976", + "who": "All Firefox users who have this plugin installed. Updated versions can be found <a href=\"https://www.cuminas.jp/en/downloads/download/?pid=1\">on this site</a>.", + "why": "Versions 6.1.4.27993 and earlier of this plugin are known to have security vulnerabilities.", + "name": "DjVu Plugin Viewer 6.1.4.27993 and earlier (Windows)", + "created": "2014-04-08T14:42:05Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "6.1.4.27993", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npdjvu\\.dll", + "id": "8a416fb1-29bf-ace9-02de-605d805b6e79", + "last_modified": 1519390914928 + }, + { + "schema": 1519242099615, + "blockID": "p592", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=980355", + "who": "All Firefox users who have installed a version of the Cisco Web Communicator plugin lower than 3.0.6.", + "why": "Versions lower than 3.0.6 of the Cisco Web Communicator plugin are known to have security issues and should not be used. All users should update to version 3.0.6 or later. <a href=\"https://developer.cisco.com/site/collaboration/jabber/websdk/develop-and-test/voice-and-video/troubleshooting/#plugin_vulnerabilities_user\">Find updates here.</a>", + "name": "Cisco Web Communicator < 3.0.6 (Mac OS X)", + "created": "2014-06-11T16:48:48Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "3.0.5.99999999999999", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "CiscoWebCommunicator\\.plugin", + "id": "1f9f9b90-e733-3929-821f-1b78a8698747", + "last_modified": 1519390914905 + }, + { + "schema": 1519242099615, + "blockID": "p912", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 44 and lower (click-to-play), Linux", + "created": "2015-05-19T09:05:55Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 11\\.(\\d|[1-3]\\d|4[0-4])(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "f0a40537-9a13-1b4b-60e5-b9121835c1ca", + "last_modified": 1519390914882 + }, + { + "schema": 1519242099615, + "blockID": "p908", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 44 and lower (click-to-play), Windows", + "created": "2015-05-19T09:03:44Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 8( U([1-3]?\\d|4[0-4]))?(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "79e165b4-599b-433d-d618-146f0c121ead", + "last_modified": 1519390914856 + }, + { + "schema": 1519242099615, + "blockID": "p1146", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 64 to 76 (click-to-play), Linux", + "created": "2016-03-31T16:19:31Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 11\\.(6[4-9]|7[0-6])(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "bc99ed93-b527-12e2-c6e6-c61668a2e7d0", + "last_modified": 1519390914833 + }, + { + "schema": 1519242099615, + "blockID": "p958", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 79 to 80 (click-to-play), Windows", + "created": "2015-07-15T10:49:33Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 7 U(79|80)(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "2cfa79ef-a2ab-d868-467d-d182242136a5", + "last_modified": 1519390914810 + }, + { + "schema": 1519242099615, + "blockID": "p156", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=797378", + "who": "All Firefox users who have this plugin installed.", + "why": "This plugin is outdated and is potentially insecure. Affected users should go to <a href=\"http://www.mozilla.com/plugincheck/\">the plugin check page</a> and update to the latest version.", + "name": "Adobe Reader 9.5.1 and lower", + "created": "2012-10-05T10:38:46Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "9.5.1", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "nppdf32\\.dll", + "id": "0dce3611-19e7-e8e4-5b5c-13593230f83c", + "last_modified": 1519390914787 + }, + { + "schema": 1519242099615, + "blockID": "p574", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=992976", + "who": "All Firefox users who have this plugin installed. Updated versions can be found <a href=\"https://www.cuminas.jp/en/downloads/download/?pid=1\">on this site</a>.", + "why": "Versions 6.1.1 and earlier of this plugin are known to have security vulnerabilities.\r\n", + "name": "DjVu Plugin Viewer 6.1.1 and earlier (Mac OS)", + "created": "2014-04-08T14:43:54Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "6.1.1", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "NPDjVu\\.plugin", + "id": "c841472f-f976-c49a-169e-0c751012c3b1", + "last_modified": 1519390914764 + }, + { + "schema": 1519242099615, + "blockID": "p556", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=974012", + "who": "All Firefox users who have these versions of the plugin installed.", + "why": "Current versions of the Unity Web Player plugin have known vulnerabilities that can put users at risk.", + "name": "Unity Web Player 4.6.6f1 and lower (click-to-play), Windows", + "created": "2014-02-25T08:29:41Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "4.6.6f1", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npUnity3D32\\.dll", + "id": "b90eb83c-1314-5b27-687b-98d8115b6106", + "last_modified": 1519390914741 + }, + { + "schema": 1519242099615, + "blockID": "p956", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 45 (click-to-play), Mac OS X", + "created": "2015-07-15T10:48:44Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 8 Update 45", + "minVersion": "Java 8 Update 45", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "111881f9-a5bd-4919-4bab-9d7581d959d3", + "last_modified": 1519390914718 + }, + { + "schema": 1519242099615, + "blockID": "p80", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=739955", + "who": "All Firefox users who have installed the Java plugin, JRE versions below 1.6.0_31 or between 1.7.0 and 1.7.0_2.", + "why": "Outdated versions of the Java plugin are vulnerable to an actively exploited security issue. All users are strongly encouraged to <a href=\"http://java.com/inc/BrowserRedirect1.jsp\">update their Java plugin</a>. For more information, please <a href=\"http://blog.mozilla.com/addons/2012/04/02/blocking-java/\">read our blog post</a> or <a href=\"http://www.oracle.com/technetwork/topics/security/javacpufeb2012-366318.html\">Oracle's Advisory</a>.", + "name": "Java Plugin", + "created": "2012-04-02T15:18:50Z" + }, + "enabled": true, + "matchName": "\\(TM\\)", + "versionRange": [ + { + "severity": 1, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "(npjp2\\.dll)|(libnpjp2\\.so)", + "matchDescription": "[^\\d\\._]((0(\\.\\d+(\\.\\d+([_\\.]\\d+)?)?)?)|(1\\.(([0-5](\\.\\d+([_\\.]\\d+)?)?)|(6(\\.0([_\\.](0?\\d|1\\d|2\\d|30))?)?)|(7(\\.0([_\\.][0-2])?)?))))([^\\d\\._]|$)", + "id": "34deed93-d9d9-81b4-7983-0594fb829ae7", + "last_modified": 1519390914684 + }, + { + "schema": 1519242099615, + "blockID": "p1052", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=829111", + "who": "All users who have this version of the plugin installed.", + "why": "The Java plugin is causing significant security problems. All users are strongly recommended to keep the plugin disabled unless necessary.", + "name": "Java Plugin 7 update 11 (click-to-play), Mac OS X", + "created": "2015-11-06T13:30:02Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 11", + "minVersion": "Java 7 Update 11", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "c62a171f-7564-734f-0310-bae88c3baf85", + "last_modified": 1519390914657 + }, + { + "schema": 1519242099615, + "blockID": "p910", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 45 to 78 (click-to-play), Linux", + "created": "2015-05-19T09:04:46Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 10\\.(4[5-9]|(5|6)\\d|7[0-8])(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "f322be12-1b08-0d57-ed51-f7a6d6ec2c17", + "last_modified": 1519390914634 + }, + { + "schema": 1519242099615, + "blockID": "p428", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=636633", + "who": "All Firefox users who have this plugin installed.", + "why": "The Java Deployment Toolkit plugin is known to be insecure and is unnecessary in most cases. Users should keep it disabled unless strictly necessary.", + "name": "Java Deployment Toolkit (click-to-play)", + "created": "2013-07-18T15:39:12Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 2 + } + ], + "matchFilename": "np[dD]eployJava1\\.dll", + "id": "d30a5b90-b84a-dfec-6147-fc04700a0d8b", + "last_modified": 1519390914610 + }, + { + "schema": 1519242099615, + "blockID": "p594", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=980355", + "who": "All Firefox users who have installed a version of the Cisco Web Communicator plugin lower than 3.0.6.", + "why": "Versions lower than 3.0.6 of the Cisco Web Communicator plugin are known to have security issues and should not be used. All users should update to version 3.0.6 or later. <a href=\"https://developer.cisco.com/site/collaboration/jabber/websdk/develop-and-test/voice-and-video/troubleshooting/#plugin_vulnerabilities_user\">Find updates here.</a>", + "name": "Cisco Web Communicator < 3.0.6 (Windows)", + "created": "2014-06-11T16:49:00Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 0, + "maxVersion": "3.0.5.99999999999999", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npCiscoWebCommunicator\\.dll", + "id": "f11f91d6-f65e-8a05-310b-ad20494a868a", + "last_modified": 1519390914588 + }, + { + "schema": 1519242099615, + "blockID": "p1064", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 46 to 64 (click-to-play), Linux", + "created": "2015-12-02T12:42:34Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 11\\.(4[6-9]|5\\d|6[0-4])(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "d5cb1745-d144-5375-f3ff-cd603d4c2cee", + "last_modified": 1519390914565 + }, + { + "schema": 1519242099615, + "blockID": "p906", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1159917", + "who": "All users who have these versions of the plugin installed in Firefox.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 45 to 78 (click-to-play), Windows", + "created": "2015-05-19T09:02:45Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 7 U(4[5-9]|(5|6)\\d|7[0-8])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "f254e5bc-12c7-7954-fe6b-8f1fdab0ae88", + "last_modified": 1519390914542 + }, + { + "schema": 1519242099615, + "blockID": "p240", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=821972", + "who": "All Firefox users who have versions of the DivX Web Player plugin lower than 1.4.", + "why": "Old versions of the DivX Web Player plugin are causing significant stability problems on Firefox 18 and above, on Mac OS X. All users should update their DivX software to the latest available version, available at <a href=\"http://www.divx.com\">divx.com</a>.", + "name": "DivX Web Player 1.4 (DivX 7) and below", + "created": "2012-12-19T13:03:27Z" + }, + "enabled": true, + "versionRange": [ + { + "severity": 1, + "maxVersion": "1.4", + "minVersion": "0", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ] + } + ], + "matchFilename": "DivXBrowserPlugin\\.plugin", + "id": "d7f644bb-61aa-4594-f9fc-8dba7d9f3306", + "last_modified": 1519390914518 + }, + { + "schema": 1519242099615, + "blockID": "p962", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 79 to 80 (click-to-play), Linux", + "created": "2015-07-15T10:51:12Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 10\\.(79|80)(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "7120e3c2-b293-65b5-2498-6451eab1e2c5", + "last_modified": 1519390914495 + }, + { + "schema": 1519242099615, + "blockID": "p1062", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 8 update 46 to 64 (click-to-play), Windows", + "created": "2015-12-02T12:40:28Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 8 U(4[6-9]|5\\d|6[0-4])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "a20c77af-721b-833b-8fa8-49091d1c69d4", + "last_modified": 1519390914472 + }, + { + "schema": 1519242099615, + "blockID": "p1143", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1259458", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 91 to 97 (click-to-play), Windows", + "created": "2016-03-31T16:17:32Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java\\(TM\\) Platform SE 7 U(9[1-7])(\\s[^\\d\\._U]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "npjp2\\.dll", + "id": "be53f658-e719-fa77-e7fe-6bd6086f888e", + "last_modified": 1519390914448 + }, + { + "schema": 1519242099615, + "blockID": "p954", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1183369", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 79 to 80 (click-to-play), Mac OS X", + "created": "2015-07-15T10:47:55Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "Java 7 Update 80", + "minVersion": "Java 7 Update 79", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "JavaAppletPlugin\\.plugin", + "id": "9b6830b4-a4b0-ee63-ee21-e0bd6ba0d6fe", + "last_modified": 1519390914424 + }, + { + "schema": 1519242099615, + "blockID": "p1246", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286972", + "who": "All users who have this plugin installed.", + "why": "Old versions of the Adobe Reader plugin have known vulnerabilities. All users are strongly recommended to check for updates on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Adobe Reader 10.1.6 to 11.0.16", + "created": "2016-07-20T19:19:58Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/reader", + "versionRange": [ + { + "severity": 0, + "maxVersion": "11.0.16 ", + "minVersion": "10.1.6", + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "(nppdf32\\.dll)|(AdobePDFViewerNPAPI\\.plugin)", + "id": "4e29bd83-d074-bd40-f238-5ea38ff0d8d0", + "last_modified": 1519390914400 + }, + { + "schema": 1519242099615, + "blockID": "p1063", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1217932", + "who": "All users who have these versions of the Java plugin installed.", + "why": "Old versions of the Java plugin are potentially insecure and unstable. All users are strongly recommended to update on our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check page</a>.", + "name": "Java Plugin 7 update 81 to 90 (click-to-play), Linux", + "created": "2015-12-02T12:41:34Z" + }, + "enabled": true, + "infoURL": "https://java.com/", + "matchName": "Java(\\(TM\\))? Plug-in 10\\.(8[1-9]|90)(\\.[0-9]+)?([^\\d\\._]|$)", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "57.0.*", + "minVersion": "0" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libnpjp2\\.so", + "id": "b6b9c6b9-b8c4-66a9-ed07-125b32e7a5ef", + "last_modified": 1519390914376 + }, + { + "schema": 1480349144394, + "blockID": "p1055", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1218880", + "who": "All users who have these versions of this plugin installed.", + "why": "Versions 12.2.0.162 and earlier of this plugin are affected by a critical security vulnerability that puts users at risk.", + "name": "Shockwave for Director 12.2.0.162 and earlier, Mac OS X (click-to-play)", + "created": "2015-11-13T14:17:24Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/shockwave/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "12.2.0.162", + "minVersion": "0", + "targetApplication": [], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "DirectorShockwave\\.plugin", + "id": "da3808c0-b460-e177-1c78-0496b3671480", + "last_modified": 1480349148897 + }, + { + "schema": 1480349144394, + "blockID": "p1054", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1218880", + "who": "All users who have these versions of this plugin installed.", + "why": "Versions 12.2.0.162 and earlier of this plugin are affected by a critical security vulnerability that puts users at risk.", + "name": "Shockwave for Director 12.2.0.162 and earlier, Windows (click-to-play)", + "created": "2015-11-13T14:15:12Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/shockwave/", + "versionRange": [ + { + "severity": 0, + "maxVersion": "12.2.0.162", + "minVersion": "0", + "targetApplication": [], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "np32dsw_[0-9]+\\.dll", + "id": "5cc4739b-ba83-7a19-4d85-c5362f5e0ccd", + "last_modified": 1480349148638 + }, + { + "schema": 1480349144394, + "blockID": "p330", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=854550", + "who": "All Firefox users who have these versions on the Abode Flash plugin installed.", + "why": "Old versions of the Adobe Flash plugin are potentially insecure and unstable. All users are recommended to visit our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check</a> page and check for updates.", + "name": "Adobe Flash for Linux 10.3.182.* and lower (click-to-play)", + "created": "2013-03-26T09:43:25Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/flashplayer/", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "19.0a1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.16a1" + }, + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "17.0.*", + "minVersion": "17.0.4" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libflashplayer\\.so", + "matchDescription": "^Shockwave Flash (([1-9]\\.[0-9]+)|(10\\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)", + "id": "abaff0eb-314f-e882-b96c-5c5a4755688f", + "last_modified": 1480349147797 + }, + { + "schema": 1480349144394, + "blockID": "p332", + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=854550", + "who": "All Firefox users who have these versions of the Adobe Flash Player plugin installed.", + "why": "Old versions of the Adobe Flash Player plugin are potentially insecure and unstable. All users are recommended to visit our <a href=\"http://www.mozilla.com/plugincheck/\">plugin check</a> to check for updates.", + "name": "Adobe Flash for Linux 11.0 to 11.1.* (click-to-play)", + "created": "2013-03-26T09:46:14Z" + }, + "enabled": true, + "infoURL": "https://get.adobe.com/flashplayer/", + "versionRange": [ + { + "severity": 0, + "targetApplication": [ + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "*", + "minVersion": "19.0a1" + }, + { + "guid": "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}", + "maxVersion": "*", + "minVersion": "2.16a1" + }, + { + "guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}", + "maxVersion": "17.0.*", + "minVersion": "17.0.4" + } + ], + "vulnerabilityStatus": 1 + } + ], + "matchFilename": "libflashplayer\\.so", + "matchDescription": "^Shockwave Flash 11.(0|1) r[0-9]{1,3}$", + "id": "38797744-cb92-1a49-4c81-2a900691fdba", + "last_modified": 1480349146047 + } + ], + "timestamp": 1603126502200 +} diff --git a/services/settings/dumps/gen_last_modified.py b/services/settings/dumps/gen_last_modified.py new file mode 100644 index 0000000000..d03f8fc096 --- /dev/null +++ b/services/settings/dumps/gen_last_modified.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import glob +import json +import os + +import buildconfig +import mozpack.path as mozpath + + +def get_last_modified(full_path_to_remote_settings_dump_file): + """ + Get the last_modified for the given file name. + - File must exist + - Must be a JSON dictionary with a data list and a timestamp, + e.g. `{"data": [], "timestamp": 42}` + - Every element in `data` should contain a "last_modified" key. + - The first element must have the highest "last_modified" value. + """ + with open(full_path_to_remote_settings_dump_file, "r", encoding="utf-8") as f: + changeset = json.load(f) + + records = changeset["data"] + assert isinstance(records, list) + last_modified = changeset.get("timestamp") + assert isinstance( + last_modified, int + ), f"{full_path_to_remote_settings_dump_file} is missing the timestamp. See Bug 1725660" + + return last_modified + + +def main(output): + """ + Generates a JSON file that maps "bucket/collection" to the last_modified + value within. + + Returns a set of the file locations of the recorded RemoteSettings dumps, + so that the build backend can invoke this script again when needed. + + The validity of the JSON file is verified through unit tests at + services/settings/test/unit/test_remote_settings_dump_lastmodified.js + """ + # The following build targets currently use RemoteSettings dumps: + # Firefox https://searchfox.org/mozilla-central/rev/94d6086481754e154b6f042820afab6bc9900a30/browser/installer/package-manifest.in#281-285 # NOQA: E501 + # Firefox for Android https://searchfox.org/mozilla-central/rev/94d6086481754e154b6f042820afab6bc9900a30/mobile/android/installer/package-manifest.in#88-91 # NOQA: E501 + # Thunderbird https://searchfox.org/comm-central/rev/89f957706bbda77e5f34e64e117e7ce121bb5d83/mail/installer/package-manifest.in#280-285 # NOQA: E501 + # SeaMonkey https://searchfox.org/comm-central/rev/89f957706bbda77e5f34e64e117e7ce121bb5d83/suite/installer/package-manifest.in#307-309 # NOQA: E501 + assert buildconfig.substs["MOZ_BUILD_APP"] in ( + "browser", + "mobile/android", + "comm/mail", + "comm/suite", + ), ( + "Cannot determine location of Remote Settings " + f"dumps for platform {buildconfig.substs['MOZ_BUILD_APP']}" + ) + + dumps_locations = [] + if buildconfig.substs["MOZ_BUILD_APP"] == "browser": + dumps_locations += ["services/settings/dumps/"] + elif buildconfig.substs["MOZ_BUILD_APP"] == "mobile/android": + dumps_locations += ["services/settings/dumps/"] + elif buildconfig.substs["MOZ_BUILD_APP"] == "comm/mail": + dumps_locations += ["services/settings/dumps/"] + dumps_locations += ["comm/mail/app/settings/dumps/"] + elif buildconfig.substs["MOZ_BUILD_APP"] == "comm/suite": + dumps_locations += ["services/settings/dumps/"] + + remotesettings_dumps = {} + for dumps_location in dumps_locations: + dumps_root_dir = mozpath.join(buildconfig.topsrcdir, *dumps_location.split("/")) + for path in glob.iglob(mozpath.join(dumps_root_dir, "*", "*.json")): + folder, filename = os.path.split(path) + bucket = os.path.basename(folder) + collection, _ = os.path.splitext(filename) + remotesettings_dumps[f"{bucket}/{collection}"] = path + + output_dict = {} + input_files = set() + + for key, input_file in remotesettings_dumps.items(): + input_files.add(input_file) + output_dict[key] = get_last_modified(input_file) + + json.dump(output_dict, output, sort_keys=True) + return input_files diff --git a/services/settings/dumps/main/anti-tracking-url-decoration.json b/services/settings/dumps/main/anti-tracking-url-decoration.json new file mode 100644 index 0000000000..dd336cbd96 --- /dev/null +++ b/services/settings/dumps/main/anti-tracking-url-decoration.json @@ -0,0 +1,11 @@ +{ + "data": [ + { + "token": "fbclid", + "schema": 1564436129080, + "id": "60e82333-914d-4cfa-95b1-5f034b5a704b", + "last_modified": 1564511755134 + } + ], + "timestamp": 1564511755134 +} diff --git a/services/settings/dumps/main/cookie-banner-rules-list.json b/services/settings/dumps/main/cookie-banner-rules-list.json new file mode 100644 index 0000000000..602b34a4b9 --- /dev/null +++ b/services/settings/dumps/main/cookie-banner-rules-list.json @@ -0,0 +1,9234 @@ +{ + "data": [ + { + "click": { + "optIn": "button#onetrust-accept-btn-handler", + "optOut": "button.onetrust-close-btn-handler", + "presence": "div#onetrust-consent-sdk" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "fnac.be", + "fnac.ch", + "fnac.com", + "fnac.pt" + ], + "id": "2d821158-5945-4134-a078-56c6da4f678d", + "last_modified": 1710331175432 + }, + { + "click": { + "optIn": ".moove-gdpr-infobar-allow-all", + "optOut": ".moove-gdpr-infobar-reject-btn", + "presence": "#moove_gdpr_cookie_info_bar" + }, + "schema": 1710174339269, + "cookies": { + "optIn": [ + { + "name": "moove_gdpr_popup", + "value": "%7B%22strict%22%3A%221%22%2C%22thirdparty%22%3A%221%22%2C%22advanced%22%3A%221%22%7D" + } + ], + "optOut": [ + { + "name": "moove_gdpr_popup", + "value": "%7B%22strict%22%3A%221%22%2C%22thirdparty%22%3A%220%22%2C%22advanced%22%3A%220%22%7D" + } + ] + }, + "domains": [ + "endorfy.com" + ], + "id": "1be1e4d7-bcad-4fc8-a348-ae64af8bcac7", + "last_modified": 1710331175428 + }, + { + "click": { + "optIn": "#popin_tc_privacy_button", + "optOut": "#popin_tc_privacy_button_3", + "presence": "#tc-privacy-wrapper" + }, + "schema": 1710174339269, + "domains": [ + "dpd.com", + "dpdgroup.com" + ], + "id": "43ad2b6b-a57b-4f7a-9d76-e32e696ddc10", + "last_modified": 1710331175424 + }, + { + "click": { + "optIn": "#popin_tc_privacy_button", + "optOut": "#popin_tc_privacy_button_2", + "presence": "#tc-privacy-wrapper" + }, + "schema": 1710174339269, + "domains": [ + "but.fr" + ], + "id": "3030D307-A610-4F3D-B589-D2BE133850D7", + "last_modified": 1710331175420 + }, + { + "click": { + "optIn": "#popin_tc_privacy_button_3", + "optOut": "#popin_tc_privacy_button_2", + "presence": "#tc-privacy-wrapper" + }, + "schema": 1710174339269, + "domains": [ + "fortuneo.fr", + "lcl.fr" + ], + "id": "98D89E26-F4B6-4C2D-BABF-4295B922E433", + "last_modified": 1710331175416 + }, + { + "click": { + "optIn": ".ei_btn_typ_validate", + "optOut": ".ei_lb_btnskip", + "presence": "#cookieLB" + }, + "schema": 1710174339269, + "domains": [ + "creditmutuel.fr" + ], + "id": "1B309A53-16B4-4BBE-91F4-86260A15B8E7", + "last_modified": 1710331175412 + }, + { + "click": { + "optIn": "#wt-cli-accept-all-btn", + "presence": "#cookie-law-info-bar" + }, + "schema": 1710174339269, + "cookies": { + "optOut": [ + { + "name": "viewed_cookie_policy", + "value": "yes" + } + ] + }, + "domains": [ + "krux.tech", + "silentiumpc.com", + "spcgear.com" + ], + "id": "04e919eb-13c2-4b37-bf7f-888767888640", + "last_modified": 1710331175407 + }, + { + "click": { + "optIn": "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "#CybotCookiebotDialogBodyLevelButtonLevelOptinDeclineAll", + "presence": "#cookiebanner" + }, + "schema": 1710174339269, + "domains": [ + "action.com", + "gog.com" + ], + "id": "04afc564-14b2-4c56-b72d-47a26e121f3b", + "last_modified": 1710331175402 + }, + { + "click": { + "optIn": ".cc-allow", + "optOut": ".cc-deny", + "presence": ".cc-window" + }, + "schema": 1710174339269, + "cookies": { + "optIn": [ + { + "name": "eclipse_cookieconsent_status", + "value": "allow" + } + ], + "optOut": [ + { + "name": "eclipse_cookieconsent_status", + "value": "deny" + } + ] + }, + "domains": [ + "adoptium.net", + "eclipse.dev", + "eclipse.org", + "glassfish.org", + "jakarta.ee", + "mbse-capella.org", + "oniroproject.org", + "open-vsx.org", + "openmdm.org", + "osgi.org", + "planeteclipse.org" + ], + "id": "92361e84-664e-46b3-ae55-95bc185dc88e", + "last_modified": 1710331175397 + }, + { + "click": { + "optIn": ".cc-allow", + "optOut": ".cc-dismiss", + "presence": ".cc-window" + }, + "schema": 1710174339269, + "domains": [ + "dell.com", + "dell.eu", + "delltechnologies.com" + ], + "id": "f1849b07-95e8-4ae0-a99d-24df5abbb3cb", + "last_modified": 1710331175393 + }, + { + "click": { + "optIn": "#btn-allowAllCookie", + "optOut": "#btn-rejectAllCookie", + "presence": "#cc-window" + }, + "schema": 1710174339269, + "cookies": { + "optIn": [ + { + "name": "_youtube_vimeo_vid", + "value": "allow" + }, + { + "name": "cookieControllerStatus", + "value": "allow" + }, + { + "name": "functionalCookieStatus", + "value": "allow" + }, + { + "name": "googleAnalyticsCookieStatus", + "value": "allow" + } + ], + "optOut": [ + { + "name": "_youtube_vimeo_vid", + "value": "deny" + }, + { + "name": "cookieControllerStatus", + "value": "deny" + }, + { + "name": "functionalCookieStatus", + "value": "deny" + }, + { + "name": "googleAnalyticsCookieStatus", + "value": "deny" + } + ] + }, + "domains": [ + "verbatim.co.il" + ], + "id": "3c0e4924-29ee-4d9a-99ec-e4805a7ffed9", + "last_modified": 1710331175385 + }, + { + "click": { + "optIn": "[data-cookiebanner=\"accept_button\"]", + "optOut": "[data-cookiebanner=\"accept_only_essential_button\"]", + "presence": "[data-testid=\"cookie-policy-manage-dialog\"]" + }, + "schema": 1710174339269, + "domains": [ + "facebook.com", + "messenger.com", + "oculus.com", + "workplace.com" + ], + "id": "d1d8ba36-ced7-4453-8b17-2e051e0ab1eb", + "last_modified": 1710331175381 + }, + { + "click": { + "optIn": "#cookiescript_accept", + "optOut": "#cookiescript_reject", + "presence": "#cookiescript_injected_wrapper" + }, + "schema": 1710174339269, + "domains": [ + "deskmodder.de", + "zyxel.com" + ], + "id": "7AF1C34C-750E-44FC-A3C4-31FB61EBF71B", + "last_modified": 1710331175376 + }, + { + "click": { + "optIn": ".sp_choice_type_11", + "presence": ".message-container > #notice", + "runContext": "child" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "thetimes.co.uk", + "qz.com", + "privacy-mgmt.com", + "independent.co.uk", + "gizmodo.com", + "e24.no", + "thesun.co.uk", + "thesun.ie", + "focus.de", + "bild.de", + "computerbild.de", + "t-online.de", + "wetteronline.de", + "chip.de", + "n-tv.de", + "newsnow.co.uk", + "telegraph.co.uk", + "theguardian.com", + "faz.net", + "sueddeutsche.de", + "rtl.de", + "gutefrage.net", + "express.de", + "tvspielfilm.de", + "finanzen.net", + "tag24.de", + "kino.de", + "heise.de", + "bunte.de", + "golem.de", + "meinestadt.de", + "berliner-zeitung.de", + "karlsruhe-insider.de", + "wetter.de" + ], + "id": "d42bbaee-f96e-47e7-8e81-efc642518e97", + "last_modified": 1710331175368 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "optOut": "span.didomi-continue-without-agreeing", + "presence": "div#didomi-popup" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "theconversation.com", + "leparisien.fr", + "lesechos.fr", + "numerama.com", + "jofogas.hu", + "orange.fr", + "meteofrance.com", + "subito.it", + "hasznaltauto.hu", + "zdnet.de", + "intersport.fr", + "decathlon.fr", + "leboncoin.fr", + "boursorama.com", + "boursobank.com", + "intermarche.com" + ], + "id": "c1d7be10-151e-4a66-b83b-31a762869a97", + "last_modified": 1710331175364 + }, + { + "click": { + "optOut": ".sp_choice_type_13", + "presence": ".message-container > #notice", + "runContext": "child" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "aktuality.sk", + "sky.it", + "azet.sk", + "bloomberg.com" + ], + "id": "ae8f7761-35ff-45b2-92df-7868ca288ad2", + "last_modified": 1710331175359 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#didomi-host" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "echo24.cz", + "jeuxvideo.com", + "24sata.hr", + "nova.cz", + "lidovky.cz", + "jutarnji.hr", + "vecernji.hr", + "sport.es", + "elespanol.com", + "in-pocasi.cz", + "20minutes.fr", + "actu.fr", + "hbvl.be", + "naszemiasto.pl", + "rtbf.be", + "20minutos.es", + "sudinfo.be", + "elpais.com", + "sinoptik.bg", + "lequipe.fr", + "abc.es", + "gva.be", + "eltiempo.es", + "eldiario.es", + "larazon.es", + "extra.cz", + "ladepeche.fr", + "marmiton.org", + "poslovni.hr", + "softonic.com", + "sydsvenskan.se", + "telecinco.es", + "giphy.com", + "filmstarts.de", + "funda.nl", + "idnes.cz", + "aktualne.cz", + "blesk.cz", + "centrum.cz", + "denik.cz", + "csfd.cz", + "hn.cz", + "moviepilot.de" + ], + "id": "af4c5b38-d210-472b-9a07-21cbe53c85ab", + "last_modified": 1710331175354 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "optOut": "button#didomi-notice-disagree-button", + "presence": "div#didomi-host" + }, + "schema": 1710174339269, + "cookies": {}, + "domains": [ + "doctolib.fr", + "pravda.sk", + "topky.sk", + "zoznam.sk", + "tvnoviny.sk", + "aukro.cz", + "krone.at", + "cas.sk", + "heureka.sk", + "free.fr", + "markiza.sk", + "willhaben.at", + "francetvinfo.fr", + "france24.com", + "opodo.at", + "opodo.ch", + "opodo.co.uk", + "opodo.de", + "opodo.dk", + "opodo.fi", + "opodo.fr", + "opodo.it", + "opodo.nl", + "opodo.no", + "opodo.pl", + "opodo.pt", + "radiofrance.fr" + ], + "id": "690aa076-4a8b-48ec-b52c-1443d44ff008", + "last_modified": 1710331175349 + }, + { + "click": { + "optOut": "button[data-js-item=\"privacy-protection-default\"]", + "presence": ".c-privacy-protection-banner" + }, + "schema": 1708699541450, + "cookies": {}, + "domains": [ + "bundeswehr.de" + ], + "id": "52ad1edd-5696-482c-855d-a8d669f9e7f5", + "last_modified": 1708772697295 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "cookiehint", + "value": "{\"matomo\":0}" + } + ] + }, + "domains": [ + "dataport.de" + ], + "id": "1ae88c1b-2b26-49b2-98d0-f6b14a85e376", + "last_modified": 1708772697291 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "dp-cookie-consent", + "value": "false" + } + ] + }, + "domains": [ + "bob-sh.de", + "bolapla-sh.de" + ], + "id": "a70791ff-0001-4238-b68f-9ba8f1c00c96", + "last_modified": 1708772697287 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "BayernMatomo", + "value": "deaktiviert" + } + ] + }, + "domains": [ + "bayern.de" + ], + "id": "07228673-9d62-4c00-b18d-37e0fcfd37e7", + "last_modified": 1708772697283 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "gsbbanner", + "value": "closed" + } + ] + }, + "domains": [ + "bmbf.de", + "bmj.de", + "bva.bund.de", + "bundesrechnungshof.de", + "schleswig-holstein.de", + "zoll.de" + ], + "id": "b2a17900-dc1f-4273-bb0e-10e73c6f63bd", + "last_modified": 1708772697279 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "isTrackingConsentGiven", + "value": "false" + } + ] + }, + "domains": [ + "bund.de" + ], + "id": "431be341-e09a-4fb5-beac-3c4366caaaa0", + "last_modified": 1708772697274 + }, + { + "click": { + "optIn": "#privacy-init-wall-button-accept", + "optOut": "#privacy-init-wall-button-deny", + "presence": "#privacy-init-wall" + }, + "schema": 1708699541450, + "domains": [ + "comdirect.de" + ], + "id": "5DC4F89B-2A66-4B91-AE73-87954F3A0B4A", + "last_modified": 1708772697270 + }, + { + "click": { + "optIn": "#ccmgt_explicit_accept", + "presence": "#GDPRConsentManagerContainer" + }, + "schema": 1708699541450, + "domains": [ + "stepstone.de" + ], + "id": "3FD9FDA8-5F69-46C4-BA33-35FE3C804B4C", + "last_modified": 1708772697256 + }, + { + "click": { + "optIn": "[data-testid=\"as24-cmp-accept-all-button\"]", + "presence": "#as24-cmp-popup" + }, + "schema": 1708699541450, + "domains": [ + "autoscout24.at", + "autoscout24.de" + ], + "id": "46FECFD3-2384-450A-8CCC-53BCFE65DF4C", + "last_modified": 1708772697251 + }, + { + "click": { + "optIn": ".lnc-acceptCookiesButton", + "optOut": ".lnc-declineCookiesButton", + "presence": ".lnc-firstRunPopup" + }, + "schema": 1708699541450, + "domains": [ + "jw.org" + ], + "id": "45AEAD27-6328-4B26-A235-4EA4D74A829E", + "last_modified": 1708772697247 + }, + { + "click": { + "optIn": ".cmp-accept", + "presence": "#cmp-modal" + }, + "schema": 1708699541450, + "domains": [ + "gamestar.de" + ], + "id": "7D7FA535-719F-4637-8DE0-9F90B17AD7A7", + "last_modified": 1708772697243 + }, + { + "click": { + "optIn": "#consent_wall_optin", + "optOut": "#consent_wall_optout", + "presence": "#consent-wall" + }, + "schema": 1708699541450, + "domains": [ + "1und1.de" + ], + "id": "C553BBDC-632F-4F18-AA7D-D6C090E7A7A2", + "last_modified": 1708772697238 + }, + { + "click": { + "optIn": ".ccm--save-settings", + "optOut": ".ccm--decline-cookies", + "presence": "#ccm-widget" + }, + "schema": 1708699541450, + "domains": [ + "kaufmich.com" + ], + "id": "72A1389D-943D-4BF0-93D5-F274F7FD3CF2", + "last_modified": 1708772697234 + }, + { + "click": { + "optIn": ".kick__data-grid__main .kick__btn", + "presence": "#kick__logi-container" + }, + "schema": 1708699541450, + "domains": [ + "kicker.de" + ], + "id": "E220B441-CAE6-4E4B-9F5D-BA167AE06812", + "last_modified": 1708772697230 + }, + { + "click": { + "optIn": "#cookieBtnAll", + "optOut": "#cookieBtnContinue", + "presence": ".cookie_consent_withings" + }, + "schema": 1708699541450, + "domains": [ + "withings.com" + ], + "id": "0132691F-247B-4CB9-BF9B-0EB61B7435F3", + "last_modified": 1708772697226 + }, + { + "click": { + "optIn": "button#onetrust-accept-btn-handler", + "optOut": ".ot-pc-refuse-all-handler, #onetrust-reject-all-handler", + "presence": "div#onetrust-consent-sdk" + }, + "schema": 1708699541450, + "cookies": {}, + "domains": [ + "espncricinfo.com", + "blackboard.com", + "roche.com", + "apnews.com", + "nationalgeographic.com", + "espn.com", + "hotjar.com", + "marriott.com", + "hootsuite.com", + "wattpad.com", + "gamespot.com", + "apa.org", + "opendns.com", + "epicgames.com", + "zendesk.com", + "drei.at", + "ikea.com", + "search.ch", + "centrum.sk", + "zoom.us", + "pluska.sk", + "hp.com", + "ceskatelevize.cz", + "telenet.be", + "adobe.com", + "rottentomatoes.com", + "dhl.com", + "dhl.de", + "nvidia.com", + "cloudflare.com", + "webex.com", + "indeed.com", + "discord.com", + "sport.ro", + "ricardo.ch", + "stirileprotv.ro", + "1177.se", + "meinbezirk.at", + "orange.ro", + "ica.se", + "flashscore.pl", + "kuleuven.be", + "tutti.ch", + "post.at", + "rezultati.com", + "nbg.gr", + "behance.net", + "zemanta.com", + "grammarly.com", + "usatoday.com", + "cnet.com", + "npr.org", + "binance.com", + "linktr.ee", + "time.com", + "cisco.com", + "udemy.com", + "shutterstock.com", + "investopedia.com", + "cbsnews.com", + "okta.com", + "appsflyer.com", + "typepad.com", + "calendly.com", + "verisign.com", + "outbrain.com", + "zdnet.com", + "deloitte.com", + "hdfcbank.com", + "media.net", + "docker.com", + "avast.com", + "bluehost.com", + "nba.com", + "hostgator.com", + "scientificamerican.com", + "aljazeera.com", + "sahibinden.com", + "rackspace.com", + "namecheap.com", + "people.com", + "branch.io", + "tv2.dk", + "criteo.com", + "trustpilot.com", + "hm.com", + "mailchimp.com", + "surveymonkey.com", + "mckinsey.com", + "rollingstone.com", + "slate.com", + "dictionary.com", + "coursera.org", + "msn.com", + "chegg.com", + "variety.com", + "cnn.com", + "proximus.be", + "adevarul.ro", + "cnbc.com", + "oe24.at", + "reuters.com", + "booking.com", + "bluewin.ch", + "viaplay.dk", + "aib.ie", + "hbomax.com", + "rtlnieuws.nl", + "buienradar.be", + "viaplay.se", + "antena3.ro", + "statista.com", + "pixabay.com", + "constantcontact.com", + "atlassian.com", + "bmj.com", + "trendyol.com", + "meetup.com", + "vmware.com", + "bitbucket.org", + "viaplay.no", + "asana.com", + "freepik.com", + "heute.at", + "mtvuutiset.fi", + "buienradar.nl", + "nypost.com", + "panasonic.com", + "safeway.com", + "amd.com", + "atg.se", + "brother.de", + "brother.eu", + "brother.fr", + "corsair.com", + "crucial.com", + "dc.com", + "dn.no", + "epson.de", + "epson.es", + "epson.eu", + "epson.fr", + "epson.it", + "evga.com", + "fortnite.com", + "fujitsu.com", + "global.canon", + "gpuopen.com", + "info.lidl", + "inpost.es", + "inpost.eu", + "inpost.it", + "intel.com", + "kaufland.de", + "lg.com", + "lidl.co.uk", + "lidl.com", + "lidl.cz", + "lidl.de", + "lidl.fr", + "lidl.it", + "lidl.pl", + "lifewire.com", + "logitech.com", + "micron.com", + "mythomson.com", + "oki.com", + "otto.de", + "razer.com", + "rightmove.co.uk", + "sbb.ch", + "seagate.com", + "soundcloud.com", + "trello.com", + "unrealengine.com", + "askubuntu.com", + "mathoverflow.net", + "serverfault.com", + "stackapps.com", + "stackexchange.com", + "stackoverflow.com", + "superuser.com" + ], + "id": "6c7366a0-4762-47b9-8eeb-04e86cc7a0cc", + "last_modified": 1708772697220 + }, + { + "click": { + "optIn": ".cc-btn.cc-allow", + "optOut": ".cc-btn.cc-deny", + "presence": ".cc-window.cc-banner" + }, + "schema": 1708732808372, + "cookies": { + "optOut": [ + { + "name": "cookieconsent_status", + "value": "deny" + } + ] + }, + "domains": [ + "magnite.com", + "mecklenburg-vorpommern.de", + "omv.com", + "omv.at", + "omv.bg", + "omv.de", + "omv.nz", + "omv.no", + "omv.ro", + "omv.co.rs", + "omv.sk", + "omv.cz", + "omv.tn", + "omv.ae", + "omv.hu", + "omv.si", + "omv-gas.com", + "omv-gas.at", + "omv-gas.be", + "omv-gas.de", + "omv-gas.hu", + "omv-gas.nl", + "omv-gas.com.tr", + "omv-gas-storage.com", + "omvpetrom.com", + "petrom.ro", + "petrom.md", + "avanti.at", + "avanti-tankstellen.de", + "diskonttanken.at", + "tocimceneje.si" + ], + "id": "8f401b10-02b6-4e05-88fa-c37012d4c8c0", + "last_modified": 1708772697214 + }, + { + "click": {}, + "schema": 1708732808372, + "cookies": { + "optOut": [ + { + "name": "cookiebanner", + "value": "closed" + } + ] + }, + "domains": [ + "thw.de", + "service.bund.de" + ], + "id": "58226c30-e975-42f3-99e4-ca140b91e96c", + "last_modified": 1708772697207 + }, + { + "click": { + "optIn": ".js-accept-cookies", + "optOut": ".js-reject-cookies", + "presence": ".js-consent-banner" + }, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "OptanonAlertBoxClosed", + "value": "2030-01-01T00:00:00.000Z" + } + ] + }, + "domains": [ + "stackoverflow.blog", + "stackoverflow.co", + "stackoverflowteams.com" + ], + "id": "71443ce9-15b8-4e07-b51b-1f2158521ea9", + "last_modified": 1708772697196 + }, + { + "schema": 1708732808372, + "cookies": { + "optOut": [ + { + "name": "js-cookie-opt-in__consent", + "value": "needed" + } + ] + }, + "domains": [ + "anexia.com", + "netcup.com", + "netcup.de", + "netcup.eu", + "netcup-news.de", + "netcup-sonderangebote.de" + ], + "id": "dea34d82-9c05-4c08-9262-18a7f62be91e", + "last_modified": 1708772697190 + }, + { + "click": {}, + "schema": 1708699541450, + "cookies": { + "optOut": [ + { + "name": "cookie-allow-necessary", + "value": "1" + }, + { + "name": "cookie-allow-tracking", + "value": "0" + }, + { + "name": "cookie-banner", + "value": "hide" + } + ] + }, + "domains": [ + "bundesfinanzministerium.de", + "bundesregierung.de" + ], + "id": "84f68b1a-18a2-478c-bc8e-9ec32d4e3e80", + "last_modified": 1708772697179 + }, + { + "click": { + "optIn": "a.cc-dismiss", + "presence": "div.cc-compliance" + }, + "schema": 1708699541450, + "cookies": { + "optIn": [ + { + "name": "cookieconsent_status", + "value": "dismiss" + } + ] + }, + "domains": [ + "021.rs", + "photobucket.com", + "brandenburg.de" + ], + "id": "5371fb3e-3242-4864-9443-62116afe5f3c", + "last_modified": 1708772697173 + }, + { + "click": { + "optIn": "a.cmpboxbtnyes ", + "presence": "div#cmpbox" + }, + "schema": 1708699541450, + "cookies": {}, + "domains": [ + "hoerzu.de", + "nzz.ch" + ], + "id": "a2404864-0163-4f71-ab4c-915713f8f349", + "last_modified": 1708772697167 + }, + { + "click": { + "optIn": ".btn-ok", + "optOut": ".btn-reject", + "presence": "#cookie-policy-info" + }, + "schema": 1708699541450, + "domains": [ + "asus.com" + ], + "id": "8c949b75-4c7b-4559-8ade-780064af370a", + "last_modified": 1708772697161 + }, + { + "click": { + "optIn": "[data-cookieman-accept-all]", + "optOut": "[data-cookieman-accept-none]", + "presence": "#cookieman-modal" + }, + "schema": 1704727928035, + "domains": [ + "hfm-frankfurt.de" + ], + "id": "5FF5EEB9-1045-4ACF-8A12-0630B71063F6", + "last_modified": 1704881609345 + }, + { + "click": {}, + "schema": 1704727928035, + "cookies": { + "optOut": [ + { + "name": "paydirektCookieAllowed", + "value": "false" + }, + { + "name": "paydirektCookieAllowedPWS", + "value": "{%22necessary%22:true,%22analytics%22:false}" + } + ] + }, + "domains": [ + "paydirekt.de" + ], + "id": "769dcf5b-afd1-438d-940d-3069ff4b2f51", + "last_modified": 1704881609341 + }, + { + "click": { + "optIn": "button.iubenda-cs-accept-btn", + "optOut": "button.iubenda-cs-reject-btn", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1704727928035, + "cookies": {}, + "domains": [ + "giallozafferano.it", + "virgilio.it", + "upfit.de", + "treedom.net" + ], + "id": "65638975-8222-425c-9be0-3f41a51db13c", + "last_modified": 1704881609336 + }, + { + "click": { + "optOut": "#zdf-cmp-deny-btn", + "presence": ".zdf-cmp-modal-content" + }, + "schema": 1704727928035, + "cookies": {}, + "domains": [ + "zdf.de", + "3sat.de" + ], + "id": "91484461-01AD-4D78-9ED8-D17C688F47E7", + "last_modified": 1704881609324 + }, + { + "click": { + "optIn": ".root__OblK1:not(.secondaryButton__N1rJw)", + "optOut": ".root__OblK1.secondaryButton__N1rJw", + "presence": ".root__XMKIj" + }, + "schema": 1704727928035, + "cookies": {}, + "domains": [ + "forbes.com" + ], + "id": "30293090-f064-473a-ae0f-cd390507c1c7", + "last_modified": 1704881609320 + }, + { + "click": { + "optIn": "[href=\"#accept\"]", + "optOut": "[href=\"#refuse\"]", + "presence": "div#cookie-consent-banner" + }, + "schema": 1704727928035, + "cookies": { + "optOut": [ + { + "name": "cck1", + "value": "%7B%22cm%22%3Atrue%2C%22all1st%22%3Afalse%2C%22closed%22%3Atrue%7D" + } + ] + }, + "domains": [ + "europa.eu" + ], + "id": "0c74749a-8c53-4bb0-b31a-ab2f89b7f493", + "last_modified": 1704881609316 + }, + { + "click": { + "optIn": ".CookieBanner_buttonContainer__NOZxH:nth-child(1) button", + "optOut": ".CookieBanner_buttonContainer__NOZxH:nth-child(2) button", + "presence": ".CookieBanner_cookieBanner__R_BOh" + }, + "schema": 1702080004246, + "domains": [ + "cbinsights.com" + ], + "id": "4CE0ADCF-E232-4F3B-981B-CA83A0C40874", + "last_modified": 1702633230760 + }, + { + "click": { + "optIn": ".modal-box__buttons--save-all", + "presence": ".new-policy-box" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "polityka15", + "value": "security_storage%3Dtrue%26functionality_storage%3Dtrue%26analytics_storage%3Dtrue%26personalization_storage%3Dtrue%26ad_storage%3Dtrue" + } + ], + "optOut": [ + { + "name": "polityka15", + "value": "security_storage%3Dtrue%26functionality_storage%3Dfalse%26analytics_storage%3Dfalse%26personalization_storage%3Dfalse%26ad_storage%3Dfalse" + } + ] + }, + "domains": [ + "nazwa.pl" + ], + "id": "ddff9528-161c-471e-bd2d-ba4d874a3931", + "last_modified": 1702633230742 + }, + { + "click": { + "optIn": ".cc-dismiss", + "optOut": ".cc-deny", + "presence": ".cc-window" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookieconsent_status", + "value": "dismiss" + } + ], + "optOut": [ + { + "name": "cookieconsent_status", + "value": "deny" + } + ] + }, + "domains": [ + "zotac.com" + ], + "id": "4159a3d2-f331-4d56-b051-a753c7e1308a", + "last_modified": 1702633230734 + }, + { + "click": { + "optIn": "[onclick*=\"cookie_Agree()\"]", + "optOut": "[onclick*=\"cookie_Disagree()\"]", + "presence": "#legal_notice" + }, + "schema": 1702080004246, + "domains": [ + "transcend-info.com" + ], + "id": "5e0387bb-1f19-4f61-b587-49d995a691c9", + "last_modified": 1702633230725 + }, + { + "click": { + "optIn": ".tp-cookie-accept-all", + "presence": "#tp-cookie" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "tp_privacy_base", + "value": "1" + }, + { + "name": "tp_privacy_marketing", + "value": "1" + } + ], + "optOut": [ + { + "name": "tp_privacy_base", + "value": "1" + } + ] + }, + "domains": [ + "tp-link.com" + ], + "id": "48e9b863-c642-4a7a-9ee5-c085d337233e", + "last_modified": 1702633230721 + }, + { + "click": { + "optIn": ".acceptAll", + "presence": ".privacyArea" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "privacy", + "value": "{\"necessary\":\"ok\",\"functional\":\"ok\",\"marketing\":\"ok\"}" + } + ], + "optOut": [ + { + "name": "privacy", + "value": "{\"necessary\":\"ok\"}" + } + ] + }, + "domains": [ + "teamgroupinc.com" + ], + "id": "9034f6e4-09ca-42a2-a8da-4f65968b8b36", + "last_modified": 1702633230717 + }, + { + "click": { + "optIn": ".btn_accept", + "presence": ".syno_cookie_element" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "syno_confirm_v4_answer", + "value": "{\"necessary\":true,\"performance\":true,\"functionality\":true,\"targeting\":true}" + } + ], + "optOut": [ + { + "name": "syno_confirm_v4_answer", + "value": "{\"necessary\":true,\"performance\":false,\"functionality\":false,\"targeting\":false}" + } + ] + }, + "domains": [ + "synology.cn", + "synology.com" + ], + "id": "d01204bd-8a94-4e6e-8ce4-d155b0681053", + "last_modified": 1702633230713 + }, + { + "click": { + "optIn": "#cp-yes", + "optOut": "#cp-no", + "presence": "#cp-overlay" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookiepermission", + "value": "yes" + } + ], + "optOut": [ + { + "name": "cookiepermission", + "value": "no" + } + ] + }, + "domains": [ + "seasonic.com" + ], + "id": "6c9b123a-ec42-4128-91a2-a4cdd65059bc", + "last_modified": 1702633230709 + }, + { + "click": { + "optIn": "[value=\"Accept\"]", + "optOut": "[value=\"Reject\"]", + "presence": "#pp_info" + }, + "schema": 1702080004246, + "domains": [ + "gainward.com", + "palit.com" + ], + "id": "c79fc6da-0143-46a0-abfc-debbd4d05f4b", + "last_modified": 1702633230705 + }, + { + "click": { + "optIn": "#Footer_butAccept", + "optOut": "#Footer_butCancel", + "presence": "#Footer_Cookie" + }, + "schema": 1702080004246, + "domains": [ + "cablexpert.be", + "cablexpert.com", + "cablexpert.de", + "cablexpert.gr", + "cablexpert.nl", + "energenie.com", + "gembird.be", + "gembird.com", + "gembird.com.pl", + "gembird.es", + "gembird.nl", + "gembird3.com", + "gembird3.nl", + "gmb-online.nl", + "gmb.nl" + ], + "id": "f7aa3175-3c2a-4458-b822-da0bd57c2524", + "last_modified": 1702633230701 + }, + { + "click": { + "optIn": "#Cookies_consent_btn", + "optOut": "#Cookies_consent_internal_btn", + "presence": "#qkies_info" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookiesconsent", + "value": "true" + } + ], + "optOut": [ + { + "name": "cookiesconsent", + "value": "internal" + } + ] + }, + "domains": [ + "dreammachines.by", + "dreammachines.eu", + "dreammachines.io", + "dreammachines.nl", + "dreammachines.pl", + "dreammachines.ru" + ], + "id": "c8cb50a3-7604-4de0-a0ac-c2e7b0ad45c0", + "last_modified": 1702633230692 + }, + { + "click": { + "optIn": "[name=\"accept\"]", + "presence": ".ck-notiz" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookieSettings", + "value": "%7B%22necessary%22%3Atrue%2C%22analytics%22%3Atrue%2C%22thirdParty%22%3Atrue%2C%22dismissed%22%3Atrue%7D" + } + ], + "optOut": [ + { + "name": "cookieSettings", + "value": "%7B%22necessary%22%3Atrue%2C%22analytics%22%3Afalse%2C%22thirdParty%22%3Afalse%2C%22dismissed%22%3Atrue%7D" + } + ] + }, + "domains": [ + "creative.com" + ], + "id": "32fc1292-e26f-49c8-8de8-c41966c0bd34", + "last_modified": 1702633230688 + }, + { + "click": { + "optIn": ".cookie-accept", + "optOut": "#cookie-accept-technical", + "presence": ".cookie-banner" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "_CookiePolicyHint", + "value": "true" + }, + { + "name": "cookie_functional", + "value": "on" + }, + { + "name": "cookie_marketing", + "value": "on" + } + ], + "optOut": [ + { + "name": "_CookiePolicyHint", + "value": "true" + } + ] + }, + "domains": [ + "bequiet.com" + ], + "id": "de38e8a0-25d2-42d2-974d-04684e54b7ce", + "last_modified": 1702633230684 + }, + { + "click": { + "optIn": "#cookiesPrivacyPolicyAllowBtn", + "optOut": "#cookiesPrivacyPolicyDenyBtn", + "presence": "#cookiesPrivacyPolicyContainerWrapper" + }, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookiesPrivacyPolicy", + "value": "1" + }, + { + "name": "cookiesPrivacyPolicyExtended", + "value": "1" + } + ], + "optOut": [ + { + "name": "cookiesPrivacyPolicy", + "value": "1" + }, + { + "name": "cookiesPrivacyPolicyExtended", + "value": "0" + } + ] + }, + "domains": [ + "akyga.com" + ], + "id": "3bf04e3c-efe8-49af-bf80-506f12ba2da4", + "last_modified": 1702633230681 + }, + { + "click": { + "optIn": "button.fcQwZX", + "optOut": "button.fLZgds", + "presence": ".kDNyTh.hbTFXs" + }, + "schema": 1702598407647, + "domains": [ + "androidpolice.com" + ], + "id": "5B57603A-0CE0-4511-B324-18D34F60EC51", + "last_modified": 1702633230676 + }, + { + "click": { + "optIn": "button[data-testid=\"allow-all-cookies-button\"]", + "optOut": "div[data-testid=\"cookie-popover\"] button:nth-child(2)", + "presence": "div[data-testid=\"cookie-popover\"]" + }, + "schema": 1702598407647, + "domains": [ + "ondo.finance" + ], + "id": "50105C82-FEDE-424E-884D-430FA7BCBED", + "last_modified": 1702633230671 + }, + { + "click": { + "optIn": "#gdpr-cookie-accept", + "presence": "#gdpr-cookie-message" + }, + "schema": 1702598407647, + "cookies": { + "optIn": [ + { + "name": "cookieControl", + "value": "true" + }, + { + "name": "cookieControlPrefs", + "value": "[\"analytics\",\"marketing\"]" + } + ], + "optOut": [ + { + "name": "cookieControl", + "value": "true" + }, + { + "name": "cookieControlPrefs", + "value": "[]" + } + ] + }, + "domains": [ + "teamspeak.com" + ], + "id": "8005fd7b-26ec-43c0-ad94-1c6834cc7905", + "last_modified": 1702633230667 + }, + { + "click": {}, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "happycow-cookie-policy", + "value": "1" + } + ], + "optOut": [ + { + "name": "happycow-cookie-policy", + "value": "0" + } + ] + }, + "domains": [ + "happycow.net" + ], + "id": "4710a874-0ff4-4072-8476-36a22d7f698e", + "last_modified": 1702633230662 + }, + { + "click": {}, + "schema": 1702080004246, + "cookies": { + "optIn": [ + { + "name": "cookiebanner_accepted", + "value": "1" + } + ] + }, + "domains": [ + "raspberrypi.com" + ], + "id": "61dfb6a1-21b4-4f95-aa6d-946eb09f8511", + "last_modified": 1702633230658 + }, + { + "click": { + "optOut": "button.js-decline-all-cookies", + "presence": "div.cookie-consent-spice" + }, + "schema": 1702080004246, + "cookies": {}, + "domains": [ + "thomann.de" + ], + "id": "5aa2d4df-2a5d-4abf-bb5a-bd714951f790", + "last_modified": 1702633230653 + }, + { + "click": { + "optIn": "button[data-testid=\"gdpr-btn-accept-all\"]", + "optOut": "button[data-testid=\"gdpr-btn-refuse-all\"]", + "presence": "[data-testid=\"cookie-banner\"]" + }, + "schema": 1702080004246, + "domains": [ + "deezer.com" + ], + "id": "5bdcb3ce-6270-4270-af7b-d1fdef5cecb4", + "last_modified": 1702633230649 + }, + { + "click": { + "optIn": "button[data-cookie_consent=\"1\"]", + "optOut": "button[data-cookie_consent=\"0\"]", + "presence": "#js_reveal_cookie_content" + }, + "schema": 1702080004246, + "cookies": {}, + "domains": [ + "voelkner.de" + ], + "id": "bcf09922-64d7-4879-974a-119e8bd05fee", + "last_modified": 1702633230645 + }, + { + "click": { + "optIn": "#onetrust-accept-btn-handler", + "presence": "div#onetrust-consent-sdk" + }, + "schema": 1702598407647, + "cookies": {}, + "domains": [ + "gitlab.com", + "speedtest.net", + "name.com", + "mlb.com", + "qualtrics.com", + "tim.it", + "hotnews.ro", + "mashable.com", + "pcmag.com", + "barnesandnoble.com", + "politico.com", + "quillbot.com", + "newyorker.com", + "upwork.com", + "mediafax.ro", + "elisa.fi", + "blick.ch", + "tvn24.pl", + "olx.pl", + "olx.bg", + "gsp.ro", + "fastly.com", + "spotify.com", + "20min.ch", + "olx.ro", + "olx.pt", + "sportal.bg", + "gazeta.pl", + "romaniatv.net", + "teamviewer.com", + "ted.com", + "tripadvisor.com", + "webmd.com", + "cambridge.org", + "investing.com", + "businesswire.com", + "istockphoto.com", + "iso.org", + "quizlet.com", + "genius.com", + "jstor.org", + "trendmicro.com", + "duolingo.com", + "sophos.com", + "rte.ie", + "euro.com.pl", + "wired.com", + "arstechnica.com", + "gartner.com", + "thelancet.com", + "weebly.com", + "irishtimes.com", + "libertatea.ro", + "otomoto.pl", + "sport.pl", + "novini.bg", + "stiripesurse.ro", + "suomi24.fi", + "ziare.com", + "irishexaminer.com", + "tripadvisor.it", + "thejournal.ie", + "superbet.ro", + "g4media.ro", + "wyborcza.pl", + "nachrichten.at", + "tt.com", + "three.ie", + "tripadvisor.co.uk", + "dcnews.ro", + "vol.at", + "plotek.pl", + "howstuffworks.com", + "tripadvisor.de", + "acer.com", + "allaboutcookies.org", + "bankier.pl", + "brother.co.uk", + "brother.es", + "brother.it", + "digicert.com", + "epson.co.uk", + "fiverr.com", + "frontiersin.org", + "glassdoor.com", + "global.brother", + "gq-magazine.co.uk", + "ingka.com", + "inpost.pl", + "instructure.com", + "komputronik.pl", + "mediaexpert.pl", + "oleole.pl", + "ookla.com", + "otodom.pl", + "play.pl", + "prnewswire.com", + "salesforce.com", + "slack.com", + "thawte.com", + "ui.com", + "uisp.com" + ], + "id": "256259D5-28AA-44C4-A837-8A30424005BB", + "last_modified": 1702633230640 + }, + { + "click": { + "optIn": "button[data-qa=\"privacy-settings-action-info\"]", + "optOut": "button[data-qa=\"privacy-settings-action-close\"]", + "presence": "[data-qa=\"privacy-settings\"]" + }, + "schema": 1702598407647, + "domains": [ + "lieferando.de", + "lieferando.at", + "just-eat.ch" + ], + "id": "31d9971f-e23d-4dd9-a891-99a85d97ad19", + "last_modified": 1702633230631 + }, + { + "click": { + "optIn": "button.osano-cm-accept-all", + "optOut": ".osano-cm-denyAll", + "presence": "div.osano-cm-dialog__buttons" + }, + "schema": 1702598407647, + "cookies": {}, + "domains": [ + "slideshare.net", + "ieee.org", + "linuxfoundation.eu" + ], + "id": "23710ccf-85e6-450e-953d-7ffc3f80bbf0", + "last_modified": 1702633230618 + }, + { + "click": { + "optIn": "button#popin_tc_privacy_button_2", + "presence": "div#popin_tc_privacy_container_button" + }, + "schema": 1702598407647, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "TC_PRIVACY", + "value": "1@006%7C86%7C3315@@@1665406595598%2C1665406595598%2C1680958595598@" + } + ] + }, + "domains": [ + "credit-agricole.fr", + "sparkasse.at" + ], + "id": "850ca0e7-372f-4c9f-bfbd-76d38a076cf7", + "last_modified": 1702633230606 + }, + { + "click": { + "optIn": "button#ccc-recommended-settings", + "optOut": "button#ccc-reject-settings", + "presence": "div#ccc" + }, + "schema": 1702598407647, + "cookies": {}, + "domains": [ + "metoffice.gov.uk", + "footballmanager.com", + "sigames.com" + ], + "id": "9e4d932a-eaf9-44e4-a3ac-55861d66a7c3", + "last_modified": 1702633230599 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "cookieDeclined", + "value": "1" + } + ] + }, + "domains": [ + "grundstoff.net" + ], + "id": "ca62d977-4e2e-48ab-a186-055f9f3277e4", + "last_modified": 1701423955158 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "onleiheTracking", + "value": "false" + } + ] + }, + "domains": [ + "onleihe.de" + ], + "id": "c19b1009-d609-438c-8d7c-82fc7144eeaa", + "last_modified": 1701423955154 + }, + { + "click": { + "optIn": "button.cm-btn-success", + "optOut": "button.cm-btn-danger", + "presence": "div#klaro" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "karlsruhe.de" + ], + "id": "63b5c74c-67ae-47f2-b0ba-10c03132ad6f", + "last_modified": 1701423955150 + }, + { + "click": { + "optIn": "button.js-cookie-accept", + "optOut": "button.js-cookie-decline", + "presence": "div#toast-container" + }, + "schema": 1700826062965, + "domains": [ + "startnext.com" + ], + "id": "312f32e1-a6bf-4e87-b7a6-7480345823cf", + "last_modified": 1701423955146 + }, + { + "click": { + "optIn": ".consentAgree", + "optOut": "#consentDisagree", + "presence": "div.consent" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "strato.de" + ], + "id": "18b1dd86-aee5-4697-b601-4fa320a75dbe", + "last_modified": 1701423955143 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "hidecookie", + "value": "true" + } + ] + }, + "domains": [ + "vrn.de" + ], + "id": "0ae7f461-8705-4222-b8ee-afa03e5150ff", + "last_modified": 1701423955139 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "consent_functional", + "value": "DENY" + }, + { + "name": "consent_marketing", + "value": "DENY" + }, + { + "name": "consent_technical", + "value": "ALLOW" + }, + { + "name": "consent_version", + "value": "2.6" + } + ] + }, + "domains": [ + "huk24.de" + ], + "id": "0fbacecc-fbda-4e4b-883f-9424790ccc74", + "last_modified": 1701423955135 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "cookie-preference", + "value": "1" + } + ] + }, + "domains": [ + "korodrogerie.de" + ], + "id": "cdd5646d-06b3-4fdf-8530-b7d8a93f03df", + "last_modified": 1701423955126 + }, + { + "click": { + "optIn": "button.cl-consent__btn", + "optOut": "button.cl-consent__close-link", + "presence": "div#cl-consent" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "hdblog.it", + "hdmotori.it" + ], + "id": "342bd7ca-6502-4df7-bd3a-0b884b51aaa7", + "last_modified": 1701423955123 + }, + { + "click": {}, + "schema": 1700826062965, + "cookies": { + "optOut": [ + { + "name": "cookies_accepted", + "value": "false" + } + ] + }, + "domains": [ + "mindfactory.de" + ], + "id": "aa077f01-0574-4f1b-ad1b-3225c4dc59f7", + "last_modified": 1701423955119 + }, + { + "click": { + "optIn": "button#cookieok", + "optOut": "button#cookiecancel", + "presence": "div#cookieconsent" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "reichelt.de" + ], + "id": "b81fc066-5cdd-4af6-b288-49de860e369a", + "last_modified": 1701423955111 + }, + { + "click": { + "optIn": "button#cookie-consent-button", + "presence": "dialog.consent" + }, + "schema": 1700826062965, + "domains": [ + "computerbase.de" + ], + "id": "2c73714d-0e9b-41a1-ad12-6bd15ddade67", + "last_modified": 1701423955107 + }, + { + "click": { + "optIn": "button.cm-btn-accept-all", + "optOut": "button.cm-btn-accept", + "presence": "div#klaro" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "lancom-systems.de" + ], + "id": "efc6f62d-8d53-4f52-9dd3-172d9b04f5de", + "last_modified": 1701423955104 + }, + { + "click": { + "optIn": "button.iubenda-cs-accept-btn", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "ansa.it" + ], + "id": "8e55f0f4-262f-4d35-a461-47afea6e9069", + "last_modified": 1701423955100 + }, + { + "click": { + "optIn": "button[data-component-name=\"consent\"]", + "optOut": "button[data-component-name=\"reject\"]", + "presence": ".cookie-notice-banner" + }, + "schema": 1700826062965, + "cookies": {}, + "domains": [ + "shopify.com" + ], + "id": "531324c9-83ba-4ba3-a488-3ebde87b10af", + "last_modified": 1701423955091 + }, + { + "click": { + "optOut": "#tmart-cookie-layer-only-necessary", + "presence": ".cookieLayer" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "sparda-hessen.de" + ], + "id": "0EAF9E99-36C6-4165-87BE-A62EF1751E1D", + "last_modified": 1700826062722 + }, + { + "click": { + "optIn": "[data-testid=\"accept-all-cookies-button\"]", + "presence": "[data-testid=\"cookie-banner\"]" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "elsevier.com" + ], + "id": "0AB3A01E-10A9-4509-9350-6EF61AB223F3", + "last_modified": 1700826062716 + }, + { + "click": { + "optIn": "[data-testid=\"uc-accept-all-button\"]", + "optOut": "[data-testid=\"uc-deny-all-button\"]", + "presence": "#usercentrics-root" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "rts.ch" + ], + "id": "9f5f0c06-5221-45b2-a174-7d70fd128eb3", + "last_modified": 1700826062713 + }, + { + "click": { + "optIn": ".cky-btn.cky-btn-accept", + "optOut": ".cky-btn.cky-btn-reject", + "presence": ".cky-consent-bar" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "met.ie" + ], + "id": "536f8027-111f-4798-a9ef-745b30fe65c8", + "last_modified": 1700826062709 + }, + { + "click": { + "optIn": "#didomi-notice-agree-button", + "optOut": ".didomi-continue-without-agreeing", + "presence": "div#didomi-notice" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "orange.sk", + "hnonline.sk" + ], + "id": "688d29a8-e1c7-4d62-b3d4-53b451ff5a48", + "last_modified": 1700826062705 + }, + { + "click": { + "optIn": ".sp_choice_type_11", + "presence": ".message-container > #notice", + "runContext": "child", + "skipPresenceVisibilityCheck": true + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "hln.be", + "nu.nl", + "vg.no", + "aftonbladet.se", + "voetbalzone.nl", + "ad.nl", + "finn.no", + "sporza.be", + "derstandard.at", + "tori.fi", + "vrt.be", + "spiegel.de", + "aftenposten.no", + "vglive.no", + "oikotie.fi", + "klart.se", + "zeit.de", + "gala.de", + "gala.fr", + "volkskrant.nl", + "tek.no", + "omni.se", + "dpgmediagroup.com", + "economist.com", + "chefkoch.de", + "sport1.de", + "tagesspiegel.de", + "stern.de", + "sport.de", + "giga.de", + "2dehands.be", + "handelsblatt.com" + ], + "id": "c58a0bac-3a5f-43af-93e9-8b5462a6bcb5", + "last_modified": 1700826062689 + }, + { + "click": { + "optIn": "button.cb_accept", + "presence": "[data-module=\"cookieBanner\"]" + }, + "schema": 1700784006705, + "cookies": { + "optIn": [ + { + "host": "ok.ru", + "name": "cookieChoice", + "value": "\"PRIVACY,1,2,3\"", + "unsetValue": "\"\"" + } + ], + "optOut": [ + { + "host": "ok.ru", + "name": "cookieChoice", + "value": "PRIVACY", + "unsetValue": "\"\"" + } + ] + }, + "domains": [ + "ok.ru" + ], + "id": "384b70c3-2458-4dc9-ac6f-d9c5e90cc62a", + "last_modified": 1700826062685 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#didomi-host" + }, + "schema": 1700784006705, + "cookies": { + "optOut": [ + { + "name": "euconsent-v2", + "value": "CPgejgAPgejgAAHABBENCjCgAAAAAAAAAATIAAAAAACBoAMAAQSCEQAYAAgkEKgAwABBIIZABgACCQQA.YAAAAAAAAAAA" + } + ] + }, + "domains": [ + "b92.net", + "index.hr", + "abv.bg", + "orf.at", + "expressen.se", + "nieuwsblad.be", + "independent.ie", + "telegraaf.nl", + "as.com", + "njuskalo.hr", + "slobodnadalmacija.hr", + "dnevnik.hr", + "nova.bg", + "mundodeportivo.com", + "dn.se", + "ouest-france.fr", + "standaard.be", + "heureka.cz", + "bfmtv.com", + "filmweb.pl", + "expresso.pt", + "lavanguardia.com", + "idealista.com", + "idealista.pt", + "di.se", + "hola.com", + "lesoir.be", + "vesti.bg", + "gloria.hr", + "huffingtonpost.es", + "rtl.be", + "gong.bg", + "okdiario.com", + "dhnet.be", + "lecturas.com", + "elperiodico.com", + "dumpert.nl" + ], + "id": "77885432-826b-4d03-bb3d-dfdd36015a82", + "last_modified": 1700826062681 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "24chasa.bg" + ], + "id": "97ba1ec3-c8e7-45a0-845f-c732b71bd213", + "last_modified": 1700826062677 + }, + { + "click": { + "optIn": ".qxOn2zvg.e1sXLPUy", + "presence": ".ulheJb0a" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "wykop.pl" + ], + "id": "0caa99d5-2b67-44e1-bd45-509d8e785071", + "last_modified": 1700826062661 + }, + { + "click": { + "optIn": ".sc-1olg58b-0.bXsYmb.sc-1olg58b-1.KXemC", + "optOut": ".sc-1olg58b-0.jHiTgL.sc-1olg58b-1.sc-1hth3pd-7.KXemC.kSupJA", + "presence": ".sc-mgoo3k-0.jFxlDH" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "galaxus.de" + ], + "id": "cc78c082-2dc6-4287-9a7c-168c591810fd", + "last_modified": 1700826062657 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "plovdiv24.bg" + ], + "id": "1dbf0b53-df44-472c-a126-13740e9d2e39", + "last_modified": 1700826062654 + }, + { + "click": { + "optIn": "button#gdpr-consent-banner-accept-button", + "presence": "div.gdpr-consent-modal-footer" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "marktplaats.nl" + ], + "id": "2cb11bb1-1d52-45c7-a764-9d2e2be6c310", + "last_modified": 1700826062650 + }, + { + "click": { + "optIn": ".css-15sam98", + "optOut": ".css-1tpwlwl", + "presence": ".css-1243dq3" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "figma.com" + ], + "id": "e0dd9ba6-6514-4618-a405-3b6458c13272", + "last_modified": 1700826062646 + }, + { + "click": { + "optIn": "[data-testid=\"cookie-wall-accept\"]", + "optOut": "[data-testid=\"cookie-wall-reject\"]", + "presence": "[data-testid=\"cookie-wall-modal\"]" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "change.org" + ], + "id": "1c3ab910-8635-4007-8ea3-8aeea0c56143", + "last_modified": 1700826062642 + }, + { + "click": { + "optIn": ".dialog-actions-accept-btn", + "optOut": ".dialog-actions-decline-btn", + "presence": "[data-testid=\"cookie-dialog-root\"]" + }, + "schema": 1700784006705, + "cookies": { + "optIn": [ + { + "name": "sq", + "value": "3" + } + ], + "optOut": [ + { + "name": "sq", + "value": "0" + } + ] + }, + "domains": [ + "nike.com" + ], + "id": "719a0a85-a706-4393-9668-0b7123891058", + "last_modified": 1700826062638 + }, + { + "click": { + "optIn": ".agree-button.eu-cookie-compliance-default-button", + "optOut": ".eu-cookie-compliance-default-button.eu-cookie-compliance-reject-button", + "presence": ".eu-cookie-compliance-banner.eu-cookie-compliance-banner-info.eu-cookie-compliance-banner--categories" + }, + "schema": 1700784006705, + "cookies": { + "optIn": [ + { + "name": "_hjFirstSeen", + "value": "1" + } + ] + }, + "domains": [ + "nokia.com" + ], + "id": "c18ccca7-1b7d-4022-b9ca-c725e6030c80", + "last_modified": 1700826062635 + }, + { + "click": { + "optIn": "#accept_all_cookies_button", + "optOut": "#decline_cookies_button", + "presence": "#ccpa_consent_banner", + "runContext": "child" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "dropbox.com" + ], + "id": "7f82a27a-eaf8-4c16-b15e-66b5efe62fa2", + "last_modified": 1700826062631 + }, + { + "click": { + "optIn": "div._2j0fmugLb1FgYz6KPuB91w > button", + "optOut": "div._2j0fmugLb1FgYz6KPuB91w > button + button", + "presence": "div#cookie-banner, #wcpConsentBannerCtrl" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "microsoft.com", + "office.com" + ], + "id": "f899d35e-20af-4cfb-9856-143a80b86ba9", + "last_modified": 1700826062627 + }, + { + "click": { + "optIn": "#cookie-disclosure-accept", + "optOut": "#cookie-disclosure-reject", + "presence": "#cookie-disclosure" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "netflix.com" + ], + "id": "6037802d-9a37-4df2-bf35-9ad60c478725", + "last_modified": 1700826062623 + }, + { + "click": { + "optIn": "#bbccookies-continue-button", + "presence": "#bbccookies" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "bbc.com", + "bbc.co.uk" + ], + "id": "e9a07bcd-28b5-4034-a663-c017e6a8208e", + "last_modified": 1700826062619 + }, + { + "click": { + "optIn": ".css-1e382ig", + "optOut": ".css-7gbax3", + "presence": "#qc-cmp2-container" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "nemzetisport.hu" + ], + "id": "da1104e6-0ce0-4956-bd53-fd13c5c2c90b", + "last_modified": 1700826062613 + }, + { + "click": { + "optIn": "button.css-1kpvtti", + "presence": "div#qc-cmp2-container" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "voetbalprimeur.nl" + ], + "id": "17c60799-e59b-475f-8007-fec046609121", + "last_modified": 1700826062609 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "rtl.hr" + ], + "id": "1d9f6559-cbc8-4cdb-b5f5-ca24844621d3", + "last_modified": 1700826062605 + }, + { + "click": { + "optIn": "a.wscrOk", + "presence": "div.wscrBannerContent" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "nordea.com" + ], + "id": "fe09c786-b60e-455e-abd6-212c8878f06a", + "last_modified": 1700826062602 + }, + { + "click": { + "optIn": ".css-ofc9r3", + "optOut": ".css-1jlb8eq", + "presence": "#qc-cmp2-container" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "car.gr" + ], + "id": "690ce1d3-e595-4181-a109-8f55c1039c81", + "last_modified": 1700826062598 + }, + { + "click": { + "optIn": "button#minf-privacy-accept-btn-screen1-id", + "presence": "div.minf-privacy-rationale" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "mediaset.it" + ], + "id": "a54d7325-5847-4f20-815f-a938dacd81b4", + "last_modified": 1700826062588 + }, + { + "click": { + "optIn": "button#accept-ufti", + "presence": "div.Modal__Content-sc-1sm9281-2" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "blocket.se" + ], + "id": "12b031c4-abfd-4156-a83c-5e8418f4a866", + "last_modified": 1700826062585 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "abola.pt" + ], + "id": "8e8f6719-0350-4310-8cc1-c00bb51b72b0", + "last_modified": 1700826062581 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-dialog-container" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "sme.sk", + "ripost.hu", + "fanatik.ro", + "net.hr", + "freemail.hu", + "marica.bg", + "dn.pt", + "imhd.sk" + ], + "id": "5fd67d61-aaa7-4431-af6f-0f1c31c849fc", + "last_modified": 1700826062572 + }, + { + "click": { + "optIn": ".fc-button-label", + "presence": ".fc-consent-root" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "framar.bg" + ], + "id": "ff5b2d52-6cc0-47e5-a515-4a31c9207b81", + "last_modified": 1700826062569 + }, + { + "click": { + "optIn": "#pt-accept-all", + "optOut": ".pt-k2L", + "presence": ".pt-VnJ" + }, + "schema": 1700784006705, + "cookies": {}, + "domains": [ + "dagospia.com" + ], + "id": "794449a7-94aa-402a-a32c-3859bec7eff8", + "last_modified": 1700826062558 + }, + { + "click": { + "optIn": "button#button_i_accept", + "presence": "div#consent-info" + }, + "schema": 1700313507200, + "cookies": { + "optIn": [ + { + "name": "cookiesconsent", + "value": "eyJwZXJzb25hbGFkIjp0cnVlLCJsYXN0dmlzaXRlZCI6dHJ1ZX0" + } + ] + }, + "domains": [ + "alo.bg" + ], + "id": "9dcffb37-fccc-4c6f-9127-e9d2b0db7521", + "last_modified": 1700826062554 + }, + { + "click": { + "optIn": ".css-2rlcm4", + "presence": "#qc-cmp2-container" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "borsonline.hu" + ], + "id": "a1d99e05-e32c-4cf1-931c-25c8baf538ae", + "last_modified": 1700826062551 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1700784006705, + "cookies": { + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%277wlvZfVgG/Pfkj1y6Hfoz636NePkdUWVOV+lQLpJefS1O2ny+RqIdg==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:5%2Cutc:1699462925195%2Ciab2:%27CP07BsAP07BsACGABBENDeCgAAAAAH_AAAAAAAAS4gGgAgABkAEQAJ4AbwA_AEWAJ2AYoBMgC8wGCAS4AAAA.YAAAAAAAAAAA%27%2Cgacm:%271~%27%2Cregion:%27de%27}" + } + ] + }, + "domains": [ + "politiken.dk" + ], + "id": "1006f951-cd51-47cc-9527-5036cef4b85a", + "last_modified": 1700826062547 + }, + { + "click": { + "optIn": "#iubenda-cs-accept-btn iubenda-cs-btn-primary", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1700313507200, + "cookies": {}, + "domains": [ + "3bmeteo.com" + ], + "id": "2625ca4e-dd77-4e72-a6d0-c959f3575ad8", + "last_modified": 1700826062543 + }, + { + "schema": 1700130283852, + "domains": [ + "tumblr.com", + "paypal.com", + "amazon.se", + "amazon.fr", + "amazon.nl", + "amazon.es", + "amazon.co.uk", + "amazon.de", + "amazon.it" + ], + "id": "disabled", + "last_modified": 1700313506967 + }, + { + "click": { + "optOut": ".CookiesAlert_cookiesAlert__3qSl1 .Button_button__3Me73", + "presence": ".CookiesAlert_cookiesAlert__3qSl1" + }, + "schema": 1700130283852, + "domains": [ + "traderjoes.com" + ], + "id": "87cbcbb6-b531-4c9c-889a-aaa5678c06c5", + "last_modified": 1700313506964 + }, + { + "click": { + "hide": "section[aria-label=\"GDPR\"]", + "optOut": "#cancel", + "presence": "#gdpr" + }, + "schema": 1700130283852, + "cookies": { + "optOut": [ + { + "name": "cookies_ok", + "value": "false" + } + ] + }, + "domains": [ + "project529.com" + ], + "id": "a029bf7e-3274-4877-967e-e7517457ac18", + "last_modified": 1700313506961 + }, + { + "click": { + "optIn": ".cookielaw-banner .btn-default", + "presence": ".cookielaw-banner" + }, + "schema": 1700130283852, + "cookies": { + "optIn": [ + { + "name": "cookielaw", + "value": "1" + } + ] + }, + "domains": [ + "openwrt.org" + ], + "id": "14ea41fa-68ac-4c12-b906-7d2a259d5fb7", + "last_modified": 1700313506957 + }, + { + "click": { + "optIn": "div[data-name=\"comp-banner\"] button[aria-label=\"Dismiss\"]", + "presence": "div[data-name=\"comp-banner\"]" + }, + "schema": 1700130283852, + "cookies": { + "optIn": [ + { + "name": "cookieNotification", + "value": "true" + } + ] + }, + "domains": [ + "vegetology.com" + ], + "id": "f7633fd9-05a0-4c21-b9e7-acfcd5df5234", + "last_modified": 1700313506954 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#didomi-host" + }, + "schema": 1700130283852, + "domains": [], + "id": "didomi", + "last_modified": 1700313506945 + }, + { + "schema": 1699660804881, + "cookies": { + "optOut": [ + { + "name": "CookieConsentDeclined", + "value": "1" + } + ] + }, + "domains": [ + "hetzner.com" + ], + "id": "a8222cf2-8f6c-48df-8215-05a15d4ac592", + "last_modified": 1700130283634 + }, + { + "click": { + "optIn": ".banner-actions-container > #onetrust-accept-btn-handler, #onetrust-button-group > #onetrust-accept-btn-handler, .onetrust-banner-options > #onetrust-accept-btn-handler", + "optOut": ".banner-actions-container > #onetrust-reject-all-handler, #onetrust-button-group > #onetrust-reject-all-handler, .onetrust-banner-options > #onetrust-reject-all-handler", + "presence": "#onetrust-consent-sdk" + }, + "schema": 1699660804881, + "domains": [], + "id": "onetrust", + "last_modified": 1700130283629 + }, + { + "click": {}, + "schema": 1699660804881, + "cookies": { + "optOut": [ + { + "name": "consentLevel", + "value": "1" + }, + { + "name": "euconsent-bypass", + "value": "1" + } + ] + }, + "domains": [ + "web.de", + "gmx.net" + ], + "id": "290db348-ced2-4f16-9954-da476d0aef01", + "last_modified": 1700130283620 + }, + { + "click": { + "optIn": "div.duet--cta--cookie-banner button.py-12", + "optOut": "div.duet--cta--cookie-banner button.text-blurple", + "presence": "div.duet--cta--cookie-banner" + }, + "schema": 1699660804881, + "cookies": {}, + "domains": [ + "theverge.com" + ], + "id": "323bae6b-e146-4318-b8ba-1f819c0cfc53", + "last_modified": 1700130283616 + }, + { + "click": { + "optIn": ".fc-cta-consent", + "optOut": ".fc-cta-do-not-consent", + "presence": ".fc-dialog-container" + }, + "schema": 1697557061727, + "domains": [ + "accuweather.com" + ], + "id": "10B17126-C30B-4EE5-9CF5-A4CAFD361AEB", + "last_modified": 1697710931649 + }, + { + "click": { + "optIn": ".js-disc-cp-accept-all", + "optOut": ".js-disc-cp-deny-all", + "presence": ".disc-cp-modal__modal" + }, + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "obiConsent", + "value": "c1-1|c2-0|c3-0|c4-0|c5-0|ts-4127464398701|consent-true" + }, + { + "name": "miCookieOptOut", + "value": "1" + } + ] + }, + "domains": [ + "obi.de" + ], + "id": "02BB875E-D588-4C12-9BE8-FD467C050B86", + "last_modified": 1697710931646 + }, + { + "click": { + "optIn": "#cmpwelcomebtnyes a", + "presence": "#cmpbox" + }, + "schema": 1697557061727, + "domains": [ + "mail.ru", + "blic.rs", + "hrt.hr", + "eventim.de" + ], + "id": "EA3C8C1C-8B1A-46A4-8631-750ECC29425A", + "last_modified": 1697710931642 + }, + { + "click": { + "optIn": "#cmpwelcomebtnyes a", + "optOut": "#cmpwelcomebtnno a", + "presence": "#cmpbox" + }, + "schema": 1697557061727, + "domains": [ + "adac.de" + ], + "id": "E46708B7-B378-4AA5-A700-12D7221537A0", + "last_modified": 1697710931639 + }, + { + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "cookie_consent", + "value": "denied" + }, + { + "name": "marketing_consent", + "value": "denied" + }, + { + "name": "personalization_consent", + "value": "denied" + } + ] + }, + "domains": [ + "arbeitsagentur.de" + ], + "id": "A04DD123-A10F-4665-9C2B-2FB5CC293F42", + "last_modified": 1697710931635 + }, + { + "click": { + "optIn": "button[data-test=\"pwa-consent-layer-accept-all\"]", + "optOut": "button[data-test=\"pwa-consent-layer-deny-all\"]", + "presence": "#mms-consent-portal-container" + }, + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "pwaconsent", + "value": "v:1.0~required:1&clf:1,cli:1,gfb:1,gtm:1,jot:1,ocx:1|comfort:0&baz:0,cne:0,con:0,fix:0,gfa:0,goa:0,gom:0,grc:0,grv:0,inm:0,lib:0,lob:0,opt:0,orc:0,ore:0,pcl:0,sen:0,sis:0,spe:0,sst:0,swo:0,twi:0,usw:0,usz:0,yte:0|marketing:0&cri:0,eam:0,fab:0,fbc:0,fcm:0,gac:0,gad:0,gcl:0,gcm:0,gdv:0,gos:0,gse:0,msb:0,omp:0,pin:0,ttd:0,twt:0|" + } + ] + }, + "domains": [ + "mediamarkt.de", + "saturn.de" + ], + "id": "2A2B8102-D276-4ECE-AFBD-005E8E917D18", + "last_modified": 1697710931632 + }, + { + "click": { + "optIn": "div[data-tracking-opt-in-accept=\"true\"]", + "presence": "div[data-tracking-opt-in-overlay=\"true\"]" + }, + "schema": 1697557061727, + "domains": [ + "fandom.com" + ], + "id": "D168AF87-F481-4AD7-BE78-28A59F798406", + "last_modified": 1697710931628 + }, + { + "click": { + "optIn": "#consentAcceptAll", + "optOut": "#rejectAll", + "presence": "#__tealiumGDPRecModal" + }, + "schema": 1697557061727, + "domains": [ + "telekom.com", + "telekom.de" + ], + "id": "8907164E-17D8-4D27-B0A3-EDDA59F53DBE", + "last_modified": 1697710931625 + }, + { + "click": { + "optIn": "#dip-consent-summary-accept-all", + "optOut": "#dip-consent-summary-reject-all", + "presence": "#dip-consent" + }, + "schema": 1697557061727, + "cookies": {}, + "domains": [ + "vodafone.de" + ], + "id": "B85F7D67-D6D4-40EE-8472-A32B6CD01E0E", + "last_modified": 1697710931621 + }, + { + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "CONSENTMGR", + "value": "c1:1%7Cc2:0%7Cc3:0%7Cc4:0%7Cc5:0%7Cc6:0%7Cc7:0%7Cc8:0%7Cc9:0%7Cc10:0%7Cc11:0%7Cc12:0%7Cc13:0%7Cc14:0%7Cc15:0%7Cts:111697211775123%7Cconsent:true" + }, + { + "name": "request_consent_v", + "value": "3" + } + ] + }, + "domains": [ + "bahn.de" + ], + "id": "D4AD5843-DDBD-4D93-BFBA-F53DD8B53111", + "last_modified": 1697710931618 + }, + { + "schema": 1697557061727, + "cookies": { + "optIn": [ + { + "name": "consent_status", + "value": "true" + } + ], + "optOut": [ + { + "name": "consent_status", + "value": "false" + } + ] + }, + "domains": [ + "immobilienscout24.de" + ], + "id": "3BD1F0DA-BABB-4F75-9B56-C92A1CFD114B", + "last_modified": 1697710931613 + }, + { + "click": { + "optIn": ".cmpboxbtnyes", + "optOut": ".cmpboxbtnsave", + "presence": "#cmpwrapper" + }, + "schema": 1697557061727, + "domains": [ + "gls-pakete.de" + ], + "id": "1D4DDA28-C6FC-489F-B2F3-07EAAB4F5AFE", + "last_modified": 1697710931609 + }, + { + "click": { + "optIn": "button[data-test-id=\"cookie-notice-accept\"]", + "optOut": "button[data-test-id=\"cookie-notice-reject\"]", + "presence": ".cookie-notice" + }, + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "ECCC", + "value": "e" + } + ] + }, + "domains": [ + "ecosia.org" + ], + "id": "7BA8E4AF-438A-40FE-8941-F921AA1FEA2A", + "last_modified": 1697710931606 + }, + { + "click": { + "optIn": "button[data-testid=\"cookie-banner-strict-accept-all\"]", + "optOut": "button[data-testid=\"cookie-banner-strict-accept-selected\"]", + "presence": "div[data-testid=\"dl-cookieBanner\"]" + }, + "schema": 1697557061727, + "cookies": { + "optOut": [ + { + "name": "privacySettings", + "value": "%7B%22v%22%3A%221%22%2C%22t%22%3A4127362064%2C%22m%22%3A%22STRICT%22%2C%22consent%22%3A%5B%22NECESSARY%22%5D%7D" + } + ] + }, + "domains": [ + "deepl.com" + ], + "id": "e3968548-ebca-4d6b-b8cf-5707e90fb612", + "last_modified": 1697710931573 + }, + { + "click": { + "optIn": "button#uc-btn-accept-banner", + "optOut": "button#uc-btn-deny-banner", + "presence": "div#uc-banner-modal" + }, + "schema": 1697557061727, + "cookies": {}, + "domains": [ + "zalando.ch", + "zalando.dk", + "zalando.be", + "zalando.de", + "zalando.at", + "zalando.co.uk", + "zalando.com", + "zalando.cz", + "zalando.ee", + "zalando.es", + "zalando.fi", + "zalando.fr", + "zalando.hr" + ], + "id": "3203ac4e-2454-4022-90fb-d4f51467ce20", + "last_modified": 1697710931567 + }, + { + "schema": 1696497904676, + "cookies": { + "optOut": [ + { + "name": "c24consent", + "value": "f" + } + ] + }, + "domains": [ + "check24.de" + ], + "id": "d4f2492e-c801-4687-84c8-e2cd17b4f115", + "last_modified": 1696579709256 + }, + { + "click": { + "optIn": ".qc-cmp2-summary-buttons > :nth-child(3)", + "optOut": ".qc-cmp2-summary-buttons > :nth-child(2)", + "presence": "#qc-cmp2-container" + }, + "schema": 1696497904676, + "cookies": {}, + "domains": [ + "ign.com" + ], + "id": "FB43FF71-9546-43D1-8B52-D208D1347095", + "last_modified": 1696579709252 + }, + { + "click": { + "optIn": "#gdpr-banner-accept", + "presence": "#gdpr-banner-container" + }, + "schema": 1696497904676, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "ekConsentTcf2", + "value": "{%22customVersion%22:6%2C%22encodedConsentString%22:%22CPzMR3KPzMR3KE1ABADEC4CgAAAAAAAAAAYgJNwLgAXAA4ACWAFMAPwAzYCLAIuAZ8A14B0gD7AI8ASKAlcBMgCmwFhALqAXeAvoBggDBgGfANGAaaA1UBtADggHHgOUAc6A58B2wDuQHggPJAfaA_YCCIEFAI0gR2Aj6BIiCSIJJgSbAAAASMgAwABBKEdABgACCUJCADAAEEoSUAGAAIJQlIAMAAQShCQAYAAglCGgAwABBKERABgACCUIqADAAEEoQA%22%2C%22googleConsentGiven%22:false%2C%22consentInterpretation%22:{%22googleAdvertisingFeaturesAllowed%22:false%2C%22googleAnalyticsAllowed%22:false%2C%22infonlineAllowed%22:false%2C%22theAdexAllowed%22:false%2C%22criteoAllowed%22:false%2C%22facebookAllowed%22:false%2C%22amazonAdvertisingAllowed%22:false%2C%22rtbHouseAllowed%22:false}}" + } + ] + }, + "domains": [ + "kleinanzeigen.de" + ], + "id": "dbc0dde3-600b-40ab-ab8b-b07e3e3c7af8", + "last_modified": 1696579709220 + }, + { + "click": { + "optIn": ".implicit_privacy_prompt > .close_btn_thick", + "presence": ".implicit_privacy_prompt" + }, + "schema": 1694908806092, + "cookies": { + "optOut": [ + { + "name": "HASSEENNOTICE", + "value": "TRUE" + }, + { + "name": "CONSENTMGR", + "value": "c1:0%7Cc3:0%7Cc5:0%7Cc6:0%7Cc7:0%7Cc8:0" + } + ] + }, + "domains": [ + "ups.com" + ], + "id": "f462b0ee-4d17-4dcb-a951-1a23249227f6", + "last_modified": 1695037595332 + }, + { + "click": {}, + "schema": 1694908806092, + "cookies": { + "optOut": [ + { + "name": "CONSENT", + "path": "/", + "value": "PENDING+742", + "sameSite": 0 + }, + { + "name": "SOCS", + "path": "/", + "value": "CAESNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjMwOTEyLjA4X3AwGgJlbiACGgYIgOCTqAY", + "sameSite": 0 + } + ] + }, + "domains": [ + "youtube.com" + ], + "id": "788ed05e-cf93-41a8-b2e5-c5571d855232", + "last_modified": 1695037595328 + }, + { + "click": { + "optIn": "#footer_tc_privacy_button", + "optOut": "#footer_tc_privacy_button_2", + "presence": ".tc-privacy-wrapper.tc-privacy-override.tc-privacy-wrapper" + }, + "schema": 1690070404721, + "domains": [ + "arte.tv" + ], + "id": "cc818b41-7b46-46d3-9b17-cf924cbe87d1", + "last_modified": 1690359097318 + }, + { + "schema": 1690070404721, + "cookies": { + "optIn": [ + { + "name": "cb", + "value": "1_2055_07_11_" + } + ], + "optOut": [ + { + "name": "cb", + "value": "1_2055_07_11_2-3" + } + ] + }, + "domains": [ + "threads.net" + ], + "id": "C232EAB8-F55A-436A-8033-478746D05D98", + "last_modified": 1690359097314 + }, + { + "click": { + "optIn": "#didomi-notice-agree-button", + "presence": "#didomi-popup" + }, + "schema": 1690070404721, + "cookies": { + "optOut": [ + { + "name": "euconsent-v2", + "value": "CRolnUARolnUAAHABBENDHCgAAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAAA" + } + ] + }, + "domains": [ + "reverso.net" + ], + "id": "05157ed1-12c2-4f84-9dff-718fae5bc096", + "last_modified": 1690359097310 + }, + { + "click": { + "optIn": "#acceptAllButton", + "optOut": "#rejectAllButton", + "presence": "#cookiePrefPopup" + }, + "schema": 1686614405615, + "cookies": { + "optIn": [ + { + "name": "cookieSettings", + "value": "%7B%22version%22%3A1%2C%22preference_state%22%3A1%2C%22content_customization%22%3Anull%2C%22valve_analytics%22%3Anull%2C%22third_party_analytics%22%3Anull%2C%22third_party_content%22%3Anull%2C%22utm_enabled%22%3Atrue%7D" + } + ], + "optOut": [ + { + "name": "cookieSettings", + "value": "%7B%22version%22%3A1%2C%22preference_state%22%3A2%2C%22content_customization%22%3Anull%2C%22valve_analytics%22%3Anull%2C%22third_party_analytics%22%3Anull%2C%22third_party_content%22%3Anull%2C%22utm_enabled%22%3Atrue%7D" + } + ] + }, + "domains": [ + "steamcommunity.com", + "steampowered.com" + ], + "id": "63104024-41b5-4be5-8019-2c59eaaf612c", + "last_modified": 1686654909145 + }, + { + "click": { + "optIn": "button.accept-all", + "optOut": "button.reject-all", + "presence": "div#consent-page" + }, + "schema": 1678898205499, + "cookies": {}, + "domains": [ + "yahoo.com" + ], + "id": "2f9123b1-7d6b-460f-8da8-988f82238ec7", + "last_modified": 1679487994744 + }, + { + "click": { + "optIn": "#truste-consent-button", + "optOut": "#truste-consent-required", + "presence": "#truste-consent-content, .truste-consent-content, #truste-consent-track" + }, + "schema": 1678092903414, + "domains": [], + "id": "trustarcbar", + "last_modified": 1678276263380 + }, + { + "click": { + "optIn": ".qc-cmp2-buttons-desktop button[mode=\"primary\"], .qc-cmp2-summary-buttons button[mode=\"primary\"]", + "presence": ".qc-cmp2-container" + }, + "schema": 1678092903414, + "domains": [], + "id": "quantcast", + "last_modified": 1678276263374 + }, + { + "click": { + "optIn": "#CookieBoxSaveButton", + "optOut": "._brlbs-refuse-btn a", + "presence": "#BorlabsCookieBox" + }, + "schema": 1678092903414, + "domains": [], + "id": "borlabs", + "last_modified": 1678276263369 + }, + { + "click": { + "optIn": "#cmpwelcomebtnyes a", + "optOut": "#cmpwelcomebtnno a", + "presence": "#cmpbox" + }, + "schema": 1678092903414, + "domains": [], + "id": "consentmanagernet", + "last_modified": 1678276263363 + }, + { + "click": { + "optIn": "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "#CybotCookiebotDialogBodyButtonDecline", + "presence": "#CybotCookiebotDialog" + }, + "schema": 1678092903414, + "domains": [], + "id": "cookiebot", + "last_modified": 1678276263357 + }, + { + "click": { + "optIn": ".cmplz-btn.cmplz-accept", + "optOut": ".cmplz-btn.cmplz-deny", + "presence": "#cmplz-cookiebanner-container" + }, + "schema": 1678092903414, + "domains": [], + "id": "complianz", + "last_modified": 1678276263351 + }, + { + "click": { + "optIn": ".sp_choice_type_11", + "presence": ".message-container > #notice", + "runContext": "child", + "skipPresenceVisibilityCheck": true + }, + "schema": 1678092903414, + "domains": [], + "id": "sourcepoint", + "last_modified": 1678276263334 + }, + { + "click": { + "optIn": "button#hs-eu-confirmation-button", + "optOut": "button#hs-eu-decline-button", + "presence": "div#hs-eu-cookie-confirmation" + }, + "schema": 1678092903414, + "cookies": {}, + "domains": [ + "hubspot.com" + ], + "id": "8308357f-6a66-433d-bfc3-de401410c350", + "last_modified": 1678276263322 + }, + { + "click": {}, + "schema": 1678092903414, + "cookies": { + "optIn": [ + { + "name": "cookie-consent", + "value": "{%22ga%22:true%2C%22af%22:true%2C%22fbp%22:true%2C%22lip%22:true%2C%22bing%22:true%2C%22ttads%22:true%2C%22reddit%22:true%2C%22criteo%22:true%2C%22version%22:%22v9%22}" + } + ], + "optOut": [ + { + "name": "cookie-consent", + "value": "{%22ga%22:false%2C%22af%22:false%2C%22fbp%22:false%2C%22lip%22:false%2C%22bing%22:false%2C%22ttads%22:false%2C%22reddit%22:false%2C%22criteo%22:false%2C%22version%22:%22v9%22}" + } + ] + }, + "domains": [ + "tiktok.com" + ], + "id": "d9166ae8-dcc7-4ca2-8b02-0884fb1d6f70", + "last_modified": 1678276263311 + }, + { + "click": {}, + "schema": 1678236486316, + "cookies": { + "optOut": [ + { + "name": "_s.cookie_consent", + "value": "marketing=0:analytics=0:version=2021-07-01:timestamp=1678179320141" + } + ] + }, + "domains": [ + "smallpdf.com" + ], + "id": "bb1114eb-12ef-4465-8990-b33bbb270d3f", + "last_modified": 1678276263300 + }, + { + "click": { + "optIn": "button.cc-banner__button-accept", + "optOut": "button.cc-banner__button-reject", + "presence": "div.cc-banner__content" + }, + "schema": 1678236486316, + "cookies": {}, + "domains": [ + "biomedcentral.com" + ], + "id": "d5220250-697b-4b60-8284-f87e8ec2565c", + "last_modified": 1678276263295 + }, + { + "click": { + "optIn": "a.o-cookie-message__button", + "presence": "div.o-cookie-message" + }, + "schema": 1678236486316, + "cookies": {}, + "domains": [ + "ft.com" + ], + "id": "b485c89f-130e-4d5a-94f8-99eacad96e5f", + "last_modified": 1678276263290 + }, + { + "click": { + "optOut": "button.TCF2Popup__continueWithoutAccepting___1fMWW", + "presence": "div.TCF2Popup__container___1TN_W" + }, + "schema": 1678092903414, + "cookies": {}, + "domains": [ + "dailymotion.com" + ], + "id": "e5d31bce-4a62-4c6d-a40f-94fda7c41c9d", + "last_modified": 1678276263284 + }, + { + "click": { + "optIn": "button#L2AGLb", + "optOut": "button#W0wltc", + "presence": "div.spoKVd" + }, + "schema": 1678092903414, + "cookies": { + "optIn": [ + { + "name": "CONSENT", + "value": "PENDING" + } + ], + "optOut": [ + { + "name": "SOCS", + "value": "CAESHAgBEhJnd3NfMjAyMjA5MjktMF9SQzEaAnJvIAEaBgiAkvOZBg" + } + ] + }, + "domains": [ + "google.ad", + "google.ae", + "google.al", + "google.am", + "google.as", + "google.at", + "google.az", + "google.ba", + "google.be", + "google.bf", + "google.bg", + "google.bi", + "google.bj", + "google.bs", + "google.bt", + "google.by", + "google.ca", + "google.cat", + "google.cd", + "google.cf", + "google.cg", + "google.ch", + "google.ci", + "google.cl", + "google.cm", + "google.cn", + "google.co.ao", + "google.co.bw", + "google.co.ck", + "google.co.cr", + "google.co.id", + "google.co.il", + "google.co.in", + "google.co.jp", + "google.co.ke", + "google.co.kr", + "google.co.ls", + "google.co.ma", + "google.co.mz", + "google.co.nz", + "google.co.th", + "google.co.tz", + "google.co.ug", + "google.co.uk", + "google.co.uz", + "google.co.ve", + "google.co.vi", + "google.co.za", + "google.co.zm", + "google.co.zw", + "google.com", + "google.com.af", + "google.com.ag", + "google.com.ai", + "google.com.ar", + "google.com.au", + "google.com.bd", + "google.com.bh", + "google.com.bn", + "google.com.bo", + "google.com.br", + "google.com.bz", + "google.com.co", + "google.com.cu", + "google.com.cy", + "google.com.do", + "google.com.ec", + "google.com.eg", + "google.com.et", + "google.com.fj", + "google.com.gh", + "google.com.gi", + "google.com.gt", + "google.com.hk", + "google.com.jm", + "google.com.kh", + "google.com.kw", + "google.com.lb", + "google.com.ly", + "google.com.mm", + "google.com.mt", + "google.com.mx", + "google.com.my", + "google.com.na", + "google.com.ng", + "google.com.ni", + "google.com.np", + "google.com.om", + "google.com.pa", + "google.com.pe", + "google.com.pg", + "google.com.ph", + "google.com.pk", + "google.com.pr", + "google.com.py", + "google.com.qa", + "google.com.sa", + "google.com.sb", + "google.com.sg", + "google.com.sl", + "google.com.sv", + "google.com.tj", + "google.com.tr", + "google.com.tw", + "google.com.ua", + "google.com.uy", + "google.com.vc", + "google.com.vn", + "google.cv", + "google.cz", + "google.de", + "google.dj", + "google.dk", + "google.dm", + "google.dz", + "google.ee", + "google.es", + "google.fi", + "google.fm", + "google.fr", + "google.ga", + "google.ge", + "google.gg", + "google.gl", + "google.gm", + "google.gr", + "google.gy", + "google.hn", + "google.hr", + "google.ht", + "google.hu", + "google.ie", + "google.im", + "google.iq", + "google.is", + "google.it", + "google.je", + "google.jo", + "google.kg", + "google.ki", + "google.kz", + "google.la", + "google.li", + "google.lk", + "google.lt", + "google.lu", + "google.lv", + "google.md", + "google.me", + "google.mg", + "google.mk", + "google.ml", + "google.mn", + "google.ms", + "google.mu", + "google.mv", + "google.mw", + "google.ne", + "google.nl", + "google.no", + "google.nr", + "google.nu", + "google.pl", + "google.pn", + "google.ps", + "google.pt", + "google.ro", + "google.rs", + "google.ru", + "google.rw", + "google.sc", + "google.se", + "google.sh", + "google.si", + "google.sk", + "google.sm", + "google.sn", + "google.so", + "google.sr", + "google.st", + "google.td", + "google.tg", + "google.tl", + "google.tm", + "google.tn", + "google.to", + "google.tt", + "google.vg", + "google.vu", + "google.ws" + ], + "id": "4a37ab17-dbd7-45cf-83d8-42b84af9a0df", + "last_modified": 1678276263277 + }, + { + "click": { + "optIn": "button#bnp_btn_accept", + "optOut": "button#bnp_btn_reject", + "presence": "div#bnp_cookie_banner" + }, + "schema": 1678092903414, + "cookies": { + "optOut": [ + { + "name": "BCP", + "value": "AD=0&AL=0&SM=0" + } + ] + }, + "domains": [ + "bing.com" + ], + "id": "31dc6160-3495-4f4e-8c67-594527bd4051", + "last_modified": 1678276263272 + }, + { + "click": { + "optIn": "button.css-1d0m171", + "presence": "div#qc-cmp2-container" + }, + "schema": 1678236486316, + "cookies": {}, + "domains": [ + "in.gr" + ], + "id": "77896946-2dbf-4b65-9641-9958f9e2a228", + "last_modified": 1678276263267 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-consent-root" + }, + "schema": 1678092903414, + "cookies": {}, + "domains": [ + "dnes.bg", + "dennikn.sk", + "jn.pt", + "ojogo.pt", + "klik.hr" + ], + "id": "ed097835-3d75-4864-8cdf-ea8fb19b033c", + "last_modified": 1678276263256 + }, + { + "click": { + "optIn": "button.css-7c2c0h", + "presence": "div#qc-cmp2-container" + }, + "schema": 1678236486316, + "cookies": {}, + "domains": [ + "deviantart.com" + ], + "id": "489a59fb-1054-4d7a-ae1f-c6c561d2cd81", + "last_modified": 1678276263214 + }, + { + "click": { + "optIn": "div#gdpr_accept", + "presence": "div#gdpr" + }, + "schema": 1678236486316, + "cookies": {}, + "domains": [ + "wikihow.com" + ], + "id": "90b68b2d-46eb-4500-8cfd-9ee794653aaa", + "last_modified": 1678276263208 + }, + { + "click": { + "optIn": "button.css-1lgi3ta", + "presence": "div#qc-cmp2-container" + }, + "schema": 1678092903414, + "cookies": {}, + "domains": [ + "ethnos.gr", + "sportdog.gr" + ], + "id": "570920f9-6a8a-4a26-a68a-8fc773ba30a9", + "last_modified": 1678276263197 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "optOut": "button.fc-cta-do-not-consent", + "presence": "div.fc-consent-root" + }, + "schema": 1678092903414, + "cookies": {}, + "domains": [ + "coolinarika.com", + "ndtv.com", + "hbr.org" + ], + "id": "12552893-278a-43e6-83ba-1db5267b3d27", + "last_modified": 1678276263190 + }, + { + "click": { + "optIn": "button.snow-ali-kit_Button-Floating__button__ph4zrl", + "presence": "div.SnowPrivacyPolicyBanner_SnowPrivacyPolicyBanner__privacyPolicyBanner__1jg07" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "aliexpress.ru" + ], + "id": "407fe21e-7730-47f9-b83f-51d19dddc700", + "last_modified": 1676903765550 + }, + { + "click": { + "optIn": "a.dismiss", + "presence": "div#cookie-disclaimer" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "arukereso.hu" + ], + "id": "7cbce78d-ec34-4e9b-a4f5-5b401f155e36", + "last_modified": 1676903765539 + }, + { + "click": { + "optIn": "div.cookieinfo-close", + "presence": "div.cookieinfo" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "glasistre.hr" + ], + "id": "266c0df8-d8bc-4824-88d8-06d3970f0c2a", + "last_modified": 1676903765535 + }, + { + "click": { + "optIn": "button.css-wum6af", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "flash.pt" + ], + "id": "f081b651-7065-46a9-8d71-d7531850382e", + "last_modified": 1676903765531 + }, + { + "click": { + "optIn": "label.c-notifier-btn", + "presence": "div.c-notifier-container" + }, + "schema": 1676828360243, + "cookies": { + "optOut": [ + { + "name": "gdpr", + "value": "[]" + } + ] + }, + "domains": [ + "ria.com" + ], + "id": "3d2936df-3ca5-42fc-924b-1144c3d5b424", + "last_modified": 1676903765523 + }, + { + "click": { + "optIn": "button#acceptAllCookiesBtn", + "presence": "div#cookie-popover" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "sverigesradio.se" + ], + "id": "aa18156e-646e-4c5c-b550-6979d181f192", + "last_modified": 1676903765516 + }, + { + "click": { + "optIn": "a.btn", + "presence": "div#gdpr-box" + }, + "schema": 1676828360243, + "cookies": { + "optIn": [ + { + "name": "privacy_accepted", + "value": "1" + } + ] + }, + "domains": [ + "naslovi.net" + ], + "id": "87d2bcfa-8685-4289-8c0a-d0fc31c74cc5", + "last_modified": 1676903765512 + }, + { + "click": { + "optIn": "button.orange-gradient", + "presence": "div.cookie-bar-wrap" + }, + "schema": 1676828360243, + "cookies": { + "optIn": [ + { + "name": "politica_cookie", + "value": "1" + } + ] + }, + "domains": [ + "dedeman.ro" + ], + "id": "84a2ec9b-bf4a-4638-aa94-d1b13652aae8", + "last_modified": 1676903765508 + }, + { + "click": { + "optIn": "button.jad_cmp_paywall_button-cookies", + "presence": "div#cmp-main" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "allocine.fr" + ], + "id": "7d70347b-fe27-4c60-a9f2-6e0638f0ce37", + "last_modified": 1676903765505 + }, + { + "click": { + "optIn": "button.primary", + "presence": "div.cookie-consent-overlay" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "komplett.no" + ], + "id": "be728b6d-8eeb-4179-beac-c1234fc0ae9f", + "last_modified": 1676903765501 + }, + { + "click": { + "optIn": "div.cookieButton", + "presence": "div#cookieConsentContainer" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "chmi.cz" + ], + "id": "d8923166-cf05-4f6c-8829-270fc9b2efe1", + "last_modified": 1676903765497 + }, + { + "click": { + "optIn": "button.PONCHO-button--decide", + "presence": "div#TRCO-application" + }, + "schema": 1676828360243, + "cookies": { + "optOut": [ + { + "name": "cookie-policy-agreement", + "value": "%7B%22revision%22%3A20%2C%22consentLevel%22%3A1%7D" + } + ] + }, + "domains": [ + "anwb.nl" + ], + "id": "e63a688a-7eca-49b1-9b0f-bebae773a6ec", + "last_modified": 1676903765493 + }, + { + "click": { + "optIn": "button.shared-module_w-full_3cBSk", + "presence": "div#consent-modal-yr73k" + }, + "schema": 1676828360243, + "cookies": { + "optOut": [ + { + "name": "euconsent-v2", + "value": "CPmmxEAPmmxEADhADBPLC1CgAAAAAAAAAB5YAAAAAAAA" + } + ] + }, + "domains": [ + "se.pl" + ], + "id": "d0b8bc2c-c633-4628-aa39-e840060efc2e", + "last_modified": 1676903765490 + }, + { + "click": {}, + "schema": 1676828360243, + "cookies": { + "optOut": [ + { + "name": "OPTOUTCONSENT", + "value": "1:1&2:0&3:0&4:0" + } + ] + }, + "domains": [ + "vodafone.pt" + ], + "id": "522a2d72-8131-406e-b058-b27ec07808fc", + "last_modified": 1676903765486 + }, + { + "click": { + "optIn": "div#cookiescript_accept", + "optOut": "div#cookiescript_reject ", + "presence": "div#cookiescript_injected" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "idealmedia.io" + ], + "id": "7f8a8bfd-343c-4fa5-969b-35a0690f6f4f", + "last_modified": 1676903765482 + }, + { + "click": { + "optIn": "a.cookieBarConsentButton", + "presence": "div#cookieBar" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "pki.goog" + ], + "id": "259f5d9f-b127-47c2-a42e-161cb70a0a81", + "last_modified": 1676903765475 + }, + { + "click": { + "optIn": "button.css-11uejzu", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676828360243, + "cookies": {}, + "domains": [ + "huffpost.com" + ], + "id": "e9d772f8-92fd-4e0e-b742-0bfd6555877e", + "last_modified": 1676903765471 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "cookiesAccepted", + "value": "essential" + } + ] + }, + "domains": [ + "onlyfans.com" + ], + "id": "647013d0-7a3f-4e80-8c26-00f3389ba551", + "last_modified": 1676903765463 + }, + { + "click": { + "optIn": "button#cookie-accept", + "presence": "div.banner" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "tandfonline.com" + ], + "id": "73c5220c-3cdb-446b-b2f6-ce9e0f1d9a8b", + "last_modified": 1676903765459 + }, + { + "click": { + "optIn": "button.banner-lgpd-consent__accept", + "presence": "div.banner-lgpd-consent-container" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "uol.com.br" + ], + "id": "a491dd76-8759-4c10-a4fa-2e87b1034f23", + "last_modified": 1676903765456 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "uw_marketo_opt_in", + "value": "true" + } + ], + "optOut": [ + { + "name": "mkto_opt_out", + "value": "id:true" + } + ] + }, + "domains": [ + "washington.edu" + ], + "id": "1de8aa61-94f1-49b4-a05d-d19a57829c71", + "last_modified": 1676903765452 + }, + { + "click": { + "optIn": "button.css-2wu0d3", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "buzzfeed.com" + ], + "id": "a4186ce7-bb7a-4c55-aec6-34f265943367", + "last_modified": 1676903765448 + }, + { + "click": { + "optIn": "button.cky-btn-accept", + "optOut": "button.cky-btn-reject", + "presence": "div.cky-consent-bar" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "hugedomains.com" + ], + "id": "b3b80b81-90f0-497f-b78c-53b611d4dfaf", + "last_modified": 1676903765444 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "trackingconsent", + "value": "{\"marketingenabled\":true}" + } + ], + "optOut": [ + { + "name": "trackingconsent", + "value": "{\"marketingenabled\":false}" + } + ] + }, + "domains": [ + "abc.net.au" + ], + "id": "c213b36a-4893-46e2-bb0e-f98a03d58287", + "last_modified": 1676903765437 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "presence": "div#CookieBanner" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "jotform.com" + ], + "id": "6efa8f4a-97a3-4677-8cde-8727f270d267", + "last_modified": 1676903765433 + }, + { + "click": { + "optIn": "button.bcpConsentButton", + "presence": "div.bcpNotificationBar" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "inc.com" + ], + "id": "990f03a5-c180-407b-9a0e-f6db1278330d", + "last_modified": 1676903765429 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "cookiePolicy", + "value": "accept" + } + ] + }, + "domains": [ + "pnas.org" + ], + "id": "a327ad8a-daa9-4d7c-abb5-0f3e3f3c59db", + "last_modified": 1676903765426 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optOut": [ + { + "name": "consent", + "value": "{\"Marketing\":false,\"created_time\":\"2023-02-14T12:27:52.910Z\"}" + } + ] + }, + "domains": [ + "science.org" + ], + "id": "67702151-14e2-41c7-af4b-1f22de8185a5", + "last_modified": 1676903765422 + }, + { + "click": {}, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "ks-cookie-consent", + "value": "allow" + } + ], + "optOut": [ + { + "name": "ks-cookie-consent", + "value": "deny" + } + ] + }, + "domains": [ + "roku.com" + ], + "id": "08513283-d50d-4a85-93fc-cd0568b09976", + "last_modified": 1676903765410 + }, + { + "click": { + "optIn": "button.gdpr-banner__accept", + "presence": "div.gdpr-banner" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "scmp.com" + ], + "id": "68aff357-d47c-4dc2-b7fa-27ac4b982257", + "last_modified": 1676903765406 + }, + { + "click": { + "optIn": "a.cc-dismiss", + "presence": "div.cc-window" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "braze.com" + ], + "id": "28bee54e-47d3-4192-8044-0bef6870a7c1", + "last_modified": 1676903765403 + }, + { + "click": { + "optIn": "a#cookie-allow-all", + "optOut": "a#cookie-necessary-only", + "presence": "div#cookiebanner" + }, + "schema": 1676900247175, + "cookies": {}, + "domains": [ + "adjust.com" + ], + "id": "ca37bf31-bf4e-4172-bf7e-91baca32c9e2", + "last_modified": 1676903765399 + }, + { + "click": { + "optIn": "button.css-hxv78t", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "rsvplive.ie" + ], + "id": "5a256bf7-29a7-485e-8305-fcafd7a52af9", + "last_modified": 1676903765394 + }, + { + "click": { + "optIn": "button.css-1euwp1t", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "enikos.gr" + ], + "id": "20e1f1c3-0914-42b4-9022-3ce80b79a480", + "last_modified": 1676903765387 + }, + { + "click": { + "optIn": "button.css-fz9f1h", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "dikaiologitika.gr" + ], + "id": "1c742d88-b3a7-43da-b205-3ba4afd92e63", + "last_modified": 1676903765376 + }, + { + "click": { + "optIn": "button.css-v43ltw", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "filmaffinity.com" + ], + "id": "ef8548ec-5dd9-4201-a075-a32893f09953", + "last_modified": 1676903765369 + }, + { + "click": { + "optIn": "button#consent_prompt_submit", + "presence": "div.consent_prompt_footer" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "argos.co.uk" + ], + "id": "88e79126-8072-4079-aa60-1d71d9ddb65b", + "last_modified": 1676903765366 + }, + { + "click": { + "optIn": "button.iubenda-cs-accept-btn", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "ilmessaggero.it" + ], + "id": "c839d2c3-ee29-4015-a990-8cfe9884a4c4", + "last_modified": 1676903765358 + }, + { + "click": { + "optIn": "button#btn-agree-all", + "presence": "div#rgpd" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "rtp.pt" + ], + "id": "46e07f9b-9ce4-4fea-8be7-48a089bdb8d0", + "last_modified": 1676903765354 + }, + { + "click": { + "optIn": "span.inline-block", + "presence": "div#notice-cookie-block" + }, + "schema": 1676900249813, + "cookies": { + "optIn": [ + { + "name": "user_allowed_save_cookie", + "value": "true" + } + ] + }, + "domains": [ + "altex.ro" + ], + "id": "32139cbb-7e13-4cec-ba54-7bfb4f46277c", + "last_modified": 1676903765351 + }, + { + "click": { + "optIn": "button.css-47sehv", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "timeanddate.com", + "livescience.com", + "cancan.ro", + "evz.ro", + "gandul.ro", + "kurir.rs", + "capital.ro" + ], + "id": "5a3bf87a-62c2-40b2-bfbb-b2dcf8c23f84", + "last_modified": 1676903765347 + }, + { + "click": { + "optIn": "button#kc-acceptAndHide", + "presence": "div#kconsent" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "k-ruoka.fi" + ], + "id": "282ff551-ce28-4b7f-9633-eaaa7ce89890", + "last_modified": 1676903765343 + }, + { + "click": { + "optIn": "button.css-tzlaik", + "presence": "div#qc-cmp2-ui" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "nit.pt" + ], + "id": "30542b9b-2225-4c22-bbd9-0e9d8f6273df", + "last_modified": 1676903765339 + }, + { + "click": { + "optIn": "a#cookies-agree", + "presence": "div.cookies-buttons" + }, + "schema": 1676900249813, + "cookies": { + "optOut": [ + { + "name": "cookiesPolicy", + "value": "10000" + } + ] + }, + "domains": [ + "elcorteingles.es" + ], + "id": "90b22cfd-00a8-4467-9334-96773066c2c1", + "last_modified": 1676903765336 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "ingatlan.com" + ], + "id": "009f2741-56cb-485e-af5c-3102f8e9cf55", + "last_modified": 1676903765328 + }, + { + "click": { + "optIn": "button.jad_cmp_paywall_button-cookies", + "presence": "div#didomi-host" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "purepeople.com" + ], + "id": "b933bc7f-27b7-44a7-9127-66f98c93ea8f", + "last_modified": 1676903765322 + }, + { + "click": { + "optIn": "button.css-1xqnplm", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "lifo.gr" + ], + "id": "4f86f2e9-ea7f-4ee7-a4d2-b2253a5f1c1d", + "last_modified": 1676903765318 + }, + { + "click": { + "optIn": "a.close-accept", + "presence": "div.woahbar" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "meteomedia.com" + ], + "id": "8e772dc2-be57-4cf6-ad51-574b2accf86c", + "last_modified": 1676903765311 + }, + { + "click": { + "optOut": "button.accept-button", + "presence": "div.gdpr-content" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "mozzartbet.com" + ], + "id": "b8b2cf50-d9cf-432b-947c-894350121024", + "last_modified": 1676903765308 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "optOut": "button.fc-cta-do-not-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1676900247175, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgbQkAPgbQkAEsABBITCjCgAAAAAH_AABCYAAAO9QD2F2K2kKEkfjSUeYAQBCujIEIBUAAAAEKBIAAAAUgQAgFIIAgABlACEAAAABAQAQCAgAQABAAAoICgACAAAAAAAAAQAAQQAABAAIAAAAAAAAEAQAAAAAQAAAAAAAhEhCAAQQAEIAAAAAAAAAAAAAAAAAABAAAAEAA%22%2C%221~%22%2C%22A83BB90B-53A8-400D-8DAA-F4EF44E9B970%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "ilmeteo.it" + ], + "id": "71d0715b-3e8e-4077-969e-7b7722f78910", + "last_modified": 1676903765304 + }, + { + "click": { + "optIn": "button.css-78i25x", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "mirror.co.uk" + ], + "id": "21593dac-f469-4206-87d9-044e6d69404b", + "last_modified": 1676903765300 + }, + { + "click": { + "optIn": "a.js-cookies-info-accept", + "optOut": "a.js-cookies-info-reject", + "presence": "div.cookies-info__buttons" + }, + "schema": 1676900248050, + "cookies": { + "optOut": [ + { + "name": "CBARIH", + "value": "1" + } + ] + }, + "domains": [ + "alza.cz" + ], + "id": "c7a067c4-a365-4497-a990-c042545dfd6c", + "last_modified": 1676903765296 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "femina.hu" + ], + "id": "d1b9f7af-26d3-4f1b-812c-9f8cf41da900", + "last_modified": 1676903765293 + }, + { + "click": { + "optIn": "a#truste-consent-button", + "optOut": "a#truste-consent-required2", + "presence": "div#truste-consent-buttons" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "poste.it" + ], + "id": "450e91f1-22ee-4718-9e98-05f831adfeff", + "last_modified": 1676903765289 + }, + { + "click": { + "optIn": "button#almacmp-modalConfirmBtn", + "presence": "div.almacmp-controls" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "kauppalehti.fi" + ], + "id": "abb44f4b-bc8a-49bf-8f8c-204fe33806e9", + "last_modified": 1676903765285 + }, + { + "click": { + "optIn": "button.css-1j8kkja", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "news247.gr" + ], + "id": "1b1bdbb4-089c-45dc-9d42-e87a1fa85882", + "last_modified": 1676903765281 + }, + { + "click": { + "optIn": "a#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "a#CybotCookiebotDialogBodyLevelButtonLevelOptinDeclineAll", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1676900248050, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%27aQlJVTwi41tewItmxlt/w7TRvv2UDwd39lOxJC/s1TG9dFvEWjL4jQ==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cver:1%2Cutc:1665481193123%2Cregion:%27ro%27}" + } + ] + }, + "domains": [ + "rip.ie" + ], + "id": "051ccd77-b9b3-4558-9c27-5293d8d735f4", + "last_modified": 1676903765278 + }, + { + "click": { + "optIn": "button.sfc-button--primary-white", + "presence": "div.sfc-col-lg-3" + }, + "schema": 1676900248050, + "cookies": { + "optOut": [ + { + "name": "RABO_PSL", + "value": "1" + } + ] + }, + "domains": [ + "rabobank.nl" + ], + "id": "fe8a3adb-ce3c-4dd6-83db-772e71674da3", + "last_modified": 1676903765267 + }, + { + "click": { + "optIn": "a.cmptxt_btn_yes", + "presence": "div.cmpboxbtns" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "informer.rs" + ], + "id": "27572d79-fc44-49f0-b271-bd44e76f3a3f", + "last_modified": 1676903765263 + }, + { + "click": { + "optIn": "button.eu-cookie-compliance-default-button", + "presence": "div#popup-buttons" + }, + "schema": 1676900248050, + "cookies": { + "optIn": [ + { + "name": "cookie-agreed", + "value": "2" + } + ], + "optOut": [] + }, + "domains": [ + "eluniversal.com.mx" + ], + "id": "34e9d8ee-5e8d-462c-ab39-d25260124cf6", + "last_modified": 1676903765259 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "athensmagazine.gr" + ], + "id": "c0e69dcc-9602-4d7a-89b8-a4a06f0432ca", + "last_modified": 1676903765255 + }, + { + "click": { + "optIn": "button.cookie-banner-lgpd_accept-button", + "presence": "div#cookie-banner-lgpd" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "globo.com" + ], + "id": "90b8eda3-d964-4301-94fc-646e92d33d56", + "last_modified": 1676903765251 + }, + { + "click": { + "optIn": "button.css-1x99van", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "express.co.uk" + ], + "id": "edaa2c50-1fbc-4287-8d15-68af2b5dc1bf", + "last_modified": 1676903765248 + }, + { + "click": { + "optIn": "button.css-1lgi3ta", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "pronews.gr" + ], + "id": "9e16f526-4d46-4c2e-a3ed-d3b43d67b4fb", + "last_modified": 1676903765244 + }, + { + "click": { + "optIn": "button#almacmp-modalConfirmBtn", + "presence": "div.almacmp-controls" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "nettiauto.com" + ], + "id": "7e5d072f-ea5a-4a82-9ea4-3bbf06d5cc8d", + "last_modified": 1676903765237 + }, + { + "click": { + "optIn": "button.js-accept", + "presence": "div.cookie-banner-buttons" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "emag.hu" + ], + "id": "e78a3fdb-bcba-402c-b2da-63994aba1b30", + "last_modified": 1676903765229 + }, + { + "click": { + "optIn": "button.css-b2defl", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "ilgiornale.it" + ], + "id": "362b64e3-c12d-4d5e-9c8e-169df9782b28", + "last_modified": 1676903765225 + }, + { + "click": {}, + "schema": 1676900248923, + "cookies": { + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%27hLskZi6GtaCNeWwvKfJCnNdwijLGPiuqz0obMpsw2C4o6inSM80MLQ==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1675088694646%2Ciab2:%27CPmZlUAPmZlUACGABBENC1CgAAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAAA%27%2Cgacm:%271~%27%2Cregion:%27ro%27}" + } + ] + }, + "domains": [ + "worten.pt" + ], + "id": "905b5c20-000d-4b7b-a8d6-d84d2311dfdf", + "last_modified": 1676903765222 + }, + { + "click": { + "optIn": "button.css-14ubilm", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "tradera.com" + ], + "id": "deca4013-d55c-424b-9a46-2df8139fd415", + "last_modified": 1676903765218 + }, + { + "click": { + "optIn": "a.cmptxt_btn_yes", + "presence": "div.cmpboxbtns" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "alo.rs" + ], + "id": "4dce4250-5d45-4cf8-bb0c-f485de812859", + "last_modified": 1676903765214 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1676900248923, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPg1oEAPg1oEAEsABCBGClCgAAAAAAAAAAIwAAAOhQD2F2K2kKEkfjSUWYAQBCujKEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgAAAAAAAAAAAAAAQQAABAAIAAAAAAAAEAQAAIAAQAAAAAAABEhCAAQQAEAAAAAAAAAAAAAAAAAAABAAA%22%2C%221~%22%2C%22A7193324-AE65-4BF2-B769-43CD7980AA9B%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "mobile.bg" + ], + "id": "3708e6f7-9237-4ef1-8aca-bec748cc676e", + "last_modified": 1676903765210 + }, + { + "click": { + "optIn": "button.css-mn27n0", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "observador.pt" + ], + "id": "c49fa28b-a67c-42b8-a4e5-4d48d34fba04", + "last_modified": 1676903765207 + }, + { + "click": { + "optIn": "div.boton_cookies", + "presence": "div#popup_cookies" + }, + "schema": 1676900248923, + "cookies": { + "optIn": [ + { + "name": "accept_cookies", + "value": "ok" + } + ] + }, + "domains": [ + "aemet.es" + ], + "id": "60ea2eca-10af-47d1-a240-cbdf44519e9d", + "last_modified": 1676903765203 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "napi.hu" + ], + "id": "2b12c85e-4ec0-4a3d-9cbe-9892db3f5850", + "last_modified": 1676903765199 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons" + }, + "schema": 1676900248923, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPhCz0APhCz0AEsABCFIClCgAAAAAAAAAApAAAAOhQD2F2K2kKEkfjSUWYAQBCujIEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgAAAAAAAAAAAAAAQQAABAAIAAAAAAAAEAQAAIAAQAAAAAAABEhCAAQQAEAAAAAAAAAAAAAAAAAAABAAA.YAAAAAAAAAA%22%2C%221~%22%2C%22EE7DBC34-5A3A-4088-90DB-A679D6C3A244%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "telsu.fi" + ], + "id": "0d950828-684c-48f3-8b0c-3c9523c05fb6", + "last_modified": 1676903765196 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "gossip-tv.gr" + ], + "id": "ba9c17cd-19aa-4fe3-ac0c-9c3055133c5b", + "last_modified": 1676903765192 + }, + { + "click": { + "optIn": "button#pt-accept-all", + "optOut": "div#pt-close", + "presence": "div#pubtech-cmp" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "liberoquotidiano.it" + ], + "id": "531d8b09-8fce-4019-9f96-31d5add26a43", + "last_modified": 1676903765188 + }, + { + "click": { + "optIn": "button.css-19ukivv", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "vi.nl" + ], + "id": "5026561f-ffec-437e-bad8-820ac9d55659", + "last_modified": 1676903765185 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons" + }, + "schema": 1676900248923, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPhCz0APhCz0AEsABCBGClCgAAAAAAAAAAIwAAAQvQD-F2K2lKGkfjaUeYIQBKujOEIhUBgEAEKBIQAEA0gQAgFIIAgADlgCUAAAABARCQCAgAQABAAAoICgAAAAACAAAABAASQQAABAAIAAICAABAUBQAAIAARAAgAAMBBEhDAASSAECAAAAAACAAIAAAAAAAABAAAAAAAAAIAAAAAAAAAAAAAEAAA.YAAAAAAAAAA%22%2C%221~%22%2C%22924A9049-E66F-4E81-92F6-E6AE4366B6B6%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "news.bg" + ], + "id": "1a473c52-96a0-4d12-a8cd-d12d1cc8edca", + "last_modified": 1676903765181 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "zougla.gr" + ], + "id": "0587a3cf-e084-40bf-b01a-2586c2313f3b", + "last_modified": 1676903765177 + }, + { + "click": { + "optIn": "button.css-1al1vdb", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "calciomercato.com" + ], + "id": "68c6fe41-fb1d-41ed-a8c6-55fde975f0ab", + "last_modified": 1676903765174 + }, + { + "click": { + "optIn": "a#consent_prompt_submit", + "presence": "div#__tealiumGDPRecModal" + }, + "schema": 1676900248923, + "cookies": { + "optOut": [ + { + "name": "CONSENTMGR", + "value": "c1:0%7Cc6:0%7Cts:1666183304450%7Cconsent:false" + } + ] + }, + "domains": [ + "telenor.no" + ], + "id": "4ed03d1e-155c-45fa-a8c9-fbccc9ac0f33", + "last_modified": 1676903765170 + }, + { + "click": { + "optIn": "button#accept-privacy-policies", + "presence": "div.emb38ba0" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "walmart.ca" + ], + "id": "0485cf43-5df0-41aa-83da-92cd3b82d6f7", + "last_modified": 1676903765166 + }, + { + "click": { + "optIn": "button.css-1hzdrx2", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "newsbeast.gr" + ], + "id": "b9fd5871-876d-4931-9f76-dd695e613919", + "last_modified": 1676903765162 + }, + { + "click": { + "optIn": "button.rodo-popup-agree", + "presence": "div.rodo-popup-buttons" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "pomponik.pl" + ], + "id": "397fdd1a-d6b7-4bab-a105-3f1f36f755ff", + "last_modified": 1676903765159 + }, + { + "click": { + "optIn": "button.accept-cookie-button", + "presence": "div.cookie-banner-content" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "avanza.se" + ], + "id": "803fbd5e-3734-4cf6-a878-5ffe24ec6809", + "last_modified": 1676903765155 + }, + { + "click": { + "optIn": "button#consent_prompt_submit", + "presence": "div#__tealiumGDPRecModal" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "post.ch" + ], + "id": "c40f3982-0372-4cdd-8aea-c150afd8328e", + "last_modified": 1676903765151 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#buttons" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "zerozero.pt" + ], + "id": "1a37cced-b12a-44d9-a576-d1b259312471", + "last_modified": 1676903765145 + }, + { + "click": { + "optIn": "input.btn-secondary", + "presence": "div.stampenCookieContainer" + }, + "schema": 1676900248923, + "cookies": {}, + "domains": [ + "gp.se" + ], + "id": "e4e59ff1-b18c-453b-a4e0-6c0ec4008d81", + "last_modified": 1676903765138 + }, + { + "click": { + "optIn": "a#wt-cli-accept-all-btn", + "presence": "div.cli-bar-container" + }, + "schema": 1676900249813, + "cookies": { + "optIn": [ + { + "name": "viewed_cookie_policy", + "value": "yes" + } + ] + }, + "domains": [ + "katalozi.net" + ], + "id": "caddc16b-c8ad-404a-bd29-6383977695cb", + "last_modified": 1676903765135 + }, + { + "click": { + "optIn": "button.rounded-button--tertiary", + "presence": "div.legal-consent" + }, + "schema": 1676900249813, + "cookies": { + "optOut": [ + { + "name": "mal_consent_gdpr_remarketing", + "value": "f" + }, + { + "name": "mal_consent_gdpr_personalization", + "value": "f" + } + ] + }, + "domains": [ + "mall.cz" + ], + "id": "6bc9ceec-b225-4284-b924-589c3ff4f249", + "last_modified": 1676903765131 + }, + { + "click": { + "optIn": "a.wscrOk", + "optOut": "a.wscrOk2", + "presence": "div#CookieReportsPanel" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "nordea.fi" + ], + "id": "cf282f72-5333-48a8-9097-cbc89ea26634", + "last_modified": 1676903765127 + }, + { + "click": { + "optIn": "button#grantPermissionButton", + "presence": "div#cookie-bar" + }, + "schema": 1676900249813, + "cookies": { + "optOut": [ + { + "name": "CookiePermissionInfo", + "value": "%7B%22LastModifiedDate%22%3A%22%5C%2FDate(1666257491849)%5C%2F%22%2C%22ExpirationDate%22%3A%22%5C%2FDate(1697793491849)%5C%2F%22%2C%22Allow%22%3Afalse%2C%22CategoryPermission%22%3A%5B%7B%22Category%22%3A%22Cat.8%22%2C%22Permission%22%3Atrue%7D%2C%7B%22Category%22%3A%22Cat.9%22%2C%22Permission%22%3Atrue%7D%2C%7B%22Category%22%3A%22Cat.10%22%2C%22Permission%22%3Afalse%7D%2C%7B%22Category%22%3A%22Cat.11%22%2C%22Permission%22%3Afalse%7D%2C%7B%22Category%22%3A%22Cat.12%22%2C%22Permission%22%3Afalse%7D%5D%7D" + } + ] + }, + "domains": [ + "postnl.nl" + ], + "id": "e4a9e084-a37e-4f10-969c-c872f578dd15", + "last_modified": 1676903765124 + }, + { + "click": { + "optIn": "button.pr-riqlv6", + "optOut": "button.pr-1l8klmj", + "presence": "div#consent" + }, + "schema": 1676900249813, + "cookies": { + "optOut": [ + { + "name": "data_consent", + "value": "1.1.0.0.0.1675177081479.Default" + } + ] + }, + "domains": [ + "pricerunner.dk" + ], + "id": "be039e17-f095-46f4-a809-c00699799db8", + "last_modified": 1676903765114 + }, + { + "click": { + "optIn": "div.tvp-covl__ab", + "presence": "div#ip" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "tvp.pl" + ], + "id": "fb333123-4f00-4b45-acf9-21cb038c76ba", + "last_modified": 1676903765107 + }, + { + "click": { + "optIn": "button.css-1g5s5vy", + "presence": "div#qc-cmp2-ui" + }, + "schema": 1676900249813, + "cookies": {}, + "domains": [ + "meo.pt" + ], + "id": "992a91e5-9d29-411a-a8e6-e689fb75c2b7", + "last_modified": 1676903765103 + }, + { + "click": { + "optIn": "span#banner-cookie--button", + "presence": "div#banner-cookie" + }, + "schema": 1676900247175, + "cookies": { + "optIn": [ + { + "name": "cookie_banner", + "value": "1" + } + ] + }, + "domains": [ + "bitly.com" + ], + "id": "84a097a3-5cd1-448a-afcf-4be950bc5756", + "last_modified": 1676903765100 + }, + { + "click": { + "optIn": "button.ch2-allow-all-btn", + "optOut": "button.ch2-deny-all-btn", + "presence": "div.ch2-container" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "semrush.com" + ], + "id": "2f54e492-0f33-4496-ab08-99af50bf6f22", + "last_modified": 1676903765096 + }, + { + "click": { + "optIn": "button.btn-success", + "presence": "div.vue-component" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "remove.bg" + ], + "id": "86bc6c6f-c082-466f-ac77-994a2296fbb0", + "last_modified": 1676903765093 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyButtonAccept", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "sectigo.com" + ], + "id": "7078621c-2701-40e0-83f1-109c8ea0f82f", + "last_modified": 1676903765089 + }, + { + "click": { + "optOut": "button.eu-cookie-compliance-save-preferences-button", + "presence": "div.eu-cookie-compliance-banner" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "cam.ac.uk" + ], + "id": "f5f827d0-60c9-4fe6-af97-a901d0d96ddc", + "last_modified": 1676903765081 + }, + { + "click": { + "optIn": "button#_evidon-accept-button", + "optOut": "button#_evidon-decline-button", + "presence": "div#_evidon-message" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "playstation.com" + ], + "id": "23df4915-5954-41ea-8d5c-e76a1d98e70b", + "last_modified": 1676903765077 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "presence": "div#consent_blackbar" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "fortune.com" + ], + "id": "c0908337-da46-4155-8178-43e4a67693ec", + "last_modified": 1676903765069 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "presence": "div#truste-consent-track" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "mi.com" + ], + "id": "30a2b81a-b5a6-4c49-818d-de34ecddafbe", + "last_modified": 1676903765060 + }, + { + "click": { + "optIn": "button.css-1k47zha", + "presence": "div#qc-cmp2-ui" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "9gag.com" + ], + "id": "6c246e7d-2a12-4b13-94b4-d2908cd64aad", + "last_modified": 1676903765055 + }, + { + "click": { + "optIn": "a.a8c-cookie-banner__accept-all-button", + "presence": "form.a8c-cookie-banner" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "akismet.com" + ], + "id": "9a5038c6-31da-40e5-94d8-6eea3ecfa9ec", + "last_modified": 1676903765052 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "button#truste-consent-required", + "presence": "div#truste-consent-track" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "box.com", + "ea.com" + ], + "id": "b8e927a8-e0fc-4e7b-ad60-edca983accd3", + "last_modified": 1676903765048 + }, + { + "click": { + "optIn": "button.eu-cookie-compliance-default-button", + "presence": "div.cookies" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "unesco.org" + ], + "id": "689fe5d6-7792-4475-98fc-71aa1715d0d9", + "last_modified": 1676903765044 + }, + { + "click": { + "optIn": "div.zbc-cta-accept", + "presence": "div.zbottom-cookie-container" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "zoho.com" + ], + "id": "3656dd64-5840-4918-adc8-caa723503f20", + "last_modified": 1676903765040 + }, + { + "click": { + "optIn": "button.css-1k3tyyb", + "presence": "div#qc-cmp2-ui" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "theatlantic.com" + ], + "id": "43f34378-73c3-4851-a948-e073908edddd", + "last_modified": 1676903765033 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "button#truste-consent-required", + "presence": "div#truste-consent-content" + }, + "schema": 1676900250688, + "cookies": { + "optOut": [ + { + "name": "notice_gdpr_prefs", + "value": "0:" + } + ] + }, + "domains": [ + "squarespace.com" + ], + "id": "b17a2376-ae8b-40de-9b6f-61efdf25f862", + "last_modified": 1676903765030 + }, + { + "click": { + "optIn": "button.osano-cm-accept-all", + "presence": "div.osano-cm-window" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "scribd.com", + "chicagotribune.com" + ], + "id": "f6f48ce2-0487-4003-b1c7-dfcd37def8d7", + "last_modified": 1676903765026 + }, + { + "click": { + "optIn": "button.cc-banner__button-accept", + "presence": "section.cc-banner" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "springer.com", + "nature.com" + ], + "id": "9ab30eae-9592-47b1-b46e-7640c4316f14", + "last_modified": 1676903765022 + }, + { + "click": { + "optIn": "button#cookie-accept-all-secondary", + "optOut": "button.CookieConsentSecondary__RejectAllButton-sc-12do1ry-2", + "presence": "div.ModalInner__Backdrop-sc-1ghkydj-1" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "nordnet.no" + ], + "id": "9ea83ecf-3e82-4976-80e7-86872d7e4aeb", + "last_modified": 1676903765019 + }, + { + "click": { + "optIn": "button#accept_all_cookies", + "presence": "section#cookie_consent" + }, + "schema": 1676900250688, + "cookies": {}, + "domains": [ + "telekom.hu" + ], + "id": "80d51057-06fe-4469-be50-0438c9165020", + "last_modified": 1676903765015 + }, + { + "click": { + "optIn": "button.primary", + "presence": "div#camus-cookie-disclaimer" + }, + "schema": 1676900250688, + "cookies": { + "optIn": [ + { + "name": "accepts-cookies", + "value": "true" + } + ] + }, + "domains": [ + "milenio.com" + ], + "id": "befe1d72-f33a-4e63-be43-236bedc3b49a", + "last_modified": 1676903765008 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900248050, + "cookies": {}, + "domains": [ + "newsit.gr", + "protothema.gr", + "newsbomb.gr", + "youweekly.gr", + "vrisko.gr", + "index.hu", + "meteo.gr", + "fantacalcio.it" + ], + "id": "25dd408a-0a98-4e93-992c-abd320255cd1", + "last_modified": 1676903764958 + }, + { + "click": { + "optIn": "button.css-47sehv", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "pik.bg", + "prosport.ro", + "sciencedaily.com" + ], + "id": "d96f380c-c76f-47d8-a1f7-bd4063792ade", + "last_modified": 1676903764933 + }, + { + "click": { + "optIn": "button.css-1lgi3ta", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "sdna.gr", + "makeleio.gr" + ], + "id": "af566db1-982c-4837-8f0b-96a341702520", + "last_modified": 1676903764929 + }, + { + "click": { + "optIn": "div.gdpr-trigger", + "presence": "div#gdpr" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "digisport.ro" + ], + "id": "91a1519c-51f4-4d9a-bea3-06a2fff7d892", + "last_modified": 1676903764926 + }, + { + "click": { + "optIn": "button.css-1tfx6ee", + "presence": "div#qc-cmp2-container" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "startlap.hu", + "nosalty.hu", + "24.hu", + "nlc.hu" + ], + "id": "4fc95ff9-b785-49ee-8a8c-e789665a1643", + "last_modified": 1676903764922 + }, + { + "click": { + "optIn": "button.NT2yCg", + "presence": "div.e4Qmjw" + }, + "schema": 1676900251552, + "cookies": {}, + "domains": [ + "canva.com" + ], + "id": "5a03b949-b86c-4ee5-9824-97fb59849cb8", + "last_modified": 1676903764913 + }, + { + "click": { + "optIn": "button#privacy-cp-wall-accept", + "presence": "div.privacy-cp-wall" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "corriere.it" + ], + "id": "b90700d6-32a6-44e2-adf4-46acd54089c9", + "last_modified": 1674560738379 + }, + { + "click": { + "optIn": "button#privacy-cp-wall-accept", + "presence": "div.privacy-cp-wall" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "gazzetta.it" + ], + "id": "32be6ed7-749d-448f-975d-10b4d1e6e482", + "last_modified": 1674560738375 + }, + { + "click": { + "optIn": "button.css-1u05hh5", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "researchgate.net" + ], + "id": "e060a13e-e0ae-4081-8d9f-29a0c17bec79", + "last_modified": 1674560738368 + }, + { + "click": { + "optIn": "button.primary_2xk2l", + "presence": "div.container_1gQfi" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "dailymail.co.uk" + ], + "id": "4d4b2924-6e28-4d39-9a02-e31690746ab2", + "last_modified": 1674560738364 + }, + { + "click": { + "optIn": "button.osano-cm-accept-all", + "presence": "div.osano-cm-dialog__buttons" + }, + "schema": 1674146203493, + "cookies": { + "optOut": [ + { + "name": "osano_consentmanager", + "value": "29z_AY1nTFjK9XMxsOgqw44fXSaxiin_dEBjbDS7Xgl2GM5AONcjcMtZVKd_7n7XCTO5hym727ANlN292apKnDkgIe8dhCSSZpmDhDN7guy_H9a0nFHng1IsTphnnPGMG-iLZ6sS9OmdxmAq8K_mehLsgQWFmfhi5K07-UmLlGoVGx19IxTDiqf-aJmp5dbLV8kxLYjgbTFnTJ2JWUcwJYR57TX9iezs-BprQopNCs5CGWW9NOFrv-uhWhwTsKNLygKH7cMwdnp7RHTSX41-FCLSh10=" + } + ] + }, + "domains": [ + "wiley.com" + ], + "id": "7e45fd5f-63b5-4090-9120-b467b0252358", + "last_modified": 1674560738354 + }, + { + "click": { + "optIn": "div.gdpr-agree-btn", + "optOut": "div.gdpr-reject-btn", + "presence": "div#GDPR-cookies-notice" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "alibaba.com" + ], + "id": "c35bdda6-611d-4742-8f6e-4aebb574e675", + "last_modified": 1674560738349 + }, + { + "click": { + "optIn": "button.kcLcLu", + "presence": "div.gmjWml" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "turbopages.org" + ], + "id": "6ecb9b79-3666-479c-8798-5cf46702799f", + "last_modified": 1674560738346 + }, + { + "click": { + "optIn": "span.nrk-sr", + "presence": "div.nrk-masthead__info-banner--cookie" + }, + "schema": 1674146203493, + "cookies": { + "optIn": [ + { + "name": "nrkno-cookie-information", + "value": "1" + } + ] + }, + "domains": [ + "nrk.no" + ], + "id": "15cdab7d-a018-4c88-9352-5eacd4c30d1d", + "last_modified": 1674560738342 + }, + { + "click": { + "optIn": "button.css-1v69bou", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "sapo.pt" + ], + "id": "f29b0658-24ab-40fd-92fb-11850b2aaab5", + "last_modified": 1674560738339 + }, + { + "click": {}, + "schema": 1674146203493, + "cookies": { + "optOut": [ + { + "name": "yleconsent", + "value": "v1|" + } + ] + }, + "domains": [ + "yle.fi" + ], + "id": "9a727363-cec6-4641-99ab-80550b89da5b", + "last_modified": 1674560738332 + }, + { + "click": { + "optIn": "div.ok", + "presence": "div#checkUPcookies" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "pravda.com.ua" + ], + "id": "7498f52b-4273-4cfb-a22a-c9df9b84adc2", + "last_modified": 1674560738328 + }, + { + "click": { + "optIn": "button.css-w0lt3s", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "iefimerida.gr" + ], + "id": "aeff05cc-c287-4596-89b6-3559eced1133", + "last_modified": 1674560738325 + }, + { + "click": { + "optIn": "button.css-wum6af", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "record.pt" + ], + "id": "8b2a32a7-5a38-4ffc-abc5-d5827e16008b", + "last_modified": 1674560738318 + }, + { + "click": { + "optIn": "div.gdpr-trigger", + "presence": "div#gdpr" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "digi24.ro" + ], + "id": "55ed573b-2cf3-42a3-8010-d397b161568b", + "last_modified": 1674560738315 + }, + { + "click": { + "optIn": "button.CookieConsent__primaryButton___Th47k", + "presence": "div.CookieConsent__root___3GjU8" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "svt.se" + ], + "id": "87b229aa-567e-4549-895f-b6c0065dfdc5", + "last_modified": 1674560738311 + }, + { + "click": { + "optIn": "button.css-1rx6u69", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "gazzetta.gr" + ], + "id": "7eed4367-8f21-4359-aae9-cf9017ae5efc", + "last_modified": 1674560738308 + }, + { + "click": { + "optIn": "button.css-k9c0va", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674146203493, + "cookies": {}, + "domains": [ + "foreca.fi" + ], + "id": "f35220db-4382-446a-af55-063f309ccf5a", + "last_modified": 1674560738304 + }, + { + "click": { + "optIn": "button.css-1tdle7a", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "iol.pt" + ], + "id": "290d1140-dc1b-4d97-85dd-d970753ae441", + "last_modified": 1674560738290 + }, + { + "click": { + "optIn": "button.allow-all-cookies-button", + "optOut": "button.mt-2", + "presence": "div.consent-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "ilmatieteenlaitos.fi" + ], + "id": "0cfab897-c0f5-4415-b5a1-bac258d1c764", + "last_modified": 1674560738287 + }, + { + "click": { + "optIn": "button.css-kc1iqc", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "telex.hu" + ], + "id": "24df6217-9ed9-4a67-9811-ff9c8f8faaf2", + "last_modified": 1674560738283 + }, + { + "click": { + "optIn": "button#c-p-bn", + "presence": "div#cc_div" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "shmu.sk" + ], + "id": "43135a56-c361-4d88-82dd-cf17a077a6a5", + "last_modified": 1674560738280 + }, + { + "click": { + "optIn": "button#cookieBtn", + "presence": "div.c-alert__box" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "tsn.ua" + ], + "id": "04608cf9-0f96-433b-bc26-6aa9f5ebdfc8", + "last_modified": 1674560738276 + }, + { + "click": { + "optIn": "button.css-8zhqum", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "hvg.hu" + ], + "id": "83758fdc-5bdc-4d8b-b783-840f16d30f1c", + "last_modified": 1674560738269 + }, + { + "click": { + "optIn": "button.css-wum6af", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "cmjornal.pt" + ], + "id": "e400d28c-7f73-4449-8749-5f96509eb688", + "last_modified": 1674560738266 + }, + { + "click": { + "optIn": "button.css-1ruupc0", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "444.hu" + ], + "id": "eaf913b1-b628-4a57-9a1c-65bcf27f33a9", + "last_modified": 1674560738259 + }, + { + "click": { + "optIn": "i.fa-times", + "presence": "div.cookieWarning" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "kukaj.io" + ], + "id": "8f251e48-f45b-4051-bc53-0851d0d411f5", + "last_modified": 1674560738255 + }, + { + "click": { + "optIn": "button.notificationButton", + "presence": "div.polopolyNotification" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "smhi.se" + ], + "id": "27d9194f-f908-4cb8-8f57-ddb899a06a4e", + "last_modified": 1674560738252 + }, + { + "click": { + "optIn": "button.css-fxmqu", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "publico.pt" + ], + "id": "36dc7cc5-a043-4259-a9bd-07913ff208bb", + "last_modified": 1674560738245 + }, + { + "click": { + "optIn": "button.js_cookie-monster-agree", + "optOut": "a.js_cookie-monster-disagree", + "presence": "div#js_cookie-monster" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "ceneo.pl" + ], + "id": "35db03d9-02c9-4558-a31b-aa4a20d6dbaa", + "last_modified": 1674560738241 + }, + { + "click": { + "optIn": "a.js-accept-cookies", + "presence": "div.jsGdprNoticeHolder" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "polovniautomobili.com" + ], + "id": "9a6465c6-7955-4dff-9483-70635ba5fbda", + "last_modified": 1674560738230 + }, + { + "click": { + "optOut": "button.css-zpqvhj ", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521303759, + "cookies": {}, + "domains": [ + "sorozatbarat.club" + ], + "id": "fbff28bd-84e5-4f82-8023-2f00d772e7e8", + "last_modified": 1674560738226 + }, + { + "click": { + "optIn": "button.RMlTST", + "optOut": "button.WEzN4V", + "presence": "div.aspXfg" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "wix.com" + ], + "id": "aa245048-507c-11ed-bdc3-0242ac120002", + "last_modified": 1674560738215 + }, + { + "click": { + "optIn": "button.submitAll", + "presence": "div.submitButton" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%27D1AamT1V80siZwfIuWr5SREHguKw3sySW3+Q+Nb2mPwfRB9so2m1Sg==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cver:5%2Cutc:1664957253372%2Cregion:%27ro%27}" + } + ] + }, + "domains": [ + "dr.dk" + ], + "id": "9def1218-9201-4b49-bf69-ea203aeab2b3", + "last_modified": 1674560738211 + }, + { + "click": { + "optIn": "button.css-1wjnr64", + "presence": "div.qc-cmp2-container" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "idokep.hu" + ], + "id": "1dc10ed2-800f-484c-9df1-6f99d8cd2584", + "last_modified": 1674560738204 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-choice-dialog" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgbQkAPgbQkAEsABCHUCjCoAP_AAH_AAA6gHQpB7D7FbSFCyP55aLsAMAhXRkCEAqQAAASFAmABQAKQIBQCkkAQFAygBCACAAAgIAJBAQIMCAgACUEBQABAAAEEAAAABAAIIAAAgAEAAAAIAAACAIAAEAAIQAAAEAAAmQhAAIIACAAABAAAAAAAAAAAAAAAAgdCgHsLsVtIUJI_GkoswAgCFdGQIQCoAAAAIUCQAAAApAgBAKQQBAADKAEIAAAACAgAgEBAAgACAABQQFAAEAAAAAAAAAAAAggAACAAQAAAAAAAAIAgAAQAAhAAAAAAACJCEAAggAIAAAAAAAAAAAAAAAAAAACAAA.cqAADVgAAAA%22%2C%221~2052.2072.70.89.93.108.122.149.2202.162.167.196.2253.241.2299.259.2331.2357.311.317.323.2373.338.358.2415.415.440.449.2506.2526.482.486.494.495.2568.2571.2575.540.574.2624.2677.2707.817.827.2898.864.981.1031.1048.1051.1067.1095.1097.1127.1171.1201.1205.1211.1276.1301.1365.1415.1449.1570.1577.1616.1651.1716.1753.1765.1870.1878.1889.1917.1958.2012%22%2C%228BB76B67-7925-4F57-BFC5-31C47ABF23A9%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "origo.hu" + ], + "id": "f236d4c4-1f2f-4b62-a4ec-5d9388d9150e", + "last_modified": 1674560738200 + }, + { + "click": { + "optIn": "button.rodo-popup-agree", + "presence": "div.rodo-popup-buttons" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "interia.pl" + ], + "id": "ef9bacb9-d248-4129-baed-354a45277750", + "last_modified": 1674560738197 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialogBodyButtons" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%27QmpmezEASR/DS4/lr1mawVaNO945EW6sk0Hi0cjL1CFS1A36t8Os1Q==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cver:3%2Cutc:1665042549753%2Ciab2:%27CPgbQkAPgbQkACGABBENCjCgAAAAAE_AAAAAAAASCAJMNW4gC7EscCbQMIoEQIwrCQqgUAEFAMLRAYAODgp2VgE-sIEACAUARgRAhxBRgQCAAACAJCIAJAiwQCIAiAQAAgARCIQAEDAIKACwMAgABANAxRCgAECQgyICIpTAgIgSCAlsqEEoK5DTCAOssAKDRGxUACJAABSAAJCwcAwRICViwQJMUb5ACMEKAUSoVoAA.YAAAAAAAAAAA%27%2Cgacm:%271~%27%2Cregion:%27ro%27}" + } + ] + }, + "domains": [ + "ekstrabladet.dk" + ], + "id": "7c2c6827-7d60-49a5-b4c1-877ba7fcd2e7", + "last_modified": 1674560738189 + }, + { + "click": { + "optIn": "button.iubenda-cs-accept-btn", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "repubblica.it" + ], + "id": "5c862324-6a7c-43d8-ae34-47c5e7d355f6", + "last_modified": 1674560738185 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-choice-dialog" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgbQkAPgbQkAEsABCBGCjCgAAAAAAAAAAIwAAAPaQD2F2K2kKEkfjaUWYIQBCujKEIBUBAEAEKBIAAAAUgQAgFIIAgABlACEAAAABAQAQCAgAQABAAAoICgACAAAAAQAAAAAAQQAABAAIAAACAAAAEAQAAAAAQAAAAAAABEhCAAQSQEAAAAAAAAAAAAAAAAAAABAAAAAAAAAIAA.YAAAAAAAAAA%22%2C%221~%22%2C%226C13DA30-0357-4AD8-8594-DBD146A8DF46%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "blitz.bg" + ], + "id": "7850dc20-f121-417b-9eb0-d91a0a8d6a8c", + "last_modified": 1674560738181 + }, + { + "click": { + "optIn": "button.fc-primary-button", + "presence": "div.fc-footer-buttons" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgejgAPgejgAEsABCBGCkCgAAAAAAAAAAIwAAAOhQD2F2K2kKEkfjSUWYAQBCujKEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgAAAAAAAAAAAAAAQQAABAAIAAAAAAAAEAQAAIAAQAAAAAAABEhCAAQQAEAAAAAAAAAAAAAAAAAAABAAA%22%2C%221~%22%2C%2232D9A01E-9678-40B0-BD1B-AD0CDC174758%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "fakti.bg" + ], + "id": "abc3a8c3-6a15-48a6-83eb-4f7762df9270", + "last_modified": 1674560738175 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgocUAPgocUAEsABCFICkCgAAAAAAAAAApAAAAQWQD2F2q2kKEkfjSUWYgURCurIEIhcAAAAEKBoAAAAUgQAgFIMIgABlACEAAAABAQAQCAgAQABAAAoICgAAAAAAAAAAAAAAQQACBAAIAAAAAAAAEAQAAoAAQAAAAAAABEhCAAQQAEIAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAIAAA.YAAAAAAAAAA%22%2C%221~%22%2C%22AC745442-DC8E-42A9-AAB5-970367496CBA%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "seiska.fi" + ], + "id": "57d6c05c-44b6-4e02-91b6-ef91cca6a311", + "last_modified": 1674560738152 + }, + { + "click": { + "optIn": "button.css-hxv78t", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "irishmirror.ie" + ], + "id": "3cab2d97-d9eb-4254-97aa-441ad90ada69", + "last_modified": 1674560738146 + }, + { + "click": { + "optIn": "a.cmptxt_btn_yes", + "presence": "div.cmpboxinner" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "novosti.rs" + ], + "id": "53f117b7-5d9d-4387-8507-95fb64a4da86", + "last_modified": 1674560738136 + }, + { + "click": { + "optIn": "button.fc-primary-button", + "presence": "div.fc-footer-buttons" + }, + "schema": 1674521305403, + "cookies": { + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgocUAPgocUAEsABCBGCkCgAAAAAAAAAAIwAAAOhQD2F2K2kKEkfjSUWYAQBCujKEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgAAAAAAAAAAAAAAQQAABAAIAAAAAAAAEAQAAIAAQAAAAAAABEhCAAQQAEAAAAAAAAAAAAAAAAAAABAAA%22%2C%221~%22%2C%222259601D-A3C8-4B02-80F2-AF6ADF73E293%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "bazar.bg" + ], + "id": "a7cbcc28-fa3f-46cc-9010-613e5031549b", + "last_modified": 1674560738130 + }, + { + "click": { + "optIn": "button#almacmp-modalConfirmBtn", + "presence": "div.almacmp-controls" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "etuovi.com" + ], + "id": "a2cc4223-39e8-4e02-804b-fa9eb4a4ce65", + "last_modified": 1674560738126 + }, + { + "click": { + "optIn": "button.css-1tydlop", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "noticiasaominuto.com" + ], + "id": "cbd5d112-8190-40d1-ad4a-6e8f6aa17c5a", + "last_modified": 1674560738119 + }, + { + "click": { + "optIn": "button.css-47sehv", + "presence": "div#qc-cmp2-container" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "telegraf.rs" + ], + "id": "6785afba-648d-46cf-8633-3d9c433c79ff", + "last_modified": 1674560738115 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1674521305403, + "cookies": { + "optOut": [ + { + "name": "CookieConsent", + "value": "{stamp:%27hd4jdgROnfr2iNroDX0BfGjqyNhsMI7fLXjrkfZEp9hRGFDoFPyv7w==%27%2Cnecessary:true%2Cpreferences:false%2Cstatistics:false%2Cmarketing:false%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1674227765106%2Cregion:%27ro%27}" + } + ] + }, + "domains": [ + "profesia.sk" + ], + "id": "346b2412-6aef-4eea-b2d5-8b74b4917f4a", + "last_modified": 1674560738112 + }, + { + "click": { + "optIn": "button.css-1ysvf99", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "flashback.org" + ], + "id": "14d5418a-4df6-4dfe-9c81-fd07669e0f9c", + "last_modified": 1674560738108 + }, + { + "click": { + "optIn": "button#almacmp-modalConfirmBtn", + "presence": "div#almacmp-footer--layer1" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "ampparit.com" + ], + "id": "679d7000-cd04-4c35-bbf8-21263a2ff357", + "last_modified": 1674560738105 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-ui" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "videa.hu" + ], + "id": "6fed31af-f6d2-4452-88ef-8f2ca61c6f19", + "last_modified": 1674560738097 + }, + { + "click": { + "optOut": "button#modalConfirmBtn ", + "presence": "div.gravitoCMP-background-overlay" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "hitta.se" + ], + "id": "1998c717-cd3a-48a0-a9f3-67f790f98879", + "last_modified": 1674560738094 + }, + { + "click": { + "optIn": "button.css-47sehv", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1674521305403, + "cookies": {}, + "domains": [ + "espreso.co.rs" + ], + "id": "ed6379a2-717a-4571-9a81-abb0c5b29098", + "last_modified": 1674560738090 + }, + { + "click": { + "optIn": "#cmpbntyestxt", + "optOut": "#cmpbntnotxt", + "presence": "div.cmpboxbtns" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "sourceforge.net" + ], + "id": "786f7ae5-548a-43d4-97a2-7b8efb5ed7a7", + "last_modified": 1674560738080 + }, + { + "click": { + "optIn": "div#gdpr-single-choice-overlay", + "presence": "button.wt-btn--filled" + }, + "schema": 1674521304583, + "cookies": { + "optOut": [ + { + "name": "p", + "value": "eyJnZHByX3RwIjoxLCJnZHByX3AiOjF9" + } + ] + }, + "domains": [ + "etsy.com" + ], + "id": "e5706488-f2e9-47bd-a178-4d569ca26ce8", + "last_modified": 1674560738076 + }, + { + "click": {}, + "schema": 1674521303759, + "cookies": { + "optOut": [ + { + "name": "gdpr", + "value": "1" + } + ] + }, + "domains": [ + "yandex.com", + "yandex.ru", + "ya.ru", + "kinopoisk.ru" + ], + "id": "37319f5d-9484-4da8-aee1-570a78688da3", + "last_modified": 1674560738073 + }, + { + "click": { + "optIn": "button.cmp-intro_acceptAll", + "presence": "div.cmp-popup_popup" + }, + "schema": 1674521304583, + "cookies": {}, + "domains": [ + "onet.pl", + "fakt.pl", + "plejada.pl", + "medonet.pl", + "businessinsider.com.pl" + ], + "id": "be15b898-a203-45cb-8200-b9b36d2af17b", + "last_modified": 1674560738053 + }, + { + "click": {}, + "schema": 1671501963715, + "cookies": { + "optOut": [ + { + "name": "notice_gdpr_prefs", + "value": "0:" + } + ] + }, + "domains": [ + "flickr.com" + ], + "id": "22d33421-6872-41bc-9316-adba6a9af18e", + "last_modified": 1671635844937 + }, + { + "click": {}, + "schema": 1671450839828, + "cookies": { + "optOut": [ + { + "name": "OPTOUTMULTI_TYPE", + "value": "A" + } + ] + }, + "domains": [ + "autodesk.com" + ], + "id": "24fdb694-9d8f-4049-a2f1-583465e711a5", + "last_modified": 1671501963591 + }, + { + "click": { + "optIn": "button#consent-accept-button", + "presence": "div.sc-36lcln-0" + }, + "schema": 1671324527556, + "cookies": {}, + "domains": [ + "xing.com" + ], + "id": "f82cabad-efa5-4637-8906-9fb842cd6556", + "last_modified": 1671383798361 + }, + { + "click": { + "optIn": "span.flex-button", + "presence": "section.jsx-539809080" + }, + "schema": 1671324525745, + "cookies": {}, + "domains": [ + "notion.so" + ], + "id": "85205acd-fa99-438e-b353-2b40adfdf868", + "last_modified": 1671383798347 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1671324523911, + "cookies": {}, + "domains": [ + "aruba.it" + ], + "id": "469218de-6bea-475f-be47-734a412b4fa7", + "last_modified": 1671383798336 + }, + { + "click": { + "optIn": "button#AcceptButton-cookie-banner", + "presence": "div#cookie-banner" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "prisjakt.nu" + ], + "id": "6950df9f-2529-4cdc-ab8b-9ba788b6d2f7", + "last_modified": 1670498157871 + }, + { + "click": { + "optIn": "button.css-1k3zih6", + "presence": "div#qc-cmp2-container" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "addtl_consent", + "value": "1~" + } + ] + }, + "domains": [ + "capital.gr" + ], + "id": "8e9824be-3535-4d17-8e64-ae9628a0611d", + "last_modified": 1670498157859 + }, + { + "click": { + "optIn": "button#btnInfo", + "presence": "div#info-modal" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "limundo.com" + ], + "id": "5df00bf4-ecbc-4521-862b-db1453be23c9", + "last_modified": 1670498157844 + }, + { + "click": { + "optIn": "span.sd-cmp-3cRQ2", + "presence": "div#sd-cmp" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "forocoches.com" + ], + "id": "a0698f6e-dd50-4773-9aad-eb23688383f3", + "last_modified": 1670498157840 + }, + { + "click": { + "optIn": "div.accept-btn", + "optOut": "div.refuse-btn", + "presence": "div.cookie-pop" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "tencent.com" + ], + "id": "e964784f-f4f4-4e71-b973-8c68ef6f49d7", + "last_modified": 1670498157836 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "button#truste-consent-required", + "presence": "div#truste-consent-content" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "ibm.com" + ], + "id": "97c1b39b-6573-4dbc-83a4-3af02a416f0a", + "last_modified": 1670498157828 + }, + { + "click": { + "optIn": "button.fZYtinTR", + "presence": "div.CoZ9Nu8Z" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [ + { + "name": "FC2_GDPR", + "value": "true" + } + ] + }, + "domains": [ + "fc2.com" + ], + "id": "5669aa13-d825-45b6-9a65-14904ecd9ee9", + "last_modified": 1670498157820 + }, + { + "click": { + "optIn": "div.cmc-cookie-policy-banner__close", + "presence": "div#cmc-cookie-policy-banner" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [ + { + "name": "cmc_gdpr_hide", + "value": "1" + } + ] + }, + "domains": [ + "coinmarketcap.com" + ], + "id": "faccc72f-fab0-4dd8-82a5-4b97774a0058", + "last_modified": 1670498157816 + }, + { + "click": { + "optIn": "div#accept-choices", + "presence": "div.sn-inner" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "w3schools.com" + ], + "id": "230b6c9b-c717-4a88-9d64-9a892091d53d", + "last_modified": 1670498157808 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "button#truste-consent-required", + "presence": "div#truste-consent-track" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "nginx.com" + ], + "id": "aaace84f-069e-4713-85f0-7a2f419c34eb", + "last_modified": 1670498157804 + }, + { + "click": { + "optOut": "a.n-messaging-banner__button", + "presence": "div.consent-container" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [ + { + "name": "optout", + "value": "0" + } + ], + "optOut": [ + { + "name": "optout", + "value": "1" + } + ] + }, + "domains": [ + "indiatimes.com" + ], + "id": "e991cb20-0597-4f14-aabc-214a2e493f90", + "last_modified": 1670498157800 + }, + { + "click": { + "optIn": "button#_evidon-accept-button", + "optOut": "button#_evidon-decline-button", + "presence": "div#_evidon_banner" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [ + { + "name": "_tt_enable_cookie", + "value": "1" + } + ], + "optOut": [] + }, + "domains": [ + "eventbrite.com" + ], + "id": "25d7b523-6e33-414a-a1e8-b9f27c7e8a37", + "last_modified": 1670498157797 + }, + { + "click": { + "optIn": "a#okck", + "presence": "div#toast-container" + }, + "schema": 1670333809819, + "cookies": { + "optIn": [ + { + "name": "ck", + "value": "4" + } + ], + "optOut": [ + { + "name": "ck", + "value": "0" + } + ] + }, + "domains": [ + "ilovepdf.com" + ], + "id": "137eec59-6b48-413c-bb45-33420aabdc87", + "last_modified": 1670498157785 + }, + { + "click": { + "optIn": "a#CybotCookiebotDialogBodyButtonAccept", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1670333809819, + "cookies": {}, + "domains": [ + "themeforest.net", + "cpanel.net", + "envato.com" + ], + "id": "f9b5b6d8-50be-486e-bc59-a45733be492c", + "last_modified": 1670498157781 + }, + { + "click": { + "optIn": "button.welcome__button--accept", + "optOut": "button.welcome__button--decline", + "presence": "div.welcome__cookie-notice" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "wetransfer.com" + ], + "id": "8b43c95f-2617-4541-a88d-ebae3b819bee", + "last_modified": 1670498157774 + }, + { + "click": { + "optIn": "button.css-quk35p", + "presence": "div.css-103nllw" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "healthline.com" + ], + "id": "b62e5259-f7f5-4319-8175-e39266b8a031", + "last_modified": 1670498157766 + }, + { + "click": { + "optIn": "a.close", + "presence": "div.ReadPolicy" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "huawei.com" + ], + "id": "645dca4f-49b6-456e-8aa1-40dfdb35acb7", + "last_modified": 1670498157750 + }, + { + "click": { + "optIn": "button#_evidon-accept-button", + "optOut": "button#_evidon-decline-button", + "presence": "div#_evidon_banner" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "pubmatic.com" + ], + "id": "35142692-9d67-4f16-9512-36673e35c43d", + "last_modified": 1670498157747 + }, + { + "click": { + "optIn": "button.CookieBanner_button__P6xCl", + "presence": "section.CookieBanner_notificationBar__nn49h" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "kaspersky.com" + ], + "id": "1e504f8f-4377-4de7-90e4-cbbfaa30f54d", + "last_modified": 1670498157743 + }, + { + "click": { + "optIn": "a#tbp-consent-confirm", + "presence": "div.tbp-container" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "taboola.com" + ], + "id": "cade9499-6c59-4939-90e7-e5e029ce0446", + "last_modified": 1670498157735 + }, + { + "click": { + "optIn": "a.cookiepolicycontinue", + "presence": "div#cookieMessage_content" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "oup.com" + ], + "id": "2e0b3da1-4072-4e1b-b561-939789c90f4d", + "last_modified": 1670498157727 + }, + { + "click": { + "optIn": "button#cx_button_close", + "presence": "div#cx_bottom_banner" + }, + "schema": 1670460523671, + "cookies": { + "optIn": [ + { + "name": "show_gdpr_consent_messaging", + "value": "1" + } + ] + }, + "domains": [ + "nbcnews.com" + ], + "id": "fecfb874-fff9-4d91-af27-885729a4556d", + "last_modified": 1670498157720 + }, + { + "click": { + "optIn": "button.cta--is-1", + "presence": "div.cookie-banner__cta" + }, + "schema": 1670460523671, + "cookies": { + "optIn": [ + { + "name": "and_cba_EN_US", + "value": "true" + } + ] + }, + "domains": [ + "android.com" + ], + "id": "35a8e0de-9308-4dfe-abc9-764a9e3833ec", + "last_modified": 1670498157716 + }, + { + "click": { + "optIn": "a.cc-dismiss", + "presence": "div.cc-window" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "dnsmadeeasy.com" + ], + "id": "581b7d96-d1a4-43a8-9498-5650f9234e10", + "last_modified": 1670498157708 + }, + { + "click": { + "optIn": "button#gdpr-modal-agree", + "presence": "div.modal-window" + }, + "schema": 1670460523671, + "cookies": { + "optIn": [ + { + "name": "gdpr_agreed", + "value": "4" + } + ] + }, + "domains": [ + "usnews.com" + ], + "id": "d9ebe530-4e45-4315-b628-c2d8c81c48a5", + "last_modified": 1670498157693 + }, + { + "click": { + "optIn": "span.js-dismiss-cookie-banner", + "presence": "div.DesignSystem" + }, + "schema": 1670460523671, + "cookies": {}, + "domains": [ + "academia.edu" + ], + "id": "41001e78-302d-44da-bd82-a690d1968a9f", + "last_modified": 1670498157689 + }, + { + "click": { + "optIn": "button.cu-privacy-notice-close", + "presence": "div#cu-privacy-notice" + }, + "schema": 1670460524526, + "cookies": { + "optIn": [ + { + "name": "cuPivacyNotice", + "value": "1" + } + ] + }, + "domains": [ + "columbia.edu" + ], + "id": "2952e826-d897-431b-866d-f8ed95f4db64", + "last_modified": 1670498157658 + }, + { + "click": { + "optIn": "button.consent-btn", + "presence": "div.cookie-consent" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "geeksforgeeks.org" + ], + "id": "f3d0e044-1dc7-4d6e-9477-2724c5115ba4", + "last_modified": 1670498157647 + }, + { + "click": { + "optIn": "button.accept-consent", + "presence": "section#cookieconsentpopup" + }, + "schema": 1670460524526, + "cookies": { + "optIn": [ + { + "name": "consent_cookie", + "value": "1" + } + ] + }, + "domains": [ + "worldbank.org" + ], + "id": "efe049ac-d21c-4fa6-b559-5c47b5ce307b", + "last_modified": 1670498157643 + }, + { + "click": { + "optIn": "button#close_feedback_btn", + "presence": "div#feedback-bar" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "cricbuzz.com" + ], + "id": "22187533-0a56-4679-9244-f11b2c58083a", + "last_modified": 1670498157631 + }, + { + "click": { + "optIn": "button.color-secondary", + "presence": "div.footer_footer__ZK5Q_" + }, + "schema": 1670460524526, + "cookies": { + "optIn": [ + { + "name": "__cookie__agree", + "value": "Y" + } + ] + }, + "domains": [ + "sberdevices.ru" + ], + "id": "c8c58b5e-45d1-4442-ad53-b89f3603586f", + "last_modified": 1670498157620 + }, + { + "click": { + "optIn": "button.cookie-accept-btn", + "presence": "div.cookie-consent-area" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "typeform.com" + ], + "id": "c0bef9bb-67e2-4038-bfe9-795085f37719", + "last_modified": 1670498157616 + }, + { + "click": { + "optIn": "button.js-opt-in-consent-button", + "presence": "div.opt-in-modal__inner" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "wpengine.com" + ], + "id": "f89481a5-e087-4210-aeee-36bba9764a87", + "last_modified": 1670498157612 + }, + { + "click": { + "optIn": "button#cookie-policy-button-accept", + "presence": "div#modal" + }, + "schema": 1670460524526, + "cookies": { + "optIn": [ + { + "name": "_cookies_accepted", + "value": "all" + } + ], + "optOut": [ + { + "name": "_cookies_accepted", + "value": "essential" + } + ] + }, + "domains": [ + "ubuntu.com" + ], + "id": "b11001c6-7a9c-40a7-8595-3c8a9c1e7416", + "last_modified": 1670498157608 + }, + { + "click": { + "optIn": "button#privacy-consent-button", + "presence": "div#privacy-consent" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "vox.com" + ], + "id": "e552ade4-c254-4e8b-823f-8307e4b21fb3", + "last_modified": 1670498157597 + }, + { + "click": { + "optIn": "button#ccc-notify-accept", + "optOut": "button#ccc-notify-dismiss", + "presence": "div#ccc" + }, + "schema": 1670460524526, + "cookies": { + "optIn": [ + { + "name": "_hjFirstSeen", + "value": "1" + } + ] + }, + "domains": [ + "ox.ac.uk" + ], + "id": "982a2a33-f052-4acd-936e-d3b0f7bf7596", + "last_modified": 1670498157593 + }, + { + "click": { + "optIn": "a.c-button", + "presence": "div#cookiebanner" + }, + "schema": 1670460524526, + "cookies": {}, + "domains": [ + "psychologytoday.com" + ], + "id": "fcd83d7a-0cbc-4cc4-ac1f-609a303ea5a3", + "last_modified": 1670498157589 + }, + { + "click": { + "optIn": "button.consent--accept", + "presence": "div.consent-banner-buttons" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "ring.com" + ], + "id": "081717d3-32c8-40e4-8bc1-ddd5495dbddf", + "last_modified": 1670498157581 + }, + { + "click": { + "optIn": "a.accept-cookies", + "presence": "div.cookie-notice" + }, + "schema": 1670460525411, + "cookies": { + "optIn": [ + { + "name": "_hjFirstSeen", + "value": "1" + } + ] + }, + "domains": [ + "evernote.com" + ], + "id": "a78db283-f6a6-4e80-83d1-65becec1df21", + "last_modified": 1670498157574 + }, + { + "click": { + "optIn": "span.cookiePolicy-close", + "presence": "div.cookiePolicy" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "gearbest.com" + ], + "id": "0100a0ae-2e3a-4dcd-8761-596193f6be8d", + "last_modified": 1670498157570 + }, + { + "click": { + "optIn": "span.b-policy-info__cross", + "presence": "div.b-policy-info" + }, + "schema": 1670460525411, + "cookies": { + "optIn": [ + { + "name": "cookieAgree", + "value": "1" + } + ] + }, + "domains": [ + "reg.ru" + ], + "id": "783a1b6f-616d-4f9d-a84b-b0f7954e3e24", + "last_modified": 1670498157563 + }, + { + "click": { + "optIn": "button#cookie-policy-btn", + "presence": "div.cookie" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "ucla.edu" + ], + "id": "955c6f08-e07d-4b05-be04-5de69f49753b", + "last_modified": 1670498157547 + }, + { + "click": { + "optIn": "button#_evidon-banner-acceptbutton", + "presence": "div#_evidon-banner-content" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "lenovo.com" + ], + "id": "21e686fb-7250-4fbc-ab5c-6a6bf67775d3", + "last_modified": 1670498157540 + }, + { + "click": { + "optIn": "a.button--default", + "presence": "div#cookie-notification" + }, + "schema": 1670460525411, + "cookies": { + "optIn": [ + { + "name": "mdpi_cookies_accepted", + "value": "1" + } + ] + }, + "domains": [ + "mdpi.com" + ], + "id": "4f9c6403-4953-48aa-a8d7-d7acf177d437", + "last_modified": 1670498157536 + }, + { + "click": { + "optIn": "button#_evidon-decline-button", + "presence": "div#_evidon-message" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "crunchyroll.com" + ], + "id": "85aec7ab-1cc2-4651-b3fc-0cbae9a241f2", + "last_modified": 1670498157525 + }, + { + "click": { + "optIn": "button.css-ZtxlD", + "optOut": "a.css-jMqFMB", + "presence": "div.css-fPpfoE" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "uber.com" + ], + "id": "663e3527-0018-4851-912d-c75a9d610881", + "last_modified": 1670498157517 + }, + { + "click": { + "optIn": "button#ccc-notify-accept", + "optOut": "button#ccc-notify-reject", + "presence": "div#ccc" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "tapad.com" + ], + "id": "026010ef-311b-4c3e-aa1d-cb1470591c6a", + "last_modified": 1670498157514 + }, + { + "click": { + "optIn": "button#unic-agree", + "presence": "div.unic-bar" + }, + "schema": 1670460525411, + "cookies": {}, + "domains": [ + "sharethrough.com" + ], + "id": "d448486a-ad5c-45dd-b661-c22a82869036", + "last_modified": 1670498157502 + }, + { + "click": { + "optIn": "span#cookie-banner-close", + "presence": "div#cookie-banner" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "hideCookieBanner", + "value": "true" + } + ] + }, + "domains": [ + "substack.com" + ], + "id": "09f72241-e3be-4156-a8ac-ecf47145854f", + "last_modified": 1670498157487 + }, + { + "click": { + "optIn": "a#et_cookie_consent_allow", + "presence": "div.et_cookie_consent" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "et_cookies", + "value": "on" + } + ], + "optOut": [ + { + "name": "et_cookies", + "value": "off" + } + ] + }, + "domains": [ + "elegantthemes.com" + ], + "id": "ae4a00ba-e49c-4376-a5a7-67d9b647342d", + "last_modified": 1670498157484 + }, + { + "click": { + "optIn": "button.cookie-consent-banner-opt-out__action", + "presence": "div.cookie-consent-banner-opt-out" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "mercadolibre.com.ar" + ], + "id": "b6fda273-20bf-4e49-8a2b-fe2afdcc12da", + "last_modified": 1670498157472 + }, + { + "click": { + "optIn": "button.closeButton", + "presence": "div.cookieBanner" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "anchor.fm" + ], + "id": "475b40e3-18d5-457e-a084-76faa301c753", + "last_modified": 1670498157450 + }, + { + "click": { + "optIn": "button.button-accept-cookie", + "presence": "div.banner-overlay" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "analytics_accepted", + "value": "true" + }, + { + "name": "cookie_accepted", + "value": "true" + } + ], + "optOut": [ + { + "name": "cookies_denied", + "value": "true" + } + ] + }, + "domains": [ + "aboutcookies.org" + ], + "id": "2de3ce99-ba76-417e-851c-87e0c4035180", + "last_modified": 1670498157446 + }, + { + "click": { + "optIn": "a.js-gdpr-cookie-acceptLink", + "presence": "div.widget-GdprCookieBanner" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "CookieBanner_Closed", + "value": "true" + } + ] + }, + "domains": [ + "jamanetwork.com" + ], + "id": "6026221f-dd61-493e-aea8-cdf38198f401", + "last_modified": 1670498157442 + }, + { + "click": { + "optIn": "button#nhsuk-cookie-banner__link_accept_analytics", + "optOut": "button#nhsuk-cookie-banner__link_accept", + "presence": "div#nhsuk-cookie-banner" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "www.nhs.uk" + ], + "id": "e6444889-fd2f-412a-b25d-41a6b6daaf3a", + "last_modified": 1670498157435 + }, + { + "click": { + "optIn": "button.button_b1lbbqqt", + "presence": "div.modalStyle_modrqv7" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "nikkei.com" + ], + "id": "546792b9-f201-4e94-97f6-d5680abfe529", + "last_modified": 1670498157431 + }, + { + "click": { + "optIn": "button.fc-primary-button", + "presence": "div.fc-consent-root" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "17track.net" + ], + "id": "a620f10c-033a-47c1-ad2b-a8fd1df9e504", + "last_modified": 1670498157427 + }, + { + "click": { + "optIn": "a.cc-dismiss", + "presence": "div.cc-window" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "cookieconsent_status", + "value": "dismiss" + } + ] + }, + "domains": [ + "duke.edu" + ], + "id": "d614e3e1-1282-4c56-801d-525263028933", + "last_modified": 1670498157419 + }, + { + "click": { + "optIn": "a#cookie_action_close_header", + "presence": "div#cookie-law-info-bar" + }, + "schema": 1670460526327, + "cookies": { + "optIn": [ + { + "name": "viewed_cookie_policy", + "value": "yes" + } + ] + }, + "domains": [ + "usc.edu" + ], + "id": "5cedc12d-d79c-4be8-9e45-e98905147090", + "last_modified": 1670498157408 + }, + { + "click": { + "optIn": "button.btn-accept", + "presence": "div#gdpr-new-container" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "aliexpress.com" + ], + "id": "80851a39-2183-49e4-99f4-16d6189bff1e", + "last_modified": 1670498157404 + }, + { + "click": { + "optOut": "button.cookieBarButtons", + "presence": "div#cookieBar" + }, + "schema": 1670460527205, + "cookies": {}, + "domains": [ + "google-analytics.com" + ], + "id": "286028cd-7cb7-4c71-91a7-ae1a23df8a31", + "last_modified": 1670498157378 + }, + { + "click": { + "optIn": "div.global-alert-banner button[action-type=ACCEPT]", + "optOut": "div.global-alert-banner button[action-type=DENY]", + "presence": "div.global-alert-banner" + }, + "schema": 1670460527205, + "cookies": {}, + "domains": [ + "linkedin.com" + ], + "id": "f1ddc6f6-8927-4205-8003-1f6e84ae22b0", + "last_modified": 1670498157329 + }, + { + "click": { + "optIn": "button#gdpr-banner-accept", + "optOut": "button#gdpr-banner-decline", + "presence": "div#gdpr-banner" + }, + "schema": 1670460527205, + "cookies": {}, + "domains": [ + "ebay.com", + "ebay.de", + "ebay.co.uk" + ], + "id": "1e6d35e7-b907-4f5c-a09a-9f3336ef6e61", + "last_modified": 1670498157322 + }, + { + "click": { + "optIn": "button[data-a-target=consent-banner-accept]", + "presence": "div.consent-banner" + }, + "schema": 1670460527205, + "cookies": {}, + "domains": [ + "twitch.tv" + ], + "id": "542aafba-9f44-4321-88e5-ece333073360", + "last_modified": 1670498157315 + }, + { + "click": { + "optIn": "div.gdpr div > button:not([aria-label]", + "optOut": "div.gdpr div > button + button:not([aria-label])", + "presence": "div.gdpr" + }, + "schema": 1670460527205, + "cookies": { + "optOut": [ + { + "name": "purr-pref-agent", + "value": "<Go" + } + ] + }, + "domains": [ + "nytimes.com" + ], + "id": "488f07f9-0a01-4118-aab3-2b817e9fe6dc", + "last_modified": 1670498157312 + }, + { + "click": { + "hide": "div#onetrust-consent-sdk", + "optIn": "button#onetrust-accept-btn-handler", + "optOut": "button.ot-pc-refuse-all-handler", + "presence": "div#onetrust-banner-sdk" + }, + "schema": 1670460527205, + "cookies": { + "optOut": [ + { + "name": "OptanonAlertBoxClosed", + "value": "2022-08-09T16:49:23.845Z" + } + ] + }, + "domains": [ + "getpocket.com" + ], + "id": "c2f6b05e-73dc-4df4-bb54-68215bb9f176", + "last_modified": 1670498157308 + }, + { + "click": {}, + "schema": 1670460528099, + "cookies": { + "optOut": [ + { + "name": "cookie_policy_agreement", + "value": "3" + }, + { + "name": "dont-track", + "value": "1" + } + ] + }, + "domains": [ + "mydealz.de" + ], + "id": "069f4d94-8031-4b83-b8f9-89752c5c1353", + "last_modified": 1670498157271 + }, + { + "click": {}, + "schema": 1670460528099, + "cookies": { + "optIn": [ + { + "name": "wa_cb", + "value": "1_2022_08_09_" + } + ], + "optOut": [] + }, + "domains": [ + "whatsapp.com" + ], + "id": "0f4b1486-7d4b-4f73-8ec9-122daca9ebf4", + "last_modified": 1670498157268 + }, + { + "click": {}, + "schema": 1670460528099, + "cookies": { + "optIn": [ + { + "name": "sensitive_pixel_option", + "value": "yes" + } + ] + }, + "domains": [ + "wordpress.com" + ], + "id": "709f87b8-b010-4d32-b32d-3f265963c6a5", + "last_modified": 1670498157264 + }, + { + "click": {}, + "schema": 1670460528099, + "cookies": { + "optOut": [ + { + "name": "eu_cookie", + "value": "{%22opted%22:true%2C%22nonessential%22:false}" + } + ] + }, + "domains": [ + "reddit.com" + ], + "id": "c5243e7c-eb86-4a9d-947c-7129e99fbd72", + "last_modified": 1670498157243 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1670460528099, + "cookies": {}, + "domains": [ + "issuu.com", + "dobrenoviny.sk", + "voetbal24.be", + "weforum.org" + ], + "id": "019c0709-e9ef-4b0d-94bf-958d251a51b5", + "last_modified": 1670498157239 + }, + { + "click": { + "optIn": ".btn--primary", + "presence": "div#cookie-consent" + }, + "schema": 1670460528099, + "cookies": { + "optIn": [ + { + "name": "cookie_consent_essential", + "value": "true" + }, + { + "name": "cookie_consent_marketing", + "value": "true" + } + ] + }, + "domains": [ + "opera.com" + ], + "id": "09884f7b-ef06-45dd-9bda-960b5c6b7232", + "last_modified": 1670498157220 + }, + { + "click": { + "optIn": ".a8c-cookie-banner-accept-all-button", + "presence": ".a8c-cookie-banner" + }, + "schema": 1670460526327, + "cookies": {}, + "domains": [ + "gravatar.com" + ], + "id": "4b835605-77e0-457a-8409-8d499ee1131c", + "last_modified": 1670498157180 + }, + { + "click": { + "optIn": "a#accept-button", + "optOut": "a#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#c-right" + }, + "schema": 1670460540364, + "cookies": {}, + "domains": [ + "yettel.bg" + ], + "id": "4ec170c6-933c-4612-9907-80bc8ef6039c", + "last_modified": 1670498157158 + }, + { + "click": { + "optIn": "button.btn-primary", + "presence": "div.alert-secondary" + }, + "schema": 1670460540364, + "cookies": { + "optOut": [ + { + "name": "privacy_cookie", + "value": "1" + } + ] + }, + "domains": [ + "bakecaincontrii.com" + ], + "id": "a8984148-8c62-4dd5-8350-4e0da48f1829", + "last_modified": 1670498157128 + }, + { + "click": { + "optIn": "button.mat-button-base", + "presence": "section#cookie-banner" + }, + "schema": 1670460540364, + "cookies": { + "optIn": [ + { + "name": "cookie-banner-acceptance-state", + "value": "true" + } + ] + }, + "domains": [ + "migros.ch" + ], + "id": "cd670e2a-b764-4722-8ff2-a136350a60d8", + "last_modified": 1670498157110 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#buttons" + }, + "schema": 1670460540364, + "cookies": { + "optIn": [ + { + "name": "euconsent-v2", + "value": "CPhJZsAPhJZsAAHABBENClCsAP_AAH_AAAiQJFNf_X__b2_r-_5_f_t0eY1P9_7__-0zjhfdl-8N3f_X_L8X52M7vF36tq4KuR4ku3LBIUdlHOHcTUmw6okVryPsbk2cr7NKJ7PEmnMbOydYGH9_n1_z-ZKY7___f_7z_v-v___3____7-3f3__5___-__e_V__9zfn9_____9vP___9v-_9__________3_7997BIQAkw1biALsSxwJtowigRAjCsJDqBQAUUAwtEBhA6uCnZXAT6wgQAIBQBGBECHAFGDAIAAAIAkIiAkCPBAIACIBAACABUIhAARsAgoALAwCAAUA0LFGKAIQJCDIgIilMCAiRIKCeyoQSg_0NMIQ6ywAoNH_FQgIlACFYEQkLByHBEgJeLJAsxRvkAIwQoBRKhWoAAAA.f_gAD_gAAAAA" + } + ], + "optOut": [ + { + "name": "euconsent-v2", + "value": "CPhJZsAPhJZsAAHABBENClCgAAAAAH_AAAiQAAASEAJMNW4gC7EscCbaMIoEQIwrCQ6gUAFFAMLRAYQOrgp2VwE-sIEACAUARgRAhwBRgwCAAACAJCIgJAjwQCAAiAQAAgAVCIQAEbAIKACwMAgAFANCxRigCECQgyICIpTAgIkSCgnsqEEoP9DTCEOssAKDR_xUICJQAhWBEJCwchwRICXiyQLMUb5ACMEKAUSoVqAA.YAAAD_gAAAAA" + } + ] + }, + "domains": [ + "trendings.es" + ], + "id": "13dff385-bb91-414f-8fa3-88ae99621aba", + "last_modified": 1670498157100 + }, + { + "click": { + "optIn": "button.btn-cta-lg", + "optOut": "button.btn-secondary-lg", + "presence": "div.cookie-button-container" + }, + "schema": 1670460539454, + "cookies": {}, + "domains": [ + "roblox.com" + ], + "id": "1e55c3c3-63d2-45b2-8871-dfae1396c1a0", + "last_modified": 1670498157035 + }, + { + "click": { + "optIn": "div._euwdl0", + "presence": "footer._nlniiu" + }, + "schema": 1670460539454, + "cookies": {}, + "domains": [ + "2gis.ru" + ], + "id": "2a737c68-b3ed-4883-b02e-4cff18b538b4", + "last_modified": 1670498157021 + }, + { + "click": { + "optOut": "button.portal-button", + "presence": "div.CookieWall" + }, + "schema": 1670460538584, + "cookies": {}, + "domains": [ + "mozzartsport.com" + ], + "id": "7b299edf-ea10-472a-a3d9-cd622bde1ef9", + "last_modified": 1670498156963 + }, + { + "click": { + "optIn": "button#acceptButton", + "optOut": "button#declineButton", + "presence": "div.coi-button-group" + }, + "schema": 1670460538584, + "cookies": {}, + "domains": [ + "yousee.dk" + ], + "id": "31ef8068-58b4-400b-81cd-a027a16a2305", + "last_modified": 1670498156945 + }, + { + "click": { + "optIn": "button.blue", + "presence": "div.cookie-popup" + }, + "schema": 1670460538584, + "cookies": { + "optIn": [ + { + "name": "marketing", + "value": "1" + }, + { + "name": "analytic", + "value": "1" + } + ] + }, + "domains": [ + "supersport.hr" + ], + "id": "7eb3fb43-94c5-49da-80ca-1647f8398eec", + "last_modified": 1670498156938 + }, + { + "click": { + "optIn": "buton#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialogBodyButtons" + }, + "schema": 1670460538584, + "cookies": {}, + "domains": [ + "berlingske.dk" + ], + "id": "d7c0b359-8cdb-433c-b501-6fb2fdad0dc0", + "last_modified": 1670498156912 + }, + { + "click": { + "optOut": "a#ensCall", + "presence": "div.td-modal-cookie-content" + }, + "schema": 1670460538584, + "cookies": {}, + "domains": [ + "td.com" + ], + "id": "eb274e15-29fe-4004-9e6d-27046bd0b82d", + "last_modified": 1670498156901 + }, + { + "click": { + "optIn": "a.cc_btn_accept_all", + "presence": "div.cc_banner-wrapper" + }, + "schema": 1670460537691, + "cookies": { + "optIn": [ + { + "name": "cookieconsent_dismissed", + "value": "yes" + } + ], + "optOut": [] + }, + "domains": [ + "online-filmek.me" + ], + "id": "c200bbbd-3075-48ee-a507-e5934eb52567", + "last_modified": 1670498156880 + }, + { + "click": { + "optIn": "a.A", + "optOut": "a.R", + "presence": "div#CkC" + }, + "schema": 1670460537691, + "cookies": {}, + "domains": [ + "sfr.fr" + ], + "id": "dc867098-30ca-4a28-82b4-71abac243dab", + "last_modified": 1670498156876 + }, + { + "click": { + "optIn": "a.cookies-button", + "presence": "div#cookies-overlay" + }, + "schema": 1670460537691, + "cookies": { + "optIn": [ + { + "name": "approve", + "value": "yes" + } + ], + "optOut": [] + }, + "domains": [ + "linker.hr" + ], + "id": "10b58019-5dce-4018-b83d-8eef8c7564a7", + "last_modified": 1670498156872 + }, + { + "click": { + "optIn": "a.cookies-banner-button", + "presence": "div#cookies-banner" + }, + "schema": 1670460537691, + "cookies": { + "optIn": [ + { + "name": "cookiesAgreement", + "value": "1" + } + ], + "optOut": [] + }, + "domains": [ + "dnevnik.bg" + ], + "id": "74f56970-6ebe-4802-914e-72aeca0e13b3", + "last_modified": 1670498156869 + }, + { + "click": { + "optIn": "button.button--primary", + "presence": "div.modal__body" + }, + "schema": 1670460537691, + "cookies": { + "optIn": [ + { + "name": "CONSENTMGR", + "value": "c1:1%7Cc6:1%7Cc7:1%7Cc15:1%7Cconsent:true%7Cts:1665653237583" + } + ], + "optOut": [] + }, + "domains": [ + "swisscom.ch" + ], + "id": "3c03e0ec-aa84-48a1-83ca-0bf07d24e20e", + "last_modified": 1670498156807 + }, + { + "click": { + "optIn": "button#gdpr-banner-accept", + "optOut": "button#gdpr-banner-decline", + "presence": "div.gdpr-banner__wrapper" + }, + "schema": 1670460537691, + "cookies": {}, + "domains": [ + "ebay.it" + ], + "id": "0bee4c78-484e-4f21-8585-f089bc7618f5", + "last_modified": 1670498156804 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "div#truste-consent-required", + "presence": "div.truste-consent-content" + }, + "schema": 1670460537691, + "cookies": {}, + "domains": [ + "samsung.com" + ], + "id": "943a1f39-57c2-430e-b91d-81a659d9b036", + "last_modified": 1670498156789 + }, + { + "click": { + "optIn": "button.js-cookies-hide", + "presence": "div.cookie-label__btn-set" + }, + "schema": 1670460536815, + "cookies": { + "optIn": [ + { + "name": "cookieApprove", + "value": "true" + } + ] + }, + "domains": [ + "nv.ua" + ], + "id": "e8aa7447-2f27-4b65-bbdb-dcecc30c6c1f", + "last_modified": 1670498156782 + }, + { + "click": { + "optIn": "button.consent-accept", + "presence": "div#consent-accept" + }, + "schema": 1670460536815, + "cookies": { + "optIn": [ + { + "name": "s_cc", + "value": "true" + } + ] + }, + "domains": [ + "dnb.no" + ], + "id": "0b8969b6-d5a1-4358-967b-524a2c998233", + "last_modified": 1670498156769 + }, + { + "click": { + "optIn": "a#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "a#CybotCookiebotDialogBodyLevelButtonLevelOptinDeclineAll", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1670460536815, + "cookies": {}, + "domains": [ + "e-boks.com" + ], + "id": "a1bb84f0-e248-4576-8cd5-722622120a7d", + "last_modified": 1670498156759 + }, + { + "click": { + "optIn": "a#cn-accept-cookie", + "presence": "div#cookie-notice" + }, + "schema": 1670460536815, + "cookies": { + "optIn": [ + { + "name": "cookie_notice_accepted", + "value": "true" + } + ] + }, + "domains": [ + "danas.rs" + ], + "id": "d6ee6a87-66ad-4c35-88ac-966d4fff81f7", + "last_modified": 1670498156741 + }, + { + "click": { + "optIn": "button.kitt-cookie-warning__close", + "presence": "div.kitt-cookie-warning__content" + }, + "schema": 1670460536815, + "cookies": { + "optIn": [ + { + "name": "sbrf.pers_notice", + "value": "1" + } + ], + "optOut": [] + }, + "domains": [ + "sberbank.ru" + ], + "id": "40746884-0f10-4e12-ab0d-2a4410b1e0f9", + "last_modified": 1670498156737 + }, + { + "click": { + "optIn": "a#kmcc-accept-all-cookies", + "presence": "div.kmcc-cookie-bar__footer__preferences__second" + }, + "schema": 1670460536815, + "cookies": { + "optIn": [ + { + "name": "legal_cookie", + "value": "%7B%22cookies%22%3A%7B%22functional_cookie%22%3A%22true%22%2C%22analyzing_cookie%22%3A%22true%22%7D%2C%22cookie_log_id%22%3A%2225634502%22%2C%22cookie_version%22%3A20200608%7D" + } + ], + "optOut": [ + { + "name": "legal_cookie", + "value": "%7B%22cookies%22%3A%7B%22functional_cookie%22%3A%22true%22%2C%22analyzing_cookie%22%3A%22false%22%2C%22marketing_cookie%22%3A%22false%22%7D%2C%22cookie_log_id%22%3A%2225634476%22%2C%22cookie_version%22%3A20200608%7D" + } + ] + }, + "domains": [ + "meteo.be" + ], + "id": "67e420b4-05e6-4c01-a999-0d48dba85362", + "last_modified": 1670498156709 + }, + { + "click": { + "optIn": "button#didomi-notice-agree-button", + "presence": "div#buttons" + }, + "schema": 1670460535835, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "euconsent-v2", + "value": "CPgrvQAPgrvQAAHABBENCkCgAAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAAA" + } + ] + }, + "domains": [ + "story.hr" + ], + "id": "735b925d-388b-4df5-b66e-2907674cd126", + "last_modified": 1670498156674 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons" + }, + "schema": 1670460535835, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgrvQAPgrvQAEsABCSKCkCgAAAAAAAAACRQAAAOhQD2F2K2kKEkfjSUWYAQBCujIEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAgACgAAAAAAAAAAAAAAQQAABAAIAAAAAAAAAAQAAIAAQAAAAAAABEhCAAQSAEAAAAAAAAAAAAAAAAAAABAAA.YAAAAAAAAAA%22%2C%221~%22%2C%229EBAD907-6709-4964-8B38-C2711989FD29%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "modrykonik.sk" + ], + "id": "97909ba0-2fbd-410e-b2b9-3e165930ac61", + "last_modified": 1670498156652 + }, + { + "click": { + "optIn": "button.as-js-optin", + "presence": "div.as-oil-l-wrapper-layout-max-width" + }, + "schema": 1670460535835, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "oil_data", + "value": "{%22opt_in%22:true%2C%22version%22:%221.2.5-RELEASE%22%2C%22localeVariantName%22:%22de_01%22%2C%22localeVariantVersion%22:1%2C%22customPurposes%22:[]%2C%22consentString%22:%22BPgtAGPPgtAGPBQABBDECKAAAABCVY_KfQrC0oWQ3LTh5AAkALqlgRiFSEAIHYRE4AIRZUACSAEggHMoyUBIA8RAAERATIJABBgQEAISkAOAAIAgIpACEAgYAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf____3____8A%22%2C%22configVersion%22:0}" + } + ] + }, + "domains": [ + "tagesanzeiger.ch" + ], + "id": "c9ea0470-beac-43b9-aed1-d77042592d83", + "last_modified": 1670498156616 + }, + { + "click": { + "optIn": "button.css-k8o10q", + "presence": "div#qc-cmp2-container" + }, + "schema": 1670460534556, + "cookies": { + "optOut": [] + }, + "domains": [ + "sport-fm.gr" + ], + "id": "d88020d5-47d0-4926-8375-d279870f1663", + "last_modified": 1670498156564 + }, + { + "click": { + "optIn": "button.orejime-Notice-saveButton", + "optOut": "button.orejime-Notice-declineButton", + "presence": "div.orejime-Notice" + }, + "schema": 1670460534556, + "cookies": { + "optIn": [ + { + "name": "fedconsent", + "value": "{\"essential\":true,\"functional\":true,\"matomo\":true}" + } + ], + "optOut": [ + { + "name": "fedconsent", + "value": "{\"essential\":true,\"functional\":true,\"matomo\":false}" + } + ] + }, + "domains": [ + "belgium.be" + ], + "id": "98fcb883-013a-4858-a181-28c87e399c42", + "last_modified": 1670498156548 + }, + { + "click": { + "optIn": "a#cookieAccept", + "presence": "div#cookieContent" + }, + "schema": 1670460534556, + "cookies": {}, + "domains": [ + "publi24.ro" + ], + "id": "8d64659e-ece2-476d-a1c6-eec9e4070647", + "last_modified": 1670498156541 + }, + { + "click": { + "optIn": "button#cmp-btn-accept", + "presence": "div#cmp-consent" + }, + "schema": 1670460534556, + "cookies": {}, + "domains": [ + "wetter.com" + ], + "id": "8e274753-a1ee-4e02-b131-fa937ab10aea", + "last_modified": 1670498156509 + }, + { + "click": { + "optIn": "a#startsiden-gdpr-disclaimer-confirm", + "presence": "div#startsiden-gdpr-disclaimer" + }, + "schema": 1670460533399, + "cookies": { + "optIn": [ + { + "name": "startsiden-gdpr-disclaimer", + "value": "true" + } + ] + }, + "domains": [ + "startsiden.no", + "abcnyheter.no" + ], + "id": "00e9de62-0fc4-4266-8e8c-106da3a624c0", + "last_modified": 1670498156488 + }, + { + "click": { + "optIn": "button#truste-consent-button", + "optOut": "button#truste-consent-required", + "presence": "div#truste-consent-buttons" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "weather.com" + ], + "id": "8ef284ed-21d1-4825-8068-34ac22e5232d", + "last_modified": 1670498156471 + }, + { + "click": { + "optIn": "button.noticeButton", + "presence": "div.privacyNotification" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "cbc.ca" + ], + "id": "f69ced41-7e20-4b39-84a2-0fa62e7df3ef", + "last_modified": 1670498156467 + }, + { + "click": { + "optIn": "a#CybotCookiebotDialogBodyButtonAccept", + "optOut": "a#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialog" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "mondo.rs" + ], + "id": "f1502dfd-a03c-4ca1-ab5d-488b2e09b644", + "last_modified": 1670498156460 + }, + { + "click": { + "optIn": "a.js-cookies-info-accept", + "optOut": "a.js-cookies-info-reject", + "presence": "div.cookies-info__buttons" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "alza.sk" + ], + "id": "93b1a4a4-49e2-4160-98ae-e2311123c7f9", + "last_modified": 1670498156441 + }, + { + "click": { + "optIn": "button.coi-banner__accept", + "optOut": "button#declineButton", + "presence": "div.coi-button-group" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "dmi.dk" + ], + "id": "db64d8f2-1d35-4b57-a274-452efbcf2589", + "last_modified": 1670498156420 + }, + { + "click": { + "optIn": "a.close-accept", + "presence": "div.woahbar" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "theweathernetwork.com" + ], + "id": "3cfb8e27-c0b3-46c0-bee2-f4eef9c2a548", + "last_modified": 1670498156409 + }, + { + "click": { + "optIn": "button.cookiebtn", + "presence": "div.button" + }, + "schema": 1670460533399, + "cookies": {}, + "domains": [ + "watson.ch" + ], + "id": "3f8bdfb5-b898-4c25-ad68-6e10dab38e45", + "last_modified": 1670498156402 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons-container" + }, + "schema": 1670460532281, + "cookies": { + "optIn": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgocUAPgocUAEsABBHRCkCoAP_AAH_AAA6IIqNd_H__bX9n-f7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX_2E7NF36tq4KmR4ku1LBIQNtHMnUDUmxaokVrzHsak2cpyNKJ7BkknsZe2dYGFtPm5lD-QKZ7_5_d3f52T_9_9v-39z33913v3d_3-_12Ljd_599H_v_fR_bc_Kft_5-_8v8_____3_BFNjv43bra_s7x-d9R5ujRGr6v5S_VwGYcI4tGjgE5fy35WAf6wnYAu3RpWBEyPElmpAJCAhIAk4gKk2LREipaIZBACTFEBpBDYMkgtjLywKAgkh8XEoKwBTPL3NxIzvOSW_HdN_2_uW63uut-Jl63wcuhcbP3Fto21t7wAjLBwThmpJWvFpn53_-mZwAAA.cAAAD_gAAAA%22%2C%221~2052.2056.2064.20.2068.2069.2070.2072.2074.2084.39.2088.2090.43.46.2103.55.57.2107.2109.61.2115.70.2124.2130.83.2133.2137.89.2140.93.2145.2147.2150.108.2156.117.2166.122.124.2177.131.135.2183.136.2186.143.144.147.149.2202.2205.159.162.2213.167.2216.171.2219.2220.2222.2224.2225.2234.192.196.202.2253.211.2264.218.228.230.2279.2282.239.241.2292.2299.2305.259.2309.2312.266.2315.2316.272.2322.2325.2328.2331.2334.286.2335.2336.2337.291.2343.2354.2357.2358.2359.311.317.2370.322.323.2373.326.327.2376.2377.338.2387.2392.2394.2400.2403.2405.2406.358.2407.2411.2414.2415.367.2416.2418.370.371.2425.2427.385.389.2440.394.397.2447.2453.407.2459.2461.413.2462.415.2465.2468.2472.424.2477.430.2481.2484.436.2486.2488.2493.445.2496.2497.449.2498.2499.2501.453.2506.2510.2511.2517.469.2526.2527.482.2532.2534.486.2535.491.2542.494.495.501.503.2552.505.2555.2559.2563.2564.2567.2568.2569.522.2571.523.2572.2575.2577.2583.2584.540.2589.2596.2597.550.2601.2602.2604.2605.559.2608.2609.2610.2612.2614.568.2618.2621.574.2624.576.2628.2629.584.2633.2634.587.2636.591.2642.2643.2645.2646.2647.2650.2651.2652.2656.2657.2658.2660.2661.2669.2670.624.2677.2681.2684.2686.2687.2689.2690.2695.2698.2707.2713.2714.2729.2739.2767.2768.2770.2772.733.2784.737.2787.2791.2792.2793.745.2798.2801.2805.2812.2813.2814.2816.2817.2818.2821.2822.2824.2827.2830.2831.2832.2834.787.2838.2839.2840.2844.2846.798.2847.2849.2850.802.803.2852.2854.2856.2860.2862.2863.2865.817.2867.820.2869.821.2872.2873.2874.2875.827.2876.829.2878.2880.2881.2882.2883.2884.2886.2887.839.2888.2889.2891.2893.2894.2895.2897.2898.2900.2901.2908.2909.2911.2912.864.2913.2914.867.2916.2917.2918.2919.2920.2922.874.2923.2924.2927.2929.2930.2931.2935.2939.2940.2941.2942.2947.899.2949.2950.904.2956.2961.2962.2963.2964.2965.2966.2968.2970.922.2972.2973.2974.2975.2979.931.2980.2981.2983.2985.2986.938.2987.2991.2994.2995.2997.2999.3000.3002.3003.3005.3008.3009.3010.3012.3016.3017.3018.3019.3023.3024.3025.979.3028.981.3030.985.3034.3037.3038.3043.3045.3048.1003.3052.3053.3055.3058.3059.3063.3065.3066.3068.3070.1024.3072.3073.3074.1027.3075.3076.3077.3078.1031.1033.1040.3089.3090.3093.1046.3094.3095.1048.3097.1051.3099.1053.3104.3106.3109.3112.1067.3117.3118.3119.3120.3124.3126.3127.3128.3130.1085.3133.3135.3136.3137.1092.1095.1097.3145.1099.3149.3150.3151.3153.3154.1107.3155.3162.3163.3167.3172.3173.1127.3180.3182.1135.3183.3184.3185.3187.3188.3189.3190.1143.3194.3196.1149.3197.1152.3209.1162.3210.3211.1166.3214.3215.3217.1171.3219.3222.3223.3225.3226.3227.3228.3230.3231.3232.1186.3234.3235.1188.3236.3237.3238.3240.3241.3244.3245.1201.3250.3251.1205.3253.3254.3257.1211.3260.1215.3266.1220.3268.3270.3272.1226.1227.1230.3281.3286.3288.3290.3292.3293.3295.3296.1252.3300.1268.1270.3322.1276.1284.1286.1290.1301.1307.1312.1345.1356.1364.1365.1375.1403.1415.1416.1419.1440.1442.1449.1455.1456.1465.1495.1512.1516.1525.1540.1548.1555.1558.1564.1570.1577.1579.1583.1584.1591.1603.1616.1638.1651.1653.1660.1665.1667.1677.1678.1682.1697.1699.1703.1712.1716.1720.1721.1725.1732.1745.1750.1753.1765.1769.1782.1786.1800.1808.1810.1825.1827.1832.1838.1840.1842.1843.1845.1859.1866.1870.1878.1880.1889.1898.1899.1911.1917.1929.1942.1944.1958.1962.1963.1964.1967.1968.1969.1978.2003.2007.2008.2012.2027.2035.2039.2044.2047%22%2C%221B663699-8278-41B8-9A73-C98E45F0FDDF%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ], + "optOut": [ + { + "name": "UA_ADS", + "value": "{\"/21617374708/novilist.hr/home_break_v2\":5,\"/21617374708/novilist.hr/content_v1\":5,\"/21617374708/novilist.hr/content_v2\":5,\"/21617374708/novilist.hr/content_v3\":5,\"/21617374708/novilist.hr/content_parallax_v1\":5,\"/21617374708/novilist.hr/home_break_v1\":5,\"/21617374708/novilist.hr/home_break_v3\":5,\"/21617374708/novilist.hr/home_sidebar_v1\":4,\"/21617374708/novilist.hr/home_sidebar_v2\":5,\"/21617374708/novilist.hr/home_sidebar_v3\":5,\"/21617374708/novilist.hr/home_wallpaper_left\":4,\"/21617374708/novilist.hr/home_wallpaper_right\":4,\"/21617374708/novilist.hr/life_content_v1\":5,\"/21617374708/novilist.hr/life_content_v2\":5,\"/21617374708/novilist.hr/life_content_v3\":5,\"/21617374708/novilist.hr/life_sidebar_v1\":5,\"/21617374708/novilist.hr/life_home_top_v1\":5,\"/21617374708/novilist.hr/life_wallpaper_left\":5,\"/21617374708/novilist.hr/life_wallpaper_right\":5,\"/21617374708/novilist.hr/gospodarstvo_billboard_v1\":5,\"/21617374708/novilist.hr/vijesti_billboard_v1\":5,\"/21617374708/novilist.hr/gallery_v1\":5,\"/21617374708/novilist.hr/gallery_v2\":5,\"/21617374708/novilist.hr/gallery_v3\":5,\"/21617374708/novilist.hr/gallery_inarticle_v1\":5,\"/21617374708/novilist.hr/gallery_inarticle_v2\":5,\"/21617374708/novilist.hr/gallery_inarticle_v3\":5,\"/21617374708/novilist.hr/ticker_desktop_v1\":4,\"/21617374708/novilist.hr/sport_content_v1\":5,\"/21617374708/novilist.hr/sport_parallax_v1\":5,\"/21617374708/novilist.hr/home_top_v1\":3,\"/21617374708/novilist.hr/teads_v1\":5}" + } + ] + }, + "domains": [ + "novilist.hr" + ], + "id": "4e504e16-4d0e-4a57-bb3c-b6349d1a5653", + "last_modified": 1670498156372 + }, + { + "click": { + "optIn": "button.gdpr-lmd-button--main", + "presence": "div.gdpr-lmd-wall" + }, + "schema": 1670460532281, + "cookies": {}, + "domains": [ + "lemonde.fr" + ], + "id": "9182a6f2-eddc-43b2-991e-8b3a204e4eac", + "last_modified": 1670498156358 + }, + { + "click": { + "optIn": "button", + "presence": "div.gdprcookie" + }, + "schema": 1670460532281, + "cookies": { + "optOut": [ + { + "name": "cookieControlPrefs", + "value": "[\"essential\"]" + } + ] + }, + "domains": [ + "zamunda.net" + ], + "id": "03f54762-dc2a-4cf9-bf1b-412a68dbf0db", + "last_modified": 1670498156332 + }, + { + "click": { + "optIn": "button#save-all-action", + "optOut": "button#save-necessary-action", + "presence": "div.iOMuvDyuoMKpVRzmevldA3KUmWI2t3RJ" + }, + "schema": 1670460532281, + "cookies": {}, + "domains": [ + "veikkaus.fi" + ], + "id": "afa1eaa5-68fa-4fe8-97f0-3176c0eb6557", + "last_modified": 1670498156311 + }, + { + "click": { + "optIn": "button#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", + "optOut": "button#CybotCookiebotDialogBodyButtonDecline", + "presence": "div#CybotCookiebotDialogBodyButtonsWrapper" + }, + "schema": 1670460532281, + "cookies": {}, + "domains": [ + "bt.dk" + ], + "id": "3b604615-6d14-4ac8-b184-b9421b99a07c", + "last_modified": 1670498156308 + }, + { + "click": { + "optIn": "button.fc-cta-consent", + "presence": "div.fc-footer-buttons" + }, + "schema": 1670460532281, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgejgAPgejgAEsABCHRCkCgAAAAAAAAAA6IAAAOhQD2F2K2kKEkfjSUWYAQBCujIEIhUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgAAAAAAAQAAAAAAQQAABAAIAAAAAAAAEAQAAIAAQAAAAAAABEhCAAQQQEAAAABAAAAAAAQAAAAAABAAA.YAAAAAAAAAA%22%2C%221~%22%2C%22EA05584B-4C95-44E8-A602-F0B836FF2176%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "dnevno.hr" + ], + "id": "032bda24-ffad-4283-a36b-e1c1dd0c46d7", + "last_modified": 1670498156304 + }, + { + "click": { + "optIn": "button#ocm-button.ocm-button--accept-all", + "optOut": "button.ocm-button--continue", + "presence": "div.ocm-footer" + }, + "schema": 1670460531110, + "cookies": {}, + "domains": [ + "op.fi" + ], + "id": "86b4e119-8e5c-471d-bf80-c65489277135", + "last_modified": 1670498156241 + }, + { + "click": { + "optIn": "button.t_cm_ec_continue_button", + "optOut": "button.t_cm_ec_reject_button", + "presence": "div.t_cm_ec_modal_footer" + }, + "schema": 1670460531110, + "cookies": {}, + "domains": [ + "vodafone.com" + ], + "id": "a4cb7b9f-0a47-4fc8-ac4c-5e9d0d598531", + "last_modified": 1670498156229 + }, + { + "click": { + "optIn": "button.eu-cookie-compliance-secondary-button", + "optOut": "button.eu-cookie-compliance-default-button", + "presence": "div#popup-buttons" + }, + "schema": 1670460531110, + "cookies": { + "optIn": [ + { + "name": "cookie-agreed", + "value": "1" + } + ], + "optOut": [ + { + "name": "cookie-agreed", + "value": "0" + } + ] + }, + "domains": [ + "gsis.gr" + ], + "id": "c3f6134c-061a-4828-ab89-b6f12015a1f3", + "last_modified": 1670498156225 + }, + { + "click": { + "optIn": "button#usage_notice_button", + "presence": "div.button" + }, + "schema": 1670460530088, + "cookies": { + "optIn": [ + { + "name": "_accept_usage", + "value": "1" + } + ] + }, + "domains": [ + "censor.net" + ], + "id": "4e5a6503-adb4-4e4d-8113-f29a93bf11a7", + "last_modified": 1670498156170 + }, + { + "click": { + "optIn": "button.cookie-banner__button", + "presence": "div#cookie-banner" + }, + "schema": 1670460530088, + "cookies": { + "optIn": [ + { + "name": "tv2samtykke", + "value": "1" + } + ] + }, + "domains": [ + "tv2.no" + ], + "id": "b2a31215-49cc-4edb-801d-09d6ec435848", + "last_modified": 1670498156164 + }, + { + "click": { + "optIn": "button.css-1j8kkja", + "presence": "div.qc-cmp2-summary-buttons" + }, + "schema": 1670460530088, + "cookies": { + "optIn": [ + { + "name": "cto_bundle", + "value": "cETUWl96SkZFS1NJQUlCR3lQZlZ1WUt3Z3BaMW9qVEU1U25BaFRXdiUyQk1FeDhIWUJkVmh1b1VqY2dTbTB4bmRsaHliT3FZcFhXNFF6bU5HeSUyQkFiSVBBZ1NtVWR0b0w3T3pVR3dUTUhKeWJtSElWJTJCVDlRREl4dSUyRmRJdDBiM2M0VUs3S3R4UXI3Sjd5NENTUHFXcll1MEFGejJLdyUzRCUzRA" + } + ], + "optOut": [ + { + "name": "euconsent-v2", + "value": "CPgfy_fPgfy_fAKAnAELCkCgAAAAAH_AAAyIAAASIAJMNW4gC7MscGbQMIoEQIwrCQqgUAEFAMLRAYAODgp2VgE-sIEACAUARgRAhwBRgQCAAASAJCIAJAiwQAAAiAQAAgARCIQAMDAILACwMAgABANAxRCgAECQgyICIpTAgKgSCA1sqEEoLpDTCAOssAKCRGxUACIJARWAAICwcAwRICViwQJMUb5ACMEKAUSoVqIAAAAA.YAAAAAAAAAAA" + } + ] + }, + "domains": [ + "sport24.gr" + ], + "id": "02a059b8-09a4-488c-afa0-e208c4b8b84d", + "last_modified": 1670498156157 + }, + { + "click": { + "optIn": "button.cookie-close", + "presence": "div#cookiesBar" + }, + "schema": 1670460530088, + "cookies": { + "optIn": [ + { + "name": "cookieBarSeen", + "value": "true" + } + ] + }, + "domains": [ + "olx.ua" + ], + "id": "9944d588-486e-4f01-a5b9-17d1e9cc744a", + "last_modified": 1670498156137 + }, + { + "click": { + "optIn": "button.css-pp-ok", + "presence": "div.css-pp-box" + }, + "schema": 1670460530088, + "cookies": {}, + "domains": [ + "dagbladet.no", + "sol.no" + ], + "id": "ebbaf3a6-10bb-4cd3-8294-5b095984e21a", + "last_modified": 1670498156103 + }, + { + "click": { + "optIn": "button", + "presence": "div.fucking-eu-cookies" + }, + "schema": 1670460530088, + "cookies": { + "optIn": [ + { + "name": "fucking-eu-cookies", + "value": "1" + } + ] + }, + "domains": [ + "bazos.sk", + "bazos.cz" + ], + "id": "5cb47b97-2d77-4301-af71-0ec0b4aa0e9c", + "last_modified": 1670498156076 + }, + { + "click": { + "optIn": "button.CookieConsent_button__FJrIc", + "presence": "div.CookieConsent_holder__ZsT3G" + }, + "schema": 1670460529016, + "cookies": { + "optIn": [ + { + "name": "FCNEC", + "value": "%5B%5B%22AKsRol-hQ0GC8scayCxS70oIbLW375lxZBB419tjfwu1fOoWf-QLr5NOEOgKKLPu5Em82ydIwG4STH2DOpPVM65ZS_y2CCQlVsaUDCu-KZl2LrrYKqTftQDxBBuO-EmausJD4fO6ZHNWJEHTyKM8wJgrIadkxR4m9g%3D%3D%22%5D%2Cnull%2C%5B%5D%5D" + } + ], + "optOut": [ + { + "name": "FCCDCF", + "value": "%5Bnull%2Cnull%2Cnull%2C%5B%22CPgX9oAPgX9oAEsABCENCjCgAAAAAAAAABpYAAAOhQD2F2K2kKEkfjSUWYAQBCujIEIBUAAAAECBIAAAAUgQAgFIIAgAAlACAAAAABAQAQCAgAQABAAAoACgACAAAAAAAAAAAAQQAABAAIAAAAAAAAEAQAAAAAQAAAAAAABEhCAAQQAEAAAAAAAAAAAAAAAAAAABAAA.YAAAAAAAAAA%22%2C%221~%22%2C%22D2A36A86-FAFB-4BB0-951A-2BA964C5C018%22%5D%2Cnull%2Cnull%2C%5B%5D%5D" + } + ] + }, + "domains": [ + "kupujemprodajem.com" + ], + "id": "944c0cf0-271b-41f8-b0ca-3fa67db7af29", + "last_modified": 1670498156035 + }, + { + "click": { + "optIn": "button.js-accept", + "presence": "div.cookie-banner-buttons" + }, + "schema": 1670460529016, + "cookies": {}, + "domains": [ + "emag.ro" + ], + "id": "dbbccc0a-13ba-4cd8-9cf0-32420401be55", + "last_modified": 1670498156032 + }, + { + "click": { + "optIn": "button#accept-all", + "optOut": "button#accept-essential", + "presence": "div.actions" + }, + "schema": 1670460529016, + "cookies": { + "optOut": [ + { + "name": "policy_level", + "value": "%7B%22essential%22%3A%22true%22%2C%22performance%22%3A%22false%22%2C%22preference%22%3A%22false%22%2C%22targeting%22%3A%22false%22%7D" + } + ] + }, + "domains": [ + "skroutz.gr" + ], + "id": "fc149ac6-5a51-49fb-bb14-2c2dce8f6628", + "last_modified": 1670498156025 + }, + { + "click": { + "optIn": "button#almacmp-modalConfirmBtn", + "presence": "div.almacmp-controls" + }, + "schema": 1670460529016, + "cookies": {}, + "domains": [ + "iltalehti.fi" + ], + "id": "23fcab4a-3b43-4d73-bb9e-57d86121141b", + "last_modified": 1670498155976 + }, + { + "click": { + "optIn": "button.agree-btn", + "presence": "div.message-column main-container" + }, + "schema": 1670460528099, + "cookies": {}, + "domains": [ + "wsj.com" + ], + "id": "edc06db8-5073-11ed-bdc3-0242ac120002", + "last_modified": 1670498155928 + }, + { + "click": {}, + "schema": 1670460541263, + "cookies": { + "optOut": [ + { + "name": "bolConsentChoices", + "value": "source#OFC|version#6|int-tran#false|ext-tran#false|int-beh#false|ext-beh#false" + } + ] + }, + "domains": [ + "bol.com" + ], + "id": "637c8bb9-b2f6-493e-86c2-f1954aa6a96a", + "last_modified": 1670498155914 + }, + { + "click": { + "optIn": "button.js-cookies-hide", + "presence": "div.cookie-label" + }, + "schema": 1670460543039, + "cookies": { + "optIn": [ + { + "name": "is_agree", + "value": "1" + } + ] + }, + "domains": [ + "gordonua.com" + ], + "id": "165aa126-a069-4c6f-b6f9-c27f607e35cd", + "last_modified": 1670498155889 + }, + { + "click": { + "optIn": "button.css-fuqsbe", + "presence": "div#qc-cmp2-container" + }, + "schema": 1670460543039, + "cookies": { + "optIn": [], + "optOut": [ + { + "name": "addtl_consent", + "value": "1~" + } + ] + }, + "domains": [ + "promotions.hu" + ], + "id": "1991acaa-66fd-4929-8aa8-5d1d223703b9", + "last_modified": 1670498155875 + }, + { + "click": { + "optIn": "a.cookies-agree", + "presence": "div.cookies-notify" + }, + "schema": 1670460543039, + "cookies": {}, + "domains": [ + "frognews.bg" + ], + "id": "81742e04-b9e3-41e6-a255-0b326f3e28f9", + "last_modified": 1670498155868 + }, + { + "click": { + "optIn": "a.info-link-close", + "presence": "div#privacy-policy-link-wrapper" + }, + "schema": 1670460543039, + "cookies": { + "optIn": [ + { + "name": "PRIVACY_POLICY_INFO_2018_OPT_OUT", + "value": "yes" + } + ] + }, + "domains": [ + "uio.no" + ], + "id": "05ebb139-d68d-44fa-86a4-9b728131490f", + "last_modified": 1670498155853 + }, + { + "click": { + "optIn": "button#AcceptCookieLawButton", + "optOut": "button#RejectCookieLawButton", + "presence": "div.cookie-banner" + }, + "schema": 1670460542151, + "cookies": { + "optIn": [ + { + "name": "BDK_CookieLawAccepted", + "value": "true" + } + ], + "optOut": [ + { + "name": "BDK_CookieLawAccepted", + "value": "false" + } + ] + }, + "domains": [ + "borger.dk" + ], + "id": "f28b8cb3-c0fc-493c-a8b9-6b147babaf19", + "last_modified": 1670498155846 + }, + { + "click": { + "optIn": "a#finansavisen-gdpr-disclaimer-confirm", + "presence": "div#finansavisen-gdpr-disclaimer" + }, + "schema": 1670460542151, + "cookies": {}, + "domains": [ + "finansavisen.no" + ], + "id": "1779bdc8-3544-4a2f-ba38-026f6533d665", + "last_modified": 1670498155833 + }, + { + "click": { + "optIn": "button#modalConfirmBtn", + "presence": "div#gravitoCMP-modal-layer1" + }, + "schema": 1670460542151, + "cookies": {}, + "domains": [ + "verkkouutiset.fi" + ], + "id": "de4a2156-043d-431f-976c-03c2cd94ce2b", + "last_modified": 1670498155820 + }, + { + "click": { + "optIn": "a.cc-cookie-accept", + "presence": "div.cc-cookies" + }, + "schema": 1670460542151, + "cookies": { + "optIn": [ + { + "name": "cc_cookie_accept", + "value": "cc_cookie_accept" + } + ] + }, + "domains": [ + "petel.bg", + "sagepub.com" + ], + "id": "d4117908-bde7-4950-a41b-188ebd8331ad", + "last_modified": 1670498155810 + }, + { + "click": { + "optIn": "button.rounded-button--tertiary", + "presence": "div.legal-consent" + }, + "schema": 1670460542151, + "cookies": { + "optIn": [ + { + "name": "mal_consent_gdpr_remarketing", + "value": "t" + }, + { + "name": "mal_consent_gdpr_personalization", + "value": "t" + } + ], + "optOut": [ + { + "name": "mal_consent_gdpr_remarketing", + "value": "f" + }, + { + "name": "mal_consent_gdpr_personalization", + "value": "f" + } + ] + }, + "domains": [ + "mall.sk" + ], + "id": "16f5fdfe-81a4-435b-a3d2-5e20d697e1f0", + "last_modified": 1670498155773 + }, + { + "click": { + "optIn": "p.cookie-policy-btn", + "presence": "div.cookie-policy" + }, + "schema": 1670460542151, + "cookies": { + "optIn": [ + { + "name": "cookiePolicyConfirmation", + "value": "true" + } + ] + }, + "domains": [ + "halooglasi.com" + ], + "id": "a5b18ba3-6821-41c7-b9f3-c69352618b72", + "last_modified": 1670498155769 + }, + { + "click": { + "optIn": "button#footer_tc_privacy_button_2", + "optOut": "button#footer_tc_privacy_button_3", + "presence": "div#tc-privacy-wrapper" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "cdiscount.com" + ], + "id": "1871561d-65f8-4972-8e8a-84fa9eb704b4", + "last_modified": 1670498155756 + }, + { + "click": { + "optIn": "button#accept-cookies", + "optOut": "button#decline-cookies", + "presence": "div#cookie-popup" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "ah.nl" + ], + "id": "0793f76e-8e2c-46ec-9aac-111829c4c3ec", + "last_modified": 1670498155736 + }, + { + "click": { + "optIn": "button.coi-banner__accept", + "presence": "div#coi-banner-wrapper" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "telia.fi" + ], + "id": "c7991afb-190d-4c47-8080-5e2006a13dfd", + "last_modified": 1670498155730 + }, + { + "click": { + "optIn": "button", + "presence": "div.politic_confidel" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "today.ua" + ], + "id": "3c907093-136f-41e1-b0e8-b6613cab8510", + "last_modified": 1670498155726 + }, + { + "click": { + "optIn": "button.cookie-alert-extended-button", + "optOut": "button.cookie-alert-decline-button", + "presence": "div.cookie-alert-extended-controls" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "lidl.sk" + ], + "id": "fd966b51-7e62-402c-8963-70a5bc537605", + "last_modified": 1670498155720 + }, + { + "click": { + "optIn": "button#cookie-banner-initial-accept-all", + "presence": "div.cookie-initial-buttons" + }, + "schema": 1670460541263, + "cookies": { + "optIn": [ + { + "name": "SSLB", + "value": "1" + }, + { + "name": "s_cc", + "value": "true" + } + ], + "optOut": [] + }, + "domains": [ + "swedbank.se" + ], + "id": "f3afe889-1fbe-4c24-b62d-ca1bd6c5f636", + "last_modified": 1670498155697 + }, + { + "click": { + "optIn": "button[name=accept_cookie]", + "optOut": "button[name=decline_cookie]", + "presence": ".js-cookie-notification__form" + }, + "schema": 1670460541263, + "cookies": { + "optOut": [ + { + "name": "cookie-preferences", + "value": "eyJmdW5jdGlvbmFsIjp0cnVlLCJhbmFseXRpY2FsIjp0cnVlLCJtYXJrZXRpbmciOmZhbHNlfQ%3D%3D" + } + ] + }, + "domains": [ + "coolblue.nl" + ], + "id": "1bf6fa4e-7882-41cf-b5e1-ef3c91485006", + "last_modified": 1670498155694 + }, + { + "click": { + "optIn": "button.fc-button", + "presence": "div.fc-consent-root" + }, + "schema": 1670460541263, + "cookies": {}, + "domains": [ + "offnews.bg" + ], + "id": "bd5a5d6d-172e-4386-8698-1c28e83e38e5", + "last_modified": 1670498155690 + }, + { + "click": { + "optIn": "button.iubenda-cs-accept-btn", + "presence": "div#iubenda-cs-banner" + }, + "schema": 1670460543039, + "cookies": {}, + "domains": [ + "lastampa.it" + ], + "id": "999c2b81-8c59-43d0-b73e-cd76b71a2b7c", + "last_modified": 1670498155651 + }, + { + "schema": 1670460527205, + "cookies": { + "optIn": [ + { + "name": "d_prefs", + "value": "MToxLGNvbnNlbnRfdmVyc2lvbjoyLHRleHRfdmVyc2lvbjoxMDAw" + } + ], + "optOut": [ + { + "name": "d_prefs", + "value": "MjoxLGNvbnNlbnRfdmVyc2lvbjoyLHRleHRfdmVyc2lvbjoxMDAw" + } + ] + }, + "domains": [ + "twitter.com" + ], + "id": "05b3b417-c4c7-4ed0-a3cf-43053e8b33ab", + "last_modified": 1670498155641 + } + ], + "timestamp": 1710331175432 +} diff --git a/services/settings/dumps/main/devtools-compatibility-browsers.json b/services/settings/dumps/main/devtools-compatibility-browsers.json new file mode 100644 index 0000000000..013e451d25 --- /dev/null +++ b/services/settings/dumps/main/devtools-compatibility-browsers.json @@ -0,0 +1,284 @@ +{ + "data": [ + { + "name": "WebView Android", + "schema": 1710547503853, + "status": "beta", + "version": "124", + "browserid": "webview_android", + "id": "2dd2f70c-7c64-4aee-a05e-a0eb05d29ed5", + "last_modified": 1710742064656 + }, + { + "name": "Chrome", + "schema": 1710547503371, + "status": "beta", + "version": "124", + "browserid": "chrome", + "id": "f26cb7b0-cf29-4c33-8fb7-a2cc466a7e3c", + "last_modified": 1710742064653 + }, + { + "name": "Chrome", + "schema": 1710547503307, + "status": "current", + "version": "123", + "browserid": "chrome", + "id": "66e79095-2721-4a9e-9139-1a5f1afe1311", + "last_modified": 1710742064649 + }, + { + "name": "WebView Android", + "schema": 1710547503750, + "status": "current", + "version": "123", + "browserid": "webview_android", + "id": "97f38fec-64b0-4f0f-addc-03ac1b11d512", + "last_modified": 1710742064638 + }, + { + "name": "Chrome Android", + "schema": 1710547503606, + "status": "beta", + "version": "124", + "browserid": "chrome_android", + "id": "6946fbe1-bfff-4b64-aead-f001f0e486de", + "last_modified": 1710742064634 + }, + { + "name": "Chrome Android", + "schema": 1710547503508, + "status": "current", + "version": "123", + "browserid": "chrome_android", + "id": "a0ce8ad2-5c66-4e9e-987a-8a66c30e13f5", + "last_modified": 1710742064631 + }, + { + "name": "Edge", + "schema": 1710374703056, + "status": "planned", + "version": "125", + "browserid": "edge", + "id": "f1147d5f-d690-43d0-879d-117c6ca24a16", + "last_modified": 1710422368047 + }, + { + "name": "Edge", + "schema": 1710174339301, + "status": "beta", + "version": "123", + "browserid": "edge", + "id": "d0c3d84f-8d27-455b-8746-7e25607e5b78", + "last_modified": 1710422368043 + }, + { + "name": "Edge", + "schema": 1710374702983, + "status": "nightly", + "version": "124", + "browserid": "edge", + "id": "3837dc37-38b7-483b-82b3-c5593e7a4c91", + "last_modified": 1710422368040 + }, + { + "name": "Safari", + "schema": 1709769903331, + "status": "current", + "version": "17.4", + "browserid": "safari", + "id": "380463f2-6aa8-499e-b186-5f80cf4cc573", + "last_modified": 1709801393999 + }, + { + "name": "Safari on iOS", + "schema": 1709769903472, + "status": "current", + "version": "17.4", + "browserid": "safari_ios", + "id": "b99772c5-e366-42c6-b2db-330aca62dba5", + "last_modified": 1709801393995 + }, + { + "name": "Edge", + "schema": 1709424307170, + "status": "current", + "version": "122", + "browserid": "edge", + "id": "5ad01f1f-b345-4f24-9b88-4f456a928c76", + "last_modified": 1709536180465 + }, + { + "name": "Firefox", + "schema": 1709078707232, + "status": "planned", + "version": "126", + "browserid": "firefox", + "id": "70b05b0b-bbef-486c-901a-ea3221a28fc1", + "last_modified": 1709113112619 + }, + { + "name": "Firefox for Android", + "schema": 1709078707604, + "status": "planned", + "version": "126", + "browserid": "firefox_android", + "id": "b77524e9-58dc-4196-acbd-41dddc4daea2", + "last_modified": 1709113112616 + }, + { + "name": "Firefox", + "schema": 1709078707068, + "status": "beta", + "version": "124", + "browserid": "firefox", + "id": "7f93cadc-411f-4e31-938c-cc5bfc173f85", + "last_modified": 1709113112610 + }, + { + "name": "Firefox for Android", + "schema": 1709078707459, + "status": "beta", + "version": "124", + "browserid": "firefox_android", + "id": "1b3c619a-5ca8-4f5f-8f0b-57a3a27a589f", + "last_modified": 1709113112607 + }, + { + "name": "Firefox for Android", + "schema": 1709078707371, + "status": "current", + "version": "123", + "browserid": "firefox_android", + "id": "d9b7089d-7091-4794-9051-49f794c0c854", + "last_modified": 1709113112596 + }, + { + "name": "Firefox", + "schema": 1709078706998, + "status": "current", + "version": "123", + "browserid": "firefox", + "id": "2aed6f85-b118-4e91-b95a-481030c3bb78", + "last_modified": 1709113112593 + }, + { + "name": "Firefox for Android", + "schema": 1709078707531, + "status": "nightly", + "version": "125", + "browserid": "firefox_android", + "id": "a8f570e9-574d-4193-891f-fa8e0a875388", + "last_modified": 1709113112588 + }, + { + "name": "Firefox", + "schema": 1709078707153, + "status": "nightly", + "version": "125", + "browserid": "firefox", + "id": "5ae5bd40-deb0-40c4-bba6-cd411b78ee16", + "last_modified": 1709113112583 + }, + { + "name": "Opera", + "schema": 1708473904223, + "status": "beta", + "version": "108", + "browserid": "opera", + "id": "765d6fa9-857a-4918-9a85-fd40f73c9159", + "last_modified": 1708507560606 + }, + { + "name": "Opera", + "schema": 1708473904160, + "status": "current", + "version": "107", + "browserid": "opera", + "id": "8d9d78bb-ebc3-4931-a98b-bdefc339061d", + "last_modified": 1708507560573 + }, + { + "name": "Deno", + "schema": 1706659507818, + "status": "current", + "version": "1.40", + "browserid": "deno", + "id": "15c381ea-e194-48f1-99dc-3d8cd2ffdcfb", + "last_modified": 1706695152746 + }, + { + "name": "Opera Android", + "schema": 1706313908456, + "status": "current", + "version": "80", + "browserid": "opera_android", + "id": "534dc626-14f0-479e-9912-043e3da31e91", + "last_modified": 1706515991120 + }, + { + "name": "Node.js", + "schema": 1702685107693, + "status": "esr", + "version": "20.10.0", + "browserid": "nodejs", + "id": "0d78585f-e9e0-41e8-816a-22461158e36d", + "last_modified": 1702890886929 + }, + { + "name": "Node.js", + "schema": 1700957104515, + "status": "current", + "version": "21.2.0", + "browserid": "nodejs", + "id": "b9979c74-bcbd-478d-99bc-55b92fcb2833", + "last_modified": 1701078578067 + }, + { + "name": "Samsung Internet", + "schema": 1700352303815, + "status": "current", + "version": "23.0", + "browserid": "samsunginternet_android", + "id": "b6194ce4-1588-4c83-9a7c-54081c540d01", + "last_modified": 1700557930484 + }, + { + "name": "Firefox for Android", + "schema": 1698797104411, + "status": "esr", + "version": "115", + "browserid": "firefox_android", + "id": "0c24ad2e-0107-4aee-a2ef-3e6d6d089fd8", + "last_modified": 1699453952078 + }, + { + "name": "Node.js", + "schema": 1695216243625, + "status": "esr", + "version": "18.17.0", + "browserid": "nodejs", + "id": "92a501a5-a84d-48d8-a1d0-749febc58d5f", + "last_modified": 1695282430511 + }, + { + "name": "Firefox", + "schema": 1692797056261, + "status": "esr", + "version": "115", + "browserid": "firefox", + "id": "f8bed161-4be5-435b-9b42-7a7eafd672b7", + "last_modified": 1692814731149 + }, + { + "name": "Quest Browser", + "schema": 1665650596430, + "status": "current", + "version": "23.0", + "browserid": "oculus", + "id": "371ced3b-8ff1-4540-a230-bc1827964bda", + "last_modified": 1665656484764 + } + ], + "timestamp": 1710742064656 +} diff --git a/services/settings/dumps/main/devtools-devices.json b/services/settings/dumps/main/devtools-devices.json new file mode 100644 index 0000000000..f3f108fb39 --- /dev/null +++ b/services/settings/dumps/main/devtools-devices.json @@ -0,0 +1,635 @@ +{ + "data": [ + { + "os": "custom", + "name": "1080p Full HD Television", + "type": "televisions", + "touch": false, + "width": 1920, + "height": 1080, + "schema": 1653402055990, + "featured": false, + "userAgent": "", + "pixelRatio": "1", + "id": "fdc3221b-38e3-40a6-8497-6a9259a8d6b4", + "last_modified": 1653469171354 + }, + { + "os": "custom", + "name": "4K Ultra HD Television", + "type": "televisions", + "touch": false, + "width": 3840, + "height": 2160, + "schema": 1653402055166, + "featured": false, + "userAgent": "", + "pixelRatio": "1", + "id": "3c1e2dbc-a9c3-4302-9d14-5c01f56f8dba", + "last_modified": 1653469171351 + }, + { + "os": "", + "name": "Laptop with MDPI screen", + "type": "laptops", + "touch": false, + "width": 1280, + "height": 800, + "schema": 1653402054240, + "featured": false, + "userAgent": "", + "pixelRatio": "1", + "id": "77794a97-2c31-4a76-be20-897a1b91e534", + "last_modified": 1653469171347 + }, + { + "os": "", + "name": "Laptop with touch", + "type": "laptops", + "touch": true, + "width": 1280, + "height": 950, + "schema": 1653402053431, + "featured": false, + "userAgent": "", + "pixelRatio": "1", + "id": "9a1e862c-2cf0-493b-8755-9d8702b8cdbd", + "last_modified": 1653469171344 + }, + { + "os": "", + "name": "Laptop with HiDPI screen", + "type": "laptops", + "touch": false, + "width": 1440, + "height": 900, + "schema": 1653402052509, + "featured": false, + "userAgent": "", + "pixelRatio": "2", + "id": "194d32f6-88a8-451e-87a4-99e91b5426e0", + "last_modified": 1653469171341 + }, + { + "os": "iOS", + "name": "iPad Air", + "type": "tablets", + "touch": true, + "width": 820, + "height": 1180, + "schema": 1653402051661, + "featured": false, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "8c353425-dbdc-4497-a790-53df6c207e6e", + "last_modified": 1653469171338 + }, + { + "os": "custom", + "name": "720p HD Television", + "type": "televisions", + "touch": false, + "width": 1280, + "height": 720, + "schema": 1653402050864, + "featured": false, + "userAgent": "", + "pixelRatio": "1", + "id": "836e7e6c-2b2c-429c-852f-3d6659130c38", + "last_modified": 1653469171335 + }, + { + "os": "", + "name": "iPad Mini", + "type": "tablets", + "touch": true, + "width": 768, + "height": 1024, + "schema": 1653402049972, + "featured": false, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "0f41a7a2-6916-423a-b64a-047d0680a2a3", + "last_modified": 1653469171331 + }, + { + "os": "iOS", + "name": "iPad Pro (11-inch)", + "type": "tablets", + "touch": true, + "width": 834, + "height": 1194, + "schema": 1653402049176, + "featured": false, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "d3be568b-b9fb-4995-809e-9464b8e2a820", + "last_modified": 1653469171328 + }, + { + "os": "iOS", + "name": "iPad Pro (12.9-inch)", + "type": "tablets", + "touch": true, + "width": 1024, + "height": 1366, + "schema": 1653402048304, + "featured": false, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "95a71660-3141-4bd0-ae24-d5b49504c3f7", + "last_modified": 1653469171325 + }, + { + "os": "", + "name": "Nexus 10", + "type": "tablets", + "touch": true, + "width": 800, + "height": 1280, + "schema": 1653402047489, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "pixelRatio": "2", + "id": "38fcf855-b796-4e11-9565-0cfc0e11d21e", + "last_modified": 1653469171322 + }, + { + "os": "", + "name": "iPad", + "type": "tablets", + "touch": true, + "width": 810, + "height": 1080, + "schema": 1653402046688, + "featured": true, + "userAgent": "Mozilla/5.0 (iPad; CPU OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "dc9416b1-958f-4711-8b13-ccc4871efba0", + "last_modified": 1653469171319 + }, + { + "os": "iOS", + "name": "iPhone 12/13 mini", + "type": "phones", + "touch": true, + "width": 375, + "height": 812, + "schema": 1653402045852, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "3", + "id": "1fdbcd95-65b2-407b-b289-38195d966295", + "last_modified": 1653469171316 + }, + { + "os": "", + "name": "Kindle Fire HDX", + "type": "tablets", + "touch": true, + "width": 800, + "height": 1280, + "schema": 1653402045017, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true", + "pixelRatio": "2", + "id": "b1bd6309-985f-42b6-a52c-9a7bc1ed17db", + "last_modified": 1653469171313 + }, + { + "os": "iOS", + "name": "iPhone 12/13 + Pro", + "type": "phones", + "touch": true, + "width": 390, + "height": 844, + "schema": 1653402044074, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "3", + "id": "b00248a3-cb9c-47c6-ae45-b22462094974", + "last_modified": 1653469171310 + }, + { + "os": "iOS", + "name": "iPhone 12/13 Pro Max", + "type": "phones", + "touch": true, + "width": 428, + "height": 926, + "schema": 1653402043230, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "3", + "id": "2352b72f-c2bf-4cdf-9635-219c4b59414f", + "last_modified": 1653469171307 + }, + { + "os": "iOS", + "name": "iPhone 11 Pro Max", + "type": "phones", + "touch": true, + "width": 414, + "height": 896, + "schema": 1653402042024, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "3", + "id": "6cd66c7f-6dcb-4aeb-951a-52204be9b33d", + "last_modified": 1653469171304 + }, + { + "os": "iOS", + "name": "iPhone SE 2nd gen", + "type": "phones", + "touch": true, + "width": 375, + "height": 667, + "schema": 1653402041203, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "2", + "id": "1a568a84-e52e-45f1-a30f-f66bec3f99f8", + "last_modified": 1653469171301 + }, + { + "os": "iOS", + "name": "iPhone XS Max", + "type": "phones", + "touch": true, + "width": 414, + "height": 896, + "schema": 1653402040241, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1", + "pixelRatio": "3", + "id": "09f6a69f-7d40-469f-a052-dbee6f260889", + "last_modified": 1653469171298 + }, + { + "os": "iOS", + "name": "iPhone 11 Pro", + "type": "phones", + "touch": true, + "width": 375, + "height": 812, + "schema": 1653402039442, + "featured": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1", + "pixelRatio": "3", + "id": "c46bde65-07b8-41a0-98f7-bea4a3802074", + "last_modified": 1653469171295 + }, + { + "os": "iOS", + "name": "iPhone XR/11", + "type": "phones", + "touch": true, + "width": 414, + "height": 896, + "schema": 1653402038431, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1", + "pixelRatio": "2", + "id": "e0fb4908-066e-4753-bfa8-9a70d6c61832", + "last_modified": 1653469171291 + }, + { + "os": "iOS", + "name": "iPhone 6/7/8 Plus", + "type": "phones", + "touch": true, + "width": 414, + "height": 736, + "schema": 1653402037544, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1", + "pixelRatio": "3", + "id": "a163f231-2560-4e5e-b88c-e5b2aecb2944", + "last_modified": 1653469171288 + }, + { + "os": "iOS", + "name": "iPhone X/XS", + "type": "phones", + "touch": true, + "width": 375, + "height": 812, + "schema": 1653402036680, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/12.0 Mobile/15A372 Safari/604.1", + "pixelRatio": "3", + "id": "2375f7f9-e2b0-42d1-8a97-e5fe2dbdc8f0", + "last_modified": 1653469171285 + }, + { + "os": "", + "name": "Pixel 5", + "type": "phones", + "touch": true, + "width": 393, + "height": 851, + "schema": 1653402035842, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36", + "pixelRatio": "2.75", + "id": "8fb63fed-70f1-4be2-8630-00a3a3297ae1", + "last_modified": 1653469171282 + }, + { + "os": "iOS", + "name": "iPhone 6/7/8", + "type": "phones", + "touch": true, + "width": 375, + "height": 667, + "schema": 1653402034968, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1", + "pixelRatio": "2", + "id": "fd20c21d-2cf7-45bb-89c2-123f123242b1", + "last_modified": 1653469171279 + }, + { + "os": "iOS", + "name": "iPhone 5/SE", + "type": "phones", + "touch": true, + "width": 320, + "height": 568, + "schema": 1653402034037, + "featured": false, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1", + "pixelRatio": "2", + "id": "8aa68ae4-1515-466a-86ca-a4b544189863", + "last_modified": 1653469171276 + }, + { + "os": "KaiOS", + "name": "Nokia 8110 4G", + "type": "phones", + "touch": true, + "width": 240, + "height": 320, + "schema": 1653402033208, + "featured": false, + "userAgent": "Mozilla/5.0 (Mobile; Nokia 8110 4G; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5", + "pixelRatio": "1", + "id": "50677b6f-ffb3-4714-b0d0-f5621dec5d51", + "last_modified": 1653469171273 + }, + { + "os": "", + "name": "Pixel 2", + "type": "phones", + "touch": true, + "width": 411, + "height": 731, + "schema": 1653402032349, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "2.625", + "id": "9d07cebc-4d40-45cc-8e94-6385c1f24dd0", + "last_modified": 1653469171270 + }, + { + "os": "", + "name": "Pixel 2 XL", + "type": "phones", + "touch": true, + "width": 411, + "height": 823, + "schema": 1653402031510, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "3.5", + "id": "5cf20040-4d25-42e2-a4d8-c6b83927ec84", + "last_modified": 1653469171267 + }, + { + "os": "", + "name": "Nexus 5X", + "type": "phones", + "touch": true, + "width": 412, + "height": 732, + "schema": 1653402030677, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "2.625", + "id": "33c1bdd3-892b-441c-8168-dc2d82bc1c61", + "last_modified": 1653469171264 + }, + { + "os": "", + "name": "Nexus 6P", + "type": "phones", + "touch": true, + "width": 412, + "height": 732, + "schema": 1653402029836, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "3.5", + "id": "378c650b-331d-430d-bd13-d6fcab803bfe", + "last_modified": 1653469171261 + }, + { + "os": "", + "name": "LG Optimus L70", + "type": "phones", + "touch": true, + "width": 384, + "height": 640, + "schema": 1653402029013, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "1.25", + "id": "9d0f9ec1-5e69-489e-a1d9-27dbd4a7a583", + "last_modified": 1653469171258 + }, + { + "os": "", + "name": "Microsoft Lumia 950", + "type": "phones", + "touch": true, + "width": 360, + "height": 640, + "schema": 1653402028123, + "featured": false, + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263", + "pixelRatio": "4", + "id": "21498efe-cd28-40b9-85ce-7468d159d674", + "last_modified": 1653469171255 + }, + { + "os": "Android", + "name": "Galaxy Note 20 Ultra", + "type": "phones", + "touch": true, + "width": 412, + "height": 883, + "schema": 1653402027292, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "3.5", + "id": "54044ca7-882c-4372-bd5b-250f746f558d", + "last_modified": 1653469171252 + }, + { + "os": "Android", + "name": "Galaxy S20", + "type": "phones", + "touch": true, + "width": 360, + "height": 800, + "schema": 1653402026421, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "4", + "id": "fa90254c-1024-49a0-9ff5-5b7abcd1839a", + "last_modified": 1653469171248 + }, + { + "os": "Android", + "name": "Galaxy S20 Ultra", + "type": "phones", + "touch": true, + "width": 412, + "height": 915, + "schema": 1653402025526, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "3.5", + "id": "74de352e-4de2-4df7-939b-44007425ceaa", + "last_modified": 1653469171245 + }, + { + "os": "Android", + "name": "Galaxy S20+", + "type": "phones", + "touch": true, + "width": 384, + "height": 854, + "schema": 1653402024581, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "3.75", + "id": "952f7ed0-e9aa-452f-af8f-99409ef382cc", + "last_modified": 1653469171242 + }, + { + "os": "Android", + "name": "Galaxy S10/S10+", + "type": "phones", + "touch": false, + "width": 360, + "height": 760, + "schema": 1653402023695, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "4", + "id": "2ddff760-91c4-4983-ba9b-681fa36b69fb", + "last_modified": 1653469171239 + }, + { + "os": "", + "name": "Galaxy S5", + "type": "phones", + "touch": true, + "width": 360, + "height": 640, + "schema": 1653402022022, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "3", + "id": "8644e66e-6369-4470-84e6-8376622a7e4d", + "last_modified": 1653469171236 + }, + { + "os": "", + "name": "Galaxy Note 3", + "type": "phones", + "touch": true, + "width": 360, + "height": 640, + "schema": 1653402021138, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "pixelRatio": "3", + "id": "185a17fe-82a5-45bb-b745-9b532e608baf", + "last_modified": 1653469171233 + }, + { + "os": "Android", + "name": "Galaxy Note 9", + "type": "phones", + "touch": true, + "width": 414, + "height": 846, + "schema": 1653402020220, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "3.5", + "id": "cc89dcde-38dc-47cc-895d-6558b9d02f54", + "last_modified": 1653469171230 + }, + { + "os": "", + "name": "Microsoft Lumia 550", + "type": "phones", + "touch": true, + "width": 360, + "height": 640, + "schema": 1653402019422, + "featured": false, + "userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263", + "pixelRatio": "2", + "id": "b447bc92-6bef-4ac3-b56c-102a725d508c", + "last_modified": 1653469171227 + }, + { + "os": "Android", + "name": "Galaxy S9/S9+", + "type": "phones", + "touch": true, + "width": 360, + "height": 740, + "schema": 1653402018618, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/67.0.3396.87 Mobile Safari/537.36", + "pixelRatio": "4", + "id": "c1202c45-5e1b-4210-83cc-f6ad0370ddc0", + "last_modified": 1653469171224 + }, + { + "os": "Android", + "name": "Galaxy Note 20", + "type": "phones", + "touch": true, + "width": 412, + "height": 915, + "schema": 1653402017748, + "featured": true, + "userAgent": "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36", + "pixelRatio": "2.625", + "id": "8f0f0507-ce41-40e8-b4bc-b569dd4fec13", + "last_modified": 1653469171221 + }, + { + "os": "", + "name": "Nexus 7", + "type": "tablets", + "touch": true, + "width": 600, + "height": 960, + "schema": 1653401612907, + "featured": false, + "userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "pixelRatio": "2", + "id": "470c91dc-2239-4718-a6e9-7c018cf870f1", + "last_modified": 1653469171218 + } + ], + "timestamp": 1653469171354 +} diff --git a/services/settings/dumps/main/example.json b/services/settings/dumps/main/example.json new file mode 100644 index 0000000000..ac297f53d3 --- /dev/null +++ b/services/settings/dumps/main/example.json @@ -0,0 +1,4 @@ +{ + "data": [], + "timestamp": 1337 +}
\ No newline at end of file diff --git a/services/settings/dumps/main/hijack-blocklists.json b/services/settings/dumps/main/hijack-blocklists.json new file mode 100644 index 0000000000..9cf00ee773 --- /dev/null +++ b/services/settings/dumps/main/hijack-blocklists.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "schema": 1605793537508, + "matches": [ + "[https]opensearch.startpageweb.com/bing-search.xml", + "[https]opensearch.startwebsearch.com/bing-search.xml", + "[https]opensearch.webstartsearch.com/bing-search.xml", + "[https]opensearch.webofsearch.com/bing-search.xml", + "[profile]/searchplugins/Yahoo! Powered.xml", + "[profile]/searchplugins/yahoo! powered.xml", + "[profile]/searchplugins/bing-lavasoft-ff59.xml" + ], + "id": "load-paths", + "last_modified": 1605801189258 + }, + { + "schema": 1605793528951, + "matches": [ + "hspart=lvs", + "pc=COS", + "clid=2308146", + "fr=mca", + "PC=MC0", + "lavasoft.gosearchresults", + "securedsearch.lavasoft", + "fr=mcsaoffblock", + "fr=jnazafzv", + "clid=2285101", + "pc=mc", + "//defaultsearch.co/", + "//searchdefault.co/" + ], + "id": "submission-urls", + "last_modified": 1605801189254 + }, + { + "schema": 1605728369453, + "matches": [ + "hspart=lvs", + "pc=COS", + "clid=2308146", + "fr=mca", + "PC=MC0", + "lavasoft.gosearchresults", + "securedsearch.lavasoft", + "fr=mcsaoffblock", + "fr=jnazafzv", + "clid=2285101", + "pc=mc", + "//defaultsearch.co/", + "//searchdefault.co/" + ], + "id": "homepage-urls", + "last_modified": 1605801189250 + } + ], + "timestamp": 1605801189258 +} diff --git a/services/settings/dumps/main/language-dictionaries.json b/services/settings/dumps/main/language-dictionaries.json new file mode 100644 index 0000000000..6a74b25d22 --- /dev/null +++ b/services/settings/dumps/main/language-dictionaries.json @@ -0,0 +1,647 @@ +{ + "data": [ + { + "schema": 1672245423392, + "dictionaries": [ + "en-US-mozilla@dictionaries.addons.mozilla.org" + ], + "id": "en-US", + "last_modified": 1673270322227 + }, + { + "schema": 1569354115694, + "dictionaries": [ + "ca-valencia@dictionaries.addons.mozilla.org" + ], + "id": "ca-valencia", + "last_modified": 1569410800356 + }, + { + "schema": 1557258118159, + "dictionaries": [ + "bn-BD@dictionaries.addons.mozilla.org" + ], + "id": "bn", + "last_modified": 1557402267392 + }, + { + "schema": 1546371712765, + "dictionaries": [ + "pt-BR@dictionaries.addons.mozilla.org" + ], + "id": "pt-BR", + "last_modified": 1546445786254 + }, + { + "schema": 1539698850177, + "dictionaries": [ + "xh-ZA@dictionaries.addons.mozilla.org" + ], + "id": "xh", + "last_modified": 1539698851548 + }, + { + "schema": 1539698850177, + "dictionaries": [ + "dictionary@vi.mozdev.org" + ], + "id": "vi", + "last_modified": 1539698851493 + }, + { + "schema": 1539698850177, + "dictionaries": [ + "uz@dellalibera.sf.net" + ], + "id": "uz", + "last_modified": 1539698851437 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "ur@dictionaries.addons.mozilla.org" + ], + "id": "ur", + "last_modified": 1539698850171 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "uk-ua@dictionaries.addons.mozilla.org" + ], + "id": "uk", + "last_modified": 1539698850116 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "tr-fix@dictionaries.addons.mozilla.org" + ], + "id": "tr", + "last_modified": 1539698850060 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "indlinux-telugu@lists.sourceforge.net" + ], + "id": "te", + "last_modified": 1539698850002 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "mugunth@thamizha.com" + ], + "id": "ta", + "last_modified": 1539698849944 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "swedish@dictionaries.addons.mozilla.org" + ], + "id": "sv-SE", + "last_modified": 1539698849888 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "sr-RS@dictionaries.addons.mozilla.org" + ], + "id": "sr", + "last_modified": 1539698849834 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "sq-AL@dictionaries.addons.mozilla.org" + ], + "id": "sq", + "last_modified": 1539698849779 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "son-ML@dictionaries.addons.mozilla.org" + ], + "id": "son", + "last_modified": 1539698849726 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "sl@dictionaries.addons.mozilla.org" + ], + "id": "sl", + "last_modified": 1539698849672 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "sk@dictionaries.addons.mozilla.org" + ], + "id": "sk", + "last_modified": 1539698849619 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "ru@dictionaries.addons.mozilla.org" + ], + "id": "ru", + "last_modified": 1539698849564 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "ro-RO@www.archeus.ro" + ], + "id": "ro", + "last_modified": 1539698849510 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "pt-PT@dictionaries.addons.mozilla.org" + ], + "id": "pt-PT", + "last_modified": 1539698849457 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "pl@dictionaries.addons.mozilla.org" + ], + "id": "pl", + "last_modified": 1539698849351 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "pa_IN@dellalibera.sf.net" + ], + "id": "pa-IN", + "last_modified": 1539698849297 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "or_IN@dellalibera.sf.net" + ], + "id": "or", + "last_modified": 1539698849241 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "nn-NO@dictionaries.addons.mozilla.org" + ], + "id": "nn-NO", + "last_modified": 1539698849187 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "nl-NL@dictionaries.addons.mozilla.org" + ], + "id": "nl", + "last_modified": 1539698849133 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "ne-NP@dictionaries.addons.mozilla.org" + ], + "id": "ne-NP", + "last_modified": 1539698849077 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "nb-NO@dictionaries.addons.mozilla.org" + ], + "id": "nb-NO", + "last_modified": 1539698849022 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "MyEja@xanda.org" + ], + "id": "ms", + "last_modified": 1539698848966 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "mr-IN@dictionaries.addons.mozilla.org" + ], + "id": "mr", + "last_modified": 1539698848910 + }, + { + "schema": 1539698847383, + "dictionaries": [ + "mk-MK@dictionaries.addons.mozilla.org" + ], + "id": "mk", + "last_modified": 1539698848856 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "lv-LV@dictionaries.addons.mozilla.org" + ], + "id": "lv", + "last_modified": 1539698847376 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "lt@dictionaries.addons.mozilla.org" + ], + "id": "lt", + "last_modified": 1539698847321 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "lo@dictionaries.addons.mozilla.org" + ], + "id": "lo", + "last_modified": 1539698847259 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "ko-KR@dictionaries.addons.mozilla.org" + ], + "id": "ko", + "last_modified": 1539698847204 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "kn@dictionaries.addons.mozilla.org" + ], + "id": "kn", + "last_modified": 1539698847151 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "ka-GE@dictionaries.addons.mozilla.org" + ], + "id": "ka", + "last_modified": 1539698847096 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "it-IT@dictionaries.addons.mozilla.org" + ], + "id": "it", + "last_modified": 1539698847039 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "is@dictionaries.addons.mozilla.org" + ], + "id": "is", + "last_modified": 1539698846985 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "id@ewesewes.net" + ], + "id": "id", + "last_modified": 1539698846932 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "ia-xx@dictionaries.addons.mozilla.org" + ], + "id": "ia", + "last_modified": 1539698846878 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "am@dictionaries.addons.mozilla.org" + ], + "id": "hy-AM", + "last_modified": 1539698846821 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "hu@dictionaries.addons.mozilla.org" + ], + "id": "hu", + "last_modified": 1539698846767 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "hsb@dictionaries.addons.mozilla.org" + ], + "id": "hsb", + "last_modified": 1539698846709 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "hr-HR-2@dictionaries.addons.mozilla.org" + ], + "id": "hr", + "last_modified": 1539698846655 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "hi-IN@dellalibera.sf.net" + ], + "id": "hi-IN", + "last_modified": 1539698846599 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "he@dictionaries.addons.mozilla.org" + ], + "id": "he", + "last_modified": 1539698846544 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "gu-IN@dellalibera.sf.net" + ], + "id": "gu-IN", + "last_modified": 1539698846488 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "gl-es@dictionaries.addons.mozilla.org" + ], + "id": "gl", + "last_modified": 1539698846434 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "gd-GB@dictionaries.addons.mozilla.org" + ], + "id": "gd", + "last_modified": 1539698846381 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "ga-IE@dictionaries.addons.mozilla.org" + ], + "id": "ga-IE", + "last_modified": 1539698846327 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "fy@dictionaries.addons.mozilla.org" + ], + "id": "fy-NL", + "last_modified": 1539698846271 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "fr-dicollecte@dictionaries.addons.mozilla.org" + ], + "id": "fr", + "last_modified": 1539698846217 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "fa@dictionaries.addons.mozilla.org" + ], + "id": "fa", + "last_modified": 1539698846162 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "eu@dictionaries.addons.mozilla.org" + ], + "id": "eu", + "last_modified": 1539698846108 + }, + { + "schema": 1539698844602, + "dictionaries": [ + "et-EE@dictionaries.addons.mozilla.org" + ], + "id": "et", + "last_modified": 1539698846053 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "diccionario@mozilla-mexico.org" + ], + "id": "es-MX", + "last_modified": 1539698844595 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "es-es@dictionaries.addons.mozilla.org" + ], + "id": "es-ES", + "last_modified": 1539698844540 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "es_cl@dictionaries.addons.mozilla.org" + ], + "id": "es-CL", + "last_modified": 1539698844485 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "es-AR@dictionaries.addons.mozilla.org" + ], + "id": "es-AR", + "last_modified": 1539698844432 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "eo-EO@dictionaries.addons.mozilla.org" + ], + "id": "eo", + "last_modified": 1539698844376 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "marcoagpinto@mail.telepac.pt" + ], + "id": "en-GB", + "last_modified": 1539698844263 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "en-CA@dictionaries.addons.mozilla.org" + ], + "id": "en-CA", + "last_modified": 1539698844210 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "el-GR@dictionaries.addons.mozilla.org" + ], + "id": "el", + "last_modified": 1539698844158 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "dsb@dictionaries.addons.mozilla.org" + ], + "id": "dsb", + "last_modified": 1539698844105 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "de-CH@dictionaries.addons.mozilla.org", + "de-DE@dictionaries.addons.mozilla.org", + "de-AT@dictionaries.addons.mozilla.org" + ], + "id": "de", + "last_modified": 1539698844047 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "danish@dictionaries.addons.mozilla.org" + ], + "id": "da", + "last_modified": 1539698843991 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "cy-GB-1@dictionaries.addons.mozilla.org" + ], + "id": "cy", + "last_modified": 1539698843935 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "cs@dictionaries.addons.mozilla.org" + ], + "id": "cs", + "last_modified": 1539698843864 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "ca@dictionaries.addons.mozilla.org" + ], + "id": "ca", + "last_modified": 1539698843809 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "br@dictionaries.addons.mozilla.org" + ], + "id": "br", + "last_modified": 1539698843755 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "bn-BD@dictionaries.addons.mozilla.org" + ], + "id": "bn-BD", + "last_modified": 1539698843700 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "bg-BG@dictionaries.addons.mozilla.org" + ], + "id": "bg", + "last_modified": 1539698843646 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "be@dictionaries.addons.mozilla.org" + ], + "id": "be", + "last_modified": 1539698843592 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "az@dictionaries.addons.mozilla.org" + ], + "id": "az", + "last_modified": 1539698843539 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "asturianu@dictionaries.addons.mozilla.org" + ], + "id": "ast", + "last_modified": 1539698843485 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "as-IN@dictionaries.addons.mozilla.org" + ], + "id": "as", + "last_modified": 1539698843431 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "ar@dictionaries.addons.mozilla.org" + ], + "id": "ar", + "last_modified": 1539698843376 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "an-ES@dictionaries.addons.mozilla.org" + ], + "id": "an", + "last_modified": 1539698843321 + }, + { + "schema": 1537961639183, + "dictionaries": [ + "af-ZA@dictionaries.addons.mozilla.org" + ], + "id": "af", + "last_modified": 1539698843264 + } + ], + "timestamp": 1673270322227 +} diff --git a/services/settings/dumps/main/moz.build b/services/settings/dumps/main/moz.build new file mode 100644 index 0000000000..6352c00f5d --- /dev/null +++ b/services/settings/dumps/main/moz.build @@ -0,0 +1,121 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_TARGET_FILES.defaults.settings.main += [ + "anti-tracking-url-decoration.json", + "cookie-banner-rules-list.json", + "devtools-compatibility-browsers.json", + "devtools-devices.json", + "example.json", + "hijack-blocklists.json", + "language-dictionaries.json", + "password-recipes.json", + "password-rules.json", + "search-config-icons.json", + "search-config-overrides-v2.json", + "search-config-overrides.json", + "search-config-v2.json", + "search-config.json", + "search-default-override-allowlist.json", + "search-telemetry-v2.json", + "sites-classification.json", + "top-sites.json", + "translations-identification-models.json", + "translations-models.json", + "translations-wasm.json", + "url-classifier-skip-urls.json", + "websites-with-shared-credential-backends.json", +] + +FINAL_TARGET_FILES.defaults.settings.main["search-config-icons"] += [ + "search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256", + "search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256.meta.json", + "search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea", + "search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea.meta.json", + "search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937", + "search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937.meta.json", + "search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2", + "search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2.meta.json", + "search-config-icons/101ce01d-2691-b729-7f16-9d389803384b", + "search-config-icons/101ce01d-2691-b729-7f16-9d389803384b.meta.json", + "search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f", + "search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f.meta.json", + "search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c", + "search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c.meta.json", + "search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335", + "search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335.meta.json", + "search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca", + "search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca.meta.json", + "search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f", + "search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f.meta.json", + "search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11", + "search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11.meta.json", + "search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171", + "search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171.meta.json", + "search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2", + "search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2.meta.json", + "search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d", + "search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d.meta.json", + "search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41", + "search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41.meta.json", + "search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b", + "search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b.meta.json", + "search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66", + "search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66.meta.json", + "search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27", + "search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27.meta.json", + "search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173", + "search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173.meta.json", + "search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c", + "search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c.meta.json", + "search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e", + "search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e.meta.json", + "search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451", + "search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451.meta.json", + "search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361", + "search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361.meta.json", + "search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36", + "search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36.meta.json", + "search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e", + "search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e.meta.json", + "search-config-icons/890de5c4-0941-a116-473a-5d240e79497a", + "search-config-icons/890de5c4-0941-a116-473a-5d240e79497a.meta.json", + "search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716", + "search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716.meta.json", + "search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b", + "search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b.meta.json", + "search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd", + "search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd.meta.json", + "search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3", + "search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3.meta.json", + "search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d", + "search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d.meta.json", + "search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b", + "search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b.meta.json", + "search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21", + "search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21.meta.json", + "search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943", + "search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943.meta.json", + "search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd", + "search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd.meta.json", + "search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9", + "search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9.meta.json", + "search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c", + "search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c.meta.json", + "search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785", + "search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785.meta.json", + "search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a", + "search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a.meta.json", + "search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636", + "search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636.meta.json", + "search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc", + "search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc.meta.json", + "search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d", + "search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d.meta.json", + "search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136", + "search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136.meta.json", +] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DIST_SUBDIR = "browser" diff --git a/services/settings/dumps/main/password-recipes.json b/services/settings/dumps/main/password-recipes.json new file mode 100644 index 0000000000..53084a179d --- /dev/null +++ b/services/settings/dumps/main/password-recipes.json @@ -0,0 +1,145 @@ +{ + "data": [ + { + "hosts": [ + "www.modern-imm.fr", + "modern-imm.fr" + ], + "schema": 1673279471898, + "description": "The Modern'Imm website uses a field type of 'login' that's not recognized by the internal logic (Bug 1608762)", + "usernameSelector": "#login", + "id": "4f6f6f5b-c6f7-4d16-87a6-1c0172aee649", + "last_modified": 1674595048726 + }, + { + "hosts": [ + "mabanque.fortuneo.fr" + ], + "schema": 1642002521344, + "pathRegex": "\\/identification\\.jsp$", + "description": "The Fortuneo bank uses a different form for each input, defeating the password manager (Bug 1433754)", + "usernameSelector": "input[name='LOGIN']", + "id": "aebc1ea5-ca41-4258-9594-9255bbcadf1e", + "last_modified": 1642005109349 + }, + { + "hosts": [ + "login.ubuntu.com", + "login.launchpad.net", + "login.staging.ubuntu.com", + "login-lp.staging.ubuntu.com" + ], + "schema": 1636656872819, + "description": "ubuntu uses an invisible username field and requires the field being blank (Bug 1724136, Bug 1739992).", + "notUsernameSelector": "input[name='openid.usernamesecret']", + "id": "ebb01149-3568-429e-9924-8404414f6fe7", + "last_modified": 1636659060482 + }, + { + "hosts": [ + "www.aliexpress.com" + ], + "schema": 1631630510505, + "description": "An invisible email field for subscription is incorrectly selected as the username field.", + "usernameSelector": "#fm-login-id", + "id": "ad390fd4-b81c-419f-97ef-2f6211b0a6af", + "last_modified": 1631639778532 + }, + { + "hosts": [ + "www.dmv.ca.gov" + ], + "schema": 1600889016495, + "description": "www.dmv.ca.gov: Checking account form field has type=password fields and gets login suggestions (Bug 1595339)", + "notPasswordSelector": "#bankRoutingNo, #validateBankRoutingNo, #bankAcctNo, #validateBankAcctNo", + "id": "85113859-1767-4fe4-bec3-826028b7cc91", + "last_modified": 1600889167888 + }, + { + "hosts": [ + "mozilla.okta.com" + ], + "schema": 1599766903292, + "description": "okta uses a hidden password field to disable filling", + "passwordSelector": "#pass-signin", + "id": "99bed888-5309-468f-9924-2a57bd5289ee", + "last_modified": 1600889016443 + }, + { + "hosts": [ + "www.anthem.com" + ], + "schema": 1599839796360, + "description": "anthem uses a hidden password and username field to disable filling", + "passwordSelector": "#LoginContent_txtLoginPass", + "id": "ef7f2f3f-04b9-49d0-a2b5-3e3766b41d19", + "last_modified": 1600889016440 + }, + { + "hosts": [ + "www.discover.com" + ], + "schema": 1599839796979, + "description": "An ephemeral password-shim field is incorrectly selected as the username field.", + "usernameSelector": "#login-account", + "id": "dc49fe14-1969-48ab-b98b-2055480e55a4", + "last_modified": 1600889016436 + }, + { + "hosts": [ + "secure.tibia.com" + ], + "schema": 1599839797632, + "pathRegex": "^\\/account\\/", + "description": "Tibia uses type=password for its username field and puts the email address before the password field during registration", + "passwordSelector": "#password1, input[name='loginpassword']", + "usernameSelector": "#accountname, input[name='loginname']", + "id": "8fdbd3ae-4f74-4ff6-9399-b1c4cb2a6421", + "last_modified": 1600889016433 + }, + { + "hosts": [ + "www.facebook.com" + ], + "schema": 1599839798260, + "description": "Username field will be incorrectly captured in the change password form (bug 1243722)", + "notUsernameSelector": "#password_strength", + "id": "1f76dae4-c8ad-4633-b9fa-3a2420cf7e5c", + "last_modified": 1600889016430 + }, + { + "hosts": [ + "www.united.com" + ], + "schema": 1599839798880, + "pathRegex": "^\\/travel\\/checkin\\/changefqtv.aspx", + "description": "United uses a useless password field plus one per frequent flyer number during checkin. Don't save any of them (Bug 1330810)", + "notPasswordSelector": "input[type='password']", + "id": "ed57feb9-a928-4f61-8947-c676bd95a7b0", + "last_modified": 1600889016426 + }, + { + "hosts": [ + "buy.gogoinflight.com" + ], + "schema": 1599839799488, + "description": "Gogo In-Flight uses a password field for credit card numbers on the same page as login", + "notPasswordSelector": "#cardNumber", + "id": "2de99a9f-9430-411a-94d0-dbb31910742e", + "last_modified": 1600889016423 + }, + { + "hosts": [ + "www.benefits.ml.com" + ], + "schema": 1599839800780, + "pathRegex": "^\\/login\\/login$", + "description": "Merrill's benefits website has six type=password fields which is over our threshold for filling (Bug 1538026)", + "passwordSelector": "#SFText_Password", + "usernameSelector": "#SFText_UserName", + "id": "00814138-7ede-4f56-8953-b6d1c99d5f26", + "last_modified": 1600889016416 + } + ], + "timestamp": 1674595048726 +} diff --git a/services/settings/dumps/main/password-rules.json b/services/settings/dumps/main/password-rules.json new file mode 100644 index 0000000000..b190e98499 --- /dev/null +++ b/services/settings/dumps/main/password-rules.json @@ -0,0 +1,1583 @@ +{ + "data": [ + { + "Domain": "ccs-grp.com", + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower; allowed: [-!#$%&'+./=?\\^_`{|}~];", + "id": "6b15622b-1ce7-4141-89f1-c93e97aaaf88", + "last_modified": 1659924409785 + }, + { + "Domain": "lepida.it", + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];", + "id": "4af7bea9-dc2f-48dd-90e5-f0902e713b64", + "last_modified": 1659924409782 + }, + { + "Domain": "plazapremiumlounge.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%&*,@^];", + "id": "b7e46712-dfa5-463d-81af-2b5acd7e29fd", + "last_modified": 1659924409779 + }, + { + "Domain": "museumofflight.org", + "password-rules": "minlength: 8; maxlength: 15;", + "id": "1e25756f-80da-4fde-ac31-93e870adf079", + "last_modified": 1652712410939 + }, + { + "Domain": "ae.com", + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit;", + "id": "1b76634d-c4dc-467f-8b1d-458c0753faf1", + "last_modified": 1650898337595 + }, + { + "Domain": "app.digio.in", + "password-rules": "minlength: 8; maxlength: 15;", + "id": "9199eb80-2ea2-4efc-974a-13646cd9e2bf", + "last_modified": 1650898337590 + }, + { + "Domain": "app.parkmobile.io", + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];", + "id": "267bc941-86ee-4558-9d72-3f875ff0ac95", + "last_modified": 1650898337584 + }, + { + "Domain": "areariservata.bancaetica.it", + "password-rules": "minlength: 8; maxlength: 10; required: lower; required: upper; required: digit; required: [!#&*+/=@_];", + "id": "1316e054-4002-41b3-bb25-c2af275f1b48", + "last_modified": 1650898337579 + }, + { + "Domain": "bcassessment.ca", + "password-rules": "minlength: 8; maxlength: 14;", + "id": "2b72ecba-f8e4-4cc4-9c2c-01ad7b8eff60", + "last_modified": 1650898337573 + }, + { + "Domain": "belkin.com", + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [$!@~_,%&];", + "id": "89e880c3-2742-4802-bd33-ea506d3b7020", + "last_modified": 1650898337568 + }, + { + "Domain": "bilibili.com", + "password-rules": "maxlength: 16;", + "id": "3867a12f-b3e8-4343-94eb-6ddefba5b1a5", + "last_modified": 1650898337562 + }, + { + "Domain": "billerweb.com", + "password-rules": "minlength: 8; max-consecutive: 2; required: digit; required: upper,lower;", + "id": "6d89d12e-2ea9-4077-9dbc-c2566ccca370", + "last_modified": 1650898337557 + }, + { + "Domain": "bochk.com", + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [#$%&()*+,.:;<=>?@_];", + "id": "2a133fcd-fa66-473c-a09b-07014d0d3ccd", + "last_modified": 1650898337551 + }, + { + "Domain": "collectivehealth.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;", + "id": "b86c63d8-60e4-4c8a-965d-56af269f6b76", + "last_modified": 1650898337546 + }, + { + "Domain": "dan.org", + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@$%^&*];", + "id": "b7ca591b-c242-4520-a849-ccd74a90076a", + "last_modified": 1650898337540 + }, + { + "Domain": "dbs.com.hk", + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;", + "id": "fe2f51c2-8ccb-4ee4-97cb-73288337f6ab", + "last_modified": 1650898337535 + }, + { + "Domain": "equifax.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!$*+@];", + "id": "ce20ebcc-b423-45f5-9643-f4651c55a133", + "last_modified": 1650898337530 + }, + { + "Domain": "fidelity.com", + "password-rules": "minlength: 6; maxlength: 20; required: lower; allowed: upper,digit,[!$%'()+,./:;=?@^_|~];", + "id": "51bcb63b-7da1-4dd5-a024-e395d32e3c36", + "last_modified": 1650898337524 + }, + { + "Domain": "flysas.com", + "password-rules": "minlength: 8; maxlength: 14; required: lower; required: upper; required: digit; required: [-~!@#$%^&_+=`|(){}[:\"'<>,.?]];", + "id": "c7b74d79-d787-4575-bfb4-92b531f422d9", + "last_modified": 1650898337519 + }, + { + "Domain": "gamestop.com", + "password-rules": "minlength: 8; maxlength: 225; required: lower; required: upper; required: digit; required: [!@#$%];", + "id": "ef9def1f-a244-4cc1-8937-f42865ce62c7", + "last_modified": 1650898337513 + }, + { + "Domain": "hangseng.com", + "password-rules": "minlength: 8; maxlength: 30; required: lower; required: upper; required: digit;", + "id": "bb13b29e-8fa0-4d68-b400-e546834a30a7", + "last_modified": 1650898337508 + }, + { + "Domain": "hkbea.com", + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;", + "id": "a771074d-6e15-42aa-811a-731930d173b5", + "last_modified": 1650898337502 + }, + { + "Domain": "hkexpress.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: special;", + "id": "94abbd2d-2201-43f1-b3c8-33900f207b4c", + "last_modified": 1650898337497 + }, + { + "Domain": "hsbc.com.hk", + "password-rules": "minlength: 6; maxlength: 30; required: lower; required: upper; required: digit; allowed: ['.@_];", + "id": "b657a5f0-268c-4acd-85fe-5085bcdb380c", + "last_modified": 1650898337492 + }, + { + "Domain": "kfc.ca", + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%&?*];", + "id": "ca2e08a6-cc00-47a4-8517-737552dfedab", + "last_modified": 1650898337486 + }, + { + "Domain": "launtel.net.au", + "password-rules": "minlength: 8; required: digit; required: digit; allowed: lower, upper;", + "id": "eede7e13-6f0b-408a-bf1d-292130631834", + "last_modified": 1650898337480 + }, + { + "Domain": "lg.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-!#$%&'()*+,.:;=?@[^_{|}~]];", + "id": "fb2b3324-dd2d-4518-a309-af50f685689a", + "last_modified": 1650898337475 + }, + { + "Domain": "lloydsbank.co.uk", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: digit; allowed: upper;", + "id": "56265f6f-6348-4900-8abf-07c4ab2e1c72", + "last_modified": 1650898337470 + }, + { + "Domain": "loyalty.accor.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&=@];", + "id": "403e9c7d-1dbc-44f4-a825-81aa4127afca", + "last_modified": 1650898337464 + }, + { + "Domain": "lsacsso.b2clogin.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit, [-!#$%&*?@^_];", + "id": "369eb957-3b3f-4377-ac76-52026dfffbfa", + "last_modified": 1650898337458 + }, + { + "Domain": "medicare.gov", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@!$%^*()];", + "id": "27a56617-f363-4455-a60c-8582af732012", + "last_modified": 1650898337453 + }, + { + "Domain": "mpv.tickets.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;", + "id": "9deab323-bc0f-4433-bbc3-e4d26dbfa7e0", + "last_modified": 1650898337448 + }, + { + "Domain": "my.konami.net", + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;", + "id": "6a62b70b-6412-4ff4-945a-168dfc38b634", + "last_modified": 1650898337443 + }, + { + "Domain": "order.wendys.com", + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; allowed: [!#$%&()*+/=?^_{}];", + "id": "57882459-1180-4faf-a8dd-c7475bcfeb0f", + "last_modified": 1650898337437 + }, + { + "Domain": "preferredhotels.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+@^_];", + "id": "4290b8b6-dd7c-431b-a4cb-36e661f72423", + "last_modified": 1650898337432 + }, + { + "Domain": "premier.ticketek.com.au", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "26b1c942-2d06-496b-984b-65044ad2f1ab", + "last_modified": 1650898337427 + }, + { + "Domain": "premierinn.com", + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower;", + "id": "f6663626-8861-4bfc-a704-5ff1e23e9e22", + "last_modified": 1650898337421 + }, + { + "Domain": "prestocard.ca", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit,[!\"#$%&'()*+,<>?@];", + "id": "ed580dc6-16af-4ec5-8ae2-b40fe99f0260", + "last_modified": 1650898337416 + }, + { + "Domain": "questdiagnostics.com", + "password-rules": "minlength: 8; maxlength: 30; required: upper, lower; required: digit, [!#$%&()*+<>?@^_~];", + "id": "a331ccb9-a2b8-44dd-bc35-0878eb51b4fe", + "last_modified": 1650898337411 + }, + { + "Domain": "renaud-bray.com", + "password-rules": "minlength: 8; maxlength: 38; allowed: upper,lower,digit;", + "id": "210a8aea-1089-487d-bcaa-3c937374f650", + "last_modified": 1650898337405 + }, + { + "Domain": "rogers.com", + "password-rules": "minlength: 8; required: lower, upper; required: digit; required: [!@#$];", + "id": "a13b20d6-0b68-4a05-866a-e8a14adee208", + "last_modified": 1650898337400 + }, + { + "Domain": "ruc.dk", + "password-rules": "minlength: 6; maxlength: 8; required: lower, upper; required: [-!#%&(){}*+;%/<=>?_];", + "id": "85d4c3e2-7f8c-480d-a20e-c55aee12a2e7", + "last_modified": 1650898337394 + }, + { + "Domain": "runescape.com", + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit;", + "id": "e14ecd5a-951f-4c27-bf45-0ee3fb830032", + "last_modified": 1650898337389 + }, + { + "Domain": "salslimo.com", + "password-rules": "minlength: 8; maxlength: 50; required: upper; required: lower; required: digit; required: [!@#$&*];", + "id": "afa90771-c472-48cc-96d3-21c770bb958c", + "last_modified": 1650898337384 + }, + { + "Domain": "secure.snnow.ca", + "password-rules": "minlength: 7; maxlength: 16; required: digit; allowed: lower, upper;", + "id": "f6a3e835-190e-4043-a070-943d821f708e", + "last_modified": 1650898337378 + }, + { + "Domain": "tix.soundrink.com", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "0cb09a67-566e-492c-a4d7-af473e964472", + "last_modified": 1650898337373 + }, + { + "Domain": "training.confluent.io", + "password-rules": "minlength: 6; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%*@^_~];", + "id": "d00c2ee9-6f05-495c-9833-bae526019ddc", + "last_modified": 1650898337368 + }, + { + "Domain": "usps.com", + "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit; allowed: [-!\"#&'()+,./?@];", + "id": "caacf36d-df98-43ab-8d5c-d7d40d79d696", + "last_modified": 1650898337362 + }, + { + "Domain": "visabenefits-auth.axa-assistance.us", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^{|}];", + "id": "3afe232d-ff3d-4e3d-b6bd-da947dac7884", + "last_modified": 1650898337357 + }, + { + "Domain": "wegmans.com", + "password-rules": "minlength: 8; required: digit; required: upper,lower; required: [!#$%&*+=?@^];", + "id": "d7036bd9-5301-43a1-b427-25a876ff4c07", + "last_modified": 1650898337352 + }, + { + "Domain": "xvoucher.com", + "password-rules": "minlength: 11; required: upper; required: digit; required: [!@#$%&_];", + "id": "85cc3546-8a5c-4400-bd01-c0386897a442", + "last_modified": 1650898337346 + }, + { + "Domain": "zara.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;", + "id": "4496846f-b282-4965-97f8-97c2b814881a", + "last_modified": 1650898337341 + }, + { + "Domain": "americanexpress.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 4; required: lower, upper; required: digit; allowed: [%&_?#=];", + "id": "986919ff-d801-45f6-ab75-af40e0c9e0ad", + "last_modified": 1650898337335 + }, + { + "Domain": "163.com", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "df352506-8729-494d-bfad-525d90380416", + "last_modified": 1624479577563 + }, + { + "Domain": "1800flowers.com", + "password-rules": "minlength: 6; required: lower, upper; required: digit;", + "id": "aeec29cc-4e54-4ef8-85d7-b3c0dab03617", + "last_modified": 1624479577559 + }, + { + "Domain": "access.service.gov.uk", + "password-rules": "minlength: 10; required: lower; required: upper; required: digit; required: special;", + "id": "9c47a7ff-c8f0-46e8-b126-2c3ebe217722", + "last_modified": 1624479577554 + }, + { + "Domain": "admiral.com", + "password-rules": "minlength: 8; required: digit; required: [- !\"#$&'()*+,.:;<=>?@[^_`{|}~]]; allowed: lower, upper;", + "id": "b20d499f-3ee7-4ec6-9e2e-306a277cff83", + "last_modified": 1624479577550 + }, + { + "Domain": "aetna.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: upper; required: digit; allowed: lower, [-_&#@];", + "id": "70f3cf89-ed8d-4f44-84a5-9d1fda34e587", + "last_modified": 1624479577545 + }, + { + "Domain": "airasia.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;", + "id": "f83b0077-a3f1-4024-9966-4286edf46600", + "last_modified": 1624479577540 + }, + { + "Domain": "ajisushionline.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [ !#$%&*?@];", + "id": "05d720bf-9c13-4a82-9a53-de12188d1b26", + "last_modified": 1624479577536 + }, + { + "Domain": "aliexpress.com", + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;", + "id": "467a4753-9e20-4e4a-a918-140cffb7edab", + "last_modified": 1624479577531 + }, + { + "Domain": "alliantcreditunion.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$*];", + "id": "d1a38279-7778-4113-96fe-869f526e06ba", + "last_modified": 1624479577527 + }, + { + "Domain": "allianz.com.br", + "password-rules": "minlength: 4; maxlength: 4;", + "id": "46f95801-6c08-404b-99f6-ac36b347e45b", + "last_modified": 1624479577522 + }, + { + "Domain": "anatel.gov.br", + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit;", + "id": "d3c8fbd7-3b79-4132-93f1-7d0d97f99a3e", + "last_modified": 1624479577513 + }, + { + "Domain": "ancestry.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];", + "id": "23a52c2b-d500-443d-bb20-fa878152073a", + "last_modified": 1624479577508 + }, + { + "Domain": "angieslist.com", + "password-rules": "minlength: 6; maxlength: 15;", + "id": "00324bb2-6d86-4c9c-bf9d-d3f48881ae18", + "last_modified": 1624479577504 + }, + { + "Domain": "anthem.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!$*?@|];", + "id": "8cb611dc-dc73-43de-b7e5-77ad34eaaced", + "last_modified": 1624479577499 + }, + { + "Domain": "apple.com", + "password-rules": "minlength: 8; maxlength: 63; required: lower; required: upper; required: digit; allowed: ascii-printable;", + "id": "26688582-ba4c-4137-9cd7-580cb58e7bc8", + "last_modified": 1624479577494 + }, + { + "Domain": "artscyclery.com", + "password-rules": "minlength: 6; maxlength: 19;", + "id": "b2f3bf26-05d3-4035-b3b9-40b0c7bbf560", + "last_modified": 1624479577490 + }, + { + "Domain": "astonmartinf1.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;", + "id": "808d3f9e-8c76-49e6-8aa9-e91cb4d13834", + "last_modified": 1624479577485 + }, + { + "Domain": "autify.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];", + "id": "8655f1b9-4322-4c64-bc6f-8cf7c19f46ce", + "last_modified": 1624479577481 + }, + { + "Domain": "axa.de", + "password-rules": "minlength: 8; maxlength: 65; required: lower; required: upper; required: digit; allowed: [-!\"§$%&/()=?;:_+*'#];", + "id": "f616c182-ee3d-4fc0-8518-54ebae43c31b", + "last_modified": 1624479577476 + }, + { + "Domain": "baidu.com", + "password-rules": "minlength: 6; maxlength: 14;", + "id": "66b8fe2d-6da7-4741-8657-a91ee05b9c8f", + "last_modified": 1624479577472 + }, + { + "Domain": "bancochile.cl", + "password-rules": "minlength: 8; maxlength: 8; required: lower; required: upper; required: digit;", + "id": "ac6e2d7c-de54-4f75-ac0f-87371bd9d1b0", + "last_modified": 1624479577467 + }, + { + "Domain": "bankofamerica.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-@#*()+={}/?~;,._];", + "id": "049c723a-f358-4b2f-bacf-6a835d8adbff", + "last_modified": 1624479577463 + }, + { + "Domain": "battle.net", + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; allowed: digit, special;", + "id": "97efec40-121d-4b29-b25e-f98c2e12fdad", + "last_modified": 1624479577458 + }, + { + "Domain": "benefitslogin.discoverybenefits.com", + "password-rules": "minlength: 10; required: upper; required: digit; required: [!#$%&*?@]; allowed: lower;", + "id": "c553aacf-c4a7-48f7-aae6-6481875ecea1", + "last_modified": 1624479577453 + }, + { + "Domain": "benjerry.com", + "password-rules": "required: upper; required: upper; required: digit; required: digit; required: special; required: special; allowed: lower;", + "id": "c09ed551-7333-4d88-91ae-89b92f165b54", + "last_modified": 1624479577449 + }, + { + "Domain": "bestbuy.com", + "password-rules": "minlength: 20; required: lower; required: upper; required: digit; required: special;", + "id": "6f281737-361f-4e44-ad06-c659a71c2c3a", + "last_modified": 1624479577444 + }, + { + "Domain": "bhphotovideo.com", + "password-rules": "maxlength: 15;", + "id": "dc5e25f6-f093-4164-bfd1-f44f45df22c7", + "last_modified": 1624479577440 + }, + { + "Domain": "biovea.com", + "password-rules": "maxlength: 19;", + "id": "b44f6ee8-8a69-41db-b779-b401c2dfc5de", + "last_modified": 1624479577435 + }, + { + "Domain": "bitly.com", + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];", + "id": "effc177c-1d29-483d-9916-3372eadfa381", + "last_modified": 1624479577431 + }, + { + "Domain": "bloomingdales.com", + "password-rules": "minlength: 7; maxlength: 16; required: lower, upper; required: digit; required: [`!@#$%^&*()+~{}'\";:<>?]];", + "id": "f2720cdc-a347-4d1c-bb88-48291993deb3", + "last_modified": 1624479577426 + }, + { + "Domain": "bluesguitarunleashed.com", + "password-rules": "allowed: lower, upper, digit, [!$#@];", + "id": "e1f99050-bd82-4345-82ce-86fcd4af983f", + "last_modified": 1624479577422 + }, + { + "Domain": "box.com", + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;", + "id": "94ac58b8-2861-4bb0-b4cb-5c9f19273dc5", + "last_modified": 1624479577417 + }, + { + "Domain": "brighthorizons.com", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "8f7d632d-3c58-4fd0-b6f5-a9110adced33", + "last_modified": 1624479577413 + }, + { + "Domain": "callofduty.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2; required: lower, upper; required: digit;", + "id": "64f6f30c-7b6c-4769-a339-ca36526aa8dc", + "last_modified": 1624479577408 + }, + { + "Domain": "capitalone.com", + "password-rules": "minlength: 8; maxlength: 32; required: lower, upper; required: digit; allowed: [-_./\\@$*&!#];", + "id": "f18435dc-aff8-4bfa-ad85-f4f996e151fc", + "last_modified": 1624479577404 + }, + { + "Domain": "cardbenefitservices.com", + "password-rules": "minlength: 7; maxlength: 100; required: lower, upper; required: digit;", + "id": "4b6e5a1d-b1a6-4b39-bf8f-e61b41132aac", + "last_modified": 1624479577400 + }, + { + "Domain": "cb2.com", + "password-rules": "minlength: 7; maxlength: 18; required: lower, upper; required: digit;", + "id": "77c2554b-2f3b-4bbb-a97a-2ea4357b1f08", + "last_modified": 1624479577395 + }, + { + "Domain": "cecredentialtrust.com", + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!#$%&*@^];", + "id": "a656eece-935b-439a-b4b2-99660dd6155e", + "last_modified": 1624479577391 + }, + { + "Domain": "chase.com", + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 2; required: lower, upper; required: digit; required: [!#$%+/=@~];", + "id": "8d8c892d-d04f-43f1-abd0-3d58f69e8d3e", + "last_modified": 1624479577386 + }, + { + "Domain": "cigna.co.uk", + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit;", + "id": "65bddd06-cb14-4a32-95af-c9beb56e5f6e", + "last_modified": 1624479577382 + }, + { + "Domain": "cigna.com", + "password-rules": "minlength: 8; maxlength: 12; required: upper; required: digit; required: [_!.&@]; allowed: lower;", + "id": "2bfd4194-def3-4a86-91d8-cc1a7c850e7e", + "last_modified": 1624479577377 + }, + { + "Domain": "citi.com", + "password-rules": "minlength: 6; maxlength: 50; max-consecutive: 2; required: lower, upper; required: digit; allowed: [_!@$]", + "id": "dee9933c-b444-4bff-a121-455bdcca14e6", + "last_modified": 1624479577373 + }, + { + "Domain": "claimlookup.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [@#$%^&+=!];", + "id": "5b51513d-1b22-4c3e-9569-8b084980c141", + "last_modified": 1624479577368 + }, + { + "Domain": "claro.com.br", + "password-rules": "minlength: 8; required: lower; allowed: upper, digit, [-!@#$%&*_+=<>];", + "id": "3afe982a-afe3-426c-bb9e-d84befe0bf86", + "last_modified": 1624479577364 + }, + { + "Domain": "clien.net", + "password-rules": "minlength: 5; required: lower, upper; required: digit;", + "id": "15e73766-3e94-4d53-90af-766947dac02b", + "last_modified": 1624479577360 + }, + { + "Domain": "comcastpaymentcenter.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 2;required: lower, upper; required: digit;", + "id": "1fcc6a32-1cb3-48ec-bfa5-a81dba1ba86d", + "last_modified": 1624479577355 + }, + { + "Domain": "comed.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?/\\]];", + "id": "ddf996fa-5a5f-4f11-a985-3c83039e2075", + "last_modified": 1624479577351 + }, + { + "Domain": "commerzbank.de", + "password-rules": "minlength: 5; maxlength: 8; required: lower, upper; required: digit;", + "id": "6f56efe9-eb37-4c0a-ba57-b5071755b472", + "last_modified": 1624479577346 + }, + { + "Domain": "consorsbank.de", + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;", + "id": "3e3f598e-2109-4bca-b7ce-247b4de42b4a", + "last_modified": 1624479577342 + }, + { + "Domain": "consorsfinanz.de", + "password-rules": "minlength: 6; maxlength: 15; allowed: lower, upper, digit, [-.];", + "id": "49f6901f-0401-4b79-ad66-c732d68b1b5e", + "last_modified": 1624479577336 + }, + { + "Domain": "costco.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; allowed: digit, [-!#$%&'()*+/:;=?@[^_`{|}~]];", + "id": "bb4bc324-6d45-467c-b9d6-94626877a09d", + "last_modified": 1624479577331 + }, + { + "Domain": "coursera.com", + "password-rules": "minlength: 8; maxlength: 72;", + "id": "8f0ccfd9-c3fb-428b-9f5b-d4ca35b2f675", + "last_modified": 1624479577327 + }, + { + "Domain": "cox.com", + "password-rules": "minlength: 8; maxlength: 24; required: digit; required: upper,lower; allowed: [!#$%()*@^];", + "id": "a7242ae7-815e-46b6-98ab-d3bd298b4e7f", + "last_modified": 1624479577322 + }, + { + "Domain": "crateandbarrel.com", + "password-rules": "minlength: 9; maxlength: 64; required: lower; required: upper; required: digit; required: [!\"#$%&()*,.:<>?@^_{|}];", + "id": "eb446a5d-3e3f-4f9b-807c-5739f8e610e2", + "last_modified": 1624479577318 + }, + { + "Domain": "cvs.com", + "password-rules": "minlength: 8; maxlength: 25; required: lower, upper; required: digit; allowed: [!@#$%^&*()];", + "id": "3873c5fb-2500-40e6-9d3a-cda206e40208", + "last_modified": 1624479577314 + }, + { + "Domain": "dailymail.co.uk", + "password-rules": "minlength: 5; maxlength: 15;", + "id": "e53ecd8b-4469-4fc2-bc2d-d756e030b248", + "last_modified": 1624479577309 + }, + { + "Domain": "danawa.com", + "password-rules": "minlength: 8; maxlength: 21; required: lower, upper; required: digit; required: [!@$%^&*];", + "id": "cb88fc85-9057-4d0b-88ae-506fbe1b776e", + "last_modified": 1624479577305 + }, + { + "Domain": "darty.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit;", + "id": "49675eca-fe96-4900-9651-88a2943e5589", + "last_modified": 1624479577300 + }, + { + "Domain": "decluttr.com", + "password-rules": "minlength: 8; maxlength: 45; required: lower; required: upper; required: digit;", + "id": "de2194d5-6a3b-49ab-a5b9-07c586e7934c", + "last_modified": 1624479577296 + }, + { + "Domain": "delta.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit;", + "id": "b3121427-364d-4764-91b7-3a61d9fbd65e", + "last_modified": 1624479577292 + }, + { + "Domain": "deutsche-bank.de", + "password-rules": "minlength: 5; maxlength: 5; required: lower, upper, digit;", + "id": "6e343fa3-e8d4-45fd-8e94-70e3c0e96970", + "last_modified": 1624479577287 + }, + { + "Domain": "devstore.cn", + "password-rules": "minlength: 6; maxlength: 12;", + "id": "77c95723-a325-4dd3-8f4b-ab670c4dba0e", + "last_modified": 1624479577282 + }, + { + "Domain": "dickssportinggoods.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&*?@^];", + "id": "740d7562-6f36-4481-a471-3d1939dd83a4", + "last_modified": 1624479577278 + }, + { + "Domain": "dkb.de", + "password-rules": "minlength: 8; maxlength: 38; required: lower, upper; required: digit; allowed: [-äüöÄÜÖß!$%&/()=?+#,.:];", + "id": "d7250322-8ef5-474f-a5e0-defbe8542543", + "last_modified": 1624479577274 + }, + { + "Domain": "dmm.com", + "password-rules": "minlength: 4; maxlength: 16; required: lower; required: upper; required: digit;", + "id": "b83f2b23-6349-4ddc-a6ce-7add0051910d", + "last_modified": 1624479577269 + }, + { + "Domain": "dowjones.com", + "password-rules": "maxlength: 15;", + "id": "2a635fd0-1a69-40e7-90dd-ed0ec396b384", + "last_modified": 1624479577265 + }, + { + "Domain": "ea.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: special;", + "id": "925e748f-9880-4af9-b5a1-fb28e5c1c7e7", + "last_modified": 1624479577260 + }, + { + "Domain": "easycoop.com", + "password-rules": "minlength: 8; required: upper; required: special; allowed: lower, digit;", + "id": "8a5e5362-d9dd-41ff-b2ce-3e0a684c6dfb", + "last_modified": 1624479577256 + }, + { + "Domain": "easyjet.com", + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: [-];", + "id": "f73a2516-3fd1-4c24-8dc0-d33c11add1c0", + "last_modified": 1624479577252 + }, + { + "Domain": "ebrap.org", + "password-rules": "minlength: 15; required: lower; required: lower; required: upper; required: upper; required: digit; required: digit; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]]; required: [-!@#$%^&*()_+|~=`{}[:\";'?,./.]];", + "id": "812351fd-93ee-4f33-8c40-e17084b51130", + "last_modified": 1624479577247 + }, + { + "Domain": "ecompanystore.com", + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: [#$%*+.=@^_];", + "id": "5524acd6-726c-43a0-b956-6eee0753c3f7", + "last_modified": 1624479577243 + }, + { + "Domain": "eddservices.edd.ca.gov", + "password-rules": "minlength: 8; maxlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*()];", + "id": "d45dd0b1-6142-4b74-93bc-b13b4d077c56", + "last_modified": 1624479577238 + }, + { + "Domain": "empower-retirement.com", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "3f5e9f47-1c57-40b1-a977-727bd10f800f", + "last_modified": 1624479577234 + }, + { + "Domain": "epicgames.com", + "password-rules": "minlength: 7; required: lower; required: upper; required: digit; required: [-!\"#$%&'()*+,./:;<=>?@[^_`{|}~]];", + "id": "962aabf0-a55c-4748-bc6a-482eda2e671f", + "last_modified": 1624479577229 + }, + { + "Domain": "epicmix.com", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "af1a16c7-fca9-4989-84c3-7c9145df2dd1", + "last_modified": 1624479577225 + }, + { + "Domain": "essportal.excelityglobal.com", + "password-rules": "minlength: 6; maxlength: 8; allowed: lower, upper, digit;", + "id": "2ac13285-4ec2-499e-8d36-3e25260471fa", + "last_modified": 1624479577221 + }, + { + "Domain": "ettoday.net", + "password-rules": "minlength: 6; maxlength: 12;", + "id": "7eff7845-95a7-4a75-9ebd-3cca066bbef4", + "last_modified": 1624479577216 + }, + { + "Domain": "examservice.com.tw", + "password-rules": "minlength: 6; maxlength: 8;", + "id": "8f84886c-c6f5-4ef2-8ef1-d859a12fe2c4", + "last_modified": 1624479577212 + }, + { + "Domain": "expertflyer.com", + "password-rules": "minlength: 5; maxlength: 16; required: lower, upper; required: digit;", + "id": "649bf5c6-30f3-4f88-9950-83bb4437177b", + "last_modified": 1624479577207 + }, + { + "Domain": "extraspace.com", + "password-rules": "minlength: 8; maxlength: 20; allowed: lower; required: upper, digit, [!#$%&*?@];", + "id": "0c489e43-10d4-4189-a34d-19f24a943bea", + "last_modified": 1624479577202 + }, + { + "Domain": "ezpassva.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;", + "id": "109d1bd6-2480-4e36-919b-54d89dc1f302", + "last_modified": 1624479577197 + }, + { + "Domain": "fc2.com", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "51c755bd-9a6e-47d3-b573-007f9c31b89d", + "last_modified": 1624479577192 + }, + { + "Domain": "fedex.com", + "password-rules": "minlength: 8; max-consecutive: 3; required: lower; required: upper; required: digit; allowed: [-!@#$%^&*_+=`|(){}[:;,.?]];", + "id": "b3b7d81a-a658-4cbd-8119-2e5e3d31f080", + "last_modified": 1624479577187 + }, + { + "Domain": "fuelrewards.com", + "password-rules": "minlength: 8; maxlength: 16; allowed: upper,lower,digit,[!#$%@];", + "id": "7e44d18c-7aa9-479d-b99b-9ddf848abb94", + "last_modified": 1624479577177 + }, + { + "Domain": "getflywheel.com", + "password-rules": "minlength: 7; maxlength: 72;", + "id": "6d8af47c-366d-4c75-b872-fcc4063add5a", + "last_modified": 1624479577172 + }, + { + "Domain": "girlscouts.org", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [$#!];", + "id": "7168f0ef-9376-4603-9e04-6d5a27413adf", + "last_modified": 1624479577167 + }, + { + "Domain": "github.com", + "password-rules": "minlength: 8; required: lower, digit; allowed: unicode;", + "id": "6a8b1218-29b4-437e-9a36-ffc8a87b02be", + "last_modified": 1624479577162 + }, + { + "Domain": "gmx.net", + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];", + "id": "3f1c7a5f-7c8f-49ff-bf9e-450f1e4ab9fc", + "last_modified": 1624479577157 + }, + { + "Domain": "google.com", + "password-rules": "minlength: 8; allowed: lower, upper, digit, [-!\"#$%&'()*+,./:;<=>?@[^_{|}~]];", + "id": "bccf75fa-f262-41d4-895c-5dcfed9bbb35", + "last_modified": 1624479577152 + }, + { + "Domain": "guardiananytime.com", + "password-rules": "minlength: 8; maxlength: 50; max-consecutive: 2; required: lower; required: upper; required: digit, [-~!@#$%^&*_+=`|(){}[:;,.?]];", + "id": "9fadb5ca-c324-4000-b630-27b0c3a54e2c", + "last_modified": 1624479577143 + }, + { + "Domain": "gwl.greatwestlife.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [-!#$%_=+<>];", + "id": "ce187851-d5a5-49f6-ae80-46cdbea9571b", + "last_modified": 1624479577138 + }, + { + "Domain": "hawaiianairlines.com", + "password-rules": "maxlength: 16;", + "id": "3cdfee63-22ac-4785-a073-98ec59db2e40", + "last_modified": 1624479577133 + }, + { + "Domain": "hertz.com", + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower; required: upper; required: digit; required: [#$%^&!@];", + "id": "e804c8d8-8d33-463a-b228-c2fbdcad663d", + "last_modified": 1624479577129 + }, + { + "Domain": "hetzner.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, special;", + "id": "a27316a5-29c1-468f-b046-655637fe7fc6", + "last_modified": 1624479577124 + }, + { + "Domain": "hilton.com", + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit;", + "id": "3dd64887-7a1c-42bd-b636-f825d155f9fc", + "last_modified": 1624479577120 + }, + { + "Domain": "hotels.com", + "password-rules": "minlength: 6; maxlength: 20; required: digit; allowed: lower, upper, [@$!#()&^*%];", + "id": "5205a318-095f-4a19-8f68-0f1b9c8b4133", + "last_modified": 1624479577115 + }, + { + "Domain": "hotwire.com", + "password-rules": "minlength: 6; maxlength: 30; allowed: lower, upper, digit, [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];", + "id": "8488aaeb-2cf3-4596-a0b2-93d1017ab2c8", + "last_modified": 1624479577111 + }, + { + "Domain": "hrblock.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [$#%!];", + "id": "d9fbf6eb-1d43-4e98-b6d5-643e9f92cc61", + "last_modified": 1624479577107 + }, + { + "Domain": "hsbc.com.my", + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!$*.=?@_'];", + "id": "b49e95b0-7557-4a97-ad8d-1693e903a404", + "last_modified": 1624479577102 + }, + { + "Domain": "hypovereinsbank.de", + "password-rules": "minlength: 6; maxlength: 10; required: lower, upper, digit; allowed: [!\"#$%&()*+:;<=>?@[{}~]];", + "id": "fb5e1084-9f2c-4655-befe-861ddf95b5d6", + "last_modified": 1624479577098 + }, + { + "Domain": "hyresbostader.se", + "password-rules": "minlength: 6; maxlength: 20; required: lower, upper; required: digit;", + "id": "02d748f7-8453-4888-9f84-b59fcb530a24", + "last_modified": 1624479577094 + }, + { + "Domain": "id.sonyentertainmentnetwork.com", + "password-rules": "minlength: 8; maxlength: 30; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];", + "id": "5fe63697-cd12-498d-ab86-fadb9d22f9e0", + "last_modified": 1624479577090 + }, + { + "Domain": "identitytheft.gov", + "password-rules": "allowed: lower, upper, digit, [!#%&*@^];", + "id": "16c76a91-d8b6-4bd8-a6a2-4241b28372e6", + "last_modified": 1624479577085 + }, + { + "Domain": "idestination.info", + "password-rules": "maxlength: 15;", + "id": "aa7de954-25a9-42f5-9d27-a243b6343c35", + "last_modified": 1624479577081 + }, + { + "Domain": "impots.gouv.fr", + "password-rules": "minlength: 12; maxlength: 128; required: lower; required: digit; allowed: [-!#$%&*+/=?^_'.{|}];", + "id": "49ed429d-8c9a-455f-8796-0ffa07d7c1d3", + "last_modified": 1624479577077 + }, + { + "Domain": "indochino.com", + "password-rules": "minlength: 6; maxlength: 15; required: upper; required: digit; allowed: lower, special;", + "id": "b4e0c039-c1ad-4cd1-a826-b96fcfe184d7", + "last_modified": 1624479577072 + }, + { + "Domain": "internationalsos.com", + "password-rules": "required: lower; required: upper; required: digit; required: [@#$%^&+=_];", + "id": "b43e1dc8-2116-4e32-9560-de2060cd688a", + "last_modified": 1624479577068 + }, + { + "Domain": "irctc.co.in", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];", + "id": "77ee1d78-ce1c-4baa-af93-477c7a9c4d49", + "last_modified": 1624479577064 + }, + { + "Domain": "irs.gov", + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&*@];", + "id": "eae8fc2a-7aaa-4487-b1cf-0f1f954fb0b8", + "last_modified": 1624479577060 + }, + { + "Domain": "jal.co.jp", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "e2df8903-d81e-43ce-a260-d7cab2b45928", + "last_modified": 1624479577055 + }, + { + "Domain": "japanpost.jp", + "password-rules": "minlength: 8; maxlength: 16; required: digit; required: upper,lower;", + "id": "932e5e14-faa2-45cc-95ed-8152e5aba53b", + "last_modified": 1624479577051 + }, + { + "Domain": "jordancu-onlinebanking.org", + "password-rules": "minlength: 6; maxlength: 32; allowed: upper, lower, digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];", + "id": "b5f3d474-2551-4476-a53d-14ac4db8af5e", + "last_modified": 1624479577046 + }, + { + "Domain": "keldoc.com", + "password-rules": "minlength: 12; required: lower; required: upper; required: digit; required: [!@#$%^&*];", + "id": "34394898-3ab8-4b71-9241-e204176fdb6d", + "last_modified": 1624479577042 + }, + { + "Domain": "key.harvard.edu", + "password-rules": "minlength: 10; maxlength: 100; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^[']];", + "id": "fbbdfb4f-6088-4039-925a-9257af7ff7d0", + "last_modified": 1624479577037 + }, + { + "Domain": "klm.com", + "password-rules": "minlength: 8; maxlength: 12;", + "id": "c83a50cc-2fe4-43aa-9921-9e6096108d22", + "last_modified": 1624479577033 + }, + { + "Domain": "la-z-boy.com", + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper; required: digit;", + "id": "d407c1b0-ce54-4590-856a-78583670c1da", + "last_modified": 1624479577029 + }, + { + "Domain": "ladwp.com", + "password-rules": "minlength: 8; maxlength: 20; required: digit; allowed: lower, upper;", + "id": "7734c07a-02dc-4b92-8be2-2526be9cb41c", + "last_modified": 1624479577025 + }, + { + "Domain": "leetchi.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];", + "id": "462dad03-9d8d-4fda-a374-efa5c7b58e19", + "last_modified": 1624479577020 + }, + { + "Domain": "live.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];", + "id": "02398e67-5ffc-4660-8757-563039a1a0f5", + "last_modified": 1624479577015 + }, + { + "Domain": "lowes.com", + "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit;", + "id": "4cec9fb5-d836-495f-834d-015e536ffd4b", + "last_modified": 1624479577011 + }, + { + "Domain": "lufthansa.com", + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [!#$%&()*+,./:;<>?@\"_];", + "id": "20072457-629a-4b6c-80c7-776ac743d8cf", + "last_modified": 1624479577006 + }, + { + "Domain": "macys.com", + "password-rules": "minlength: 7; maxlength: 16; allowed: lower, upper, digit, [~!@#$%^&*+`(){}[:;\"'<>?]];", + "id": "76a21c02-3326-49bc-b8ce-197bc260880f", + "last_modified": 1624479577001 + }, + { + "Domain": "mailbox.org", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [-!$\"%&/()=*+#.,;:@?{}[]];", + "id": "84547f09-ea10-45f2-a21d-69db1874ad57", + "last_modified": 1624479576997 + }, + { + "Domain": "makemytrip.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [@$!%*#?&];", + "id": "93cad6a1-542d-4ae2-b523-2992cbc94fbe", + "last_modified": 1624479576993 + }, + { + "Domain": "marriott.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; allowed: [$!#&@?%=];", + "id": "c89a90d5-7008-4747-9966-c539c1551c92", + "last_modified": 1624479576988 + }, + { + "Domain": "maybank2u.com.my", + "password-rules": "minlength: 8; maxlength: 12; max-consecutive: 2; required: lower; required: upper; required: digit; required: [-~!@#$%^&*_+=`|(){}[:;\"'<>,.?];", + "id": "ab35a850-3d7b-4328-a5bf-b9bb8b6e5436", + "last_modified": 1624479576984 + }, + { + "Domain": "metlife.com", + "password-rules": "minlength: 6; maxlength: 20;", + "id": "5798403a-3e1b-4841-995c-dc97f79d7b91", + "last_modified": 1624479576980 + }, + { + "Domain": "microsoft.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;", + "id": "191ca651-273d-4fbc-9301-6763bc5cc5ef", + "last_modified": 1624479576975 + }, + { + "Domain": "minecraft.com", + "password-rules": "minlength: 8; required: lower, upper; required: digit; allowed: ascii-printable;", + "id": "d6960a4c-fd47-4147-97f7-5104086a8c4a", + "last_modified": 1624479576971 + }, + { + "Domain": "mintmobile.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: special; allowed: [!#$%&()*+:;=@[^_`{}~]];", + "id": "57403978-025c-48bc-b052-48cdd4ae3390", + "last_modified": 1624479576967 + }, + { + "Domain": "mlb.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!\"#$%&'()*+,./:;<=>?[\\^_`{|}~]];", + "id": "4414af72-3ce4-4e57-a2b3-3b5df281e8bf", + "last_modified": 1624479576963 + }, + { + "Domain": "myaccess.dmdc.osd.mil", + "password-rules": "minlength: 9; maxlength: 20; required: lower; required: upper; required: digit; allowed: [-@_#!&$`%*+()./,;~:{}|?>=<^'[]];", + "id": "f59eca2e-009f-47fd-a79c-3dad75f1ceae", + "last_modified": 1624479576958 + }, + { + "Domain": "mygoodtogo.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper, digit;", + "id": "9a113cf9-1f58-4b31-b434-7032fc88dd89", + "last_modified": 1624479576954 + }, + { + "Domain": "myhealthrecord.com", + "password-rules": "minlength: 8; maxlength: 20; allowed: lower, upper, digit, [_.!$*=];", + "id": "2814a487-2992-4fb7-a1e8-631fed19e2d6", + "last_modified": 1624479576950 + }, + { + "Domain": "mysubaru.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; allowed: [!#$%()*+,./:;=?@\\^`~];", + "id": "dd12e1e4-7851-46bf-b152-86c02ac72704", + "last_modified": 1624479576946 + }, + { + "Domain": "naver.com", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "269e3291-55d0-4305-9682-82db77080404", + "last_modified": 1624479576941 + }, + { + "Domain": "nelnet.net", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit, [!@#$&*];", + "id": "21596529-634e-43e4-8329-82e2312c5fca", + "last_modified": 1624479576937 + }, + { + "Domain": "netflix.com", + "password-rules": "minlength: 4; maxlength: 60; required: lower, upper, digit; allowed: special;", + "id": "48f6925f-ad22-4146-bb30-7b9e078e4d75", + "last_modified": 1624479576933 + }, + { + "Domain": "netgear.com", + "password-rules": "minlength: 6; maxlength: 128; allowed: lower, upper, digit, [!@#$%^&*()];", + "id": "eb58b3dd-ffce-48c9-ba89-e11119a2536f", + "last_modified": 1624479576929 + }, + { + "Domain": "nowinstock.net", + "password-rules": "minlength: 6; maxlength: 20; allowed: lower, upper, digit;", + "id": "8c168f00-e069-400d-bd5c-778386ec39b6", + "last_modified": 1624479576925 + }, + { + "Domain": "ototoy.jp", + "password-rules": "minlength: 8; allowed: upper,lower,digit,[- .=_];", + "id": "2e2bcb45-fda9-4977-b81f-b40a7e2bc4e6", + "last_modified": 1624479576920 + }, + { + "Domain": "packageconciergeadmin.com", + "password-rules": "minlength: 4; maxlength: 4; allowed: digit;", + "id": "230c5591-2938-4e8a-a990-01569d7dd695", + "last_modified": 1624479576916 + }, + { + "Domain": "parkmobile.io", + "password-rules": "minlength: 8; maxlength: 25; required: lower; required: upper; required: digit; required: [!@#$%^&];", + "id": "cd94b742-46f0-4014-982c-04a08abdb5e4", + "last_modified": 1624479576912 + }, + { + "Domain": "paypal.com", + "password-rules": "minlength: 8; maxlength: 20; max-consecutive: 3; required: lower, upper; required: digit, [!@#$%^&*()];", + "id": "37d2f563-220a-48ad-804c-18ed9a970bef", + "last_modified": 1624479576908 + }, + { + "Domain": "payvgm.youraccountadvantage.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: special;", + "id": "d02c5cbf-cfab-4784-b4dc-a27cd07c5a7c", + "last_modified": 1624479576903 + }, + { + "Domain": "pilotflyingj.com", + "password-rules": "minlength: 7; required: digit; allowed: lower, upper;", + "id": "1bddba4d-b4d6-45e4-9264-9dc35c6ed3a5", + "last_modified": 1624479576899 + }, + { + "Domain": "pixnet.cc", + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper;", + "id": "1c6aabd2-8788-43a6-ab6f-fc57e6b0522c", + "last_modified": 1624479576894 + }, + { + "Domain": "planetary.org", + "password-rules": "minlength: 5; maxlength: 20; required: lower; required: upper; required: digit; allowed: ascii-printable;", + "id": "84764ff8-9b16-4923-a32c-61af4c687a6b", + "last_modified": 1624479576889 + }, + { + "Domain": "portal.edd.ca.gov", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&()*@^];", + "id": "430d033c-fba6-48f8-a7ed-f6e94f5d4860", + "last_modified": 1624479576885 + }, + { + "Domain": "portals.emblemhealth.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()*+,./:;<>?@\\^_`{|}~[]];", + "id": "08af597f-ec27-4173-8780-70e994d0873e", + "last_modified": 1624479576881 + }, + { + "Domain": "portlandgeneral.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: [!#$%&*?@];", + "id": "bf8e5847-b010-423e-9568-e1dc77f8f208", + "last_modified": 1624479576877 + }, + { + "Domain": "poste.it", + "password-rules": "minlength: 8; maxlength: 16; max-consecutive: 2; required: lower; required: upper; required: digit; required: special;", + "id": "22ef42ad-30c2-4c8e-89ab-8bb8987dc70c", + "last_modified": 1624479576872 + }, + { + "Domain": "posteo.de", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [-~!#$%&_+=|(){}[:;\"’<>,.? ]];", + "id": "ebbb759c-4639-4822-b071-a7d7c8e09d82", + "last_modified": 1624479576868 + }, + { + "Domain": "powells.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [\"!@#$%^&*(){}[]];", + "id": "148061af-5fa0-4095-a449-ff17c505707e", + "last_modified": 1624479576864 + }, + { + "Domain": "prepaid.bankofamerica.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()+~{}'\";:<>?];", + "id": "55732a4d-5523-4a58-a148-4f669484fc23", + "last_modified": 1624479576860 + }, + { + "Domain": "propelfuels.com", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "8d5e11d8-3e97-4e53-bc48-70a49749cb6d", + "last_modified": 1624479576855 + }, + { + "Domain": "qdosstatusreview.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&@^];", + "id": "79d84875-e285-4449-b664-ac2548047773", + "last_modified": 1624479576851 + }, + { + "Domain": "rejsekort.dk", + "password-rules": "minlength: 7; maxlength: 15; required: lower; required: upper; required: digit;", + "id": "03ad72e8-7e6d-4865-9001-03c8d441b554", + "last_modified": 1624479576847 + }, + { + "Domain": "ring.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!@#$%^&*<>?];", + "id": "e4d9b593-f745-4305-a4d1-7c87d6491eab", + "last_modified": 1624479576843 + }, + { + "Domain": "riteaid.com", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit;", + "id": "f007d5e4-d650-446e-b6d7-5c1127952431", + "last_modified": 1624479576838 + }, + { + "Domain": "robinhood.com", + "password-rules": "minlength: 10;", + "id": "4a70469b-2f6a-4068-be6f-7bb56da4c7ac", + "last_modified": 1624479576834 + }, + { + "Domain": "ruten.com.tw", + "password-rules": "minlength: 6; maxlength: 15; required: lower, upper;", + "id": "a1c77d00-397b-4a17-ac98-d4e4d5045c63", + "last_modified": 1624479576830 + }, + { + "Domain": "santahelenasaude.com.br", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];", + "id": "9047ed4e-8526-4287-8318-dbf92675761a", + "last_modified": 1624479576826 + }, + { + "Domain": "santander.de", + "password-rules": "minlength: 8; maxlength: 12; required: lower, upper; required: digit; allowed: [-!#$%&'()*,.:;=?^{}];", + "id": "a87ca10c-9ad6-40fb-994f-8abcc680d3f6", + "last_modified": 1624479576821 + }, + { + "Domain": "sbisec.co.jp", + "password-rules": "minlength: 10; maxlength: 20; allowed: upper,lower,digit;", + "id": "77e3137c-3808-4338-89a1-b695435b9bb8", + "last_modified": 1624479576817 + }, + { + "Domain": "secure-arborfcu.org", + "password-rules": "minlength: 8; maxlength: 15; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];", + "id": "4a2aa792-ac47-4c69-9309-350e6d619088", + "last_modified": 1624479576813 + }, + { + "Domain": "secure.orclinic.com", + "password-rules": "minlength: 6; maxlength: 15; required: lower; required: digit; allowed: ascii-printable;", + "id": "261b0370-c9c8-45eb-9746-9e064c9b6e25", + "last_modified": 1624479576809 + }, + { + "Domain": "secure.wa.aaa.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; allowed: ascii-printable;", + "id": "647eb93a-fbf7-4d5b-8a5e-76fe582dc9da", + "last_modified": 1624479576805 + }, + { + "Domain": "sephora.com", + "password-rules": "minlength: 6; maxlength: 12;", + "id": "d1a87641-5970-4e96-811b-5a20f192c69d", + "last_modified": 1624479576801 + }, + { + "Domain": "serviziconsolari.esteri.it", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;", + "id": "4cf4cb84-9413-42d3-929c-439569a5a4fd", + "last_modified": 1624479576796 + }, + { + "Domain": "servizioelettriconazionale.it", + "password-rules": "minlength: 8; maxlength: 20; required: lower; required: upper; required: digit; required: [!#$%&*?@^_~];", + "id": "fa5fdcb6-0fd9-432e-861a-5c327f639654", + "last_modified": 1624479576792 + }, + { + "Domain": "sfwater.org", + "password-rules": "minlength: 10; maxlength: 30; required: digit; allowed: lower, upper, [!@#$%*()_+^}{:;?.];", + "id": "c65363de-aead-40d9-8fb8-14a8b46031af", + "last_modified": 1624479576788 + }, + { + "Domain": "signin.ea.com", + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper; required: digit; allowed: [-!@#^&*=+;:];", + "id": "13e6ab13-6e7d-4d0a-85a3-f55464185cf3", + "last_modified": 1624479576784 + }, + { + "Domain": "southwest.com", + "password-rules": "minlength: 8; maxlength: 16; required: upper; required: digit; allowed: lower, [!@#$%^*(),.;:/\\];", + "id": "0c6010fc-395c-459a-b61e-422500be8777", + "last_modified": 1624479576780 + }, + { + "Domain": "speedway.com", + "password-rules": "minlength: 4; maxlength: 8; required: digit;", + "id": "446131d5-8fbb-4f77-b02c-f0858f706df1", + "last_modified": 1624479576776 + }, + { + "Domain": "spirit.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [!@#$%^&*()];", + "id": "0e6bbcf6-5ec6-4417-9f8f-4b586ed4f9d3", + "last_modified": 1624479576772 + }, + { + "Domain": "splunk.com", + "password-rules": "minlength: 8; maxlength: 64; required: lower; required: upper; required: digit; required: [-!@#$%&*_+=<>];", + "id": "984b1541-aac5-40c5-a51b-7a598a4a050b", + "last_modified": 1624479576768 + }, + { + "Domain": "ssa.gov", + "password-rules": "required: lower; required: upper; required: digit; required: [~!@#$%^&*];", + "id": "2977a003-71e5-4fb7-8d97-0aa1135edee5", + "last_modified": 1624479576764 + }, + { + "Domain": "store.nvidia.com", + "password-rules": "minlength: 8; maxlength: 32; required: lower; required: upper; required: digit; required: [-!@#$%^*~:;&><[{}|_+=?]];", + "id": "48c45270-f890-4c0e-a6d7-69d5372d1953", + "last_modified": 1624479576759 + }, + { + "Domain": "store.steampowered.com", + "password-rules": "minlength: 6; required: lower; required: upper; required: digit; allowed: [~!@#$%^&*];", + "id": "1af53dc1-2c9e-4308-9323-931ec2a6ec47", + "last_modified": 1624479576755 + }, + { + "Domain": "successfactors.eu", + "password-rules": "minlength: 8; maxlength: 18; required: lower; required: upper; required: digit,[-!\"#$%&'()*+,.:;<=>?@[^_`{|}~]];", + "id": "b60f5e67-9aee-49e4-b0fd-10f7c158d3e4", + "last_modified": 1624479576751 + }, + { + "Domain": "sulamericaseguros.com.br", + "password-rules": "minlength: 6; maxlength: 6;", + "id": "2d508bf3-34d3-490f-943b-15b2a4620454", + "last_modified": 1624479576747 + }, + { + "Domain": "sunlife.com", + "password-rules": "minlength: 8; maxlength: 10; required: digit; required: lower, upper;", + "id": "002a1e5d-4cf6-4954-85a9-5d0e7c0dcafd", + "last_modified": 1624479576742 + }, + { + "Domain": "t-mobile.net", + "password-rules": "minlength: 8; maxlength: 16;", + "id": "c9f6b726-72e2-46a5-8efb-f935fbd1593a", + "last_modified": 1624479576738 + }, + { + "Domain": "target.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit, [-!\"#$%&'()*+,./:;=?@[\\^_`{|}~];", + "id": "db64457e-2772-4205-93d8-cf8aa868fe48", + "last_modified": 1624479576734 + }, + { + "Domain": "telekom-dienste.de", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [#$%&()*+,./<=>?@_{|}~];", + "id": "ecbf1f12-2ad2-4b2a-8dfb-7eeab174cf37", + "last_modified": 1624479576729 + }, + { + "Domain": "thameswater.co.uk", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: special;", + "id": "1762cf2d-678f-429b-a269-d2bab5995305", + "last_modified": 1624479576725 + }, + { + "Domain": "twitter.com", + "password-rules": "minlength: 8;", + "id": "0bcba576-c599-4b54-bc15-299e23901adc", + "last_modified": 1624479576721 + }, + { + "Domain": "ubisoft.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower; required: upper; required: digit; required: [-]; required: [!@#$%^&*()+];", + "id": "f90b4a6e-5017-4395-9428-f40fedde66a2", + "last_modified": 1624479576716 + }, + { + "Domain": "udel.edu", + "password-rules": "minlength: 12; maxlength: 30; required: lower; required: upper; required: digit; required: [!@#$%^&*()+];", + "id": "5d225840-dc7c-491e-8bff-32c6bd5b0197", + "last_modified": 1624479576712 + }, + { + "Domain": "user.ornl.gov", + "password-rules": "minlength: 8; maxlength: 30; max-consecutive: 3; required: lower, upper; required: digit; allowed: [!#$%./_];", + "id": "a255ca92-b588-4d51-8484-a08e3de891ae", + "last_modified": 1624479576708 + }, + { + "Domain": "vanguard.com", + "password-rules": "minlength: 6; maxlength: 20; required: lower; required: upper; required: digit; required: digit;", + "id": "0268a50e-7ea1-4e0e-94ca-48836ff1a505", + "last_modified": 1624479576704 + }, + { + "Domain": "vanguardinvestor.co.uk", + "password-rules": "minlength: 8; maxlength: 50; required: lower; required: upper; required: digit; required: digit;", + "id": "2e683b88-6b4e-4b98-a073-4f853bb53165", + "last_modified": 1624479576700 + }, + { + "Domain": "ventrachicago.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit, [!@#$%^];", + "id": "df652b2a-2306-4f52-b369-7c19915c5360", + "last_modified": 1624479576695 + }, + { + "Domain": "verizonwireless.com", + "password-rules": "minlength: 8; maxlength: 20; required: lower, upper; required: digit; allowed: unicode;", + "id": "9cbff1d3-244d-4f7f-a848-f7ebe4caf93f", + "last_modified": 1624479576691 + }, + { + "Domain": "vetsfirstchoice.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; allowed: [?!@$%^+=&];", + "id": "615d4909-f0b1-4cc9-9878-d4abf52a7129", + "last_modified": 1624479576687 + }, + { + "Domain": "virginmobile.ca", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$@];", + "id": "f7e1bff7-fd15-4c1b-8670-94c2d0a7a932", + "last_modified": 1624479576683 + }, + { + "Domain": "visa.com", + "password-rules": "minlength: 6; maxlength: 32;", + "id": "36c1bb07-c5e4-40ad-a61d-5a6161429373", + "last_modified": 1624479576679 + }, + { + "Domain": "vivo.com.br", + "password-rules": "maxlength: 6; max-consecutive: 3; allowed: digit;", + "id": "c224e115-06c4-4b4c-a254-782c496be427", + "last_modified": 1624479576675 + }, + { + "Domain": "walkhighlands.co.uk", + "password-rules": "minlength: 9; maxlength: 15; required: lower; required: upper; required: digit; allowed: special;", + "id": "5d69d056-af55-465e-bac3-5a7e3fda5057", + "last_modified": 1624479576671 + }, + { + "Domain": "walmart.com", + "password-rules": "allowed: lower, upper, digit, [-(~!@#$%^&*_+=`|(){}[:;\"'<>,.?]];", + "id": "83c9b1e5-6ec3-409a-a28a-3c87ef48bc97", + "last_modified": 1624479576667 + }, + { + "Domain": "waze.com", + "password-rules": "minlength: 8; maxlength: 64; required: lower, upper, digit;", + "id": "af1084bb-b652-45de-9dae-4ff49b528222", + "last_modified": 1624479576663 + }, + { + "Domain": "wccls.org", + "password-rules": "minlength: 4; maxlength: 16; allowed: lower, upper, digit;", + "id": "8fbcf918-1625-42a9-a683-a7070cfa4ad0", + "last_modified": 1624479576659 + }, + { + "Domain": "web.de", + "password-rules": "minlength: 8; maxlength: 40; allowed: lower, upper, digit, [-<=>~!|()@#{}$%,.?^'&*_+`:;\"[]];", + "id": "d5591a2b-46cb-4c6d-885b-2136a34bdc8c", + "last_modified": 1624479576655 + }, + { + "Domain": "weibo.com", + "password-rules": "minlength: 6; maxlength: 16;", + "id": "4b4d23f9-a062-4066-930a-139b43af1a4c", + "last_modified": 1624479576651 + }, + { + "Domain": "wsj.com", + "password-rules": "minlength: 5; maxlength: 15; required: digit; allowed: lower, upper, [-~!@#$^*_=`|(){}[:;\"'<>,.?]];", + "id": "a722dd4f-c158-4f57-bdf1-dcf9b76b7ed0", + "last_modified": 1624479576646 + }, + { + "Domain": "xfinity.com", + "password-rules": "minlength: 8; maxlength: 16; required: lower, upper; required: digit;", + "id": "e2e55ee1-a051-4848-bdf8-30b660e6facf", + "last_modified": 1624479576642 + }, + { + "Domain": "yatra.com", + "password-rules": "minlength: 8; required: lower; required: upper; required: digit; required: [!#$%&'()+,.:?@[_`~]];", + "id": "7e1b7e51-9976-4155-8597-d8efc3f4dd77", + "last_modified": 1624479576638 + }, + { + "Domain": "zdf.de", + "password-rules": "minlength: 8; required: upper; required: digit; allowed: lower, special;", + "id": "f98498db-c932-4ce7-8175-59f4f4bb873a", + "last_modified": 1624479576634 + }, + { + "Domain": "zoom.us", + "password-rules": "minlength: 8; maxlength: 32; max-consecutive: 6; required: lower; required: upper; required: digit;", + "id": "5fe248fb-3e63-4300-8f67-3e6006af0e87", + "last_modified": 1624479576629 + } + ], + "timestamp": 1679600032742 +} diff --git a/services/settings/dumps/main/search-config-icons.json b/services/settings/dumps/main/search-config-icons.json new file mode 100644 index 0000000000..dbb7fc8aa8 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons.json @@ -0,0 +1,736 @@ +{ + "data": [ + { + "schema": 1707265339663, + "imageSize": 16, + "attachment": { + "hash": "617dec5d635efb0a12d0de935c6999ef0249f4a63c62bdcb96551518bc3d1812", + "size": 2672, + "filename": "yahoo-jp-auctions-16-firefox.ico", + "location": "main-workspace/search-config-icons/d424ae0b-82e7-42fc-a10a-a607bba3642a.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "yahoo-jp-auctions" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "a06db97d-1210-ea2e-5474-0e2f7d295bfd", + "last_modified": 1707330724491 + }, + { + "schema": 1707265325417, + "imageSize": 16, + "attachment": { + "hash": "ca8f102ac4f35189ebcb786d080843b603b234f89b8d8b1c0ef27a0ab7148182", + "size": 5430, + "filename": "yahoo-jp-16-firefox.ico", + "location": "main-workspace/search-config-icons/b071a229-712f-4e13-99f4-61283d1c3fb4.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "yahoo-jp" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "74793ce1-a918-a5eb-d3c0-2aadaff3c88c", + "last_modified": 1707330724489 + }, + { + "schema": 1707265314687, + "imageSize": 16, + "attachment": { + "hash": "809697f48848e7c3638d5f3e0b224ea60b3800504e7bd8417854d55989b85196", + "size": 304, + "filename": "wolnelektury-pl-16-firefox.png", + "location": "main-workspace/search-config-icons/4665f707-e315-4ac0-a12e-2455f0123758.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "wolnelektury-pl" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "e718e983-09aa-e8f6-b25f-cd4b395d4785", + "last_modified": 1707330724487 + }, + { + "schema": 1707265299492, + "imageSize": 16, + "attachment": { + "hash": "5d53ef1866a08cc29011f5f2a9ce99bbf37cf42e80de7f0e8cc30d13337e8187", + "size": 318, + "filename": "wiktionary-16-firefox.ico", + "location": "main-workspace/search-config-icons/22d846d9-468d-457b-9e3a-01166e4cc404.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "wiktionary*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "91a9672d-e945-8e1e-0996-aefdb0190716", + "last_modified": 1707330724485 + }, + { + "schema": 1707265280786, + "imageSize": 16, + "attachment": { + "hash": "62d2faa3a8322b1f643aab6e045837500ebe3049c5cb140cb44c4dfc7290337a", + "size": 884, + "filename": "wikipedia-16-firefox.ico", + "location": "main-workspace/search-config-icons/bcdcafec-9b52-4f8b-82fe-abdb2c533cdb.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "wikipedia*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "101ce01d-2691-b729-7f16-9d389803384b", + "last_modified": 1707330724482 + }, + { + "schema": 1707265270195, + "imageSize": 16, + "attachment": { + "hash": "dd5cab3711f778677859e86000a127ed07a6175e8e58aecb0fba71b825ce76d7", + "size": 3638, + "filename": "webde-16-firefox.ico", + "location": "main-workspace/search-config-icons/45da3463-8c66-4b48-a422-00c54f44a96b.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "webde" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "6f4da442-d31e-28f8-03af-797d16bbdd27", + "last_modified": 1707330724480 + }, + { + "schema": 1707265259585, + "imageSize": 16, + "attachment": { + "hash": "9cd3da38e3938549434d1c3cba6fed249ffa7d91d9a6d7ffb5f4184f527cac76", + "size": 5430, + "filename": "vatera-16-firefox.ico", + "location": "main-workspace/search-config-icons/c7aa740f-cef2-45da-aaba-331a74425985.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "vatera" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41", + "last_modified": 1707330724478 + }, + { + "schema": 1707265244672, + "imageSize": 16, + "attachment": { + "hash": "1bf68aca7bfc75ca8485c3dac9a1daa13c1a3eb480688c32262096af6076adfa", + "size": 379, + "filename": "tyda-sv-SE-16-firefox.png", + "location": "main-workspace/search-config-icons/7d783537-313f-46db-bb4b-8878118884e4.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "tyda-sv-SE" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "a2c7d4e9-f770-51e1-0963-3c2c8401631d", + "last_modified": 1707330724476 + }, + { + "schema": 1707265228699, + "imageSize": 16, + "attachment": { + "hash": "a1fd5d127a5f2590ddcd439b7a2abb3456b48217ea11daf0345b26e108f520e6", + "size": 1743, + "filename": "seznam-cz-16-firefox.ico", + "location": "main-workspace/search-config-icons/aae88ab5-7f9e-4bd6-80cd-9bb0f6638a19.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "seznam-cz" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "84bb4962-e571-227a-9ef6-2ac5f2aac361", + "last_modified": 1707330724473 + }, + { + "schema": 1707265210999, + "imageSize": 16, + "attachment": { + "hash": "c4d88cfa5262f6d2cf76b167281d25821c9e1770684b739ed6ad3cf7277a121b", + "size": 3638, + "filename": "salidzinilv-16-firefox.ico", + "location": "main-workspace/search-config-icons/bc06c6d4-019c-4aa8-8958-813dc644452a.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "salidzinilv" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "fca3e3ee-56cd-f474-dc31-307fd24a891d", + "last_modified": 1707330724471 + }, + { + "schema": 1707265199776, + "imageSize": 16, + "attachment": { + "hash": "247aa26993083705ce99a8e5612cdf262aca98cde86ba19afc964329ba95986a", + "size": 2468, + "filename": "readmoo-16-firefox.ico", + "location": "main-workspace/search-config-icons/46bf2594-7779-42b7-be7b-212232ba2ba0.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "readmoo" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "e02f23df-8d48-2b1b-3b5c-6dd27302c61c", + "last_modified": 1707330724468 + }, + { + "schema": 1707265187818, + "imageSize": 16, + "attachment": { + "hash": "b75ef04a805325e303c4195833cdd077d3d406f360b25b72502fc55880b9150b", + "size": 2053, + "filename": "rakuten-16-firefox.ico", + "location": "main-workspace/search-config-icons/3d9337c7-0654-475b-bc03-710e0400b35e.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "rakuten" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "06cf7432-efd7-f244-927b-5e423005e1ea", + "last_modified": 1707330724466 + }, + { + "schema": 1707265170689, + "imageSize": 16, + "attachment": { + "hash": "91d17ba44192a6430ffdb447ff3a11533ef964628f67c13480cc9470212d3d65", + "size": 5430, + "filename": "qwantjr-16-firefox.ico", + "location": "main-workspace/search-config-icons/360f7074-bda7-4906-bd66-db38d4770056.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "qwantjr" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "f312610a-ebfb-a106-ea92-fd643c5d3636", + "last_modified": 1707330724464 + }, + { + "schema": 1707265159352, + "imageSize": 16, + "attachment": { + "hash": "91d17ba44192a6430ffdb447ff3a11533ef964628f67c13480cc9470212d3d65", + "size": 5430, + "filename": "qwant-16-firefox.ico", + "location": "main-workspace/search-config-icons/d537aa34-64e1-43f1-a90f-bb419fde1b3e.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "qwant" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b", + "last_modified": 1707330724462 + }, + { + "schema": 1707265148215, + "imageSize": 16, + "attachment": { + "hash": "3b88f3ef3cbfaed127d679ec7e44a44fe8dcad688feb89a70a1a9447c1460d15", + "size": 1406, + "filename": "prisjakt-sv-SE-16-firefox.ico", + "location": "main-workspace/search-config-icons/84ce4b9c-3998-4ca7-9856-3c9cb019da95.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "prisjakt-sv-SE" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "177aba42-9bed-4078-e36b-580e8794cd7f", + "last_modified": 1707330724459 + }, + { + "schema": 1707265137087, + "imageSize": 16, + "attachment": { + "hash": "64800e32b24b2c8c0582750e1657426d56abd74b65682e20e892f82710d120b6", + "size": 790, + "filename": "priberam-16-firefox.png", + "location": "main-workspace/search-config-icons/e9432c7b-e80b-49ad-9c4f-8a69df8a63b4.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "priberam" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "0eec5640-6fde-d6fe-322a-c72c6d5bd5a2", + "last_modified": 1707330724457 + }, + { + "schema": 1707265120610, + "imageSize": 16, + "attachment": { + "hash": "b0c6d1850265e3c946917232ca6c6ace3dad23347bfab4f81351eac569326d34", + "size": 2584, + "filename": "pazaruvaj-16-firefox.ico", + "location": "main-workspace/search-config-icons/2951bdf6-b440-4543-83ba-4ca1318db69e.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "pazaruvaj" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "7efbed51-813c-581d-d8d3-f8758434e451", + "last_modified": 1707330724454 + }, + { + "schema": 1707265108111, + "imageSize": 16, + "attachment": { + "hash": "189ed3031a2cefd3150c9e5b37bee1ffbc1f7850f7ac0621e4b8d262f2c1048c", + "size": 2639, + "filename": "odpiralni-16-firefox.png", + "location": "main-workspace/search-config-icons/2323c216-89f9-43e7-a519-9a23a256aa45.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "odpiralni" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "4e271681-3e0f-91ac-9750-03f665efc171", + "last_modified": 1707330724452 + }, + { + "schema": 1707265094264, + "imageSize": 16, + "attachment": { + "hash": "723ac3228124926537d5a61284d60e198a52895195f9f69b967c578ef7a012ad", + "size": 5430, + "filename": "naver-kr-16-firefox.ico", + "location": "main-workspace/search-config-icons/4111bef2-7258-4c9b-9f64-77cf545b697f.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "naver-kr" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335", + "last_modified": 1707330724449 + }, + { + "schema": 1707265073846, + "imageSize": 16, + "attachment": { + "hash": "d7fdfd971d874f2ec6f209df6f6b8173d126cd3f7a25daacb94de4259efbcf16", + "size": 5430, + "filename": "mercadolivre-16-firefox.ico", + "location": "main-workspace/search-config-icons/f8d4c276-9b04-4c70-8ff9-5c1faf55978c.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "mercadolivre" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "fed4f021-ff3e-942a-010e-afa43fda2136", + "last_modified": 1707330724446 + }, + { + "schema": 1707265061966, + "imageSize": 16, + "attachment": { + "hash": "d7fdfd971d874f2ec6f209df6f6b8173d126cd3f7a25daacb94de4259efbcf16", + "size": 5430, + "filename": "mercadolibre-16-firefox.ico", + "location": "main-workspace/search-config-icons/33e52a1d-1883-4a4b-aaf6-7e4f5b52cdfb.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "mercadolibre*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "47da97b5-600f-c450-fd15-a52bb2169c11", + "last_modified": 1707330724443 + }, + { + "schema": 1707265027259, + "imageSize": 16, + "attachment": { + "hash": "1474c93e49c209aca2a2df2acb61b64574805106bead6edebd67287de21920e0", + "size": 1812, + "filename": "mapy-cz-16-firefox.ico", + "location": "main-workspace/search-config-icons/e94ddd93-1c38-42c0-847a-741f49305d7b.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "mapy-cz" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "b8ca5a94-8fff-27ad-6e00-96e244a32e21", + "last_modified": 1707330724440 + }, + { + "schema": 1707265012766, + "imageSize": 16, + "attachment": { + "hash": "33ca72f1eac56793d1fd811189cedef98004a067c85b1143083b564814a4b0db", + "size": 1150, + "filename": "mailcom-16-firefox.ico", + "location": "main-workspace/search-config-icons/4c43dc1a-e163-4560-9713-fae61c9c59b9.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "mailcom" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "87ac4cde-f581-398b-1e32-eb4079183b36", + "last_modified": 1707330724438 + }, + { + "schema": 1707264998628, + "imageSize": 16, + "attachment": { + "hash": "a64f553b79fbb8c45734310dac401ad253ccd05aeabfa58bb5541daa6d8caf70", + "size": 252, + "filename": "longdo-16-firefox.png", + "location": "main-workspace/search-config-icons/9526a092-69d3-4010-bf09-d426f5574c5b.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "longdo" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "32d26d19-aeb0-5c01-32e8-f8970be9246f", + "last_modified": 1707330724435 + }, + { + "schema": 1707264911347, + "imageSize": 16, + "attachment": { + "hash": "c3e8300801c5c585662f14fd8e819d635efd9830783dc3c631212927866e9898", + "size": 749, + "filename": "leo_ende_de-16-firefox.png", + "location": "main-workspace/search-config-icons/57b3cf6a-9cb2-4d3a-8839-54a3e62f49ec.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "leo_ende_de" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "b64f09fd-52d1-c48e-af23-4ce918e7bf3b", + "last_modified": 1707330724432 + }, + { + "schema": 1707264735197, + "imageSize": 16, + "attachment": { + "hash": "c971ee33b8c0a57349669d957bf73070b0632b128c94748e845b57d5e15221a4", + "size": 1150, + "filename": "gulesider-NO-16-firefox.ico", + "location": "main-workspace/search-config-icons/f3a89a46-c15c-4622-9035-cdf6773139cb.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "gulesider-NO" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "e7547f62-187b-b641-d462-e54a3f813d9a", + "last_modified": 1707330724429 + }, + { + "schema": 1707264399810, + "imageSize": 16, + "attachment": { + "hash": "6da5620880159634213e197fafca1dde0272153be3e4590818533fab8d040770", + "size": 5430, + "filename": "google-16-firefox.ico", + "location": "main-workspace/search-config-icons/644faa05-4deb-491b-ae95-0962aefabe55.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "google" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "fa0fc42c-d91d-fca7-34eb-806ff46062dc", + "last_modified": 1707330724427 + }, + { + "schema": 1707264398352, + "imageSize": 16, + "attachment": { + "hash": "828c3ca82e9be483ae583e5a705dde57b24fd8431e192e3a2d0809871992afa5", + "size": 1122, + "filename": "gmx-16-firefox.png", + "location": "main-workspace/search-config-icons/a0cb1b08-4911-4db8-90f3-ee87da5bb9b5.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "gmx*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "25de0352-aabb-d31f-15f7-bf9299fb004c", + "last_modified": 1707330724424 + }, + { + "schema": 1707264396942, + "imageSize": 16, + "attachment": { + "hash": "f895a965b68d02e7391cc4504d9be75e1ba7f9b50a1dd59af77bb44a7769c08c", + "size": 1091, + "filename": "faclair-beag-16-firefox.jpg", + "location": "main-workspace/search-config-icons/d0e5c407-7b88-4030-8870-f44498141ec7.jpg", + "mimetype": "image/jpeg" + }, + "engineIdentifiers": [ + "faclair-beag" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "6d10d702-7bd6-1452-90a5-3df665a38f66", + "last_modified": 1707330724421 + }, + { + "schema": 1707264395634, + "imageSize": 16, + "attachment": { + "hash": "150765e8e9b985ba5b820ac9b8e7623023d5a0e24f94663d5e9203d8d7598059", + "size": 1785, + "filename": "eudict-16-firefox.ico", + "location": "main-workspace/search-config-icons/93c06725-f10f-44f6-b7db-05e22a6ab676.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "eudict" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "c411adc1-9661-4fb5-a4c1-8cfe74911943", + "last_modified": 1707330724418 + }, + { + "schema": 1707264394199, + "imageSize": 16, + "attachment": { + "hash": "fdadf15c6eae7933c3d254ae6311112e0bc8a422c38c758189dbe6a4d7f6b718", + "size": 5430, + "filename": "ecosia-16-firefox.ico", + "location": "main-workspace/search-config-icons/d0ab8a9e-0dc4-476b-bdb0-81b1d9b8f6cf.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "ecosia" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "001500a9-1a6c-3f5a-ba15-a5f5a075d256", + "last_modified": 1707330724415 + }, + { + "schema": 1707264392809, + "imageSize": 16, + "attachment": { + "hash": "31a793dad95b5ffd02d39ebf14fc40877596f418f5926247487265034181dc8f", + "size": 1455, + "filename": "ebay-16-firefox.ico", + "location": "main-workspace/search-config-icons/5a9068cd-a4fa-4600-97fc-59f380e3d651.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "ebay*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "70fdd651-6c50-b7bb-09ec-7e85da259173", + "last_modified": 1707330724412 + }, + { + "schema": 1707264391458, + "imageSize": 16, + "attachment": { + "hash": "d994f806b1e4225b50be5ab681b2cecf845cc216a19a432d878cea3cb815bafd", + "size": 2799, + "filename": "ddg-16-firefox.ico", + "location": "main-workspace/search-config-icons/c6ae7df5-0396-4892-b76a-b35a00044a13.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "ddg" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3", + "last_modified": 1707330724410 + }, + { + "schema": 1707264390072, + "imageSize": 16, + "attachment": { + "hash": "ca3cc8786977f6ffeb0546ff8f3bb2b7fd240d1956fbf86777dbf0e8bec9c03b", + "size": 5430, + "filename": "daum-kr-16-firefox.ico", + "location": "main-workspace/search-config-icons/3ce9df7f-3e0a-4b26-add1-18e2857f6213.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "daum-kr" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "8831ce10-b1e4-6eb4-4975-83c67457288e", + "last_modified": 1707330724407 + }, + { + "schema": 1707264388636, + "imageSize": 16, + "attachment": { + "hash": "7042293af6b04e421cb7b68dc599ac644b76939cdcf5970159e44f658dd6a0cc", + "size": 5430, + "filename": "coccoc-16-firefox.ico", + "location": "main-workspace/search-config-icons/82083e19-25c5-4c3c-b269-3d8c0173e4e2.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "coccoc" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "0d7668a8-c3f4-cfee-cbc8-536511528937", + "last_modified": 1707330724404 + }, + { + "schema": 1707264387232, + "imageSize": 16, + "attachment": { + "hash": "9140bd1b30953f41bc758d2c0ecc873f5163e4f51126c278991eccd38589c541", + "size": 283, + "filename": "ceneji-16-firefox.png", + "location": "main-workspace/search-config-icons/1e37d101-2191-45e3-8d61-9c1bea44ebce.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "ceneji" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "50f6171f-8e7a-b41b-862e-f97397038fb2", + "last_modified": 1707330724401 + }, + { + "schema": 1707264385889, + "imageSize": 16, + "attachment": { + "hash": "6ba1f0fd1d12014cab32f74daab24dfa16fb26613ace20a1e595267621038a07", + "size": 530, + "filename": "bok-NO-16-firefox.png", + "location": "main-workspace/search-config-icons/bcf53867-215e-40f1-9a6e-bc4c5768c5c4.png", + "mimetype": "image/png" + }, + "engineIdentifiers": [ + "bok-NO" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "890de5c4-0941-a116-473a-5d240e79497a", + "last_modified": 1707330724398 + }, + { + "schema": 1707264384393, + "imageSize": 16, + "attachment": { + "hash": "5b2c34b3c4e8dd898b664dba6c3786e2ff9869eff55d673aa48361f11325ed07", + "size": 4286, + "filename": "bing-16-firefox.ico", + "location": "main-workspace/search-config-icons/5ed361f5-5b94-4899-896a-747d107f7392.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "bing" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "cbf9e891-d079-2b28-5617-283450d463dd", + "last_modified": 1707330724395 + }, + { + "schema": 1707264382904, + "imageSize": 16, + "attachment": { + "hash": "877fb3aca13d2a7c656df1f94df3fa052afbb40b65c99ba5382392ff5499016e", + "size": 5430, + "filename": "baidu-16-firefox.ico", + "location": "main-workspace/search-config-icons/4ffe56b6-6f76-4841-9f79-cd5e7dac0e10.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "baidu" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "5ded611d-44b2-dc46-fd67-fb116888d75d", + "last_modified": 1707330724392 + }, + { + "schema": 1707264381509, + "imageSize": 16, + "attachment": { + "hash": "24daa27a3234d01b5add42e027b0a34000d0ab47c17fe3924c2ca267b7b61c19", + "size": 5430, + "filename": "azerdict-16-firefox.ico", + "location": "main-workspace/search-config-icons/4d6f988d-8905-4aa7-aeea-5b04a6197767.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "azerdict" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e", + "last_modified": 1707330724389 + }, + { + "schema": 1707264380172, + "imageSize": 16, + "attachment": { + "hash": "16ea89d4baa39529d7a84d5152867a4c6ed6867198c4dfa1648b1f43ce6a3f6f", + "size": 1407, + "filename": "amazon-16-firefox.ico", + "location": "main-workspace/search-config-icons/9b60e56c-34cc-4447-a20d-21b4ecad7e8a.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "amazon*" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "2e835b0e-9709-d1bb-9725-87f59f3445ca", + "last_modified": 1707330724387 + }, + { + "schema": 1707264378878, + "imageSize": 16, + "attachment": { + "hash": "ca6e972004f62355c1ea97656bc2328e1643971bdecab9c6b563d45593b8122e", + "size": 1150, + "filename": "allegro-pl-16-firefox.ico", + "location": "main-workspace/search-config-icons/26f2abe5-ac6b-4375-822e-b86fb75637a3.ico", + "mimetype": "image/x-icon" + }, + "engineIdentifiers": [ + "allegro-pl" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "96327a73-c433-5eb4-a16d-b090cadfb80b", + "last_modified": 1707330724384 + }, + { + "schema": 1707264328903, + "imageSize": 16, + "attachment": { + "hash": "865d76c8175a8f11dedc93f0bc212242a97a8a76adac870e8249368cecc81402", + "size": 159, + "filename": "1und1-16-firefox.gif", + "location": "main-workspace/search-config-icons/d4eeae67-e96c-4fd0-bbbd-c05ff235f622.gif", + "mimetype": "image/gif" + }, + "engineIdentifiers": [ + "1und1" + ], + "filter_expression": "env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"", + "id": "d87f251c-3e12-a8bf-e2d0-afd43d36c5f9", + "last_modified": 1707330724381 + } + ], + "timestamp": 1707330724491 +} diff --git a/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256 b/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256 Binary files differnew file mode 100644 index 0000000000..cc72d09d6d --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256 diff --git a/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256.meta.json b/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256.meta.json new file mode 100644 index 0000000000..c20891d174 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/001500a9-1a6c-3f5a-ba15-a5f5a075d256.meta.json @@ -0,0 +1 @@ +{"schema":1707264394199,"imageSize":16,"attachment":{"hash":"fdadf15c6eae7933c3d254ae6311112e0bc8a422c38c758189dbe6a4d7f6b718","size":5430,"filename":"ecosia-16-firefox.ico","location":"main-workspace/search-config-icons/d0ab8a9e-0dc4-476b-bdb0-81b1d9b8f6cf.ico","mimetype":"image/x-icon"},"engineIdentifiers":["ecosia"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"001500a9-1a6c-3f5a-ba15-a5f5a075d256","last_modified":1707330724415}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea b/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea Binary files differnew file mode 100644 index 0000000000..66afe98469 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea diff --git a/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea.meta.json b/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea.meta.json new file mode 100644 index 0000000000..f8591fcfa0 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/06cf7432-efd7-f244-927b-5e423005e1ea.meta.json @@ -0,0 +1 @@ +{"schema":1707265187818,"imageSize":16,"attachment":{"hash":"b75ef04a805325e303c4195833cdd077d3d406f360b25b72502fc55880b9150b","size":2053,"filename":"rakuten-16-firefox.ico","location":"main-workspace/search-config-icons/3d9337c7-0654-475b-bc03-710e0400b35e.ico","mimetype":"image/x-icon"},"engineIdentifiers":["rakuten"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"06cf7432-efd7-f244-927b-5e423005e1ea","last_modified":1707330724466}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937 b/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937 Binary files differnew file mode 100644 index 0000000000..f128244fed --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937 diff --git a/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937.meta.json b/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937.meta.json new file mode 100644 index 0000000000..e338e8b715 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/0d7668a8-c3f4-cfee-cbc8-536511528937.meta.json @@ -0,0 +1 @@ +{"schema":1707264388636,"imageSize":16,"attachment":{"hash":"7042293af6b04e421cb7b68dc599ac644b76939cdcf5970159e44f658dd6a0cc","size":5430,"filename":"coccoc-16-firefox.ico","location":"main-workspace/search-config-icons/82083e19-25c5-4c3c-b269-3d8c0173e4e2.ico","mimetype":"image/x-icon"},"engineIdentifiers":["coccoc"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"0d7668a8-c3f4-cfee-cbc8-536511528937","last_modified":1707330724404}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2 b/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2 Binary files differnew file mode 100644 index 0000000000..98924439d5 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2 diff --git a/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2.meta.json b/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2.meta.json new file mode 100644 index 0000000000..3d6c797ba4 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/0eec5640-6fde-d6fe-322a-c72c6d5bd5a2.meta.json @@ -0,0 +1 @@ +{"schema":1707265137087,"imageSize":16,"attachment":{"hash":"64800e32b24b2c8c0582750e1657426d56abd74b65682e20e892f82710d120b6","size":790,"filename":"priberam-16-firefox.png","location":"main-workspace/search-config-icons/e9432c7b-e80b-49ad-9c4f-8a69df8a63b4.png","mimetype":"image/png"},"engineIdentifiers":["priberam"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"0eec5640-6fde-d6fe-322a-c72c6d5bd5a2","last_modified":1707330724457}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b b/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b Binary files differnew file mode 100644 index 0000000000..4314071e24 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b diff --git a/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b.meta.json b/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b.meta.json new file mode 100644 index 0000000000..c74c9595c8 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/101ce01d-2691-b729-7f16-9d389803384b.meta.json @@ -0,0 +1 @@ +{"schema":1707265280786,"imageSize":16,"attachment":{"hash":"62d2faa3a8322b1f643aab6e045837500ebe3049c5cb140cb44c4dfc7290337a","size":884,"filename":"wikipedia-16-firefox.ico","location":"main-workspace/search-config-icons/bcdcafec-9b52-4f8b-82fe-abdb2c533cdb.ico","mimetype":"image/x-icon"},"engineIdentifiers":["wikipedia*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"101ce01d-2691-b729-7f16-9d389803384b","last_modified":1707330724482}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f b/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f Binary files differnew file mode 100644 index 0000000000..feac665f71 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f diff --git a/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f.meta.json b/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f.meta.json new file mode 100644 index 0000000000..781f6f2bf4 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/177aba42-9bed-4078-e36b-580e8794cd7f.meta.json @@ -0,0 +1 @@ +{"schema":1707265148215,"imageSize":16,"attachment":{"hash":"3b88f3ef3cbfaed127d679ec7e44a44fe8dcad688feb89a70a1a9447c1460d15","size":1406,"filename":"prisjakt-sv-SE-16-firefox.ico","location":"main-workspace/search-config-icons/84ce4b9c-3998-4ca7-9856-3c9cb019da95.ico","mimetype":"image/x-icon"},"engineIdentifiers":["prisjakt-sv-SE"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"177aba42-9bed-4078-e36b-580e8794cd7f","last_modified":1707330724459}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c b/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c Binary files differnew file mode 100644 index 0000000000..020006b5e4 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c diff --git a/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c.meta.json b/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c.meta.json new file mode 100644 index 0000000000..44c857a28d --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/25de0352-aabb-d31f-15f7-bf9299fb004c.meta.json @@ -0,0 +1 @@ +{"schema":1707264398352,"imageSize":16,"attachment":{"hash":"828c3ca82e9be483ae583e5a705dde57b24fd8431e192e3a2d0809871992afa5","size":1122,"filename":"gmx-16-firefox.png","location":"main-workspace/search-config-icons/a0cb1b08-4911-4db8-90f3-ee87da5bb9b5.png","mimetype":"image/png"},"engineIdentifiers":["gmx*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"25de0352-aabb-d31f-15f7-bf9299fb004c","last_modified":1707330724424}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335 b/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335 Binary files differnew file mode 100644 index 0000000000..eed93a92cb --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335 diff --git a/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335.meta.json b/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335.meta.json new file mode 100644 index 0000000000..cb6eb0da64 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335.meta.json @@ -0,0 +1 @@ +{"schema":1707265094264,"imageSize":16,"attachment":{"hash":"723ac3228124926537d5a61284d60e198a52895195f9f69b967c578ef7a012ad","size":5430,"filename":"naver-kr-16-firefox.ico","location":"main-workspace/search-config-icons/4111bef2-7258-4c9b-9f64-77cf545b697f.ico","mimetype":"image/x-icon"},"engineIdentifiers":["naver-kr"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"2bbe48f4-d3b8-c9e0-86e3-a54c37ec3335","last_modified":1707330724449}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca b/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca Binary files differnew file mode 100644 index 0000000000..1c39eaf8fe --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca diff --git a/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca.meta.json b/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca.meta.json new file mode 100644 index 0000000000..527a944a8d --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/2e835b0e-9709-d1bb-9725-87f59f3445ca.meta.json @@ -0,0 +1 @@ +{"schema":1707264380172,"imageSize":16,"attachment":{"hash":"16ea89d4baa39529d7a84d5152867a4c6ed6867198c4dfa1648b1f43ce6a3f6f","size":1407,"filename":"amazon-16-firefox.ico","location":"main-workspace/search-config-icons/9b60e56c-34cc-4447-a20d-21b4ecad7e8a.ico","mimetype":"image/x-icon"},"engineIdentifiers":["amazon*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"2e835b0e-9709-d1bb-9725-87f59f3445ca","last_modified":1707330724387}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f b/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f Binary files differnew file mode 100644 index 0000000000..aa42cda97f --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f diff --git a/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f.meta.json b/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f.meta.json new file mode 100644 index 0000000000..2c2b13b7d6 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/32d26d19-aeb0-5c01-32e8-f8970be9246f.meta.json @@ -0,0 +1 @@ +{"schema":1707264998628,"imageSize":16,"attachment":{"hash":"a64f553b79fbb8c45734310dac401ad253ccd05aeabfa58bb5541daa6d8caf70","size":252,"filename":"longdo-16-firefox.png","location":"main-workspace/search-config-icons/9526a092-69d3-4010-bf09-d426f5574c5b.png","mimetype":"image/png"},"engineIdentifiers":["longdo"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"32d26d19-aeb0-5c01-32e8-f8970be9246f","last_modified":1707330724435}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11 b/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11 Binary files differnew file mode 100644 index 0000000000..dc9ad5b2a9 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11 diff --git a/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11.meta.json b/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11.meta.json new file mode 100644 index 0000000000..0f05513d23 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/47da97b5-600f-c450-fd15-a52bb2169c11.meta.json @@ -0,0 +1 @@ +{"schema":1707265061966,"imageSize":16,"attachment":{"hash":"d7fdfd971d874f2ec6f209df6f6b8173d126cd3f7a25daacb94de4259efbcf16","size":5430,"filename":"mercadolibre-16-firefox.ico","location":"main-workspace/search-config-icons/33e52a1d-1883-4a4b-aaf6-7e4f5b52cdfb.ico","mimetype":"image/x-icon"},"engineIdentifiers":["mercadolibre*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"47da97b5-600f-c450-fd15-a52bb2169c11","last_modified":1707330724443}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171 b/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171 Binary files differnew file mode 100644 index 0000000000..044d4f13d4 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171 diff --git a/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171.meta.json b/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171.meta.json new file mode 100644 index 0000000000..078e2fdb43 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/4e271681-3e0f-91ac-9750-03f665efc171.meta.json @@ -0,0 +1 @@ +{"schema":1707265108111,"imageSize":16,"attachment":{"hash":"189ed3031a2cefd3150c9e5b37bee1ffbc1f7850f7ac0621e4b8d262f2c1048c","size":2639,"filename":"odpiralni-16-firefox.png","location":"main-workspace/search-config-icons/2323c216-89f9-43e7-a519-9a23a256aa45.png","mimetype":"image/png"},"engineIdentifiers":["odpiralni"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"4e271681-3e0f-91ac-9750-03f665efc171","last_modified":1707330724452}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2 b/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2 Binary files differnew file mode 100644 index 0000000000..3c77b64d3c --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2 diff --git a/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2.meta.json b/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2.meta.json new file mode 100644 index 0000000000..c79bf57fc7 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/50f6171f-8e7a-b41b-862e-f97397038fb2.meta.json @@ -0,0 +1 @@ +{"schema":1707264387232,"imageSize":16,"attachment":{"hash":"9140bd1b30953f41bc758d2c0ecc873f5163e4f51126c278991eccd38589c541","size":283,"filename":"ceneji-16-firefox.png","location":"main-workspace/search-config-icons/1e37d101-2191-45e3-8d61-9c1bea44ebce.png","mimetype":"image/png"},"engineIdentifiers":["ceneji"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"50f6171f-8e7a-b41b-862e-f97397038fb2","last_modified":1707330724401}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d b/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d Binary files differnew file mode 100644 index 0000000000..e1c770cc4b --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d diff --git a/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d.meta.json b/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d.meta.json new file mode 100644 index 0000000000..6cbe5535e6 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/5ded611d-44b2-dc46-fd67-fb116888d75d.meta.json @@ -0,0 +1 @@ +{"schema":1707264382904,"imageSize":16,"attachment":{"hash":"877fb3aca13d2a7c656df1f94df3fa052afbb40b65c99ba5382392ff5499016e","size":5430,"filename":"baidu-16-firefox.ico","location":"main-workspace/search-config-icons/4ffe56b6-6f76-4841-9f79-cd5e7dac0e10.ico","mimetype":"image/x-icon"},"engineIdentifiers":["baidu"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"5ded611d-44b2-dc46-fd67-fb116888d75d","last_modified":1707330724392}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41 b/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41 Binary files differnew file mode 100644 index 0000000000..5b02f16cb9 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41 diff --git a/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41.meta.json b/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41.meta.json new file mode 100644 index 0000000000..5a2dc6dbaa --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41.meta.json @@ -0,0 +1 @@ +{"schema":1707265259585,"imageSize":16,"attachment":{"hash":"9cd3da38e3938549434d1c3cba6fed249ffa7d91d9a6d7ffb5f4184f527cac76","size":5430,"filename":"vatera-16-firefox.ico","location":"main-workspace/search-config-icons/c7aa740f-cef2-45da-aaba-331a74425985.ico","mimetype":"image/x-icon"},"engineIdentifiers":["vatera"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"5e03d6f4-6ee9-8bc8-cf22-7a5f2cf55c41","last_modified":1707330724478}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b b/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b Binary files differnew file mode 100644 index 0000000000..d43d1d5aa6 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b diff --git a/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b.meta.json b/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b.meta.json new file mode 100644 index 0000000000..4faacc8e1b --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b.meta.json @@ -0,0 +1 @@ +{"schema":1707265159352,"imageSize":16,"attachment":{"hash":"91d17ba44192a6430ffdb447ff3a11533ef964628f67c13480cc9470212d3d65","size":5430,"filename":"qwant-16-firefox.ico","location":"main-workspace/search-config-icons/d537aa34-64e1-43f1-a90f-bb419fde1b3e.ico","mimetype":"image/x-icon"},"engineIdentifiers":["qwant"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"6a83583a-f0ba-fd39-2fdb-fd2b6990ea3b","last_modified":1707330724462}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66 b/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66 Binary files differnew file mode 100644 index 0000000000..990cf93298 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66 diff --git a/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66.meta.json b/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66.meta.json new file mode 100644 index 0000000000..b5efefd6f3 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6d10d702-7bd6-1452-90a5-3df665a38f66.meta.json @@ -0,0 +1 @@ +{"schema":1707264396942,"imageSize":16,"attachment":{"hash":"f895a965b68d02e7391cc4504d9be75e1ba7f9b50a1dd59af77bb44a7769c08c","size":1091,"filename":"faclair-beag-16-firefox.jpg","location":"main-workspace/search-config-icons/d0e5c407-7b88-4030-8870-f44498141ec7.jpg","mimetype":"image/jpeg"},"engineIdentifiers":["faclair-beag"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"6d10d702-7bd6-1452-90a5-3df665a38f66","last_modified":1707330724421}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27 b/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27 Binary files differnew file mode 100644 index 0000000000..f0ef93d209 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27 diff --git a/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27.meta.json b/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27.meta.json new file mode 100644 index 0000000000..50966bda02 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/6f4da442-d31e-28f8-03af-797d16bbdd27.meta.json @@ -0,0 +1 @@ +{"schema":1707265270195,"imageSize":16,"attachment":{"hash":"dd5cab3711f778677859e86000a127ed07a6175e8e58aecb0fba71b825ce76d7","size":3638,"filename":"webde-16-firefox.ico","location":"main-workspace/search-config-icons/45da3463-8c66-4b48-a422-00c54f44a96b.ico","mimetype":"image/x-icon"},"engineIdentifiers":["webde"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"6f4da442-d31e-28f8-03af-797d16bbdd27","last_modified":1707330724480}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173 b/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173 Binary files differnew file mode 100644 index 0000000000..3af7a36484 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173 diff --git a/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173.meta.json b/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173.meta.json new file mode 100644 index 0000000000..5448081821 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/70fdd651-6c50-b7bb-09ec-7e85da259173.meta.json @@ -0,0 +1 @@ +{"schema":1707264392809,"imageSize":16,"attachment":{"hash":"31a793dad95b5ffd02d39ebf14fc40877596f418f5926247487265034181dc8f","size":1455,"filename":"ebay-16-firefox.ico","location":"main-workspace/search-config-icons/5a9068cd-a4fa-4600-97fc-59f380e3d651.ico","mimetype":"image/x-icon"},"engineIdentifiers":["ebay*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"70fdd651-6c50-b7bb-09ec-7e85da259173","last_modified":1707330724412}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c b/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c Binary files differnew file mode 100644 index 0000000000..34a916ccde --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c diff --git a/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c.meta.json b/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c.meta.json new file mode 100644 index 0000000000..b81c54cfab --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/74793ce1-a918-a5eb-d3c0-2aadaff3c88c.meta.json @@ -0,0 +1 @@ +{"schema":1707265325417,"imageSize":16,"attachment":{"hash":"ca8f102ac4f35189ebcb786d080843b603b234f89b8d8b1c0ef27a0ab7148182","size":5430,"filename":"yahoo-jp-16-firefox.ico","location":"main-workspace/search-config-icons/b071a229-712f-4e13-99f4-61283d1c3fb4.ico","mimetype":"image/x-icon"},"engineIdentifiers":["yahoo-jp"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"74793ce1-a918-a5eb-d3c0-2aadaff3c88c","last_modified":1707330724489}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e b/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e Binary files differnew file mode 100644 index 0000000000..ba687ca8e7 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e diff --git a/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e.meta.json b/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e.meta.json new file mode 100644 index 0000000000..39391f2892 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e.meta.json @@ -0,0 +1 @@ +{"schema":1707264381509,"imageSize":16,"attachment":{"hash":"24daa27a3234d01b5add42e027b0a34000d0ab47c17fe3924c2ca267b7b61c19","size":5430,"filename":"azerdict-16-firefox.ico","location":"main-workspace/search-config-icons/4d6f988d-8905-4aa7-aeea-5b04a6197767.ico","mimetype":"image/x-icon"},"engineIdentifiers":["azerdict"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"7bbe6c5c-fdb8-2845-a4f4-e1382e708a0e","last_modified":1707330724389}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451 b/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451 Binary files differnew file mode 100644 index 0000000000..36f0cff233 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451 diff --git a/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451.meta.json b/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451.meta.json new file mode 100644 index 0000000000..95d1a268e0 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/7efbed51-813c-581d-d8d3-f8758434e451.meta.json @@ -0,0 +1 @@ +{"schema":1707265120610,"imageSize":16,"attachment":{"hash":"b0c6d1850265e3c946917232ca6c6ace3dad23347bfab4f81351eac569326d34","size":2584,"filename":"pazaruvaj-16-firefox.ico","location":"main-workspace/search-config-icons/2951bdf6-b440-4543-83ba-4ca1318db69e.ico","mimetype":"image/x-icon"},"engineIdentifiers":["pazaruvaj"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"7efbed51-813c-581d-d8d3-f8758434e451","last_modified":1707330724454}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361 b/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361 Binary files differnew file mode 100644 index 0000000000..f3e078a107 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361 diff --git a/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361.meta.json b/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361.meta.json new file mode 100644 index 0000000000..bf8cd02485 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/84bb4962-e571-227a-9ef6-2ac5f2aac361.meta.json @@ -0,0 +1 @@ +{"schema":1707265228699,"imageSize":16,"attachment":{"hash":"a1fd5d127a5f2590ddcd439b7a2abb3456b48217ea11daf0345b26e108f520e6","size":1743,"filename":"seznam-cz-16-firefox.ico","location":"main-workspace/search-config-icons/aae88ab5-7f9e-4bd6-80cd-9bb0f6638a19.ico","mimetype":"image/x-icon"},"engineIdentifiers":["seznam-cz"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"84bb4962-e571-227a-9ef6-2ac5f2aac361","last_modified":1707330724473}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36 b/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36 Binary files differnew file mode 100644 index 0000000000..9f1bed60f8 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36 diff --git a/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36.meta.json b/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36.meta.json new file mode 100644 index 0000000000..7ee6d3dc72 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/87ac4cde-f581-398b-1e32-eb4079183b36.meta.json @@ -0,0 +1 @@ +{"schema":1707265012766,"imageSize":16,"attachment":{"hash":"33ca72f1eac56793d1fd811189cedef98004a067c85b1143083b564814a4b0db","size":1150,"filename":"mailcom-16-firefox.ico","location":"main-workspace/search-config-icons/4c43dc1a-e163-4560-9713-fae61c9c59b9.ico","mimetype":"image/x-icon"},"engineIdentifiers":["mailcom"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"87ac4cde-f581-398b-1e32-eb4079183b36","last_modified":1707330724438}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e b/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e Binary files differnew file mode 100644 index 0000000000..ed803f50e2 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e diff --git a/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e.meta.json b/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e.meta.json new file mode 100644 index 0000000000..cd745a0766 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/8831ce10-b1e4-6eb4-4975-83c67457288e.meta.json @@ -0,0 +1 @@ +{"schema":1707264390072,"imageSize":16,"attachment":{"hash":"ca3cc8786977f6ffeb0546ff8f3bb2b7fd240d1956fbf86777dbf0e8bec9c03b","size":5430,"filename":"daum-kr-16-firefox.ico","location":"main-workspace/search-config-icons/3ce9df7f-3e0a-4b26-add1-18e2857f6213.ico","mimetype":"image/x-icon"},"engineIdentifiers":["daum-kr"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"8831ce10-b1e4-6eb4-4975-83c67457288e","last_modified":1707330724407}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a b/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a Binary files differnew file mode 100644 index 0000000000..c2d46117ef --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a diff --git a/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a.meta.json b/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a.meta.json new file mode 100644 index 0000000000..91d8b3e9e3 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/890de5c4-0941-a116-473a-5d240e79497a.meta.json @@ -0,0 +1 @@ +{"schema":1707264385889,"imageSize":16,"attachment":{"hash":"6ba1f0fd1d12014cab32f74daab24dfa16fb26613ace20a1e595267621038a07","size":530,"filename":"bok-NO-16-firefox.png","location":"main-workspace/search-config-icons/bcf53867-215e-40f1-9a6e-bc4c5768c5c4.png","mimetype":"image/png"},"engineIdentifiers":["bok-NO"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"890de5c4-0941-a116-473a-5d240e79497a","last_modified":1707330724398}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716 b/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716 Binary files differnew file mode 100644 index 0000000000..31b0e38092 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716 diff --git a/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716.meta.json b/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716.meta.json new file mode 100644 index 0000000000..62a2049b40 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/91a9672d-e945-8e1e-0996-aefdb0190716.meta.json @@ -0,0 +1 @@ +{"schema":1707265299492,"imageSize":16,"attachment":{"hash":"5d53ef1866a08cc29011f5f2a9ce99bbf37cf42e80de7f0e8cc30d13337e8187","size":318,"filename":"wiktionary-16-firefox.ico","location":"main-workspace/search-config-icons/22d846d9-468d-457b-9e3a-01166e4cc404.ico","mimetype":"image/x-icon"},"engineIdentifiers":["wiktionary*"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"91a9672d-e945-8e1e-0996-aefdb0190716","last_modified":1707330724485}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b b/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b Binary files differnew file mode 100644 index 0000000000..42b4f90149 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b diff --git a/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b.meta.json b/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b.meta.json new file mode 100644 index 0000000000..b5c1ae9302 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/96327a73-c433-5eb4-a16d-b090cadfb80b.meta.json @@ -0,0 +1 @@ +{"schema":1707264378878,"imageSize":16,"attachment":{"hash":"ca6e972004f62355c1ea97656bc2328e1643971bdecab9c6b563d45593b8122e","size":1150,"filename":"allegro-pl-16-firefox.ico","location":"main-workspace/search-config-icons/26f2abe5-ac6b-4375-822e-b86fb75637a3.ico","mimetype":"image/x-icon"},"engineIdentifiers":["allegro-pl"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"96327a73-c433-5eb4-a16d-b090cadfb80b","last_modified":1707330724384}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd b/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd Binary files differnew file mode 100644 index 0000000000..4401c7a40e --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd diff --git a/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd.meta.json b/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd.meta.json new file mode 100644 index 0000000000..6a602ff623 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a06db97d-1210-ea2e-5474-0e2f7d295bfd.meta.json @@ -0,0 +1 @@ +{"schema":1707265339663,"imageSize":16,"attachment":{"hash":"617dec5d635efb0a12d0de935c6999ef0249f4a63c62bdcb96551518bc3d1812","size":2672,"filename":"yahoo-jp-auctions-16-firefox.ico","location":"main-workspace/search-config-icons/d424ae0b-82e7-42fc-a10a-a607bba3642a.ico","mimetype":"image/x-icon"},"engineIdentifiers":["yahoo-jp-auctions"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"a06db97d-1210-ea2e-5474-0e2f7d295bfd","last_modified":1707330724491}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3 b/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3 Binary files differnew file mode 100644 index 0000000000..3ad20825c1 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3 diff --git a/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3.meta.json b/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3.meta.json new file mode 100644 index 0000000000..d27019ffda --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3.meta.json @@ -0,0 +1 @@ +{"schema":1707264391458,"imageSize":16,"attachment":{"hash":"d994f806b1e4225b50be5ab681b2cecf845cc216a19a432d878cea3cb815bafd","size":2799,"filename":"ddg-16-firefox.ico","location":"main-workspace/search-config-icons/c6ae7df5-0396-4892-b76a-b35a00044a13.ico","mimetype":"image/x-icon"},"engineIdentifiers":["ddg"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"a06dc3fd-4bdb-41f3-2ebc-4cbed06a9bd3","last_modified":1707330724410}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d b/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d Binary files differnew file mode 100644 index 0000000000..7415cbb160 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d diff --git a/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d.meta.json b/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d.meta.json new file mode 100644 index 0000000000..dbf810d81a --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/a2c7d4e9-f770-51e1-0963-3c2c8401631d.meta.json @@ -0,0 +1 @@ +{"schema":1707265244672,"imageSize":16,"attachment":{"hash":"1bf68aca7bfc75ca8485c3dac9a1daa13c1a3eb480688c32262096af6076adfa","size":379,"filename":"tyda-sv-SE-16-firefox.png","location":"main-workspace/search-config-icons/7d783537-313f-46db-bb4b-8878118884e4.png","mimetype":"image/png"},"engineIdentifiers":["tyda-sv-SE"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"a2c7d4e9-f770-51e1-0963-3c2c8401631d","last_modified":1707330724476}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b b/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b Binary files differnew file mode 100644 index 0000000000..04e5e344ef --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b diff --git a/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b.meta.json b/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b.meta.json new file mode 100644 index 0000000000..33d71b84b0 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/b64f09fd-52d1-c48e-af23-4ce918e7bf3b.meta.json @@ -0,0 +1 @@ +{"schema":1707264911347,"imageSize":16,"attachment":{"hash":"c3e8300801c5c585662f14fd8e819d635efd9830783dc3c631212927866e9898","size":749,"filename":"leo_ende_de-16-firefox.png","location":"main-workspace/search-config-icons/57b3cf6a-9cb2-4d3a-8839-54a3e62f49ec.png","mimetype":"image/png"},"engineIdentifiers":["leo_ende_de"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"b64f09fd-52d1-c48e-af23-4ce918e7bf3b","last_modified":1707330724432}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21 b/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21 Binary files differnew file mode 100644 index 0000000000..051204c35c --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21 diff --git a/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21.meta.json b/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21.meta.json new file mode 100644 index 0000000000..892d7fbf2f --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/b8ca5a94-8fff-27ad-6e00-96e244a32e21.meta.json @@ -0,0 +1 @@ +{"schema":1707265027259,"imageSize":16,"attachment":{"hash":"1474c93e49c209aca2a2df2acb61b64574805106bead6edebd67287de21920e0","size":1812,"filename":"mapy-cz-16-firefox.ico","location":"main-workspace/search-config-icons/e94ddd93-1c38-42c0-847a-741f49305d7b.ico","mimetype":"image/x-icon"},"engineIdentifiers":["mapy-cz"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"b8ca5a94-8fff-27ad-6e00-96e244a32e21","last_modified":1707330724440}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943 b/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943 Binary files differnew file mode 100644 index 0000000000..20750d0c19 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943 diff --git a/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943.meta.json b/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943.meta.json new file mode 100644 index 0000000000..dc557c4452 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/c411adc1-9661-4fb5-a4c1-8cfe74911943.meta.json @@ -0,0 +1 @@ +{"schema":1707264395634,"imageSize":16,"attachment":{"hash":"150765e8e9b985ba5b820ac9b8e7623023d5a0e24f94663d5e9203d8d7598059","size":1785,"filename":"eudict-16-firefox.ico","location":"main-workspace/search-config-icons/93c06725-f10f-44f6-b7db-05e22a6ab676.ico","mimetype":"image/x-icon"},"engineIdentifiers":["eudict"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"c411adc1-9661-4fb5-a4c1-8cfe74911943","last_modified":1707330724418}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd b/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd Binary files differnew file mode 100644 index 0000000000..fdc021cfeb --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd diff --git a/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd.meta.json b/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd.meta.json new file mode 100644 index 0000000000..7d22ad49d4 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/cbf9e891-d079-2b28-5617-283450d463dd.meta.json @@ -0,0 +1 @@ +{"schema":1707264384393,"imageSize":16,"attachment":{"hash":"5b2c34b3c4e8dd898b664dba6c3786e2ff9869eff55d673aa48361f11325ed07","size":4286,"filename":"bing-16-firefox.ico","location":"main-workspace/search-config-icons/5ed361f5-5b94-4899-896a-747d107f7392.ico","mimetype":"image/x-icon"},"engineIdentifiers":["bing"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"cbf9e891-d079-2b28-5617-283450d463dd","last_modified":1707330724395}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9 b/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9 Binary files differnew file mode 100644 index 0000000000..ac5a2bbb0a --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9 diff --git a/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9.meta.json b/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9.meta.json new file mode 100644 index 0000000000..a14c67f643 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/d87f251c-3e12-a8bf-e2d0-afd43d36c5f9.meta.json @@ -0,0 +1 @@ +{"schema":1707264328903,"imageSize":16,"attachment":{"hash":"865d76c8175a8f11dedc93f0bc212242a97a8a76adac870e8249368cecc81402","size":159,"filename":"1und1-16-firefox.gif","location":"main-workspace/search-config-icons/d4eeae67-e96c-4fd0-bbbd-c05ff235f622.gif","mimetype":"image/gif"},"engineIdentifiers":["1und1"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"d87f251c-3e12-a8bf-e2d0-afd43d36c5f9","last_modified":1707330724381}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c b/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c Binary files differnew file mode 100644 index 0000000000..75396dc9ca --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c diff --git a/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c.meta.json b/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c.meta.json new file mode 100644 index 0000000000..5492e6ac32 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e02f23df-8d48-2b1b-3b5c-6dd27302c61c.meta.json @@ -0,0 +1 @@ +{"schema":1707265199776,"imageSize":16,"attachment":{"hash":"247aa26993083705ce99a8e5612cdf262aca98cde86ba19afc964329ba95986a","size":2468,"filename":"readmoo-16-firefox.ico","location":"main-workspace/search-config-icons/46bf2594-7779-42b7-be7b-212232ba2ba0.ico","mimetype":"image/x-icon"},"engineIdentifiers":["readmoo"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"e02f23df-8d48-2b1b-3b5c-6dd27302c61c","last_modified":1707330724468}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785 b/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785 Binary files differnew file mode 100644 index 0000000000..77f6db5322 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785 diff --git a/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785.meta.json b/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785.meta.json new file mode 100644 index 0000000000..3f6aaf67ea --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e718e983-09aa-e8f6-b25f-cd4b395d4785.meta.json @@ -0,0 +1 @@ +{"schema":1707265314687,"imageSize":16,"attachment":{"hash":"809697f48848e7c3638d5f3e0b224ea60b3800504e7bd8417854d55989b85196","size":304,"filename":"wolnelektury-pl-16-firefox.png","location":"main-workspace/search-config-icons/4665f707-e315-4ac0-a12e-2455f0123758.png","mimetype":"image/png"},"engineIdentifiers":["wolnelektury-pl"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"e718e983-09aa-e8f6-b25f-cd4b395d4785","last_modified":1707330724487}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a b/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a Binary files differnew file mode 100644 index 0000000000..e35572a557 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a diff --git a/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a.meta.json b/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a.meta.json new file mode 100644 index 0000000000..43a455a006 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/e7547f62-187b-b641-d462-e54a3f813d9a.meta.json @@ -0,0 +1 @@ +{"schema":1707264735197,"imageSize":16,"attachment":{"hash":"c971ee33b8c0a57349669d957bf73070b0632b128c94748e845b57d5e15221a4","size":1150,"filename":"gulesider-NO-16-firefox.ico","location":"main-workspace/search-config-icons/f3a89a46-c15c-4622-9035-cdf6773139cb.ico","mimetype":"image/x-icon"},"engineIdentifiers":["gulesider-NO"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"e7547f62-187b-b641-d462-e54a3f813d9a","last_modified":1707330724429}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636 b/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636 Binary files differnew file mode 100644 index 0000000000..d43d1d5aa6 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636 diff --git a/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636.meta.json b/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636.meta.json new file mode 100644 index 0000000000..b17e2268f8 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/f312610a-ebfb-a106-ea92-fd643c5d3636.meta.json @@ -0,0 +1 @@ +{"schema":1707265170689,"imageSize":16,"attachment":{"hash":"91d17ba44192a6430ffdb447ff3a11533ef964628f67c13480cc9470212d3d65","size":5430,"filename":"qwantjr-16-firefox.ico","location":"main-workspace/search-config-icons/360f7074-bda7-4906-bd66-db38d4770056.ico","mimetype":"image/x-icon"},"engineIdentifiers":["qwantjr"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"f312610a-ebfb-a106-ea92-fd643c5d3636","last_modified":1707330724464}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc b/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc Binary files differnew file mode 100644 index 0000000000..82339b3b1d --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc diff --git a/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc.meta.json b/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc.meta.json new file mode 100644 index 0000000000..d2c0a5357b --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fa0fc42c-d91d-fca7-34eb-806ff46062dc.meta.json @@ -0,0 +1 @@ +{"schema":1707264399810,"imageSize":16,"attachment":{"hash":"6da5620880159634213e197fafca1dde0272153be3e4590818533fab8d040770","size":5430,"filename":"google-16-firefox.ico","location":"main-workspace/search-config-icons/644faa05-4deb-491b-ae95-0962aefabe55.ico","mimetype":"image/x-icon"},"engineIdentifiers":["google"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"fa0fc42c-d91d-fca7-34eb-806ff46062dc","last_modified":1707330724427}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d b/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d Binary files differnew file mode 100644 index 0000000000..0a7d01cae8 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d diff --git a/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d.meta.json b/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d.meta.json new file mode 100644 index 0000000000..63897ace26 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fca3e3ee-56cd-f474-dc31-307fd24a891d.meta.json @@ -0,0 +1 @@ +{"schema":1707265210999,"imageSize":16,"attachment":{"hash":"c4d88cfa5262f6d2cf76b167281d25821c9e1770684b739ed6ad3cf7277a121b","size":3638,"filename":"salidzinilv-16-firefox.ico","location":"main-workspace/search-config-icons/bc06c6d4-019c-4aa8-8958-813dc644452a.ico","mimetype":"image/x-icon"},"engineIdentifiers":["salidzinilv"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"fca3e3ee-56cd-f474-dc31-307fd24a891d","last_modified":1707330724471}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136 b/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136 Binary files differnew file mode 100644 index 0000000000..dc9ad5b2a9 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136 diff --git a/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136.meta.json b/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136.meta.json new file mode 100644 index 0000000000..5127f06288 --- /dev/null +++ b/services/settings/dumps/main/search-config-icons/fed4f021-ff3e-942a-010e-afa43fda2136.meta.json @@ -0,0 +1 @@ +{"schema":1707265073846,"imageSize":16,"attachment":{"hash":"d7fdfd971d874f2ec6f209df6f6b8173d126cd3f7a25daacb94de4259efbcf16","size":5430,"filename":"mercadolivre-16-firefox.ico","location":"main-workspace/search-config-icons/f8d4c276-9b04-4c70-8ff9-5c1faf55978c.ico","mimetype":"image/x-icon"},"engineIdentifiers":["mercadolivre"],"filter_expression":"env.appinfo.ID == \"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}\"","id":"fed4f021-ff3e-942a-010e-afa43fda2136","last_modified":1707330724446}
\ No newline at end of file diff --git a/services/settings/dumps/main/search-config-overrides-v2.json b/services/settings/dumps/main/search-config-overrides-v2.json new file mode 100644 index 0000000000..d8f5f04182 --- /dev/null +++ b/services/settings/dumps/main/search-config-overrides-v2.json @@ -0,0 +1,32 @@ +{ + "data": [ + { + "urls": { + "search": { + "params": [ + { + "name": "tag", + "value": "{partnerCode}" + }, + { + "name": "ref", + "value": "pd_sl_a71c226e8a96bfdb7ae5bc6d1f30e9e88d9e4e3436d7bfb941a95d0a" + }, + { + "name": "mfadid", + "value": "adm" + } + ] + } + }, + "schema": 1710170050767, + "clickUrl": "https://firefoxsearchwith.ampxdirect.com/amazon?partner=firefoxsearchwith&sub1=amazon&sub2=us&ctag=pd_sl_a71c226e8a96bfdb7ae5bc6d1f30e9e88d9e4e3436d7bfb941a95d0a", + "identifier": "amazondotcom-us", + "partnerCode": "admarketus-20", + "telemetrySuffix": "adm", + "id": "c12f71be-f484-4f50-a8cc-79ed92ab0b42", + "last_modified": 1710333238310 + } + ], + "timestamp": 1710333238310 +} diff --git a/services/settings/dumps/main/search-config-overrides.json b/services/settings/dumps/main/search-config-overrides.json new file mode 100644 index 0000000000..f3162806a3 --- /dev/null +++ b/services/settings/dumps/main/search-config-overrides.json @@ -0,0 +1,33 @@ +{ + "data": [ + { + "params": { + "searchUrlGetParams": [ + { + "name": "tag", + "value": "admarketus-20" + }, + { + "name": "ref", + "value": "pd_sl_a71c226e8a96bfdb7ae5bc6d1f30e9e88d9e4e3436d7bfb941a95d0a" + }, + { + "name": "mfadid", + "value": "adm" + }, + { + "name": "k", + "value": "{searchTerms}" + } + ] + }, + "schema": 1709131995996, + "clickUrl": "https://firefoxsearchwith.ampxdirect.com/amazon?partner=firefoxsearchwith&sub1=amazon&sub2=us&ctag=pd_sl_a71c226e8a96bfdb7ae5bc6d1f30e9e88d9e4e3436d7bfb941a95d0a", + "telemetryId": "amazondotcom-us", + "telemetrySuffix": "adm", + "id": "96b35c63-fa03-4502-872e-9a57a9069f71", + "last_modified": 1709217485713 + } + ], + "timestamp": 1709217485713 +} diff --git a/services/settings/dumps/main/search-config-v2.json b/services/settings/dumps/main/search-config-v2.json new file mode 100644 index 0000000000..e360ad9ee3 --- /dev/null +++ b/services/settings/dumps/main/search-config-v2.json @@ -0,0 +1,7345 @@ +{ + "data": [ + { + "base": { + "name": "Qwant", + "urls": { + "search": { + "base": "https://www.qwant.com/", + "params": [ + { + "name": "client", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://api.qwant.com/api/suggest/", + "params": [ + { + "name": "client", + "value": "opensearch" + } + ], + "searchTermParamName": "q" + } + }, + "aliases": [ + "qwant" + ], + "partnerCode": "brz-moz", + "classification": "general" + }, + "schema": 1710460806956, + "variants": [ + { + "environment": { + "locales": [ + "fr" + ] + } + }, + { + "environment": { + "regions": [ + "be", + "ch", + "es", + "fr", + "it", + "nl" + ] + } + }, + { + "environment": { + "distributions": [ + "qwant-001", + "qwant-002" + ] + }, + "partnerCode": "firefoxqwant", + "telemetrySuffix": "qwant" + } + ], + "identifier": "qwant", + "recordType": "engine", + "id": "2e62746e-b90a-42ee-b0f2-9ed0e1e2eaf0", + "last_modified": 1710766863306 + }, + { + "orders": [ + { + "order": [ + "baidu", + "bing", + "google", + "wikipedia*" + ], + "environment": { + "distributions": [ + "MozillaOnline" + ] + } + }, + { + "order": [ + "qwant", + "qwantjr" + ], + "environment": { + "distributions": [ + "qwant-001", + "qwant-002" + ] + } + } + ], + "schema": 1707824831520, + "recordType": "engineOrders", + "id": "3e1fed64-5ec7-4b1c-bedc-741fe3c59bc3", + "last_modified": 1707833224345 + }, + { + "base": { + "name": "Bing", + "urls": { + "search": { + "base": "https://www.bing.com/search", + "params": [ + { + "name": "pc", + "value": "{partnerCode}" + }, + { + "name": "form", + "searchAccessPoint": { + "newtab": "MOZTSB", + "homepage": "MOZSPG", + "searchbar": "MOZSBR", + "addressbar": "MOZLBR", + "contextmenu": "MOZCON" + } + } + ], + "searchTermParamName": "q" + }, + "trending": { + "base": "https://www.bing.com/osjson.aspx" + }, + "suggestions": { + "base": "https://www.bing.com/osjson.aspx", + "params": [ + { + "name": "form", + "value": "OSDJAS" + } + ], + "searchTermParamName": "query" + } + }, + "aliases": [ + "bing" + ], + "partnerCode": "MOZI", + "classification": "general" + }, + "schema": 1706918405822, + "variants": [ + { + "environment": { + "locales": [ + "ach", + "af", + "an", + "ar", + "ast", + "az", + "bn", + "bs", + "ca", + "ca-valencia", + "cak", + "cs", + "cy", + "da", + "de", + "dsb", + "el", + "en-CA", + "en-GB", + "en-US", + "eo", + "es-CL", + "es-ES", + "es-MX", + "eu", + "fa", + "ff", + "fi", + "fr", + "fur", + "fy-NL", + "gd", + "gl", + "gn", + "gu-IN", + "he", + "hi-IN", + "hr", + "hsb", + "hy-AM", + "ia", + "id", + "is", + "it", + "ja", + "ja-JP-macos", + "ka", + "kab", + "km", + "kn", + "lij", + "lo", + "lt", + "meh", + "mk", + "ms", + "my", + "nb-NO", + "ne-NP", + "nl", + "nn-NO", + "oc", + "pa-IN", + "pt-BR", + "rm", + "ro", + "sc", + "sco", + "son", + "sq", + "sr", + "sv-SE", + "te", + "th", + "tl", + "tr", + "trs", + "uk", + "ur", + "uz", + "wo", + "xh", + "zh-CN" + ] + } + }, + { + "environment": { + "channels": [ + "esr" + ] + }, + "partnerCode": "MOZR", + "telemetrySuffix": "esr" + } + ], + "identifier": "bing", + "recordType": "engine", + "id": "05645095-d26e-4f20-9137-f24a14a23f28", + "last_modified": 1707833224310 + }, + { + "base": { + "name": "Google", + "urls": { + "search": { + "base": "https://www.google.com/search", + "params": [ + { + "name": "client", + "value": "{partnerCode}" + }, + { + "name": "channel", + "experimentConfig": "google_channel_row" + } + ], + "searchTermParamName": "q" + }, + "trending": { + "base": "https://www.google.com/complete/search", + "method": "GET", + "params": [ + { + "name": "client", + "value": "firefox" + }, + { + "name": "channel", + "value": "ftr" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://www.google.com/complete/search", + "params": [ + { + "name": "client", + "value": "firefox" + }, + { + "name": "channel", + "experimentConfig": "search_rich_suggestions" + } + ], + "searchTermParamName": "q" + } + }, + "aliases": [ + "google" + ], + "partnerCode": "firefox-b-d", + "classification": "general" + }, + "schema": 1702901739026, + "variants": [ + { + "environment": { + "allRegionsAndLocales": true + }, + "telemetrySuffix": "b-d" + }, + { + "environment": { + "channels": [ + "esr" + ] + }, + "partnerCode": "firefox-b-e", + "telemetrySuffix": "b-e" + }, + { + "urls": { + "search": { + "params": [] + } + }, + "environment": { + "regions": [ + "ru", + "tr", + "by", + "kz" + ] + }, + "telemetrySuffix": "com-nocodes" + }, + { + "urls": { + "search": { + "params": [ + { + "name": "client", + "value": "{partnerCode}" + }, + { + "name": "channel", + "experimentConfig": "google_channel_us" + } + ] + } + }, + "environment": { + "regions": [ + "us" + ] + }, + "partnerCode": "firefox-b-1-d", + "telemetrySuffix": "b-1-d" + }, + { + "urls": { + "search": { + "params": [ + { + "name": "client", + "value": "{partnerCode}" + }, + { + "name": "channel", + "experimentConfig": "google_channel_us" + } + ] + } + }, + "environment": { + "regions": [ + "us" + ], + "channels": [ + "esr" + ] + }, + "partnerCode": "firefox-b-1-e", + "telemetrySuffix": "b-1-e" + }, + { + "urls": { + "search": { + "params": [ + { + "name": "client", + "value": "{partnerCode}" + }, + { + "name": "channel", + "value": "fs" + } + ] + } + }, + "environment": { + "distributions": [ + "canonical", + "canonical-001" + ] + }, + "partnerCode": "ubuntu", + "telemetrySuffix": "canonical" + }, + { + "urls": { + "search": { + "params": [ + { + "name": "client", + "value": "{partnerCode}" + }, + { + "name": "channel", + "value": "fs" + } + ] + } + }, + "environment": { + "distributions": [ + "canonical-002" + ] + }, + "partnerCode": "ubuntu-sn", + "telemetrySuffix": "ubuntu-sn" + }, + { + "environment": { + "distributions": [ + "mint-001" + ] + }, + "partnerCode": "firefox-b-lm", + "telemetrySuffix": "b-lm" + }, + { + "environment": { + "regions": [ + "us" + ], + "distributions": [ + "mint-001" + ] + }, + "partnerCode": "firefox-b-1-lm", + "telemetrySuffix": "b-1-lm" + } + ], + "identifier": "google", + "recordType": "engine", + "id": "7ace4aa1-e762-4f4b-87b9-b23b3c3a930b", + "last_modified": 1702906502757 + }, + { + "base": { + "name": "DuckDuckGo", + "urls": { + "search": { + "base": "https://duckduckgo.com/", + "params": [ + { + "name": "t", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://ac.duckduckgo.com/ac/", + "params": [ + { + "name": "type", + "value": "list" + } + ], + "searchTermParamName": "q" + } + }, + "aliases": [ + "duckduckgo", + "ddg" + ], + "partnerCode": "ffab", + "classification": "general" + }, + "schema": 1702901739665, + "variants": [ + { + "environment": { + "allRegionsAndLocales": true + } + }, + { + "environment": { + "channels": [ + "esr" + ] + }, + "partnerCode": "ftsa", + "telemetrySuffix": "esr" + }, + { + "environment": { + "distributions": [ + "mint-001" + ] + }, + "partnerCode": "lm", + "telemetrySuffix": "lm" + } + ], + "identifier": "ddg", + "recordType": "engine", + "id": "04e99a38-13ee-47d8-8aa4-64482b3dea99", + "last_modified": 1702906502754 + }, + { + "base": { + "name": "百度", + "urls": { + "search": { + "base": "https://www.baidu.com/baidu", + "params": [ + { + "name": "ie", + "value": "utf-8" + } + ], + "searchTermParamName": "wd" + }, + "suggestions": { + "base": "https://www.baidu.com/su", + "params": [ + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "wd" + } + }, + "aliases": [ + "百度", + "baidu" + ], + "classification": "general" + }, + "schema": 1702901740286, + "variants": [ + { + "environment": { + "locales": [ + "zh-CN" + ] + } + } + ], + "identifier": "baidu", + "recordType": "engine", + "id": "ad58537f-f397-46ec-a1a8-935e12f0aab6", + "last_modified": 1702906502749 + }, + { + "base": { + "name": "Wikipedia (en)", + "urls": { + "search": { + "base": "https://en.wikipedia.org/wiki/Special:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://en.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901740904, + "variants": [ + { + "environment": { + "excludedLocales": [ + "af", + "an", + "ar", + "ast", + "az", + "be", + "bg", + "bn", + "br", + "bs", + "ca", + "ca-valencia", + "cs", + "cy", + "da", + "de", + "dsb", + "el", + "eo", + "cak", + "es-AR", + "es-CL", + "es-ES", + "es-MX", + "trs", + "et", + "eu", + "fa", + "fi", + "fr", + "ff", + "son", + "fy-NL", + "ga-IE", + "gd", + "gl", + "gn", + "gu-IN", + "hi-IN", + "he", + "hr", + "hsb", + "hu", + "hy-AM", + "ia", + "id", + "is", + "ja", + "ja-JP-macos", + "ka", + "kab", + "kk", + "km", + "kn", + "ko", + "it", + "fur", + "sc", + "lij", + "lo", + "lt", + "ltg", + "lv", + "mk", + "mr", + "ms", + "my", + "nb-NO", + "ne-NP", + "nl", + "nn-NO", + "oc", + "pa-IN", + "pl", + "szl", + "pt-BR", + "pt-PT", + "rm", + "ro", + "ru", + "si", + "sk", + "sl", + "sq", + "sr", + "sv-SE", + "ta", + "te", + "th", + "tl", + "tr", + "uk", + "ur", + "uz", + "vi", + "wo", + "zh-CN", + "zh-TW" + ], + "allRegionsAndLocales": true + } + } + ], + "identifier": "wikipedia", + "recordType": "engine", + "id": "7f6d23c2-191e-483e-af3a-ce6451e3a8dd", + "last_modified": 1702906502744 + }, + { + "base": { + "name": "Wikipedia (af)", + "urls": { + "search": { + "base": "https://af.wikipedia.org/wiki/Spesiaal:Soek", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://af.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901741515, + "variants": [ + { + "environment": { + "locales": [ + "af" + ] + } + } + ], + "identifier": "wikipedia-af", + "recordType": "engine", + "id": "9dcc6d57-1b00-4cdc-bc11-d70593a3da30", + "last_modified": 1702906502739 + }, + { + "base": { + "name": "Biquipedia (an)", + "urls": { + "search": { + "base": "https://an.wikipedia.org/wiki/Especial:Mirar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://an.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901742155, + "variants": [ + { + "environment": { + "locales": [ + "an" + ] + } + } + ], + "identifier": "wikipedia-an", + "recordType": "engine", + "id": "1c2173e4-1ad3-4347-900a-17f4103c6bbe", + "last_modified": 1702906502736 + }, + { + "base": { + "name": "ويكيبيديا (ar)", + "urls": { + "search": { + "base": "https://ar.wikipedia.org/wiki/خاص:بحث", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ar.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901742756, + "variants": [ + { + "environment": { + "locales": [ + "ar" + ] + } + } + ], + "identifier": "wikipedia-ar", + "recordType": "engine", + "id": "3ed57e7f-df2a-4bb0-aecf-10327d5ff1ea", + "last_modified": 1702906502733 + }, + { + "base": { + "name": "Wikipedia (ast)", + "urls": { + "search": { + "base": "https://ast.wikipedia.org/wiki/Especial:Gueta", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ast.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901743369, + "variants": [ + { + "environment": { + "locales": [ + "ast" + ] + } + } + ], + "identifier": "wikipedia-ast", + "recordType": "engine", + "id": "e5f07079-9153-4104-a8b9-a9d15ff75853", + "last_modified": 1702906502729 + }, + { + "base": { + "name": "Vikipediya (az)", + "urls": { + "search": { + "base": "https://az.wikipedia.org/wiki/Xüsusi:Axtar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://az.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901743987, + "variants": [ + { + "environment": { + "locales": [ + "az" + ] + } + } + ], + "identifier": "wikipedia-az", + "recordType": "engine", + "id": "2230b4af-8fdf-4aa0-a496-5178b25382ed", + "last_modified": 1702906502726 + }, + { + "base": { + "name": "Уикипедия (bg)", + "urls": { + "search": { + "base": "https://bg.wikipedia.org/wiki/Специални:Търсене", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://bg.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901744580, + "variants": [ + { + "environment": { + "locales": [ + "bg" + ] + } + } + ], + "identifier": "wikipedia-bg", + "recordType": "engine", + "id": "6324bcb4-9aad-4aa2-b10c-f320ae1242c3", + "last_modified": 1702906502723 + }, + { + "base": { + "name": "Wikipedia (br)", + "urls": { + "search": { + "base": "https://br.wikipedia.org/wiki/Dibar:Klask", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://br.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901745178, + "variants": [ + { + "environment": { + "locales": [ + "br" + ] + } + } + ], + "identifier": "wikipedia-br", + "recordType": "engine", + "id": "c9266f36-f954-44df-924b-a1c205f66d7c", + "last_modified": 1702906502718 + }, + { + "base": { + "name": "Wikipedia (bs)", + "urls": { + "search": { + "base": "https://bs.wikipedia.org/wiki/Posebno:Pretraga", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://bs.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901745796, + "variants": [ + { + "environment": { + "locales": [ + "bs" + ] + } + } + ], + "identifier": "wikipedia-bs", + "recordType": "engine", + "id": "c88785cc-48de-4f07-a40f-5d6564de3183", + "last_modified": 1702906502715 + }, + { + "base": { + "name": "Wicipedia (cy)", + "urls": { + "search": { + "base": "https://cy.wikipedia.org/wiki/Arbennig:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://cy.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901746399, + "variants": [ + { + "environment": { + "locales": [ + "cy" + ] + } + } + ], + "identifier": "wikipedia-cy", + "recordType": "engine", + "id": "0df7934b-5903-4382-ae3d-d898ac0487aa", + "last_modified": 1702906502711 + }, + { + "base": { + "name": "Wikipedia (da)", + "urls": { + "search": { + "base": "https://da.wikipedia.org/wiki/Speciel:Søgning", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://da.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901746995, + "variants": [ + { + "environment": { + "locales": [ + "da" + ] + } + } + ], + "identifier": "wikipedia-da", + "recordType": "engine", + "id": "ae42fcaa-ddee-4212-acb9-8bc15d09a39d", + "last_modified": 1702906502707 + }, + { + "base": { + "name": "Wikipedia (de)", + "urls": { + "search": { + "base": "https://de.wikipedia.org/wiki/Spezial:Suche", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://de.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901747613, + "variants": [ + { + "environment": { + "locales": [ + "de" + ] + } + } + ], + "identifier": "wikipedia-de", + "recordType": "engine", + "id": "def1d4ba-b8c8-46c4-9800-134b989ca058", + "last_modified": 1702906502704 + }, + { + "base": { + "name": "Wikipedija (dsb)", + "urls": { + "search": { + "base": "https://dsb.wikipedia.org/wiki/Specialne:Pytaś", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://dsb.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901748278, + "variants": [ + { + "environment": { + "locales": [ + "dsb" + ] + } + } + ], + "identifier": "wikipedia-dsb", + "recordType": "engine", + "id": "c8a9299d-c9d2-4bf3-8f94-812d38af35f7", + "last_modified": 1702906502701 + }, + { + "base": { + "name": "Wikipedia (el)", + "urls": { + "search": { + "base": "https://el.wikipedia.org/wiki/Ειδικό:Αναζήτηση", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://el.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901748903, + "variants": [ + { + "environment": { + "locales": [ + "el" + ] + } + } + ], + "identifier": "wikipedia-el", + "recordType": "engine", + "id": "6ebc40f2-9307-4e58-8fd7-0e1a23b80723", + "last_modified": 1702906502698 + }, + { + "base": { + "name": "Vikipedio (eo)", + "urls": { + "search": { + "base": "https://eo.wikipedia.org/wiki/Specialaĵo:Serĉi", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://eo.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901749556, + "variants": [ + { + "environment": { + "locales": [ + "eo" + ] + } + } + ], + "identifier": "wikipedia-eo", + "recordType": "engine", + "id": "25b42f42-d6e4-4193-be32-b4ca61cb55c7", + "last_modified": 1702906502695 + }, + { + "base": { + "name": "Vikipeedia (et)", + "urls": { + "search": { + "base": "https://et.wikipedia.org/wiki/Eri:Otsimine", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://et.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901750177, + "variants": [ + { + "environment": { + "locales": [ + "et" + ] + } + } + ], + "identifier": "wikipedia-et", + "recordType": "engine", + "id": "87388ad0-c470-4a90-b79d-231e28b86eda", + "last_modified": 1702906502691 + }, + { + "base": { + "name": "Wikipedia (eu)", + "urls": { + "search": { + "base": "https://eu.wikipedia.org/wiki/Berezi:Bilatu", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://eu.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901750807, + "variants": [ + { + "environment": { + "locales": [ + "eu" + ] + } + } + ], + "identifier": "wikipedia-eu", + "recordType": "engine", + "id": "0d6704d1-3aed-420c-8c16-6aeefede499f", + "last_modified": 1702906502688 + }, + { + "base": { + "name": "ویکیپدیا (fa)", + "urls": { + "search": { + "base": "https://fa.wikipedia.org/wiki/ویژه:جستجو", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://fa.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901751455, + "variants": [ + { + "environment": { + "locales": [ + "fa" + ] + } + } + ], + "identifier": "wikipedia-fa", + "recordType": "engine", + "id": "ce737d17-06f0-46f2-8c88-97f796ad88dc", + "last_modified": 1702906502685 + }, + { + "base": { + "name": "Wikipedia (fi)", + "urls": { + "search": { + "base": "https://fi.wikipedia.org/wiki/Toiminnot:Haku", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://fi.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901752065, + "variants": [ + { + "environment": { + "locales": [ + "fi" + ] + } + } + ], + "identifier": "wikipedia-fi", + "recordType": "engine", + "id": "0820d593-6616-476a-ab3d-763c4d14dcc6", + "last_modified": 1702906502682 + }, + { + "base": { + "name": "Wikipedy (fy)", + "urls": { + "search": { + "base": "https://fy.wikipedia.org/wiki/Wiki:Sykje", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://fy.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901752745, + "variants": [ + { + "environment": { + "locales": [ + "fy-NL" + ] + } + } + ], + "identifier": "wikipedia-fy-NL", + "recordType": "engine", + "id": "4a6a7952-460d-4951-a011-9fc414f2569a", + "last_modified": 1702906502679 + }, + { + "base": { + "name": "Vicipéid (ga)", + "urls": { + "search": { + "base": "https://ga.wikipedia.org/wiki/Speisialta:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ga.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901753350, + "variants": [ + { + "environment": { + "locales": [ + "ga-IE" + ] + } + } + ], + "identifier": "wikipedia-ga-IE", + "recordType": "engine", + "id": "d8d0e94c-27e7-42b1-a5e2-b24198021234", + "last_modified": 1702906502675 + }, + { + "base": { + "name": "Uicipeid (gd)", + "urls": { + "search": { + "base": "https://gd.wikipedia.org/wiki/Sònraichte:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://gd.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901753945, + "variants": [ + { + "environment": { + "locales": [ + "gd" + ] + } + } + ], + "identifier": "wikipedia-gd", + "recordType": "engine", + "id": "df26225d-6726-4630-94ce-7a398598139b", + "last_modified": 1702906502672 + }, + { + "base": { + "name": "Wikipedia (gl)", + "urls": { + "search": { + "base": "https://gl.wikipedia.org/wiki/Especial:Procurar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://gl.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901754558, + "variants": [ + { + "environment": { + "locales": [ + "gl" + ] + } + } + ], + "identifier": "wikipedia-gl", + "recordType": "engine", + "id": "cd385c40-96bc-43c4-9f30-eff738828401", + "last_modified": 1702906502668 + }, + { + "base": { + "name": "Vikipetã (gn)", + "urls": { + "search": { + "base": "https://gn.wikipedia.org/wiki/Mba'echĩchĩ:Buscar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://gn.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901755160, + "variants": [ + { + "environment": { + "locales": [ + "gn" + ] + } + } + ], + "identifier": "wikipedia-gn", + "recordType": "engine", + "id": "b66515df-d7b1-403c-b0df-22a5ce9dd07f", + "last_modified": 1702906502665 + }, + { + "base": { + "name": "ויקיפדיה", + "urls": { + "search": { + "base": "https://he.wikipedia.org/wiki/מיוחד:חיפוש", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://he.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901755766, + "variants": [ + { + "environment": { + "locales": [ + "he" + ] + } + } + ], + "identifier": "wikipedia-he", + "recordType": "engine", + "id": "f96aab0d-3562-445c-a70f-5805ea1d25fe", + "last_modified": 1702906502662 + }, + { + "base": { + "name": "Wikipedija (hr)", + "urls": { + "search": { + "base": "https://hr.wikipedia.org/wiki/Posebno:Traži", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://hr.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901756375, + "variants": [ + { + "environment": { + "locales": [ + "hr" + ] + } + } + ], + "identifier": "wikipedia-hr", + "recordType": "engine", + "id": "eb03a427-1f71-4df9-b609-a36064d2d0e2", + "last_modified": 1702906502658 + }, + { + "base": { + "name": "Wikipedija (hsb)", + "urls": { + "search": { + "base": "https://hsb.wikipedia.org/wiki/Specialnje:Pytać", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://hsb.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901756979, + "variants": [ + { + "environment": { + "locales": [ + "hsb" + ] + } + } + ], + "identifier": "wikipedia-hsb", + "recordType": "engine", + "id": "5235d5f1-e04d-4ba3-b53b-35a8d09d2142", + "last_modified": 1702906502655 + }, + { + "base": { + "name": "Wikipédia (hu)", + "urls": { + "search": { + "base": "https://hu.wikipedia.org/wiki/Speciális:Keresés", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://hu.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901757634, + "variants": [ + { + "environment": { + "locales": [ + "hu" + ] + } + } + ], + "identifier": "wikipedia-hu", + "recordType": "engine", + "id": "9a10f883-2da7-4070-8a73-543153bdd52c", + "last_modified": 1702906502651 + }, + { + "base": { + "name": "Wikipedia (ia)", + "urls": { + "search": { + "base": "https://ia.wikipedia.org/wiki/Special:Recerca", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ia.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901758249, + "variants": [ + { + "environment": { + "locales": [ + "ia" + ] + } + } + ], + "identifier": "wikipedia-ia", + "recordType": "engine", + "id": "6787b684-aa4f-48d2-9673-fce3b528c71c", + "last_modified": 1702906502647 + }, + { + "base": { + "name": "Wikipedia (id)", + "urls": { + "search": { + "base": "https://id.wikipedia.org/wiki/Istimewa:Pencarian", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://id.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901758856, + "variants": [ + { + "environment": { + "locales": [ + "id" + ] + } + } + ], + "identifier": "wikipedia-id", + "recordType": "engine", + "id": "163da4a0-e59f-423a-bebc-3ec3bd68b941", + "last_modified": 1702906502644 + }, + { + "base": { + "name": "Wikipedia (is)", + "urls": { + "search": { + "base": "https://is.wikipedia.org/wiki/Kerfissíða:Leit", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://is.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901759494, + "variants": [ + { + "environment": { + "locales": [ + "is" + ] + } + } + ], + "identifier": "wikipedia-is", + "recordType": "engine", + "id": "26ca2f38-0472-45de-83fd-78d67aaecd22", + "last_modified": 1702906502640 + }, + { + "base": { + "name": "ვიკიპედია (ka)", + "urls": { + "search": { + "base": "https://ka.wikipedia.org/wiki/სპეციალური:ძიება", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ka.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901760115, + "variants": [ + { + "environment": { + "locales": [ + "ka" + ] + } + } + ], + "identifier": "wikipedia-ka", + "recordType": "engine", + "id": "ca784382-7beb-4a93-b211-745d4d71ec6c", + "last_modified": 1702906502636 + }, + { + "base": { + "name": "Wikipedia (kab)", + "urls": { + "search": { + "base": "https://kab.wikipedia.org/wiki/Uslig:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://kab.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901760752, + "variants": [ + { + "environment": { + "locales": [ + "kab" + ] + } + } + ], + "identifier": "wikipedia-kab", + "recordType": "engine", + "id": "e6f20ba5-3013-4b18-ad7a-351a64e62ec4", + "last_modified": 1702906502632 + }, + { + "base": { + "name": "Уикипедия (kk)", + "urls": { + "search": { + "base": "https://kk.wikipedia.org/wiki/Арнайы:Іздеу", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://kk.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901761359, + "variants": [ + { + "environment": { + "locales": [ + "kk" + ] + } + } + ], + "identifier": "wikipedia-kk", + "recordType": "engine", + "id": "15f00a29-0d68-4ff1-89d1-5e58b95618b7", + "last_modified": 1702906502628 + }, + { + "base": { + "name": "វីគីភីឌា (km)", + "urls": { + "search": { + "base": "https://km.wikipedia.org/wiki/ពិសេស:ស្វែងរក", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://km.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901761969, + "variants": [ + { + "environment": { + "locales": [ + "km" + ] + } + } + ], + "identifier": "wikipedia-km", + "recordType": "engine", + "id": "c8a7bec0-7f33-44e1-9521-363530993acc", + "last_modified": 1702906502625 + }, + { + "base": { + "name": "Wikipedia (kn)", + "urls": { + "search": { + "base": "https://kn.wikipedia.org/wiki/ವಿಶೇಷ:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://kn.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901762594, + "variants": [ + { + "environment": { + "locales": [ + "kn" + ] + } + } + ], + "identifier": "wikipedia-kn", + "recordType": "engine", + "id": "ce510c56-9d5e-416c-aef0-277bfbaac464", + "last_modified": 1702906502622 + }, + { + "base": { + "name": "Wikipedia (lij)", + "urls": { + "search": { + "base": "https://lij.wikipedia.org/wiki/Speçiale:Riçerca", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://lij.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901763202, + "variants": [ + { + "environment": { + "locales": [ + "lij" + ] + } + } + ], + "identifier": "wikipedia-lij", + "recordType": "engine", + "id": "5ec04310-966d-40da-af5b-f24383d40c8a", + "last_modified": 1702906502619 + }, + { + "base": { + "name": "ວິກິພີເດຍ (lo)", + "urls": { + "search": { + "base": "https://lo.wikipedia.org/wiki/ພິເສດ:ຊອກຫາ", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://lo.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901763817, + "variants": [ + { + "environment": { + "locales": [ + "lo" + ] + } + } + ], + "identifier": "wikipedia-lo", + "recordType": "engine", + "id": "515c4522-284d-468f-926a-eb7eb8a55e22", + "last_modified": 1702906502615 + }, + { + "base": { + "name": "Wikipedia (lt)", + "urls": { + "search": { + "base": "https://lt.wikipedia.org/wiki/Specialus:Paieška", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://lt.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901764432, + "variants": [ + { + "environment": { + "locales": [ + "lt" + ] + } + } + ], + "identifier": "wikipedia-lt", + "recordType": "engine", + "id": "774990fb-aabc-4008-86d3-adf12f89ccd4", + "last_modified": 1702906502612 + }, + { + "base": { + "name": "Vikipedeja (ltg)", + "urls": { + "search": { + "base": "https://ltg.wikipedia.org/wiki/Seviškuo:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ltg.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901765044, + "variants": [ + { + "environment": { + "locales": [ + "ltg" + ] + } + } + ], + "identifier": "wikipedia-ltg", + "recordType": "engine", + "id": "52871fd0-a24a-443b-a2d4-17eb09951691", + "last_modified": 1702906502609 + }, + { + "base": { + "name": "Vikipēdija", + "urls": { + "search": { + "base": "https://lv.wikipedia.org/wiki/Special:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://lv.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901765673, + "variants": [ + { + "environment": { + "locales": [ + "lv" + ] + } + } + ], + "identifier": "wikipedia-lv", + "recordType": "engine", + "id": "395b6c93-f7ff-47b8-b895-6f7bb99f9c56", + "last_modified": 1702906502606 + }, + { + "base": { + "name": "Википедија (mk)", + "urls": { + "search": { + "base": "https://mk.wikipedia.org/wiki/Специјална:Барај", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://mk.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901766290, + "variants": [ + { + "environment": { + "locales": [ + "mk" + ] + } + } + ], + "identifier": "wikipedia-mk", + "recordType": "engine", + "id": "a3aa8882-cbb0-4002-b0fc-b6ff5cdb9d92", + "last_modified": 1702906502602 + }, + { + "base": { + "name": "विकिपीडिया (mr)", + "urls": { + "search": { + "base": "https://mr.wikipedia.org/wiki/विशेष:शोधा", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://mr.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901766916, + "variants": [ + { + "environment": { + "locales": [ + "mr" + ] + } + } + ], + "identifier": "wikipedia-mr", + "recordType": "engine", + "id": "09f48c9b-b420-4312-8c60-aeb4358e0b97", + "last_modified": 1702906502599 + }, + { + "base": { + "name": "Wikipedia (ms)", + "urls": { + "search": { + "base": "https://ms.wikipedia.org/wiki/Khas:Gelintar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ms.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901767543, + "variants": [ + { + "environment": { + "locales": [ + "ms" + ] + } + } + ], + "identifier": "wikipedia-ms", + "recordType": "engine", + "id": "7039c888-4bd6-4b82-a164-ca3bfc93b24c", + "last_modified": 1702906502595 + }, + { + "base": { + "name": "Wikipedia (my)", + "urls": { + "search": { + "base": "https://my.wikipedia.org/wiki/Special:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://my.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901768156, + "variants": [ + { + "environment": { + "locales": [ + "my" + ] + } + } + ], + "identifier": "wikipedia-my", + "recordType": "engine", + "id": "0616dd06-2bfd-46f0-9870-832a1db8a622", + "last_modified": 1702906502592 + }, + { + "base": { + "name": "Wikipedia (nl)", + "urls": { + "search": { + "base": "https://nl.wikipedia.org/wiki/Speciaal:Zoeken", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://nl.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901768793, + "variants": [ + { + "environment": { + "locales": [ + "nl" + ] + } + } + ], + "identifier": "wikipedia-nl", + "recordType": "engine", + "id": "9e423eca-df00-4385-ab15-4e61bc220b05", + "last_modified": 1702906502588 + }, + { + "base": { + "name": "Wikipèdia (oc)", + "urls": { + "search": { + "base": "https://oc.wikipedia.org/wiki/Especial:Recèrca", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://oc.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901769405, + "variants": [ + { + "environment": { + "locales": [ + "oc" + ] + } + } + ], + "identifier": "wikipedia-oc", + "recordType": "engine", + "id": "e3e2c4a1-4914-4a27-96f8-63fb6a90f490", + "last_modified": 1702906502585 + }, + { + "base": { + "name": "Wikipedia (rm)", + "urls": { + "search": { + "base": "https://rm.wikipedia.org/wiki/Spezial:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://rm.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901770011, + "variants": [ + { + "environment": { + "locales": [ + "rm" + ] + } + } + ], + "identifier": "wikipedia-rm", + "recordType": "engine", + "id": "4042df85-6b55-4a40-9c3f-ef74b05ef5be", + "last_modified": 1702906502581 + }, + { + "base": { + "name": "Wikipedia (ro)", + "urls": { + "search": { + "base": "https://ro.wikipedia.org/wiki/Special:Căutare", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ro.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901770632, + "variants": [ + { + "environment": { + "locales": [ + "ro" + ] + } + } + ], + "identifier": "wikipedia-ro", + "recordType": "engine", + "id": "c2f3213e-f43f-4776-b1cb-b70d8457b6c7", + "last_modified": 1702906502577 + }, + { + "base": { + "name": "Википедия (ru)", + "urls": { + "search": { + "base": "https://ru.wikipedia.org/wiki/Служебная:Поиск", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ru.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901771244, + "variants": [ + { + "environment": { + "locales": [ + "ru" + ] + } + } + ], + "identifier": "wikipedia-ru", + "recordType": "engine", + "id": "8f2b997a-8bfe-436d-9393-b9abb52522ef", + "last_modified": 1702906502574 + }, + { + "base": { + "name": "Wikipedia (si)", + "urls": { + "search": { + "base": "https://si.wikipedia.org/wiki/විශේෂ:ගවේෂණය", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://si.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901771856, + "variants": [ + { + "environment": { + "locales": [ + "si" + ] + } + } + ], + "identifier": "wikipedia-si", + "recordType": "engine", + "id": "6a54dc6e-623a-4839-afd7-224e06d7ba73", + "last_modified": 1702906502571 + }, + { + "base": { + "name": "Wikipédia (sk)", + "urls": { + "search": { + "base": "https://sk.wikipedia.org/wiki/Špeciálne:Hľadanie", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://sk.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901772461, + "variants": [ + { + "environment": { + "locales": [ + "sk" + ] + } + } + ], + "identifier": "wikipedia-sk", + "recordType": "engine", + "id": "f9d77028-e386-4093-aacd-a8c2a56acbc0", + "last_modified": 1702906502568 + }, + { + "base": { + "name": "Wikipedija (sl)", + "urls": { + "search": { + "base": "https://sl.wikipedia.org/wiki/Posebno:Iskanje", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://sl.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901773075, + "variants": [ + { + "environment": { + "locales": [ + "sl" + ] + } + } + ], + "identifier": "wikipedia-sl", + "recordType": "engine", + "id": "887d2a1b-7275-4a4e-afa2-9157eb98a7d5", + "last_modified": 1702906502565 + }, + { + "base": { + "name": "Wikipedia (sq)", + "urls": { + "search": { + "base": "https://sq.wikipedia.org/wiki/Speciale:Kërkim", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://sq.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901773681, + "variants": [ + { + "environment": { + "locales": [ + "sq" + ] + } + } + ], + "identifier": "wikipedia-sq", + "recordType": "engine", + "id": "bc8c03e7-747a-408c-a0ee-6598ca9fad4a", + "last_modified": 1702906502561 + }, + { + "base": { + "name": "Википедија (sr)", + "urls": { + "search": { + "base": "https://sr.wikipedia.org/wiki/Посебно:Претражи", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://sr.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901774302, + "variants": [ + { + "environment": { + "locales": [ + "sr" + ] + } + } + ], + "identifier": "wikipedia-sr", + "recordType": "engine", + "id": "d381964d-d1e3-4378-962c-6ac5b89161f8", + "last_modified": 1702906502558 + }, + { + "base": { + "name": "Wikipedia (sv)", + "urls": { + "search": { + "base": "https://sv.wikipedia.org/wiki/Special:Sök", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://sv.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901774920, + "variants": [ + { + "environment": { + "locales": [ + "sv-SE" + ] + } + } + ], + "identifier": "wikipedia-sv-SE", + "recordType": "engine", + "id": "d0708bee-d246-4eb9-81ff-50d5947837ec", + "last_modified": 1702906502555 + }, + { + "base": { + "name": "விக்கிப்பீடியா (ta)", + "urls": { + "search": { + "base": "https://ta.wikipedia.org/wiki/சிறப்பு:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ta.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901775549, + "variants": [ + { + "environment": { + "locales": [ + "ta" + ] + } + } + ], + "identifier": "wikipedia-ta", + "recordType": "engine", + "id": "78ed73b5-4afd-467e-b7ee-c96658f345c6", + "last_modified": 1702906502552 + }, + { + "base": { + "name": "వికీపీడియా (te)", + "urls": { + "search": { + "base": "https://te.wikipedia.org/wiki/ప్రత్యేక:అన్వేషణ", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://te.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901776173, + "variants": [ + { + "environment": { + "locales": [ + "te" + ] + } + } + ], + "identifier": "wikipedia-te", + "recordType": "engine", + "id": "391eb3c6-de7a-4aaf-8027-3ad1b8c3c00a", + "last_modified": 1702906502548 + }, + { + "base": { + "name": "วิกิพีเดีย", + "urls": { + "search": { + "base": "https://th.wikipedia.org/wiki/พิเศษ:ค้นหา", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://th.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901776774, + "variants": [ + { + "environment": { + "locales": [ + "th" + ] + } + } + ], + "identifier": "wikipedia-th", + "recordType": "engine", + "id": "5c7903a3-5a44-41a0-aac2-fc26fe8f8fe7", + "last_modified": 1702906502545 + }, + { + "base": { + "name": "Wikipedia (tl)", + "urls": { + "search": { + "base": "https://tl.wikipedia.org/wiki/Natatangi:Maghanap", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://tl.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901777391, + "variants": [ + { + "environment": { + "locales": [ + "tl" + ] + } + } + ], + "identifier": "wikipedia-tl", + "recordType": "engine", + "id": "d0c8b2f5-accc-45f4-adf4-a7377692869b", + "last_modified": 1702906502542 + }, + { + "base": { + "name": "Wikipedia (tr)", + "urls": { + "search": { + "base": "https://tr.wikipedia.org/wiki/Özel:Ara", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://tr.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901777988, + "variants": [ + { + "environment": { + "locales": [ + "tr" + ] + } + } + ], + "identifier": "wikipedia-tr", + "recordType": "engine", + "id": "8885ffbb-f450-4642-8204-ed9128b29e51", + "last_modified": 1702906502539 + }, + { + "base": { + "name": "Вікіпедія (uk)", + "urls": { + "search": { + "base": "https://uk.wikipedia.org/wiki/Спеціальна:Пошук", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://uk.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901778586, + "variants": [ + { + "environment": { + "locales": [ + "uk" + ] + } + } + ], + "identifier": "wikipedia-uk", + "recordType": "engine", + "id": "3b65a693-177a-4bd2-9d66-8c038e005800", + "last_modified": 1702906502536 + }, + { + "base": { + "name": "ویکیپیڈیا (ur)", + "urls": { + "search": { + "base": "https://ur.wikipedia.org/wiki/خاص:تلاش", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ur.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901779213, + "variants": [ + { + "environment": { + "locales": [ + "ur" + ] + } + } + ], + "identifier": "wikipedia-ur", + "recordType": "engine", + "id": "52a714ba-0471-47be-9557-fc5fe9c9ff50", + "last_modified": 1702906502533 + }, + { + "base": { + "name": "Vikipediya (uz)", + "urls": { + "search": { + "base": "https://uz.wikipedia.org/wiki/Maxsus:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://uz.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901779828, + "variants": [ + { + "environment": { + "locales": [ + "uz" + ] + } + } + ], + "identifier": "wikipedia-uz", + "recordType": "engine", + "id": "1b07d255-6f2c-4e2d-a4b4-b0e99aae39e9", + "last_modified": 1702906502529 + }, + { + "base": { + "name": "Wikipedia (vi)", + "urls": { + "search": { + "base": "https://vi.wikipedia.org/wiki/Đặc_biệt:Tìm_kiếm", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://vi.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901780448, + "variants": [ + { + "environment": { + "locales": [ + "vi" + ] + } + } + ], + "identifier": "wikipedia-vi", + "recordType": "engine", + "id": "5b6f0a3f-1f94-473d-8bcc-c48984523b0c", + "last_modified": 1702906502525 + }, + { + "base": { + "name": "Wikipedia (wo)", + "urls": { + "search": { + "base": "https://wo.wikipedia.org/wiki/Jagleel:Ceet", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://wo.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901781072, + "variants": [ + { + "environment": { + "locales": [ + "wo" + ] + } + } + ], + "identifier": "wikipedia-wo", + "recordType": "engine", + "id": "3f1ba3c7-23f1-4ca1-aa0c-623fcae80ddd", + "last_modified": 1702906502521 + }, + { + "base": { + "name": "维基百科", + "urls": { + "search": { + "base": "https://zh.wikipedia.org/wiki/Special:搜索", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://zh.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901781680, + "variants": [ + { + "environment": { + "locales": [ + "zh-CN" + ] + } + } + ], + "identifier": "wikipedia-zh-CN", + "recordType": "engine", + "id": "c0f736ce-c219-4388-a242-48168237e3f5", + "last_modified": 1702906502518 + }, + { + "base": { + "name": "Wikipedia (zh)", + "urls": { + "search": { + "base": "https://zh.wikipedia.org/wiki/Special:搜索", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + }, + { + "name": "variant", + "value": "zh-tw" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://zh.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901782303, + "variants": [ + { + "environment": { + "locales": [ + "zh-TW" + ] + } + } + ], + "identifier": "wikipedia-zh-TW", + "recordType": "engine", + "id": "864df79e-c03d-47ee-aacf-06242cffe951", + "last_modified": 1702906502515 + }, + { + "base": { + "name": "Вікіпедыя (be)", + "urls": { + "search": { + "base": "https://be.wikipedia.org/wiki/Адмысловае:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://be.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901782971, + "variants": [ + { + "environment": { + "locales": [ + "be" + ] + } + } + ], + "identifier": "wikipedia-be", + "recordType": "engine", + "id": "5327659a-3b79-4071-95b5-b86f7cc25a25", + "last_modified": 1702906502510 + }, + { + "base": { + "name": "Вікіпэдыя (be-tarask)", + "urls": { + "search": { + "base": "https://be-tarask.wikipedia.org/wiki/Спэцыяльныя:Пошук", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://be-tarask.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901783595, + "variants": [ + { + "environment": { + "locales": [ + "be" + ] + } + } + ], + "identifier": "wikipedia-be-tarask", + "recordType": "engine", + "id": "d7cb9b4b-f53a-49ea-ae04-27172fb108e9", + "last_modified": 1702906502506 + }, + { + "base": { + "name": "উইকিপিডিয়া (bn)", + "urls": { + "search": { + "base": "https://bn.wikipedia.org/wiki/বিশেষ:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://bn.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901784204, + "variants": [ + { + "environment": { + "locales": [ + "bn", + "bn-BD", + "bn-IN" + ] + } + } + ], + "identifier": "wikipedia-bn", + "recordType": "engine", + "id": "a0a95a2c-83b7-4507-a4e9-5632ca9825dd", + "last_modified": 1702906502503 + }, + { + "base": { + "name": "Viquipèdia (ca)", + "urls": { + "search": { + "base": "https://ca.wikipedia.org/wiki/Especial:Cerca", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ca.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901784840, + "variants": [ + { + "environment": { + "locales": [ + "ca", + "ca-valencia" + ] + } + } + ], + "identifier": "wikipedia-ca", + "recordType": "engine", + "id": "7ed640c7-bc86-45dc-b07a-f7d8b5a8e079", + "last_modified": 1702906502499 + }, + { + "base": { + "name": "Wikipedia (es)", + "urls": { + "search": { + "base": "https://es.wikipedia.org/wiki/Especial:Buscar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://es.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901785455, + "variants": [ + { + "environment": { + "locales": [ + "cak", + "es-AR", + "es-CL", + "es-ES", + "es-MX", + "trs" + ] + } + } + ], + "identifier": "wikipedia-es", + "recordType": "engine", + "id": "82025e38-8eda-48c2-b844-2b5e7d489c34", + "last_modified": 1702906502496 + }, + { + "base": { + "name": "Wikipedie (cs)", + "urls": { + "search": { + "base": "https://cs.wikipedia.org/wiki/Speciální:Hledání", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://cs.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901786063, + "variants": [ + { + "environment": { + "locales": [ + "cs" + ] + } + } + ], + "identifier": "wikipedia-cz", + "recordType": "engine", + "id": "2574b200-f878-4b0c-b7e0-49804b3f4b8e", + "last_modified": 1702906502493 + }, + { + "base": { + "name": "Wikipédia (fr)", + "urls": { + "search": { + "base": "https://fr.wikipedia.org/wiki/Spécial:Recherche", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://fr.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901786670, + "variants": [ + { + "environment": { + "locales": [ + "ff", + "fr", + "son" + ] + } + } + ], + "identifier": "wikipedia-fr", + "recordType": "engine", + "id": "a0261c4c-7b60-42c5-b35b-00ab57c5fbc4", + "last_modified": 1702906502488 + }, + { + "base": { + "name": "વિકિપીડિયા (gu)", + "urls": { + "search": { + "base": "https://gu.wikipedia.org/wiki/વિશેષ:શોધ", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://gu.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901787288, + "variants": [ + { + "environment": { + "locales": [ + "gu-IN" + ] + } + } + ], + "identifier": "wikipedia-gu", + "recordType": "engine", + "id": "ede0b78c-500a-4d1b-8236-cd5e7ceee815", + "last_modified": 1702906502485 + }, + { + "base": { + "name": "विकिपीडिया (hi)", + "urls": { + "search": { + "base": "https://hi.wikipedia.org/wiki/विशेष:खोज", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://hi.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901787905, + "variants": [ + { + "environment": { + "locales": [ + "hi-IN" + ] + } + } + ], + "identifier": "wikipedia-hi", + "recordType": "engine", + "id": "8fc18864-53e1-47b4-8730-2e67962c7b32", + "last_modified": 1702906502482 + }, + { + "base": { + "name": "Wikipedia (hy)", + "urls": { + "search": { + "base": "https://hy.wikipedia.org/wiki/Սպասարկող:Որոնել", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://hy.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901788513, + "variants": [ + { + "environment": { + "locales": [ + "hy-AM" + ] + } + } + ], + "identifier": "wikipedia-hy", + "recordType": "engine", + "id": "c6286785-b61b-4288-aec1-9a7820c32694", + "last_modified": 1702906502479 + }, + { + "base": { + "name": "Wikipedia (it)", + "urls": { + "search": { + "base": "https://it.wikipedia.org/wiki/Speciale:Ricerca", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://it.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901789119, + "variants": [ + { + "environment": { + "locales": [ + "fur", + "it", + "sc" + ] + } + } + ], + "identifier": "wikipedia-it", + "recordType": "engine", + "id": "de65dd23-0d4b-4bf0-bd97-0f536e184ded", + "last_modified": 1702906502476 + }, + { + "base": { + "name": "Wikipedia (ja)", + "urls": { + "search": { + "base": "https://ja.wikipedia.org/wiki/特別:検索", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ja.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901789723, + "variants": [ + { + "environment": { + "locales": [ + "ja", + "ja-JP-macos" + ] + } + } + ], + "identifier": "wikipedia-ja", + "recordType": "engine", + "id": "a6bbf9df-acd4-47ea-9d26-6382b587c9c3", + "last_modified": 1702906502473 + }, + { + "base": { + "name": "위키백과 (ko)", + "urls": { + "search": { + "base": "https://ko.wikipedia.org/wiki/특수기능:찾기", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ko.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901790335, + "variants": [ + { + "environment": { + "locales": [ + "ko" + ] + } + } + ], + "identifier": "wikipedia-kr", + "recordType": "engine", + "id": "831d7fed-79d2-4aac-9481-826300a03d65", + "last_modified": 1702906502470 + }, + { + "base": { + "name": "Wikipedia (no)", + "urls": { + "search": { + "base": "https://no.wikipedia.org/wiki/Spesial:Søk", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://no.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901790944, + "variants": [ + { + "environment": { + "locales": [ + "nb-NO" + ] + } + } + ], + "identifier": "wikipedia-NO", + "recordType": "engine", + "id": "607d401f-70cd-4810-b9b5-497d5a9489f6", + "last_modified": 1702906502467 + }, + { + "base": { + "name": "विकिपीडिया (ne)", + "urls": { + "search": { + "base": "https://ne.wikipedia.org/wiki/विशेष:Search", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://ne.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901791565, + "variants": [ + { + "environment": { + "locales": [ + "ne-NP" + ] + } + } + ], + "identifier": "wikipedia-ne", + "recordType": "engine", + "id": "dd1c5a28-733d-46b4-ae5e-fff82f94aec9", + "last_modified": 1702906502465 + }, + { + "base": { + "name": "Wikipedia (nn)", + "urls": { + "search": { + "base": "https://nn.wikipedia.org/wiki/Spesial:Søk", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://nn.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901792200, + "variants": [ + { + "environment": { + "locales": [ + "nn-NO" + ] + } + } + ], + "identifier": "wikipedia-NN", + "recordType": "engine", + "id": "c3462c25-acc1-4003-a57b-3931bfacdce5", + "last_modified": 1702906502462 + }, + { + "base": { + "name": "Wikipedia (pa)", + "urls": { + "search": { + "base": "https://pa.wikipedia.org/wiki/ਖ਼ਾਸ:ਖੋਜੋ", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://pa.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901792817, + "variants": [ + { + "environment": { + "locales": [ + "pa-IN" + ] + } + } + ], + "identifier": "wikipedia-pa", + "recordType": "engine", + "id": "fa428a75-44d5-4f21-84bd-64935d483540", + "last_modified": 1702906502459 + }, + { + "base": { + "name": "Wikipedia (pt)", + "urls": { + "search": { + "base": "https://pt.wikipedia.org/wiki/Especial:Pesquisar", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://pt.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901793438, + "variants": [ + { + "environment": { + "locales": [ + "pt-BR", + "pt-PT" + ] + } + } + ], + "identifier": "wikipedia-pt", + "recordType": "engine", + "id": "f2e04c1b-b19f-40d9-841d-9b48b7ba9da2", + "last_modified": 1702906502456 + }, + { + "base": { + "name": "Wikipedia (pl)", + "urls": { + "search": { + "base": "https://pl.wikipedia.org/wiki/Specjalna:Szukaj", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://pl.wikipedia.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + } + ], + "searchTermParamName": "search" + } + }, + "aliases": [ + "wikipedia" + ], + "classification": "unknown" + }, + "schema": 1702901794065, + "variants": [ + { + "environment": { + "locales": [ + "pl", + "szl" + ] + } + } + ], + "identifier": "wikipedia-pl", + "recordType": "engine", + "id": "980cba80-0c46-4c05-8df0-aaea23d57732", + "last_modified": 1702906502453 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.com/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "711-53200-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "0" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901794691, + "variants": [ + { + "environment": { + "locales": [ + "en-US" + ], + "regions": [ + "us" + ] + } + } + ], + "identifier": "ebay", + "recordType": "engine", + "id": "d8b7ed81-00f3-478a-a835-dc0677d7190a", + "last_modified": 1702906502450 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.es/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "1185-53479-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "186" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901795317, + "variants": [ + { + "environment": { + "locales": [ + "an", + "ast", + "ca", + "ca-valencia", + "es-ES", + "eu", + "gl" + ] + } + } + ], + "identifier": "ebay-es", + "recordType": "engine", + "id": "0592aedc-f4e5-4873-a8d4-da9482071613", + "last_modified": 1702906502448 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.fr/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "709-53476-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "71" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901795929, + "variants": [ + { + "environment": { + "locales": [ + "br", + "fr", + "wo" + ], + "excludedRegions": [ + "be", + "ca", + "ch" + ] + } + } + ], + "identifier": "ebay-fr", + "recordType": "engine", + "id": "d327156c-dfc4-47f6-a620-3846a21144d9", + "last_modified": 1702906502445 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.befr.ebay.be/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "1553-53471-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "23" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901796541, + "variants": [ + { + "environment": { + "locales": [ + "br", + "en-US", + "fr", + "wo", + "fy-NL", + "nl" + ], + "regions": [ + "be" + ] + } + } + ], + "identifier": "ebay-be", + "recordType": "engine", + "id": "5f40614e-4fc6-4dee-a897-07b258e00820", + "last_modified": 1702906502442 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.ca/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "706-53473-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "2" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901797167, + "variants": [ + { + "environment": { + "locales": [ + "br", + "en-US", + "fr", + "wo" + ], + "regions": [ + "ca" + ] + } + }, + { + "environment": { + "locales": [ + "en-CA" + ] + } + } + ], + "identifier": "ebay-ca", + "recordType": "engine", + "id": "336d7fa9-7579-4981-bfb0-024c6c5f977d", + "last_modified": 1702906502438 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.ch/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "5222-53480-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "193" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901797777, + "variants": [ + { + "environment": { + "locales": [ + "br", + "de", + "dsb", + "en-US", + "fr", + "hsb", + "wo" + ], + "regions": [ + "ch" + ] + } + }, + { + "environment": { + "locales": [ + "rm" + ] + } + } + ], + "identifier": "ebay-ch", + "recordType": "engine", + "id": "8d8484a2-b856-4edd-93d7-c53a7aaca6e1", + "last_modified": 1702906502435 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.co.uk/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "710-53481-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "3" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901798395, + "variants": [ + { + "environment": { + "locales": [ + "cy", + "en-GB", + "gd" + ], + "excludedRegions": [ + "au", + "ie" + ] + } + }, + { + "environment": { + "locales": [ + "sco" + ], + "regions": [ + "gb" + ] + } + }, + { + "environment": { + "locales": [ + "en-US" + ], + "regions": [ + "gb" + ] + } + } + ], + "identifier": "ebay-uk", + "recordType": "engine", + "id": "37f83917-8d6e-46d6-9d15-8e7779339d18", + "last_modified": 1702906502432 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.com.au/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "705-53470-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "15" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901799015, + "variants": [ + { + "environment": { + "locales": [ + "cy", + "en-GB", + "en-US", + "gd" + ], + "regions": [ + "au" + ] + } + } + ], + "identifier": "ebay-au", + "recordType": "engine", + "id": "e5a64fe0-2cb9-4b22-83d3-4e529c3702a9", + "last_modified": 1702906502429 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.ie/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "5282-53468-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "205" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901799624, + "variants": [ + { + "environment": { + "locales": [ + "cy", + "en-GB", + "en-US", + "gd" + ], + "regions": [ + "ie" + ] + } + }, + { + "environment": { + "locales": [ + "ga-IE" + ] + } + } + ], + "identifier": "ebay-ie", + "recordType": "engine", + "id": "0a9fed24-cd33-4293-bac8-eb26a285c9f1", + "last_modified": 1702906502427 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.de/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "707-53477-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "77" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901800234, + "variants": [ + { + "environment": { + "locales": [ + "de", + "dsb", + "hsb" + ], + "excludedRegions": [ + "at", + "ch" + ] + } + } + ], + "identifier": "ebay-de", + "recordType": "engine", + "id": "e991a5f7-616e-43b0-b349-7995b837a520", + "last_modified": 1702906502424 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.at/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "5221-53469-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "16" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901800849, + "variants": [ + { + "environment": { + "locales": [ + "de", + "dsb", + "hsb" + ], + "regions": [ + "at" + ] + } + } + ], + "identifier": "ebay-at", + "recordType": "engine", + "id": "8dee7454-6c6c-4b22-a411-4e474c7afbb3", + "last_modified": 1702906502421 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.nl/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "1346-53482-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "146" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901801464, + "variants": [ + { + "environment": { + "locales": [ + "fy-NL", + "nl" + ], + "excludedRegions": [ + "be" + ] + } + }, + { + "environment": { + "locales": [ + "en-US" + ], + "regions": [ + "nl" + ] + } + } + ], + "identifier": "ebay-nl", + "recordType": "engine", + "id": "670475c3-d56f-4f87-9834-ddb12d0e8cbd", + "last_modified": 1702906502418 + }, + { + "base": { + "name": "eBay", + "urls": { + "search": { + "base": "https://www.ebay.it/sch/", + "params": [ + { + "name": "toolid", + "value": "20004" + }, + { + "name": "campid", + "value": "{partnerCode}" + }, + { + "name": "mkevt", + "value": "1" + }, + { + "name": "mkcid", + "value": "1" + }, + { + "name": "mkrid", + "value": "724-53478-19255-0" + } + ], + "searchTermParamName": "kw" + }, + "suggestions": { + "base": "https://autosug.ebay.com/autosug", + "params": [ + { + "name": "sId", + "value": "101" + }, + { + "name": "fmt", + "value": "osr" + } + ], + "searchTermParamName": "kwd" + } + }, + "aliases": [ + "ebay" + ], + "partnerCode": "5338192028", + "classification": "unknown" + }, + "schema": 1702901802076, + "variants": [ + { + "environment": { + "locales": [ + "fur", + "it", + "lij", + "sc" + ] + } + } + ], + "identifier": "ebay-it", + "recordType": "engine", + "id": "f36dd44c-ba5f-44e5-8b19-672eb1ed62fd", + "last_modified": 1702906502415 + }, + { + "base": { + "name": "Amazon.com", + "urls": { + "search": { + "base": "https://www.amazon.com/s", + "searchTermParamName": "k" + } + }, + "aliases": [ + "amazon" + ], + "classification": "unknown" + }, + "notes": "Amazon.com (us only)", + "schema": 1702901802672, + "variants": [ + { + "environment": { + "regions": [ + "us" + ] + } + } + ], + "identifier": "amazondotcom-us", + "recordType": "engine", + "id": "788a2ded-6872-4742-8115-c14dde7a18f9", + "last_modified": 1702906502412 + }, + { + "base": { + "name": "Amazon.co.jp", + "urls": { + "search": { + "base": "https://www.amazon.co.jp/exec/obidos/external-search/", + "params": [ + { + "name": "mode", + "value": "blended" + }, + { + "name": "tag", + "value": "mozillajapan-fx-22" + }, + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "field-keywords" + } + }, + "aliases": [ + "amazon" + ], + "classification": "unknown" + }, + "schema": 1702901805111, + "variants": [ + { + "environment": { + "regions": [ + "jp" + ] + } + } + ], + "identifier": "amazon-jp", + "recordType": "engine", + "id": "9d089e46-dc94-4d4f-8f17-cc07b59aa9e4", + "last_modified": 1702906502401 + }, + { + "base": { + "name": "Wolne Lektury", + "urls": { + "search": { + "base": "https://wolnelektury.pl/szukaj/", + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://wolnelektury.pl/katalog/jtags/", + "params": [ + { + "name": "mozhint", + "value": "1" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901811217, + "variants": [ + { + "environment": { + "locales": [ + "pl", + "szl" + ] + } + } + ], + "identifier": "wolnelektury-pl", + "recordType": "engine", + "id": "42348cb0-69a9-4451-8bd6-71bad5bc4e3f", + "last_modified": 1702906502368 + }, + { + "base": { + "name": "Allegro", + "urls": { + "search": { + "base": "https://allegro.pl/listing", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "string" + } + }, + "classification": "unknown" + }, + "schema": 1702901811834, + "variants": [ + { + "environment": { + "locales": [ + "pl", + "szl" + ] + } + } + ], + "identifier": "allegro-pl", + "recordType": "engine", + "id": "3cc3f699-284a-4320-b131-395d9b218ade", + "last_modified": 1702906502364 + }, + { + "base": { + "name": "GMX Suche", + "urls": { + "search": { + "base": "https://go.gmx.net/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.ui-portal.de/s", + "params": [ + { + "name": "brand", + "value": "gmx" + }, + { + "name": "origin", + "value": "br_splugin_ff_sg" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901812448, + "variants": [ + { + "environment": { + "distributions": [ + "gmx" + ] + } + } + ], + "identifier": "gmx-de", + "recordType": "engine", + "id": "5240e0d8-8c2c-436b-b478-3fa0b506410f", + "last_modified": 1702906502361 + }, + { + "base": { + "name": "GMX Shopping", + "urls": { + "search": { + "base": "https://shopping.gmx.net/", + "params": [ + { + "name": "origin", + "value": "br_osd" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://shopping.gmx.net/suggest/ca/", + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901813066, + "variants": [ + { + "environment": { + "distributions": [ + "gmx" + ] + } + } + ], + "identifier": "gmx-shopping", + "recordType": "engine", + "id": "3a738904-cf8c-4d87-8972-ea98768d44f0", + "last_modified": 1702906502357 + }, + { + "base": { + "name": "GMX Search", + "urls": { + "search": { + "base": "https://go.gmx.co.uk/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.gmx.co.uk/s", + "params": [ + { + "name": "brand", + "value": "gmxcouk" + }, + { + "name": "origin", + "value": "moz_splugin_ff" + }, + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901813683, + "variants": [ + { + "environment": { + "distributions": [ + "gmxcouk" + ] + } + } + ], + "identifier": "gmx-en-GB", + "recordType": "engine", + "id": "d5f10a5b-19b7-4214-9e84-be0b04d6414f", + "last_modified": 1702906502354 + }, + { + "base": { + "name": "GMX - Búsqueda web", + "urls": { + "search": { + "base": "https://go.gmx.es/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.gmx.es/s", + "params": [ + { + "name": "brand", + "value": "gmxes" + }, + { + "name": "origin", + "value": "moz_splugin_ff" + }, + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901814287, + "variants": [ + { + "environment": { + "distributions": [ + "gmxes" + ] + } + } + ], + "identifier": "gmx-es", + "recordType": "engine", + "id": "bdb578f4-6362-4789-911d-193c98ec5378", + "last_modified": 1702906502351 + }, + { + "base": { + "name": "GMX - Recherche web", + "urls": { + "search": { + "base": "https://go.gmx.fr/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.gmx.fr/s", + "params": [ + { + "name": "brand", + "value": "gmxfr" + }, + { + "name": "origin", + "value": "moz_splugin_ff" + }, + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901814882, + "variants": [ + { + "environment": { + "distributions": [ + "gmxfr" + ] + } + } + ], + "identifier": "gmx-fr", + "recordType": "engine", + "id": "2b628809-284d-468a-831f-95dc479f30da", + "last_modified": 1702906502349 + }, + { + "base": { + "name": "Qwant Junior", + "urls": { + "search": { + "base": "https://www.qwantjunior.com/", + "params": [ + { + "name": "client", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://api.qwant.com/egp/suggest/", + "params": [ + { + "name": "client", + "value": "opensearch" + } + ], + "searchTermParamName": "q" + } + }, + "partnerCode": "firefoxqwant", + "classification": "unknown" + }, + "schema": 1702901815498, + "variants": [ + { + "environment": { + "locales": [ + "fr" + ], + "distributions": [ + "qwant-001", + "qwant-002" + ] + } + } + ], + "identifier": "qwantjr", + "recordType": "engine", + "id": "7996de41-caf5-4e52-8d96-4ee89ede5d5c", + "last_modified": 1702906502346 + }, + { + "base": { + "name": "Seznam", + "urls": { + "search": { + "base": "https://search.seznam.cz/", + "params": [ + { + "name": "sourceid", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggest.seznam.cz/fulltext_ff", + "searchTermParamName": "phrase" + } + }, + "partnerCode": "firefox", + "classification": "unknown" + }, + "schema": 1702901816717, + "variants": [ + { + "environment": { + "locales": [ + "cs" + ] + } + } + ], + "identifier": "seznam-cz", + "recordType": "engine", + "id": "b0cdb724-b7df-47d0-8ead-93aa0a8f60a2", + "last_modified": 1702906502339 + }, + { + "base": { + "name": "1&1 Suche", + "urls": { + "search": { + "base": "https://go.1und1.de/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.ui-portal.de/s", + "params": [ + { + "name": "brand", + "value": "1und1" + }, + { + "name": "origin", + "value": "br_splugin_ff_sg" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901817337, + "variants": [ + { + "environment": { + "distributions": [ + "1und1" + ] + } + } + ], + "identifier": "1und1", + "recordType": "engine", + "id": "e870e51d-efed-41d3-8d65-f833ee8e9d96", + "last_modified": 1702906502336 + }, + { + "base": { + "name": "WEB.DE Suche", + "urls": { + "search": { + "base": "https://go.web.de/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggestplugin.ui-portal.de/s", + "params": [ + { + "name": "brand", + "value": "webde" + }, + { + "name": "origin", + "value": "br_splugin_ff_sg" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901817944, + "variants": [ + { + "environment": { + "distributions": [ + "webde" + ] + } + } + ], + "identifier": "webde", + "recordType": "engine", + "id": "3d42fa9a-08ca-4057-b79d-917c576e2634", + "last_modified": 1702906502332 + }, + { + "base": { + "name": "mail.com search", + "urls": { + "search": { + "base": "https://go.mail.com/br/moz_search_web/", + "params": [ + { + "name": "enc", + "value": "UTF-8" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://search.mail.com/SuggestSearch/s", + "params": [ + { + "name": "brand", + "value": "mailcom" + }, + { + "name": "origin", + "value": "br_splugin_ff_sg" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901818560, + "variants": [ + { + "environment": { + "distributions": [ + "mail.com" + ] + } + } + ], + "identifier": "mailcom", + "recordType": "engine", + "id": "39375b5f-77bb-46cb-979d-345c002fe35f", + "last_modified": 1702906502329 + }, + { + "base": { + "name": "楽天市場", + "urls": { + "search": { + "base": "https://pt.afl.rakuten.co.jp/c/013ca98b.cd7c5f0c/", + "params": [ + { + "name": "sv", + "value": "2" + }, + { + "name": "p", + "value": "0" + } + ], + "searchTermParamName": "sitem" + } + }, + "charset": "EUC-JP", + "classification": "unknown" + }, + "schema": 1702901819182, + "variants": [ + { + "environment": { + "locales": [ + "ja", + "ja-JP-macos" + ] + } + } + ], + "identifier": "rakuten", + "recordType": "engine", + "id": "ab6f7178-b0be-452a-82f2-f5df8df2d836", + "last_modified": 1702906502326 + }, + { + "base": { + "name": "Azerdict", + "urls": { + "search": { + "base": "https://azerdict.com/english/", + "searchTermParamName": "word" + } + }, + "classification": "unknown" + }, + "schema": 1702901819798, + "variants": [ + { + "environment": { + "locales": [ + "az" + ] + } + } + ], + "identifier": "azerdict", + "recordType": "engine", + "id": "786aa916-a331-4b0c-8099-85927dd87be8", + "last_modified": 1702906502323 + }, + { + "base": { + "name": "Ordbok", + "urls": { + "search": { + "base": "https://ordbok.uib.no/perl/ordbok.cgi", + "params": [ + { + "name": "sourceid", + "value": "Mozilla-search" + } + ], + "searchTermParamName": "OPP" + } + }, + "classification": "unknown" + }, + "schema": 1702901820444, + "variants": [ + { + "environment": { + "locales": [ + "nb-NO", + "nn-NO" + ] + } + } + ], + "identifier": "bok-NO", + "recordType": "engine", + "id": "7ed2240b-b161-4b49-a7fe-35db89544f74", + "last_modified": 1702906502320 + }, + { + "base": { + "name": "Ceneje.si", + "urls": { + "search": { + "base": "https://www.ceneje.si/search_new.aspx", + "params": [ + { + "name": "FF-SearchBox", + "value": "1" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901821058, + "variants": [ + { + "environment": { + "locales": [ + "sl" + ] + } + } + ], + "identifier": "ceneji", + "recordType": "engine", + "id": "5e515970-4129-48fc-b0d0-8a61c92903d2", + "last_modified": 1702906502317 + }, + { + "base": { + "name": "Cốc Cốc", + "urls": { + "search": { + "base": "https://coccoc.com/search", + "params": [ + { + "name": "s", + "value": "ff" + }, + { + "name": "utm_source", + "value": "firefox" + } + ], + "searchTermParamName": "query" + }, + "suggestions": { + "base": "https://coccoc.com/composer/autocomplete", + "params": [ + { + "name": "of", + "value": "b" + }, + { + "name": "s", + "value": "ff" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901821664, + "variants": [ + { + "environment": { + "locales": [ + "vi" + ] + } + } + ], + "identifier": "coccoc", + "recordType": "engine", + "id": "20104daa-cb8a-40b9-a8c5-2433f4476c3e", + "last_modified": 1702906502314 + }, + { + "base": { + "name": "다음", + "urls": { + "search": { + "base": "https://search.daum.net/search", + "params": [ + { + "name": "w", + "value": "tot" + }, + { + "name": "nil_ch", + "value": "ffsr" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://suggest.search.daum.net/sushi/opensearch/pc", + "params": [ + { + "name": "DA", + "value": "JU2" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901822275, + "variants": [ + { + "environment": { + "locales": [ + "ko" + ] + } + } + ], + "identifier": "daum-kr", + "recordType": "engine", + "id": "3330e927-6733-4239-9738-b41c5c460b11", + "last_modified": 1702906502311 + }, + { + "base": { + "name": "Ecosia", + "urls": { + "search": { + "base": "https://www.ecosia.org/search", + "params": [ + { + "name": "tt", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://ac.ecosia.org/autocomplete", + "params": [ + { + "name": "type", + "value": "list" + } + ], + "searchTermParamName": "q" + } + }, + "partnerCode": "mzl", + "classification": "general" + }, + "schema": 1702901822899, + "variants": [ + { + "environment": { + "locales": [ + "de" + ] + } + } + ], + "identifier": "ecosia", + "recordType": "engine", + "id": "e8e4a7e3-aead-43e3-887d-4064a186bd70", + "last_modified": 1702906502308 + }, + { + "base": { + "name": "EUdict Eng->Cro", + "urls": { + "search": { + "base": "https://eudict.com/", + "params": [ + { + "name": "lang", + "value": "engcro" + } + ], + "searchTermParamName": "word" + } + }, + "classification": "unknown" + }, + "schema": 1702901823499, + "variants": [ + { + "environment": { + "locales": [ + "hr" + ] + } + } + ], + "identifier": "eudict", + "recordType": "engine", + "id": "8fe5af92-3b6d-4cbe-a736-4cad01a21efe", + "last_modified": 1702906502306 + }, + { + "base": { + "name": "Am Faclair Beag", + "urls": { + "search": { + "base": "https://www.faclair.com/", + "searchTermParamName": "txtSearch" + } + }, + "classification": "unknown" + }, + "schema": 1702901824109, + "variants": [ + { + "environment": { + "locales": [ + "gd" + ] + } + } + ], + "identifier": "faclair-beag", + "recordType": "engine", + "id": "d21e561b-64ae-4a33-b2f0-c4a490a5cf82", + "last_modified": 1702906502303 + }, + { + "base": { + "name": "Gule sider", + "urls": { + "search": { + "base": "https://www.gulesider.no/search", + "params": [ + { + "name": "what", + "value": "all" + }, + { + "name": "cmpid", + "value": "fre_partner_fire_gssbtop" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901824715, + "variants": [ + { + "environment": { + "locales": [ + "nb-NO", + "nn-NO" + ] + } + } + ], + "identifier": "gulesider-NO", + "recordType": "engine", + "id": "0f9d78f8-e8ad-40be-8f50-b71d5da0f4cc", + "last_modified": 1702906502300 + }, + { + "base": { + "name": "LEO Eng-Deu", + "urls": { + "search": { + "base": "https://dict.leo.org/englisch-deutsch/{searchTerms}" + }, + "suggestions": { + "base": "https://dict.leo.org/dictQuery/m-query/conf/ende/query.conf/strlist.json", + "params": [ + { + "name": "sort", + "value": "PLa" + }, + { + "name": "shortQuery", + "value": "undefined" + }, + { + "name": "noDescription", + "value": "undefined" + }, + { + "name": "noQueryURLs", + "value": "undefined" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901825311, + "variants": [ + { + "environment": { + "locales": [ + "de", + "dsb", + "hsb", + "rm" + ] + } + } + ], + "identifier": "leo_ende_de", + "recordType": "engine", + "id": "2669ab65-3d93-4432-a960-90413be80ae0", + "last_modified": 1702906502297 + }, + { + "base": { + "name": "พจนานุกรม ลองดู", + "urls": { + "search": { + "base": "https://dict.longdo.org/", + "params": [ + { + "name": "src", + "value": "moz" + } + ], + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://search.longdo.com/Suggest/HeadSearch", + "params": [ + { + "name": "ds", + "value": "head" + }, + { + "name": "fxjson", + "value": "1" + } + ], + "searchTermParamName": "key" + } + }, + "classification": "unknown" + }, + "schema": 1702901825919, + "variants": [ + { + "environment": { + "locales": [ + "th" + ] + } + } + ], + "identifier": "longdo", + "recordType": "engine", + "id": "fcc54178-e432-4d2b-820e-50f389bfb396", + "last_modified": 1702906502295 + }, + { + "base": { + "name": "Mapy.cz", + "urls": { + "search": { + "base": "https://www.mapy.cz/", + "params": [ + { + "name": "sourceid", + "value": "Searchmodule_3" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901826526, + "variants": [ + { + "environment": { + "locales": [ + "cs" + ] + } + } + ], + "identifier": "mapy-cz", + "recordType": "engine", + "id": "1ad708f0-5736-4f65-aeb4-31e5eca1c598", + "last_modified": 1702906502292 + }, + { + "base": { + "name": "MercadoLibre Argentina", + "urls": { + "search": { + "base": "https://www.mercadolibre.com.ar/jm/search", + "searchTermParamName": "as_word" + } + }, + "classification": "unknown" + }, + "schema": 1702901827142, + "variants": [ + { + "environment": { + "locales": [ + "es-AR" + ] + } + } + ], + "identifier": "mercadolibre", + "recordType": "engine", + "id": "8111d157-e064-40fa-993d-e1d972534754", + "last_modified": 1702906502289 + }, + { + "base": { + "name": "MercadoLibre Chile", + "urls": { + "search": { + "base": "https://www.mercadolibre.cl/jm/search", + "searchTermParamName": "as_word" + } + }, + "classification": "unknown" + }, + "schema": 1702901827757, + "variants": [ + { + "environment": { + "locales": [ + "es-CL" + ] + } + } + ], + "identifier": "mercadolibre-cl", + "recordType": "engine", + "id": "5659791c-6637-4ba9-b46b-83f16df084cd", + "last_modified": 1702906502286 + }, + { + "base": { + "name": "MercadoLibre Mexico", + "urls": { + "search": { + "base": "https://www.mercadolibre.com.mx/jm/search", + "searchTermParamName": "as_word" + } + }, + "classification": "unknown" + }, + "schema": 1702901828372, + "variants": [ + { + "environment": { + "locales": [ + "es-MX" + ] + } + } + ], + "identifier": "mercadolibre-mx", + "recordType": "engine", + "id": "162c9f26-f269-4b8e-b75f-01cf3c15e177", + "last_modified": 1702906502283 + }, + { + "base": { + "name": "MercadoLivre", + "urls": { + "search": { + "base": "https://www.mercadolivre.com.br/jm/search", + "searchTermParamName": "as_word" + } + }, + "classification": "unknown" + }, + "schema": 1702901829001, + "variants": [ + { + "environment": { + "locales": [ + "pt-BR" + ] + } + } + ], + "identifier": "mercadolivre", + "recordType": "engine", + "id": "62453dfa-0d82-4961-b6e2-241647aa04b6", + "last_modified": 1702906502281 + }, + { + "base": { + "name": "네이버", + "urls": { + "search": { + "base": "https://search.naver.com/search.naver", + "params": [ + { + "name": "where", + "value": "nexearch" + }, + { + "name": "frm", + "value": "ff" + }, + { + "name": "sm", + "value": "oss" + }, + { + "name": "ie", + "value": "utf8" + } + ], + "searchTermParamName": "query" + }, + "suggestions": { + "base": "https://ac.search.naver.com/nx/ac", + "params": [ + { + "name": "of", + "value": "os" + }, + { + "name": "ie", + "value": "utf-8" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901829625, + "variants": [ + { + "environment": { + "locales": [ + "ko" + ] + } + } + ], + "identifier": "naver-kr", + "recordType": "engine", + "id": "88d22607-619b-4c7a-8708-bb0caef15e18", + "last_modified": 1702906502278 + }, + { + "base": { + "name": "Odpiralni Časi", + "urls": { + "search": { + "base": "https://www.odpiralnicasi.com/spots", + "params": [ + { + "name": "source", + "value": "1" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901830238, + "variants": [ + { + "environment": { + "locales": [ + "sl" + ] + } + } + ], + "identifier": "odpiralni", + "recordType": "engine", + "id": "2039744c-adce-459b-a547-e3dba931c2a0", + "last_modified": 1702906502275 + }, + { + "base": { + "name": "Pazaruvaj", + "urls": { + "search": { + "base": "https://www.pazaruvaj.com/CategorySearch.php", + "searchTermParamName": "st" + } + }, + "classification": "unknown" + }, + "schema": 1702901830856, + "variants": [ + { + "environment": { + "locales": [ + "bg" + ] + } + } + ], + "identifier": "pazaruvaj", + "recordType": "engine", + "id": "ddf4875a-61d4-4677-8aad-74fb3ef2e1df", + "last_modified": 1702906502272 + }, + { + "base": { + "name": "Priberam", + "urls": { + "search": { + "base": "https://www.priberam.pt/dlpo/firefox.aspx", + "searchTermParamName": "pal" + } + }, + "charset": "ISO-8859-15", + "classification": "unknown" + }, + "schema": 1702901831479, + "variants": [ + { + "environment": { + "locales": [ + "pt-PT" + ] + } + } + ], + "identifier": "priberam", + "recordType": "engine", + "id": "4bc3282e-f318-443c-8b2f-c144205657d4", + "last_modified": 1702906502270 + }, + { + "base": { + "name": "Prisjakt", + "urls": { + "search": { + "base": "https://www.prisjakt.nu/search", + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://www.prisjakt.nu/plugins/opensearch/suggestions.php", + "searchTermParamName": "search" + } + }, + "classification": "unknown" + }, + "schema": 1702901832077, + "variants": [ + { + "environment": { + "locales": [ + "sv-SE" + ] + } + } + ], + "identifier": "prisjakt-sv-SE", + "recordType": "engine", + "id": "e4f4c143-7b0a-4ae5-b461-cd22da9da90a", + "last_modified": 1702906502267 + }, + { + "base": { + "name": "Readmoo 讀墨電子書", + "urls": { + "search": { + "base": "https://readmoo.com/search/keyword", + "params": [ + { + "name": "pi", + "value": "0" + }, + { + "name": "st", + "value": "true" + } + ], + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901832677, + "variants": [ + { + "environment": { + "locales": [ + "zh-TW" + ] + } + } + ], + "identifier": "readmoo", + "recordType": "engine", + "id": "f1570611-5dfa-4028-851c-fafaa8324bbd", + "last_modified": 1702906502264 + }, + { + "base": { + "name": "Salidzini.lv", + "urls": { + "search": { + "base": "https://www.salidzini.lv/search.php", + "params": [ + { + "name": "utm_source", + "value": "firefox-plugin" + } + ], + "searchTermParamName": "q" + }, + "suggestions": { + "base": "https://www.salidzini.lv/search_suggest_opensearch.php", + "searchTermParamName": "q" + } + }, + "classification": "unknown" + }, + "schema": 1702901833283, + "variants": [ + { + "environment": { + "locales": [ + "ltg", + "lv" + ] + } + } + ], + "identifier": "salidzinilv", + "recordType": "engine", + "id": "883c6f10-cac4-4dc0-b631-9a28cbadda49", + "last_modified": 1702906502262 + }, + { + "base": { + "name": "Tyda.se", + "urls": { + "search": { + "base": "https://tyda.se/", + "searchTermParamName": "w" + } + }, + "classification": "unknown" + }, + "schema": 1702901833893, + "variants": [ + { + "environment": { + "locales": [ + "sv-SE" + ] + } + } + ], + "identifier": "tyda-sv-SE", + "recordType": "engine", + "id": "511f0050-e322-456f-9f15-acafef69c898", + "last_modified": 1702906502259 + }, + { + "base": { + "name": "Vatera.hu", + "urls": { + "search": { + "base": "https://www.vatera.hu/listings/index.php", + "params": [ + { + "name": "c", + "value": "0" + }, + { + "name": "td", + "value": "on" + } + ], + "searchTermParamName": "q" + } + }, + "charset": "ISO-8859-2", + "classification": "unknown" + }, + "schema": 1702901834503, + "variants": [ + { + "environment": { + "locales": [ + "hu" + ] + } + } + ], + "identifier": "vatera", + "recordType": "engine", + "id": "ede75b07-cdc0-485b-8a9c-8282f15c4ede", + "last_modified": 1702906502256 + }, + { + "base": { + "name": "విక్షనరీ (te)", + "urls": { + "search": { + "base": "https://te.wiktionary.org/wiki/ప్రత్యేక:అన్వేషణ", + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://te.wiktionary.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + }, + { + "name": "namespace", + "value": "0" + } + ], + "searchTermParamName": "search" + } + }, + "classification": "unknown" + }, + "schema": 1702901835109, + "variants": [ + { + "environment": { + "locales": [ + "te" + ] + } + } + ], + "identifier": "wiktionary-te", + "recordType": "engine", + "id": "45b4556e-8133-40c8-9a9b-63bc3ad91f53", + "last_modified": 1702906502253 + }, + { + "base": { + "name": "Wikiccionari (oc)", + "urls": { + "search": { + "base": "https://oc.wiktionary.org/wiki/Especial:Recèrca", + "searchTermParamName": "search" + }, + "suggestions": { + "base": "https://oc.wiktionary.org/w/api.php", + "params": [ + { + "name": "action", + "value": "opensearch" + }, + { + "name": "namespace", + "value": "0" + } + ], + "searchTermParamName": "search" + } + }, + "classification": "unknown" + }, + "schema": 1702901835738, + "variants": [ + { + "environment": { + "locales": [ + "oc" + ] + } + } + ], + "identifier": "wiktionary", + "recordType": "engine", + "id": "7c9d2fc3-1e6b-40f6-80ad-080bd94fe24b", + "last_modified": 1702906502251 + }, + { + "base": { + "name": "Yahoo! JAPAN", + "urls": { + "search": { + "base": "https://search.yahoo.co.jp/search", + "params": [ + { + "name": "ei", + "value": "UTF-8" + }, + { + "name": "fr", + "value": "{partnerCode}" + } + ], + "searchTermParamName": "p" + } + }, + "partnerCode": "mozff", + "classification": "general" + }, + "schema": 1702901836360, + "variants": [ + { + "environment": { + "locales": [ + "ja", + "ja-JP-macos" + ] + } + } + ], + "identifier": "yahoo-jp", + "recordType": "engine", + "id": "d45d4cf0-6ce3-4419-a160-ae2ada64c3e8", + "last_modified": 1702906502248 + }, + { + "base": { + "name": "Yahoo!オークション", + "urls": { + "search": { + "base": "https://auctions.yahoo.co.jp/search/search", + "params": [ + { + "name": "ei", + "value": "EUC-JP" + }, + { + "name": "fr", + "value": "mozff" + } + ], + "searchTermParamName": "p" + } + }, + "charset": "EUC-JP", + "classification": "unknown" + }, + "schema": 1702901836972, + "variants": [ + { + "environment": { + "locales": [ + "ja", + "ja-JP-macos" + ] + } + } + ], + "identifier": "yahoo-jp-auctions", + "recordType": "engine", + "id": "3d422460-2ea0-43df-b10d-2e8c9e42462d", + "last_modified": 1702906502244 + }, + { + "schema": 1702901837584, + "recordType": "defaultEngines", + "globalDefault": "google", + "specificDefaults": [ + { + "default": "baidu", + "environment": { + "locales": [ + "zh-CN" + ], + "regions": [ + "cn" + ] + } + }, + { + "default": "baidu", + "environment": { + "distributions": [ + "MozillaOnline" + ] + } + }, + { + "default": "1und1", + "environment": { + "distributions": [ + "1und1" + ] + } + }, + { + "default": "webde", + "environment": { + "distributions": [ + "webde" + ] + } + }, + { + "default": "mailcom", + "environment": { + "distributions": [ + "mail.com" + ] + } + }, + { + "default": "qwant", + "environment": { + "distributions": [ + "qwant-001", + "qwant-002" + ] + } + }, + { + "default": "gmx-de", + "environment": { + "distributions": [ + "gmx" + ] + } + }, + { + "default": "gmx-en-GB", + "environment": { + "distributions": [ + "gmxcouk" + ] + } + }, + { + "default": "gmx-es", + "environment": { + "distributions": [ + "gmxes" + ] + } + }, + { + "default": "gmx-fr", + "environment": { + "distributions": [ + "gmxfr" + ] + } + } + ], + "id": "f3891684-2348-4e7a-9765-0c5d2d0ab1b9", + "last_modified": 1702906502241 + } + ], + "timestamp": 1710766863306 +} diff --git a/services/settings/dumps/main/search-config.json b/services/settings/dumps/main/search-config.json new file mode 100644 index 0000000000..e01247d81f --- /dev/null +++ b/services/settings/dumps/main/search-config.json @@ -0,0 +1,2400 @@ +{ + "data": [ + { + "schema": 1710460807412, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "fr" + ] + } + } + }, + { + "included": { + "regions": [ + "be", + "ch", + "es", + "fr", + "it", + "nl" + ] + }, + "application": { + "minVersion": "124.0" + } + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefoxqwant" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "default": "yes", + "override": true, + "orderHint": 2000, + "application": { + "distributions": [ + "qwant-001", + "qwant-002" + ] + } + } + ], + "webExtension": { + "id": "qwant@search.mozilla.org" + }, + "id": "f59fddbc-580b-4672-9cda-32941ceec729", + "last_modified": 1710766850143 + }, + { + "schema": 1707831978005, + "appliesTo": [ + { + "included": { + "regions": [ + "jp" + ] + }, + "telemetryId": "amazon-jp", + "webExtension": { + "locales": [ + "jp" + ] + } + } + ], + "webExtension": { + "id": "amazon@search.mozilla.org" + }, + "id": "a620b506-c3ae-4332-97bb-190a73868580", + "last_modified": 1707833207283 + }, + { + "schema": 1707824831376, + "appliesTo": [ + { + "included": { + "regions": [ + "us" + ] + }, + "application": { + "minVersion": "89.0a1" + }, + "telemetryId": "amazondotcom-us", + "webExtension": { + "locales": [ + "us" + ] + } + }, + { + "included": { + "regions": [ + "us" + ] + }, + "application": { + "maxVersion": "89.0a1" + } + } + ], + "telemetryId": "amazondotcom", + "webExtension": { + "id": "amazondotcom@search.mozilla.org" + }, + "id": "071c671c-7c1b-469d-a771-ab16ff6e8beb", + "last_modified": 1707833207280 + }, + { + "urls": { + "trending": { + "fullPath": "https://www.bing.com/osjson.aspx" + } + }, + "params": { + "searchUrlGetParams": [ + { + "name": "pc", + "value": "MOZI" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "schema": 1706313607068, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ach", + "af", + "an", + "ar", + "ast", + "az", + "bs", + "ca", + "ca-valencia", + "cak", + "cs", + "cy", + "da", + "de", + "dsb", + "el", + "eo", + "es-CL", + "es-ES", + "es-MX", + "eu", + "fa", + "ff", + "fi", + "fr", + "fur", + "fy-NL", + "gd", + "gl", + "gn", + "gu-IN", + "he", + "hi-IN", + "hr", + "hsb", + "hy-AM", + "ia", + "id", + "is", + "it", + "ja-JP-macos", + "ja", + "ka", + "kab", + "km", + "kn", + "lij", + "lo", + "lt", + "meh", + "mk", + "ms", + "my", + "nb-NO", + "ne-NP", + "nl", + "nn-NO", + "oc", + "pa-IN", + "pt-BR", + "rm", + "ro", + "sc", + "sco", + "son", + "sq", + "sr", + "sv-SE", + "te", + "th", + "tl", + "tr", + "trs", + "uk", + "ur", + "uz", + "wo", + "xh", + "zh-CN" + ], + "startsWith": [ + "bn", + "en" + ] + } + } + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "pc", + "value": "MOZR" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "included": { + "locales": { + "matches": [ + "ach", + "af", + "an", + "ar", + "ast", + "az", + "bs", + "ca", + "ca-valencia", + "cak", + "cs", + "cy", + "da", + "de", + "dsb", + "el", + "eo", + "es-CL", + "es-ES", + "es-MX", + "eu", + "fa", + "ff", + "fi", + "fr", + "fur", + "fy-NL", + "gd", + "gl", + "gn", + "gu-IN", + "he", + "hi-IN", + "hr", + "hsb", + "hy-AM", + "ia", + "id", + "is", + "it", + "ja-JP-macos", + "ja", + "ka", + "kab", + "km", + "kn", + "lij", + "lo", + "lt", + "meh", + "mk", + "ms", + "my", + "nb-NO", + "ne-NP", + "nl", + "nn-NO", + "oc", + "pa-IN", + "pt-BR", + "rm", + "ro", + "sc", + "sco", + "son", + "sq", + "sr", + "sv-SE", + "te", + "th", + "tl", + "tr", + "trs", + "uk", + "ur", + "uz", + "wo", + "xh", + "zh-CN" + ], + "startsWith": [ + "bn", + "en" + ] + } + }, + "application": { + "channel": [ + "esr" + ] + }, + "telemetryId": "bing-esr" + }, + { + "override": true, + "orderHint": 2500, + "application": { + "distributions": [ + "MozillaOnline" + ] + } + } + ], + "webExtension": { + "id": "bing@search.mozilla.org" + }, + "id": "7ec766f6-639a-4618-91bc-33eb3d4378c6", + "last_modified": 1707231832220 + }, + { + "schema": 1701806001667, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "cs" + ] + } + } + } + ], + "webExtension": { + "id": "seznam-cz@search.mozilla.org" + }, + "id": "c50dcc87-0192-4461-bb88-17a55ba181c7", + "last_modified": 1701806851414 + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "wd", + "value": "{searchTerms}" + } + ], + "suggestUrlGetParams": [ + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "action", + "value": "opensearch" + }, + { + "name": "wd", + "value": "{searchTerms}" + } + ] + }, + "schema": 1701806000003, + "appliesTo": [ + { + "params": { + "searchUrlGetParams": [ + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "wd", + "value": "{searchTerms}" + } + ], + "suggestUrlGetParams": [ + { + "name": "ie", + "value": "utf-8" + }, + { + "name": "action", + "value": "opensearch" + }, + { + "name": "wd", + "value": "{searchTerms}" + } + ] + }, + "default": "yes", + "override": true, + "orderHint": 3000, + "application": { + "distributions": [ + "MozillaOnline" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "zh-CN" + ] + } + } + }, + { + "default": "yes", + "included": { + "locales": { + "matches": [ + "zh-CN" + ] + }, + "regions": [ + "cn" + ] + } + } + ], + "telemetryId": "baidu", + "webExtension": { + "id": "baidu@search.mozilla.org" + }, + "id": "52dd490a-89e8-45f2-96b0-f2c72e157526", + "last_modified": 1701806851411 + }, + { + "urls": { + "trending": { + "query": "client=firefox&channel=ftr&q={searchTerms}", + "fullPath": "https://www.google.com/complete/search" + } + }, + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-d" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "schema": 1687361570970, + "appliesTo": [ + { + "default": "yes-if-no-other", + "excluded": { + "regions": [ + "ru", + "tr", + "by", + "kz" + ] + }, + "included": { + "everywhere": true + } + }, + { + "override": true, + "orderHint": 2000, + "application": { + "distributions": [ + "MozillaOnline" + ] + } + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-e" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "included": { + "everywhere": true + }, + "application": { + "channel": [ + "esr" + ] + }, + "telemetryId": "google-b-e" + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "default": "yes", + "included": { + "regions": [ + "ru", + "tr", + "by", + "kz" + ] + }, + "telemetryId": "google-com-nocodes" + }, + { + "default": "no", + "included": { + "locales": { + "matches": [ + "zh-CN" + ] + }, + "regions": [ + "cn" + ] + } + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-e" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "default": "no", + "included": { + "locales": { + "matches": [ + "zh-CN" + ] + }, + "regions": [ + "cn" + ] + }, + "application": { + "channel": [ + "esr" + ] + } + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-1-d" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "included": { + "regions": [ + "us" + ] + }, + "extraParams": [ + { + "name": "channel", + "pref": "google_channel_us", + "condition": "pref" + } + ], + "telemetryId": "google-b-1-d", + "suggestExtraParams": [ + { + "name": "channel", + "pref": "search_rich_suggestions", + "condition": "pref" + } + ] + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-1-e" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "included": { + "regions": [ + "us" + ] + }, + "application": { + "channel": [ + "esr" + ] + }, + "extraParams": [ + { + "name": "channel", + "pref": "google_channel_us", + "condition": "pref" + } + ], + "telemetryId": "google-b-1-e" + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "ubuntu" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "override": true, + "application": { + "distributions": [ + "canonical", + "canonical-001" + ] + }, + "extraParams": [ + { + "name": "channel", + "value": "fs" + } + ], + "telemetryId": "google-canonical" + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "ubuntu-sn" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "override": true, + "application": { + "distributions": [ + "canonical-002" + ] + }, + "extraParams": [ + { + "name": "channel", + "value": "fs" + } + ], + "telemetryId": "google-ubuntu-sn" + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-lm" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "override": true, + "application": { + "distributions": [ + "mint-001" + ] + }, + "telemetryId": "google-b-lm" + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "client", + "value": "firefox-b-1-lm" + }, + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "included": { + "regions": [ + "us" + ] + }, + "override": true, + "application": { + "distributions": [ + "mint-001" + ] + }, + "telemetryId": "google-b-1-lm" + } + ], + "extraParams": [ + { + "name": "channel", + "pref": "google_channel_row", + "condition": "pref" + } + ], + "telemetryId": "google-b-d", + "webExtension": { + "id": "google@search.mozilla.org" + }, + "suggestExtraParams": [ + { + "name": "channel", + "pref": "search_rich_suggestions", + "condition": "pref" + } + ], + "id": "cb8e7210-9f0b-48fa-8708-b9a03df79eea", + "last_modified": 1687526306160 + }, + { + "params": { + "searchUrlGetParams": [ + { + "name": "q", + "value": "{searchTerms}" + } + ] + }, + "schema": 1685563823313, + "appliesTo": [ + { + "included": { + "everywhere": true + } + }, + { + "included": { + "everywhere": true + }, + "application": { + "channel": [ + "esr" + ] + }, + "extraParams": [ + { + "name": "t", + "value": "ftsa", + "purpose": "searchbar", + "condition": "purpose" + } + ], + "telemetryId": "ddg-esr" + }, + { + "override": true, + "application": { + "distributions": [ + "mint-001" + ] + }, + "extraParams": [ + { + "name": "t", + "value": "lm", + "purpose": "searchbar", + "condition": "purpose" + } + ] + } + ], + "extraParams": [ + { + "name": "t", + "value": "ffab", + "purpose": "searchbar", + "condition": "purpose" + } + ], + "telemetryId": "ddg", + "webExtension": { + "id": "ddg@search.mozilla.org" + }, + "id": "c0b26c0e-63e6-4235-b2ce-5f16b6a8bf87", + "last_modified": 1685625615515 + }, + { + "schema": 1674842123977, + "appliesTo": [ + { + "included": { + "everywhere": true + } + }, + { + "included": { + "locales": { + "matches": [ + "af", + "an", + "ar", + "ast", + "az", + "bg", + "br", + "bs", + "cy", + "da", + "de", + "dsb", + "el", + "eo", + "et", + "eu", + "fa", + "fi", + "fy-NL", + "ga-IE", + "gd", + "gl", + "gn", + "he", + "hr", + "hsb", + "hu", + "ia", + "id", + "is", + "it", + "ka", + "kab", + "kk", + "km", + "kn", + "lij", + "lo", + "lt", + "ltg", + "lv", + "mk", + "mr", + "ms", + "my", + "nl", + "oc", + "pl", + "rm", + "ro", + "ru", + "si", + "sk", + "sl", + "sq", + "sr", + "sv-SE", + "ta", + "te", + "th", + "tl", + "tr", + "uk", + "ur", + "uz", + "vi", + "wo", + "zh-CN", + "zh-TW" + ] + } + }, + "webExtension": { + "locales": [ + "$USER_LOCALE" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "be" + ] + } + }, + "webExtension": { + "locales": [ + "be", + "be-tarask" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "bn", + "bn-BD", + "bn-IN" + ] + } + }, + "webExtension": { + "locales": [ + "bn" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ca", + "ca-valencia" + ] + } + }, + "webExtension": { + "locales": [ + "ca" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "cak", + "es-AR", + "es-CL", + "es-ES", + "es-MX", + "trs" + ] + } + }, + "webExtension": { + "locales": [ + "es" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "cs" + ] + } + }, + "webExtension": { + "locales": [ + "cz" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ff", + "fr", + "son" + ] + } + }, + "webExtension": { + "locales": [ + "fr" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "gu-IN" + ] + } + }, + "webExtension": { + "locales": [ + "gu" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "hi-IN" + ] + } + }, + "webExtension": { + "locales": [ + "hi" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "hy-AM" + ] + } + }, + "webExtension": { + "locales": [ + "hy" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "fur", + "sc" + ] + } + }, + "webExtension": { + "locales": [ + "it" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ja-JP-macos", + "ja" + ] + } + }, + "webExtension": { + "locales": [ + "ja" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ko" + ] + } + }, + "webExtension": { + "locales": [ + "kr" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "nb-NO" + ] + } + }, + "webExtension": { + "locales": [ + "NO" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ne-NP" + ] + } + }, + "webExtension": { + "locales": [ + "ne" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "nn-NO" + ] + } + }, + "webExtension": { + "locales": [ + "NN" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "pa-IN" + ] + } + }, + "webExtension": { + "locales": [ + "pa" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "pt-BR", + "pt-PT" + ] + } + }, + "webExtension": { + "locales": [ + "pt" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "szl" + ] + } + }, + "webExtension": { + "locales": [ + "pl" + ] + } + }, + { + "override": true, + "orderHint": 1000, + "application": { + "distributions": [ + "MozillaOnline" + ] + } + } + ], + "webExtension": { + "id": "wikipedia@search.mozilla.org" + }, + "id": "3f3beb1d-e32e-40a4-b6ed-56741803e1d8", + "last_modified": 1675353179510 + }, + { + "schema": 1674842117992, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "en-US" + ] + }, + "regions": [ + "us" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "an", + "ast", + "ca", + "ca-valencia", + "es-ES", + "eu", + "gl" + ] + } + }, + "telemetryId": "ebay-es", + "webExtension": { + "locales": [ + "es" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "br", + "fr", + "wo" + ] + } + }, + "telemetryId": "ebay-fr", + "webExtension": { + "locales": [ + "fr" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "br", + "en-US", + "fr", + "wo" + ] + }, + "regions": [ + "be" + ] + }, + "telemetryId": "ebay-be", + "webExtension": { + "locales": [ + "be" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "br", + "en-US", + "fr", + "wo" + ] + }, + "regions": [ + "ca" + ] + }, + "telemetryId": "ebay-ca", + "webExtension": { + "locales": [ + "ca" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "br", + "en-US", + "fr", + "wo" + ] + }, + "regions": [ + "ch" + ] + }, + "telemetryId": "ebay-ch", + "webExtension": { + "locales": [ + "ch" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "cy", + "en-GB", + "gd" + ] + } + }, + "telemetryId": "ebay-uk", + "webExtension": { + "locales": [ + "uk" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "sco" + ] + }, + "regions": [ + "gb" + ] + }, + "telemetryId": "ebay-uk", + "webExtension": { + "locales": [ + "uk" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "en-US" + ] + }, + "regions": [ + "gb" + ] + }, + "telemetryId": "ebay-uk", + "webExtension": { + "locales": [ + "uk" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "cy", + "en-GB", + "en-US", + "gd" + ] + }, + "regions": [ + "au" + ] + }, + "telemetryId": "ebay-au", + "webExtension": { + "locales": [ + "au" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "cy", + "en-GB", + "en-US", + "gd" + ] + }, + "regions": [ + "ie" + ] + }, + "telemetryId": "ebay-ie", + "webExtension": { + "locales": [ + "ie" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "de", + "dsb", + "hsb" + ] + } + }, + "telemetryId": "ebay-de", + "webExtension": { + "locales": [ + "de" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "de", + "dsb", + "hsb" + ] + }, + "regions": [ + "at" + ] + }, + "telemetryId": "ebay-at", + "webExtension": { + "locales": [ + "at" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "de", + "dsb", + "hsb" + ] + }, + "regions": [ + "ch" + ] + }, + "telemetryId": "ebay-ch", + "webExtension": { + "locales": [ + "ch" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "en-CA" + ] + } + }, + "telemetryId": "ebay-ca", + "webExtension": { + "locales": [ + "ca" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "fy-NL", + "nl" + ] + } + }, + "telemetryId": "ebay-nl", + "webExtension": { + "locales": [ + "nl" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "en-US" + ] + }, + "regions": [ + "nl" + ] + }, + "telemetryId": "ebay-nl", + "webExtension": { + "locales": [ + "nl" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "fy-NL", + "nl" + ] + }, + "regions": [ + "be" + ] + }, + "telemetryId": "ebay-be", + "webExtension": { + "locales": [ + "be" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "ga-IE" + ] + } + }, + "telemetryId": "ebay-ie", + "webExtension": { + "locales": [ + "ie" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "fur", + "it", + "lij", + "sc" + ] + } + }, + "telemetryId": "ebay-it", + "webExtension": { + "locales": [ + "it" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "rm" + ] + } + }, + "telemetryId": "ebay-ch", + "webExtension": { + "locales": [ + "ch" + ] + } + } + ], + "webExtension": { + "id": "ebay@search.mozilla.org" + }, + "id": "cbe309e0-f638-4996-9dfc-ea5c19ef16e9", + "last_modified": 1675353179507 + }, + { + "schema": 1613395976252, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "pl", + "szl" + ] + } + } + } + ], + "webExtension": { + "id": "wolnelektury-pl@search.mozilla.org" + }, + "id": "1960ccff-4aab-48cd-9bf5-0eb2adde76f9", + "last_modified": 1613587855073 + }, + { + "schema": 1613395970632, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "pl", + "szl" + ] + } + } + } + ], + "webExtension": { + "id": "allegro-pl@search.mozilla.org" + }, + "id": "d915b6bc-bbae-47a6-8323-1b2990ad0e57", + "last_modified": 1613587855067 + }, + { + "schema": 1596483697391, + "appliesTo": [ + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "gmx" + ] + }, + "webExtension": { + "locales": [ + "de", + "shopping" + ] + } + }, + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "gmxcouk" + ] + }, + "webExtension": { + "locales": [ + "en-GB" + ] + } + }, + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "gmxes" + ] + }, + "webExtension": { + "locales": [ + "es" + ] + } + }, + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "gmxfr" + ] + }, + "webExtension": { + "locales": [ + "fr" + ] + } + } + ], + "webExtension": { + "id": "gmx@search.mozilla.org" + }, + "id": "1571e5fc-17e4-492b-b6d2-106d8669b6c8", + "last_modified": 1597327602800 + }, + { + "schema": 1588177655532, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "fr" + ] + } + }, + "orderHint": 1500, + "application": { + "distributions": [ + "qwant-001", + "qwant-002" + ] + } + } + ], + "webExtension": { + "id": "qwantjr@search.mozilla.org" + }, + "id": "f59fddbc-580b-4672-9cda-32941ceec730", + "last_modified": 1589299342073 + }, + { + "schema": 1588177648193, + "appliesTo": [ + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "1und1" + ] + } + } + ], + "webExtension": { + "id": "1und1@search.mozilla.org" + }, + "id": "e98c663d-408d-4901-9666-66e67a3084b4", + "last_modified": 1589299342018 + }, + { + "schema": 1588177646165, + "appliesTo": [ + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "webde" + ] + } + } + ], + "webExtension": { + "id": "webde@search.mozilla.org" + }, + "id": "613d1391-4be5-4296-80e2-cfb740de6607", + "last_modified": 1589299342015 + }, + { + "schema": 1588016493937, + "appliesTo": [ + { + "default": "yes", + "included": { + "everywhere": true + }, + "application": { + "distributions": [ + "mail.com" + ] + } + } + ], + "webExtension": { + "id": "mailcom@search.mozilla.org" + }, + "id": "62d232d5-304e-4c35-a4da-46626a9b54c2", + "last_modified": 1589299342012 + }, + { + "schema": 1586273556552, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ja-JP-macos", + "ja" + ] + } + } + } + ], + "webExtension": { + "id": "rakuten@search.mozilla.org" + }, + "id": "8551c432-c68d-4934-836e-98513ae4b4a5", + "last_modified": 1586375636481 + }, + { + "schema": 1582890773480, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "az" + ] + } + } + } + ], + "webExtension": { + "id": "azerdict@search.mozilla.org" + }, + "id": "c1a6845f-015e-4e67-bc64-6e39a843643f", + "last_modified": 1583937832363 + }, + { + "schema": 1582890776067, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "nb-NO", + "nn-NO" + ] + } + } + } + ], + "webExtension": { + "id": "bok-NO@search.mozilla.org" + }, + "id": "64ce88a6-fd55-48d1-85a9-724c753f5721", + "last_modified": 1583937832356 + }, + { + "schema": 1582890777775, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "sl" + ] + } + } + } + ], + "webExtension": { + "id": "ceneji@search.mozilla.org" + }, + "id": "d908d622-0387-4d36-8098-1a9e32c0c697", + "last_modified": 1583937832352 + }, + { + "schema": 1582890779439, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "vi" + ] + } + } + } + ], + "webExtension": { + "id": "coccoc@search.mozilla.org" + }, + "id": "4e447f5a-9eb1-436e-8de1-9e4b4b709efb", + "last_modified": 1583937832348 + }, + { + "schema": 1582890780276, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ko" + ] + } + } + } + ], + "webExtension": { + "id": "daum-kr@search.mozilla.org" + }, + "id": "0cdc44be-173a-4ca8-96e3-43e3000da75d", + "last_modified": 1583937832346 + }, + { + "schema": 1582890782823, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "de" + ] + } + } + } + ], + "webExtension": { + "id": "ecosia@search.mozilla.org" + }, + "id": "1fc1b257-3e6b-4cfb-a323-64c1429bcf51", + "last_modified": 1583937832339 + }, + { + "schema": 1582890784534, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "hr" + ] + } + } + } + ], + "webExtension": { + "id": "eudict@search.mozilla.org" + }, + "id": "5cd4781c-2906-4fe7-9d4b-ea9ddbdc2c3b", + "last_modified": 1583937832334 + }, + { + "schema": 1582890785390, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "gd" + ] + } + } + } + ], + "webExtension": { + "id": "faclair-beag@search.mozilla.org" + }, + "id": "6491a2b8-c487-4798-a817-a0fd08b40ee2", + "last_modified": 1583937832332 + }, + { + "schema": 1582890787961, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "nn-NO", + "nb-NO" + ] + } + } + } + ], + "webExtension": { + "id": "gulesider-NO@search.mozilla.org" + }, + "id": "f458e78b-9128-4027-b344-538f5661c148", + "last_modified": 1583937832325 + }, + { + "schema": 1582890791315, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "de", + "dsb", + "hsb", + "rm" + ] + } + } + } + ], + "webExtension": { + "id": "leo_ende_de@search.mozilla.org" + }, + "id": "03d60939-2c92-45b0-874f-11c8e6d0c2b9", + "last_modified": 1583937832316 + }, + { + "schema": 1582890793008, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "th" + ] + } + } + } + ], + "webExtension": { + "id": "longdo@search.mozilla.org" + }, + "id": "8b4f2887-0c24-49ce-b467-2ba8cc9a4945", + "last_modified": 1583937832311 + }, + { + "schema": 1582890794722, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "cs" + ] + } + } + } + ], + "webExtension": { + "id": "mapy-cz@search.mozilla.org" + }, + "id": "9a404348-0998-42c4-9d87-45ef47672c95", + "last_modified": 1583937832306 + }, + { + "schema": 1582890796393, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "es-AR" + ] + } + }, + "webExtension": { + "locales": [ + "ar" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "es-CL" + ] + } + }, + "webExtension": { + "locales": [ + "cl" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "es-MX" + ] + } + }, + "webExtension": { + "locales": [ + "mx" + ] + } + } + ], + "webExtension": { + "id": "mercadolibre@search.mozilla.org" + }, + "id": "df070348-e771-4bd5-964e-d19d82c1384e", + "last_modified": 1583937832302 + }, + { + "schema": 1582890797236, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "pt-BR" + ] + } + } + } + ], + "webExtension": { + "id": "mercadolivre@search.mozilla.org" + }, + "id": "53bf6974-0e60-4551-994f-2d5176a5d1c6", + "last_modified": 1583937832300 + }, + { + "schema": 1582890799757, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ko" + ] + } + } + } + ], + "webExtension": { + "id": "naver-kr@search.mozilla.org" + }, + "id": "4135dc8f-f0bf-475b-b39f-74c4256572af", + "last_modified": 1583937832292 + }, + { + "schema": 1582890801444, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "sl" + ] + } + } + } + ], + "webExtension": { + "id": "odpiralni@search.mozilla.org" + }, + "id": "fa42b23a-30c5-46f3-8997-4a84029b5349", + "last_modified": 1583937832287 + }, + { + "schema": 1582890806533, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "bg" + ] + } + } + } + ], + "webExtension": { + "id": "pazaruvaj@search.mozilla.org" + }, + "id": "acc27094-9f0a-49af-868b-8b6ecbbda976", + "last_modified": 1583937832267 + }, + { + "schema": 1582890808245, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "pt-PT" + ] + } + } + } + ], + "webExtension": { + "id": "priberam@search.mozilla.org" + }, + "id": "a3f1a5b6-cd8d-41b3-bb2b-de6bb1943a3d", + "last_modified": 1583937832261 + }, + { + "schema": 1582890809928, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "sv-SE" + ] + } + } + } + ], + "webExtension": { + "id": "prisjakt-sv-SE@search.mozilla.org" + }, + "id": "8a7de784-efa5-428f-9193-a0d3368b80d4", + "last_modified": 1583937832257 + }, + { + "schema": 1582890814195, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "zh-TW" + ] + } + } + } + ], + "webExtension": { + "id": "readmoo@search.mozilla.org" + }, + "id": "2d24515e-3e35-42ed-bf17-1388acfe80fa", + "last_modified": 1583937832245 + }, + { + "schema": 1582890815048, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ltg", + "lv" + ] + } + } + } + ], + "webExtension": { + "id": "salidzinilv@search.mozilla.org" + }, + "id": "e7dcde21-5b5e-4406-b602-0611130fd3a6", + "last_modified": 1583937832242 + }, + { + "schema": 1582890819290, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "sv-SE" + ] + } + } + } + ], + "webExtension": { + "id": "tyda-sv-SE@search.mozilla.org" + }, + "id": "32eec495-ce07-4ce5-8d07-4cb32050459d", + "last_modified": 1583937832229 + }, + { + "schema": 1582890820131, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "hu" + ] + } + } + } + ], + "webExtension": { + "id": "vatera@search.mozilla.org" + }, + "id": "d0eab7e7-9084-4a43-b845-568dbe9f07dc", + "last_modified": 1583937832225 + }, + { + "schema": 1582890820983, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "te" + ] + } + }, + "webExtension": { + "locales": [ + "te" + ] + } + }, + { + "included": { + "locales": { + "matches": [ + "oc" + ] + } + }, + "webExtension": { + "locales": [ + "oc" + ] + } + } + ], + "webExtension": { + "id": "wiktionary@search.mozilla.org" + }, + "id": "781f42f0-3a5c-4d14-8be2-665eefa65653", + "last_modified": 1583937832222 + }, + { + "schema": 1582890822671, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ja-JP-macos", + "ja" + ] + } + } + } + ], + "webExtension": { + "id": "yahoo-jp@search.mozilla.org" + }, + "id": "9e73a342-4e27-4e9f-8a29-be193d59236a", + "last_modified": 1583937832213 + }, + { + "schema": 1582890823520, + "appliesTo": [ + { + "included": { + "locales": { + "matches": [ + "ja-JP-macos", + "ja" + ] + } + } + } + ], + "webExtension": { + "id": "yahoo-jp-auctions@search.mozilla.org" + }, + "id": "43559b11-05dc-4750-b131-afdbd9d25905", + "last_modified": 1583937832210 + } + ], + "timestamp": 1710766850143 +} diff --git a/services/settings/dumps/main/search-default-override-allowlist.json b/services/settings/dumps/main/search-default-override-allowlist.json new file mode 100644 index 0000000000..f92ca735e5 --- /dev/null +++ b/services/settings/dumps/main/search-default-override-allowlist.json @@ -0,0 +1,67 @@ +{ + "data": [ + { + "urls": [ + { + "search_url": "https://www.qwant.com?q={searchTerms}&client=opensearch" + } + ], + "schema": 1710167605850, + "engineName": "Qwant", + "overridesId": "qwant@search.mozilla.org", + "thirdPartyId": "opensearch@search.mozilla.org", + "id": "fc297e76-6e20-48ba-8474-919e3e4380bf", + "last_modified": 1710168995103 + }, + { + "urls": [ + { + "search_url": "https://www.ecosia.org/search?q={searchTerms}&addon=opensearch" + } + ], + "schema": 1710167605249, + "engineName": "Ecosia", + "overridesId": "ecosia@search.mozilla.org", + "thirdPartyId": "opensearch@search.mozilla.org", + "id": "a5f64364-3fdf-426e-94cf-b499460a58c9", + "last_modified": 1710168995099 + }, + { + "urls": [ + { + "search_url": "https://www.qwant.com/?q={searchTerms}&client=ext-firefox-sb" + } + ], + "schema": 1710167604639, + "overridesId": "qwant@search.mozilla.org", + "thirdPartyId": "qwantcomforfirefox@jetpack", + "id": "b5b71917-3375-40d2-a538-3d07bfb989ac", + "last_modified": 1710168995095 + }, + { + "urls": [ + { + "search_url": "https://www.qwant.com/?q={searchTerms}&client=ext-firefox-sb" + } + ], + "schema": 1710115218582, + "overridesId": "qwant@search.mozilla.org", + "thirdPartyId": "qwant-search-firefox@qwant.com", + "id": "c5de2b3f-da15-4bc5-b36f-9555043b5ef4", + "last_modified": 1710168995091 + }, + { + "urls": [ + { + "search_url": "https://duckduckgo.com/?q={searchTerms}&t=newext" + } + ], + "schema": 1590435694495, + "overridesId": "ddg@search.mozilla.org", + "thirdPartyId": "jid1-ZAdIEUB7XOzOJw@jetpack", + "id": "c5de2b3f-da15-4bc5-b36f-9555043b5ef3", + "last_modified": 1595254618540 + } + ], + "timestamp": 1710168995103 +} diff --git a/services/settings/dumps/main/search-telemetry-v2.json b/services/settings/dumps/main/search-telemetry-v2.json new file mode 100644 index 0000000000..1803977187 --- /dev/null +++ b/services/settings/dumps/main/search-telemetry-v2.json @@ -0,0 +1,657 @@ +{ + "data": [ + { + "isSPA": true, + "schema": 1707523204491, + "components": [ + { + "type": "ad_image_row", + "included": { + "parent": { + "selector": "[data-testid='pam.container']" + }, + "children": [ + { + "selector": "[data-slide-index]", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "included": { + "parent": { + "selector": "[data-testid='adResult']" + } + } + }, + { + "type": "incontent_searchbox", + "topDown": true, + "included": { + "parent": { + "selector": "._1zdrb._1cR1n" + }, + "related": { + "selector": "#search-suggestions" + }, + "children": [ + { + "selector": "input[type='search']" + } + ] + } + }, + { + "type": "ad_link", + "default": true + } + ], + "taggedCodes": [ + "brz-moz", + "firefoxqwant" + ], + "telemetryId": "qwant", + "organicCodes": [], + "codeParamName": "client", + "queryParamName": "q", + "queryParamNames": [ + "q" + ], + "searchPageRegexp": "^https://www\\.qwant\\.com/", + "filter_expression": "env.version|versionCompare(\"124.0a1\")>=0", + "followOnParamNames": [], + "defaultPageQueryParam": { + "key": "t", + "value": "web" + }, + "extraAdServersRegexps": [ + "^https://www\\.bing\\.com/acli?c?k", + "^https://api\\.qwant\\.com/v3/r/" + ], + "id": "19c434a3-d173-4871-9743-290ac92a3f6b", + "last_modified": 1707833261849 + }, + { + "schema": 1705948294201, + "components": [ + { + "type": "ad_carousel", + "included": { + "parent": { + "selector": ".pla-exp-container" + }, + "related": { + "selector": "g-right-button, g-left-button, .exp-button" + }, + "children": [ + { + "selector": "[data-dtld]", + "countChildren": true + } + ] + } + }, + { + "type": "refined_search_buttons", + "topDown": true, + "included": { + "parent": { + "selector": "#appbar g-scrolling-carousel" + }, + "related": { + "selector": "g-right-button, g-left-button" + }, + "children": [ + { + "selector": "a" + } + ] + } + }, + { + "type": "ad_link", + "excluded": { + "parent": { + "selector": "#rhs" + } + }, + "included": { + "parent": { + "selector": "[data-text-ad='1']" + }, + "children": [ + { + "type": "ad_sitelink", + "selector": "[role='list']" + } + ] + } + }, + { + "type": "ad_sidebar", + "included": { + "parent": { + "selector": "#rhs" + }, + "children": [ + { + "selector": ".pla-unit, .mnr-c", + "countChildren": true + } + ] + } + }, + { + "type": "incontent_searchbox", + "topDown": true, + "included": { + "parent": { + "selector": "form[role='search']" + }, + "related": { + "selector": "div.logo + div + div" + }, + "children": [ + { + "selector": "input[type='text']" + }, + { + "selector": "textarea[name='q']" + } + ] + } + }, + { + "type": "ad_image_row", + "excluded": { + "parent": { + "selector": ".pla-exp-container" + } + }, + "included": { + "parent": { + "selector": ".top-pla-group-inner" + }, + "children": [ + { + "selector": "[data-dtld]", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "default": true + } + ], + "shoppingTab": { + "regexp": "&tbm=shop", + "selector": "div[role='navigation'] a", + "inspectRegexpInSERP": true + }, + "taggedCodes": [ + "firefox-a", + "firefox-b", + "firefox-b-1", + "firefox-b-ab", + "firefox-b-1-ab", + "firefox-b-d", + "firefox-b-1-d", + "firefox-b-e", + "firefox-b-1-e", + "firefox-b-m", + "firefox-b-1-m", + "firefox-b-o", + "firefox-b-1-o", + "firefox-b-lm", + "firefox-b-1-lm", + "firefox-b-lg", + "firefox-b-huawei-h1611", + "firefox-b-is-oem1", + "firefox-b-oem1", + "firefox-b-oem2", + "firefox-b-tinno", + "firefox-b-pn-wt", + "firefox-b-pn-wt-us", + "ubuntu", + "ubuntu-sn" + ], + "telemetryId": "google", + "organicCodes": [], + "codeParamName": "client", + "queryParamName": "q", + "queryParamNames": [ + "q" + ], + "domainExtraction": { + "ads": [ + { + "method": "data-attribute", + "options": { + "dataAttributeKey": "dtld" + }, + "selectors": "[data-dtld]" + } + ], + "nonAds": [ + { + "method": "href", + "selectors": "#rso div.g[jscontroller] > div > div > div > div a[data-usg]" + } + ] + }, + "searchPageRegexp": "^https://www\\.google\\.(?:.+)/search", + "nonAdsLinkRegexps": [ + "^https?://www\\.google\\.(?:.+)/url?(?:.+)&url=" + ], + "adServerAttributes": [ + "rw" + ], + "followOnParamNames": [ + "oq", + "ved", + "ei" + ], + "extraAdServersRegexps": [ + "^https?://www\\.google(?:adservices)?\\.com/(?:pagead/)?aclk" + ], + "id": "635a3325-1995-42d6-be09-dbe4b2a95453", + "last_modified": 1706198445460 + }, + { + "schema": 1705363206938, + "components": [ + { + "type": "ad_carousel", + "included": { + "parent": { + "selector": ".module--carousel" + }, + "related": { + "selector": ".module--carousel__left, .module--carousel__right" + }, + "children": [ + { + "selector": ".module--carousel__item", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "excluded": { + "parent": { + "selector": ".js-results-sidebar" + } + }, + "included": { + "parent": { + "selector": "article[data-testid='ad']" + }, + "children": [ + { + "type": "ad_sitelink", + "selector": "ul" + } + ] + } + }, + { + "type": "incontent_searchbox", + "topDown": true, + "included": { + "parent": { + "selector": "form#search_form" + }, + "related": { + "selector": "input#search_button, .search__autocomplete" + }, + "children": [ + { + "selector": " input#search_form_input" + } + ] + } + }, + { + "type": "ad_sidebar", + "included": { + "parent": { + "selector": ".js-results-sidebar" + }, + "children": [ + { + "selector": "article[data-testid='ad']", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "default": true + } + ], + "shoppingTab": { + "regexp": "&iax=shopping&ia=shopping", + "selector": "#duckbar a[data-zci-link='products']" + }, + "taggedCodes": [ + "ffab", + "ffcm", + "ffhp", + "ffip", + "ffit", + "ffnt", + "ffocus", + "ffos", + "ffsb", + "fpas", + "fpsa", + "ftas", + "ftsa", + "lm", + "newext" + ], + "telemetryId": "duckduckgo", + "organicCodes": [], + "codeParamName": "t", + "queryParamName": "q", + "queryParamNames": [ + "q" + ], + "domainExtraction": { + "ads": [ + { + "method": "href", + "options": { + "queryParamKey": "ad_domain" + }, + "selectors": ".products-carousel a.js-carousel-item-title, [data-testid='ad'] a[data-testid='result-title-a']" + } + ], + "nonAds": [ + { + "method": "href", + "selectors": "[data-layout='organic'] a[data-testid='result-title-a']" + } + ] + }, + "searchPageRegexp": "^https://duckduckgo\\.com/", + "expectedOrganicCodes": [ + "h_", + "ha", + "hb", + "hc", + "hd", + "he", + "hf", + "hg", + "hh", + "hi", + "hj", + "hk", + "hl", + "hm", + "hn", + "ho", + "hp", + "hq", + "hr", + "hs", + "ht", + "hu", + "hv", + "hw", + "hx", + "hy", + "hz" + ], + "extraAdServersRegexps": [ + "^https://duckduckgo.com/y\\.js?.*ad_provider\\=", + "^https://www\\.amazon\\.(?:[a-z.]{2,24}).*(?:tag=duckduckgo-)" + ], + "id": "9dfd626b-26f2-4913-9d0a-27db6cb7d8ca", + "last_modified": 1706198445456 + }, + { + "schema": 1698656464939, + "taggedCodes": [ + "monline_dg", + "monline_3_dg", + "monline_4_dg", + "monline_7_dg" + ], + "telemetryId": "baidu", + "organicCodes": [], + "codeParamName": "tn", + "queryParamName": "wd", + "queryParamNames": [ + "wd", + "word" + ], + "searchPageRegexp": "^https://(?:m|www)\\.baidu\\.com/(?:s|baidu)", + "followOnParamNames": [ + "oq" + ], + "extraAdServersRegexps": [ + "^https?://www\\.baidu\\.com/baidu\\.php?" + ], + "id": "19c434a3-d173-4871-9743-290ac92a3f6a", + "last_modified": 1698666532326 + }, + { + "schema": 1698656463945, + "components": [ + { + "type": "ad_carousel", + "included": { + "parent": { + "selector": ".product-ads-carousel" + }, + "related": { + "selector": ".snippet__control" + }, + "children": [ + { + "selector": ".product-ads-carousel__item", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "included": { + "parent": { + "selector": ".ad-result" + }, + "children": [ + { + "type": "ad_sitelink", + "selector": ".result__extra-content .deep-links--descriptions" + } + ] + } + }, + { + "type": "incontent_searchbox", + "topDown": true, + "included": { + "parent": { + "selector": "form.search-form" + }, + "related": { + "selector": ".search-form__suggestions" + }, + "children": [ + { + "selector": ".search-form__input, .search-form__submit" + } + ] + } + }, + { + "type": "ad_link", + "default": true + } + ], + "shoppingTab": { + "regexp": "/shopping?", + "selector": "nav li[data-test-id='search-navigation-item-shopping'] a" + }, + "taggedCodes": [ + "mzl", + "813cf1dd", + "16eeffc4" + ], + "telemetryId": "ecosia", + "organicCodes": [], + "codeParamName": "tt", + "queryParamName": "q", + "queryParamNames": [ + "q" + ], + "searchPageRegexp": "^https://www\\.ecosia\\.org/", + "filter_expression": "env.version|versionCompare(\"110.0a1\")>=0", + "expectedOrganicCodes": [], + "extraAdServersRegexps": [ + "^https://www\\.bing\\.com/acli?c?k" + ], + "id": "9a487171-3a06-4647-8866-36250ec84f3a", + "last_modified": 1698666532324 + }, + { + "schema": 1698656462833, + "components": [ + { + "type": "ad_carousel", + "included": { + "parent": { + "selector": ".adsMvCarousel" + }, + "related": { + "selector": ".cr" + }, + "children": [ + { + "selector": ".pa_item", + "countChildren": true + } + ] + } + }, + { + "type": "ad_link", + "excluded": { + "parent": { + "selector": "aside" + } + }, + "included": { + "parent": { + "selector": ".sb_adTA" + }, + "children": [ + { + "type": "ad_sitelink", + "selector": ".b_vlist2col" + } + ] + } + }, + { + "type": "ad_sidebar", + "included": { + "parent": { + "selector": "aside" + }, + "children": [ + { + "selector": ".pa_item, .sb_adTA", + "countChildren": true + } + ] + } + }, + { + "type": "incontent_searchbox", + "topDown": true, + "included": { + "parent": { + "selector": "form#sb_form" + }, + "related": { + "selector": "#sw_as" + }, + "children": [ + { + "selector": "input[name='q']" + } + ] + } + }, + { + "type": "ad_link", + "default": true + } + ], + "shoppingTab": { + "regexp": "^/shop?", + "selector": "#b-scopeListItem-shop a" + }, + "taggedCodes": [ + "MOZ2", + "MOZ4", + "MOZ5", + "MOZA", + "MOZB", + "MOZD", + "MOZE", + "MOZI", + "MOZL", + "MOZM", + "MOZO", + "MOZR", + "MOZT", + "MOZW", + "MOZX", + "MZSL01", + "MZSL02", + "MZSL03" + ], + "telemetryId": "bing", + "organicCodes": [], + "codeParamName": "pc", + "queryParamName": "q", + "followOnCookies": [ + { + "host": "www.bing.com", + "name": "SRCHS", + "codeParamName": "PC", + "extraCodePrefixes": [ + "QBRE" + ], + "extraCodeParamName": "form" + } + ], + "queryParamNames": [ + "q" + ], + "searchPageRegexp": "^https://www\\.bing\\.com/search", + "nonAdsLinkRegexps": [ + "^https://www.bing.com/ck/a" + ], + "extraAdServersRegexps": [ + "^https://www\\.bing\\.com/acli?c?k" + ], + "id": "e1eec461-f1f3-40de-b94b-3b670b78108c", + "last_modified": 1698666532321 + } + ], + "timestamp": 1707833261849 +} diff --git a/services/settings/dumps/main/sites-classification.json b/services/settings/dumps/main/sites-classification.json new file mode 100644 index 0000000000..4d3afd4496 --- /dev/null +++ b/services/settings/dumps/main/sites-classification.json @@ -0,0 +1,463 @@ +{ + "data": [ + { + "type": "known-hijacker-v3", + "weight": 5, + "criteria": [ + { + "url": "https://search.avast.com/AV752/" + }, + { + "url": "https://www.bing.com/?pc=COSP&ptag=N752A9E7F85937E&form=CONMHP&conlogo=CT3210127" + }, + { + "url": "https://www.yahoo.com/?fr=hp-avast&type=752" + }, + { + "url": "https://www.google.com/?bcutc=sp-004-752" + }, + { + "hostname": "mystart.incredibar.com" + }, + { + "hostname": "search.babylon.com" + }, + { + "hostname": "astromenda.com" + }, + { + "hostname": "ask.com" + }, + { + "hostname": "binkiland.com" + }, + { + "hostname": "esurf.biz" + }, + { + "hostname": "search.conduit.com" + }, + { + "hostname": "en.4yendex.com" + }, + { + "hostname": "istartsurf.com" + }, + { + "hostname": "sear4m.xyz" + }, + { + "hostname": "search-daily.com" + }, + { + "hostname": "searchhult.com" + }, + { + "hostname": "searchgol.com" + }, + { + "hostname": "searchnu.com" + }, + { + "hostname": "shorte.st" + }, + { + "hostname": "snap.do" + }, + { + "hostname": "taplika.com/" + }, + { + "hostname": "vosteran.com" + }, + { + "hostname": "trovi.com" + }, + { + "hostname": "myway.com" + }, + { + "hostname": "ap.myway.com" + }, + { + "hostname": "av.myway.com" + }, + { + "hostname": "bc.myway.com" + }, + { + "hostname": "canada.myway.com" + }, + { + "hostname": "db.myway.com" + }, + { + "hostname": "dc.myway.com" + }, + { + "hostname": "dell.myway.com" + }, + { + "hostname": "di.myway.com" + }, + { + "hostname": "ed.myway.com" + }, + { + "hostname": "es.myway.com" + }, + { + "hostname": "games.myway.com" + }, + { + "hostname": "gr.myway.com" + }, + { + "hostname": "finance.myway.com" + }, + { + "hostname": "fr.myway.com" + }, + { + "hostname": "health.myway.com" + }, + { + "hostname": "hp.myway.com" + }, + { + "hostname": "home.myway.com" + }, + { + "hostname": "info.myway.com" + }, + { + "hostname": "iq.myway.com" + }, + { + "hostname": "me.myway.com" + }, + { + "hostname": "my.myway.com" + }, + { + "hostname": "news.myway.com" + }, + { + "hostname": "nl.myway.com" + }, + { + "hostname": "nr.myway.com" + }, + { + "hostname": "ns.myway.com" + }, + { + "hostname": "nt.myway.com" + }, + { + "hostname": "ro.myway.com" + }, + { + "hostname": "search.myway.com" + }, + { + "hostname": "shipping.myway.com" + }, + { + "hostname": "sw.myway.com" + }, + { + "hostname": "ta.myway.com" + }, + { + "hostname": "td.myway.com" + }, + { + "hostname": "tr.myway.com" + }, + { + "hostname": "ts.myway.com" + }, + { + "hostname": "video.myway.com" + }, + { + "hostname": "www1.myway.com" + }, + { + "hostname": "www2.myway.com" + } + ], + "id": "dc211d38-924a-4aec-8375-75f7d9abff56", + "last_modified": 1544035467383 + }, + { + "type": "search-engine-mozilla-tag", + "weight": 4, + "criteria": [ + { + "sld": "google", + "params": [ + { + "key": "client", + "prefix": "firefox" + } + ] + }, + { + "params": [ + { + "key": "t", + "prefix": "ff" + } + ], + "hostname": "duckduckgo.com" + }, + { + "params": [ + { + "key": "tn", + "prefix": "monline_dg" + } + ], + "hostname": "baidu.com" + }, + { + "params": [ + { + "key": "pc", + "prefix": "MOZ" + } + ], + "hostname": "bing.com" + }, + { + "params": [ + { + "key": "pc", + "prefix": "MZ" + } + ], + "hostname": "bing.com" + } + ], + "id": "351864eb-eca8-4c84-b036-b847d046c864", + "last_modified": 1539907365852 + }, + { + "type": "search-engine-other-tag", + "weight": 3, + "criteria": [ + { + "sld": "google", + "params": [ + { + "key": "client" + } + ] + }, + { + "params": [ + { + "key": "t" + } + ], + "hostname": "duckduckgo.com" + }, + { + "params": [ + { + "key": "tn" + } + ], + "hostname": "baidu.com" + }, + { + "params": [ + { + "key": "pc" + } + ], + "hostname": "bing.com" + }, + { + "params": [ + { + "key": "fr" + } + ], + "hostname": "search.yahoo.com" + }, + { + "params": [ + { + "key": "hsimp" + } + ], + "hostname": "search.yahoo.com" + } + ], + "id": "9a3ed0f2-4207-4d03-8bed-6c38242dbdbd", + "last_modified": 1539907352029 + }, + { + "type": "search-engine", + "weight": 2, + "criteria": [ + { + "sld": "google" + }, + { + "hostname": "duckduckgo.com" + }, + { + "hostname": "baidu.com" + }, + { + "hostname": "bing.com" + }, + { + "hostname": "search.yahoo.com" + }, + { + "sld": "yandex" + } + ], + "id": "c32a3278-e9ba-4090-8269-49d262fdea04", + "last_modified": 1539907334747 + }, + { + "type": "news-portal", + "weight": 1, + "criteria": [ + { + "sld": "yahoo" + }, + { + "hostname": "msn.com" + }, + { + "hostname": "digg.com" + }, + { + "hostname": "aol.com" + }, + { + "hostname": "cnn.com" + }, + { + "hostname": "bbc.com" + }, + { + "hostname": "bbc.co.uk" + }, + { + "hostname": "nytimes.com" + }, + { + "hostname": "qq.com" + }, + { + "hostname": "sohu.com" + } + ], + "id": "12743b39-7f79-4620-bc37-a87b8ab2ba46", + "last_modified": 1539907318995 + }, + { + "type": "social-media", + "weight": 1, + "criteria": [ + { + "hostname": "facebook.com" + }, + { + "hostname": "twitter.com" + }, + { + "hostname": "instagram.com" + }, + { + "hostname": "reddit.com" + }, + { + "hostname": "myspace.com" + }, + { + "hostname": "linkedin.com" + }, + { + "hostname": "pinterest.com" + }, + { + "hostname": "quora.com" + }, + { + "hostname": "tuenti.com" + }, + { + "hostname": "tumblr.com" + }, + { + "hostname": "untappd.com" + }, + { + "hostname": "yammer.com" + }, + { + "hostname": "slack.com" + }, + { + "hostname": "youtube.com" + }, + { + "hostname": "vk.com" + }, + { + "hostname": "twitch.tv" + } + ], + "id": "bdde6edd-83a5-4c34-b302-4810e9d10d04", + "last_modified": 1539907302037 + }, + { + "type": "ecommerce", + "weight": 1, + "criteria": [ + { + "sld": "amazon" + }, + { + "sld": "ebay" + }, + { + "hostname": "alibaba.com" + }, + { + "hostname": "walmart.com" + }, + { + "hostname": "taobao.com" + }, + { + "hostname": "tmall.com" + }, + { + "hostname": "flipkart.com" + }, + { + "hostname": "snapdeal.com" + }, + { + "hostname": "bestbuy.com" + }, + { + "hostname": "jabong.com" + } + ], + "id": "48ec9db9-0de1-49aa-9b82-1a2372dce31e", + "last_modified": 1539907283705 + } + ], + "timestamp": 1544035467383 +} diff --git a/services/settings/dumps/main/top-sites.json b/services/settings/dumps/main/top-sites.json new file mode 100644 index 0000000000..828fe742d5 --- /dev/null +++ b/services/settings/dumps/main/top-sites.json @@ -0,0 +1,401 @@ +{ + "data": [ + { + "url": "https://www.wikipedia.org/", + "order": 0, + "title": "Wikipedia", + "schema": 1646585618139, + "include_regions": [ + "RU", + "BY", + "KZ", + "UA", + "UZ", + "TR" + ], + "id": "060ad5fe-40c3-490c-92bc-1ba5fc14a451", + "last_modified": 1646597012924 + }, + { + "url": "https://www.wikipedia.org/", + "order": 30, + "title": "Wikipedia", + "schema": 1646445825860, + "exclude_regions": [ + "GB", + "CN", + "RU", + "BY", + "KZ", + "UA", + "UZ", + "TR" + ], + "id": "c109ad2c-eae5-4b47-bac7-ac2eb9eb9aab", + "last_modified": 1646597012919 + }, + { + "url": "https://www.amazon.ca/", + "order": 50, + "title": "Amazon", + "schema": 1624370092747, + "include_regions": [ + "CA" + ], + "search_shortcut": false, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "82386dfa-cc7a-4fcb-9b1e-c9343d3da79d", + "last_modified": 1624376393938 + }, + { + "url": "https://www.amazon.com/", + "order": 50, + "title": "Amazon", + "schema": 1623956287723, + "include_locales": [], + "include_regions": [ + "ES" + ], + "include_experiments": [ + "ebay-2020-1_fallback" + ], + "id": "354690ae-3d9d-4745-a82b-7ac43e4b3213", + "last_modified": 1623959042903 + }, + { + "url": "https://www.amazon.fr/", + "order": 40, + "title": "Amazon", + "schema": 1615464108938, + "include_regions": [ + "FR" + ], + "search_shortcut": true, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "61f01feb-8243-4fef-b360-733e8c5a150e", + "last_modified": 1616085500499 + }, + { + "url": "https://www.amazon.de/", + "order": 25, + "title": "Amazon", + "schema": 1615385045177, + "include_regions": [ + "DE" + ], + "search_shortcut": true, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "6e34a6b9-353f-4df1-91f7-25912d621d23", + "last_modified": 1616085500495 + }, + { + "url": "https://www.amazon.co.uk/", + "order": 50, + "title": "Amazon", + "schema": 1615463950377, + "include_regions": [ + "GB" + ], + "search_shortcut": true, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "bd30e776-5537-47e9-b66c-e46d6daccc50", + "last_modified": 1616085500491 + }, + { + "url": "https://www.amazon.com/", + "order": 23, + "title": "Amazon", + "schema": 1615463623635, + "include_regions": [ + "US", + "IT", + "JP" + ], + "search_shortcut": true, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "2bf453fd-b588-4d99-b7e7-0d48387a62f8", + "last_modified": 1616085500488 + }, + { + "url": "https://www.amazon.com/", + "order": 50, + "title": "Amazon", + "schema": 1615377805685, + "exclude_regions": [ + "PL", + "RU", + "CN", + "GB", + "FR", + "CA", + "DE", + "US", + "IT", + "JP" + ], + "search_shortcut": false, + "exclude_experiments": [ + "ebay-2020-1" + ], + "send_attribution_request": false, + "id": "78e5e650-e27c-485c-a321-f1ea3d161c34", + "last_modified": 1615385045111 + }, + { + "url": "https://www.google.com/", + "order": 0, + "title": "Google", + "schema": 1611836508660, + "exclude_regions": [ + "BY", + "KZ", + "RU", + "TR", + "CN" + ], + "search_shortcut": true, + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "0ee067b7-c0ca-4cf6-96f8-320c2a1b927b", + "last_modified": 1611838808382 + }, + { + "url": "https://www.baidu.com/", + "order": 10, + "schema": 1611835746769, + "include_regions": [ + "CN" + ], + "search_shortcut": true, + "id": "1bfdbe7c-570f-494a-b61c-20c5c64dd69d", + "last_modified": 1611838808375 + }, + { + "url": "https://www.ebay.co.uk/", + "order": 70, + "title": "eBay", + "schema": 1610661703633, + "include_regions": [ + "GB" + ], + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "2abe6385-2f49-49ca-a4ff-0e635bc2ab7c", + "last_modified": 1611076538693 + }, + { + "url": "https://www.ebay.de/", + "order": 26, + "title": "eBay", + "schema": 1608200991177, + "include_regions": [ + "DE" + ], + "exclude_experiments": [ + "ebay-2020-1" + ], + "id": "61b60614-1285-41c6-a0c1-9fe12f3983c4", + "last_modified": 1608239138349 + }, + { + "url": "https://www.reddit.com/", + "order": 25, + "title": "Reddit", + "schema": 1600861208039, + "include_regions": [ + "US", + "CA" + ], + "id": "cfac4c2a-4788-450d-8e92-ba6b26a1faf1", + "last_modified": 1603813848271 + }, + { + "url": "https://www.reddit.com/", + "order": 40, + "title": "Reddit", + "schema": 1600861199348, + "exclude_regions": [ + "PL", + "RU", + "FR", + "CN", + "US", + "CA" + ], + "id": "7a129df1-ec8f-4bf3-a83e-49b90409d1be", + "last_modified": 1603813848259 + }, + { + "url": "https://www.zhihu.com/", + "order": 20, + "schema": 1599055903630, + "include_regions": [ + "CN" + ], + "id": "568fca49-6921-4895-997b-60276d6ea000", + "last_modified": 1599146777461 + }, + { + "url": "https://www.ifeng.com/", + "order": 30, + "schema": 1599055904425, + "include_regions": [ + "CN" + ], + "id": "8d392627-d892-4b51-96a7-3b2ec47c6cdd", + "last_modified": 1599146777456 + }, + { + "url": "https://weibo.com/", + "order": 40, + "schema": 1599055905429, + "include_regions": [ + "CN" + ], + "id": "e707b8ac-e844-4b14-a1bc-ce6968c777d1", + "last_modified": 1599146777450 + }, + { + "url": "https://www.ctrip.com/", + "order": 50, + "schema": 1599055906460, + "include_regions": [ + "CN" + ], + "id": "38ac6a09-6d0b-43fc-894b-d214e6d5b684", + "last_modified": 1599146777446 + }, + { + "url": "https://www.iqiyi.com/", + "order": 60, + "schema": 1599055907301, + "include_regions": [ + "CN" + ], + "id": "cbb986d2-665d-4967-b74b-41ecdd7c6188", + "last_modified": 1599146777441 + }, + { + "url": "https://allegro.pl/", + "order": 25, + "schema": 1599055926547, + "include_regions": [ + "PL" + ], + "id": "dfbd59db-d7d5-4efe-b379-5c3af46e390b", + "last_modified": 1599146777436 + }, + { + "url": "https://www.olx.pl/", + "order": 40, + "schema": 1599056211503, + "include_regions": [ + "PL" + ], + "id": "88f3bf01-350c-4fbb-97c9-c2b72b93274c", + "last_modified": 1599146777431 + }, + { + "url": "https://www.wykop.pl/", + "order": 50, + "schema": 1599056212302, + "include_regions": [ + "PL" + ], + "id": "7fcb96b5-b24f-4bb0-8b0d-c363ef24973b", + "last_modified": 1599146777426 + }, + { + "url": "https://www.avito.ru/", + "order": 12, + "schema": 1599056215077, + "include_regions": [ + "RU" + ], + "id": "92af59ac-7e4f-48b0-b20c-a7a39f0496ee", + "last_modified": 1599146777411 + }, + { + "url": "https://www.aliexpress.com/", + "order": 13, + "schema": 1599056215899, + "include_regions": [ + "RU" + ], + "id": "881bc01e-45f8-4c9d-b650-3940f2aa430b", + "last_modified": 1599146777406 + }, + { + "url": "https://www.leboncoin.fr/", + "order": 50, + "schema": 1599056218470, + "include_regions": [ + "FR" + ], + "id": "aa4bb35d-ddaf-452f-af32-cbf80d67a135", + "last_modified": 1599146777401 + }, + { + "url": "https://twitter.com/", + "order": 60, + "title": "Twitter", + "schema": 1599056605399, + "exclude_regions": [ + "PL", + "RU", + "DE", + "GB", + "CN" + ], + "id": "4c49a54c-6515-495f-b24d-4aae165479fc", + "last_modified": 1599146777375 + }, + { + "url": "https://www.youtube.com/", + "order": 10, + "title": "YouTube", + "schema": 1599056606419, + "exclude_regions": [ + "CN" + ], + "id": "12abfe01-c608-4060-8fff-c3df73ff3fc0", + "last_modified": 1599146777370 + }, + { + "url": "https://www.facebook.com/", + "order": 20, + "title": "Facebook", + "schema": 1599056607418, + "exclude_regions": [ + "RU", + "CN" + ], + "id": "19045f47-6dde-43a8-9511-3fc0c4555a09", + "last_modified": 1599146777365 + }, + { + "url": "https://www.bbc.co.uk/", + "order": 60, + "title": "BBC", + "schema": 1599056678589, + "include_regions": [ + "GB" + ], + "id": "89e3da9e-1d84-4c24-beab-d0692fc7cc45", + "last_modified": 1599146777345 + } + ], + "timestamp": 1647020600359 +} diff --git a/services/settings/dumps/main/translations-identification-models.json b/services/settings/dumps/main/translations-identification-models.json new file mode 100644 index 0000000000..4f77db6321 --- /dev/null +++ b/services/settings/dumps/main/translations-identification-models.json @@ -0,0 +1,20 @@ +{ + "data": [ + { + "name": "lid.176.ftz", + "schema": 1681239259655, + "version": "1.0", + "attachment": { + "hash": "8f3472cfe8738a7b6099e8e999c3cbfae0dcd15696aac7d7738a8039db603e83", + "size": 938013, + "filename": "lid.176.ftz", + "location": "main-workspace/translations-identification-models/fc2a4381-bc9e-47ad-8cff-a31163538e27.ftz", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "a4446164-b886-11ed-932c-c7bfd1c4d2fa", + "last_modified": 1681500405555 + } + ], + "timestamp": 1681500405555 +} diff --git a/services/settings/dumps/main/translations-models.json b/services/settings/dumps/main/translations-models.json new file mode 100644 index 0000000000..f81d4b9002 --- /dev/null +++ b/services/settings/dumps/main/translations-models.json @@ -0,0 +1,2723 @@ +{ + "data": [ + { + "name": "model.enca.intgemm.alphas.bin", + "schema": 1710172593713, + "toLang": "ca", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "3fff1f35345b69c8507993d1c75bef9bb7a46effcb18118dff9873c6cdc9c2e1", + "size": 17140898, + "filename": "model.enca.intgemm.alphas.bin", + "location": "main-workspace/translations-models/2a25e493-cc96-4da0-b3d7-efbf5e3ec440.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "8316e03c-098d-4b7a-8f85-d13d44cf0cbd", + "last_modified": 1710173317976 + }, + { + "name": "vocab.enca.spm", + "schema": 1710172596644, + "toLang": "ca", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "4fbb317b58e31b330f642a8ecc4c4cb719e328b6ef412624f24e9705898cf72a", + "size": 791579, + "filename": "vocab.enca.spm", + "location": "main-workspace/translations-models/db8b8599-a90b-409f-a110-88d25f8c7920.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "b2276df1-06e3-4c4a-9ffb-5ba710cf64dc", + "last_modified": 1710173317972 + }, + { + "name": "lex.50.50.enca.s2t.bin", + "schema": 1710172598190, + "toLang": "ca", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "88c3229e6fc203545b5ec8131e31c589a79dc0ba21c5b19b786c9ec8f6bb2733", + "size": 3576936, + "filename": "lex.50.50.enca.s2t.bin", + "location": "main-workspace/translations-models/0586f6fa-d677-4322-8c3e-38c7a70dd04a.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "b7036cef-6fb4-44a6-8384-98294f7b5de1", + "last_modified": 1710173317967 + }, + { + "name": "lex.50.50.elen.s2t.bin", + "schema": 1709574542054, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "el", + "attachment": { + "hash": "be11aaabd9e63829bf3b050c17bb9e6f6b818e4ace9f0e4acac4e161d852b099", + "size": 4573592, + "filename": "lex.50.50.elen.s2t.bin", + "location": "main-workspace/translations-models/f0113abb-73c7-4c51-b409-79ccebc6d0af.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "b8978449-252d-4a1c-8ff3-e067d34c84ce", + "last_modified": 1709674886827 + }, + { + "name": "model.elen.intgemm.alphas.bin", + "schema": 1709574544513, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "el", + "attachment": { + "hash": "59bf496544b25de12c9c5cde2cfae9975fd55a50f5c3d4974335da8d6eb2cb91", + "size": 17140995, + "filename": "model.elen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/fe282f62-b7c1-4172-88d9-48cf666ba9a2.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "9c641673-c94a-4b27-a063-0bfe87e13ad6", + "last_modified": 1709674886823 + }, + { + "name": "vocab.elen.spm", + "schema": 1709574551051, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "el", + "attachment": { + "hash": "d1b19ddb554741b3f707cb29eafca21c6ef42d205cdeb8aa7cb4202f61cbf518", + "size": 929962, + "filename": "vocab.elen.spm", + "location": "main-workspace/translations-models/cb46f282-9c5b-48f2-beab-82e7eb621009.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "08ffda53-64c4-4072-89ce-e3bd264084da", + "last_modified": 1709674886819 + }, + { + "name": "vocab.enet.spm", + "schema": 1709574554242, + "toLang": "et", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "e3b66bc141f6123cd40746e2fb9b8ee4f89cbf324ab27d6bbf3782e52f15fa2d", + "size": 828426, + "filename": "vocab.enet.spm", + "location": "main-workspace/translations-models/23fb2442-5e99-4416-868d-4b6d25c8399f.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "892e7b74-fe49-4591-9d22-cd27b8bfdf9a", + "last_modified": 1709674886815 + }, + { + "name": "qualityModel.enet.bin", + "schema": 1709574556632, + "toLang": "et", + "version": "1.0", + "fileType": "qualityModel", + "fromLang": "en", + "attachment": { + "hash": "bb9b9c449c705297fe6b83542d64406201960971f102787b9b6c733416406707", + "size": 68, + "filename": "qualityModel.enet.bin", + "location": "main-workspace/translations-models/e1233b94-e22d-4e37-a065-92d66e49fe97.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "5d7d9bb7-def8-4424-965d-a06ae69b0654", + "last_modified": 1709674886811 + }, + { + "name": "model.enet.intgemm.alphas.bin", + "schema": 1709574558010, + "toLang": "et", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "a28874a8b702a519a14dc71bcee726a5cb4b539eeaada2d06492f751469a1fd6", + "size": 17140754, + "filename": "model.enet.intgemm.alphas.bin", + "location": "main-workspace/translations-models/f2bf6812-5286-479a-b157-3ac715ef2336.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "a032f712-f56a-45bc-8bf4-97f3caa04fad", + "last_modified": 1709674886807 + }, + { + "name": "lex.50.50.enet.s2t.bin", + "schema": 1709574563184, + "toLang": "et", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "3d1b40ff43ebef82cf98d416a88a1ea19eb325a85785eef102f59878a63a829d", + "size": 2700780, + "filename": "lex.50.50.enet.s2t.bin", + "location": "main-workspace/translations-models/0142f35c-cddd-4c25-bd66-4a78acd9d168.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "d993444d-b849-4fef-993c-63d88bda2bb0", + "last_modified": 1709674886802 + }, + { + "name": "model.eten.intgemm.alphas.bin", + "schema": 1709574566700, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "et", + "attachment": { + "hash": "aac98a2371e216ee2d4843cbe896c617f6687501e17225ac83482eba52fd0028", + "size": 17140754, + "filename": "model.eten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/68967300-3d28-434b-885b-3ff4dfe5a2e3.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "82cf53f7-1e22-4a19-95cf-46afbaa5a8f5", + "last_modified": 1709674886799 + }, + { + "name": "vocab.eten.spm", + "schema": 1709574571034, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "et", + "attachment": { + "hash": "e3b66bc141f6123cd40746e2fb9b8ee4f89cbf324ab27d6bbf3782e52f15fa2d", + "size": 828426, + "filename": "vocab.eten.spm", + "location": "main-workspace/translations-models/a2c21e02-2bbf-4a67-9cd0-748263eb74b3.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "0a62f996-89aa-4c38-ae06-99efb1fcaabf", + "last_modified": 1709674886794 + }, + { + "name": "lex.50.50.eten.s2t.bin", + "schema": 1709574573013, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "et", + "attachment": { + "hash": "6992bedc590e60e610a28129c80746fe5f33144a4520e2c5508d87db14ca54f8", + "size": 3974944, + "filename": "lex.50.50.eten.s2t.bin", + "location": "main-workspace/translations-models/0bbaec62-644a-4c86-a1ad-eeab59e8ee33.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "61d450f0-1555-4571-aefe-2dc55ac918ba", + "last_modified": 1709674886791 + }, + { + "name": "vocab.huen.spm", + "schema": 1709574576621, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "hu", + "attachment": { + "hash": "0db772702235b02d1f29abafb7a49ed77e54c60245b3a46e90716e74263aedd6", + "size": 820746, + "filename": "vocab.huen.spm", + "location": "main-workspace/translations-models/42b6df14-fdf1-4b3f-afc1-c961941a6695.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "4ccbd054-ad84-4cd6-ac31-f8e22bb38ba8", + "last_modified": 1709674886787 + }, + { + "name": "lex.50.50.huen.s2t.bin", + "schema": 1709574578517, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "hu", + "attachment": { + "hash": "fff56b2501258ec4c46a8fc715caee7aeb15d853f859cdfacd3ef9903ed2fff1", + "size": 5162428, + "filename": "lex.50.50.huen.s2t.bin", + "location": "main-workspace/translations-models/c6506124-8f60-44c6-b53d-c4840f1c0695.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "a03c22a7-a4bc-4bec-bf87-271d759b5b4e", + "last_modified": 1709674886783 + }, + { + "name": "model.huen.intgemm.alphas.bin", + "schema": 1709574581113, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "hu", + "attachment": { + "hash": "518356dbb0c071739318601963a87580fb41732652f52bd3635246330c186d9e", + "size": 17140899, + "filename": "model.huen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/38508025-9089-4c59-bedd-26f2ac0fbc13.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "67fa095b-dcb0-4974-b606-f92bdc510698", + "last_modified": 1709674886779 + }, + { + "name": "lex.50.50.fien.s2t.bin", + "schema": 1709574586045, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "fi", + "attachment": { + "hash": "07110471bcdf71ce248a471adc00b6fbb4ead00591a2fbfd24c0d00bcb552595", + "size": 5046280, + "filename": "lex.50.50.fien.s2t.bin", + "location": "main-workspace/translations-models/25a42fc1-8141-4331-8684-f4fb4d000b31.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "58395a47-10dd-4789-9a69-56710c92e14d", + "last_modified": 1709674886775 + }, + { + "name": "vocab.fien.spm", + "schema": 1709574588773, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "fi", + "attachment": { + "hash": "30feb41f8297548bd94addd2e3dda57ad1153deaf88870f13764029d673518e0", + "size": 821437, + "filename": "vocab.fien.spm", + "location": "main-workspace/translations-models/6e34d9a9-0f0a-4448-a9c1-c73cda71528d.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "004224b3-fdf3-40a9-8efb-36f263255ac3", + "last_modified": 1709674886771 + }, + { + "name": "model.fien.intgemm.alphas.bin", + "schema": 1709574590996, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "fi", + "attachment": { + "hash": "1bcc9d3b178ca8d6a99e4ed4263044308cc94381c27b46930252d92fd9445c67", + "size": 17140995, + "filename": "model.fien.intgemm.alphas.bin", + "location": "main-workspace/translations-models/e5f53859-6906-4300-b811-bc9772b84730.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "f775f58b-6713-4a1d-8729-354f46db1fad", + "last_modified": 1709674886767 + }, + { + "name": "model.ruen.intgemm.alphas.bin", + "schema": 1709574596598, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "ru", + "attachment": { + "hash": "3b6a0305e3d232fadd54f5a765365b7b96ad6d8f2e818cba594b02fbd8fadb3d", + "size": 17140836, + "filename": "model.ruen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/b7dbeaa4-6524-424f-8c0b-7784e2b209e5.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "1664c32a-737d-48da-a152-dfb876eee228", + "last_modified": 1709674886763 + }, + { + "name": "lex.50.50.ruen.s2t.bin", + "schema": 1709574602330, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "ru", + "attachment": { + "hash": "e6667e22f5f86be4872e3768b7184727f5dd8c9f2ccfb0639baabcb1176f5d11", + "size": 5090836, + "filename": "lex.50.50.ruen.s2t.bin", + "location": "main-workspace/translations-models/cc7768c2-ff5f-4dc6-a7c8-82088e36a378.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "1bec1222-0010-4dab-9381-b5737837a4c3", + "last_modified": 1709674886759 + }, + { + "name": "vocab.ruen.spm", + "schema": 1709574605349, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "ru", + "attachment": { + "hash": "aaf9a325c0a988c507d0312cb6ba1a02bac7a370bcd879aedee626a40bfbda78", + "size": 936576, + "filename": "vocab.ruen.spm", + "location": "main-workspace/translations-models/f450af1b-d1d6-49e4-a7a9-c2e325d80933.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "686217b6-2655-4e02-8d7e-d5c3af976ac3", + "last_modified": 1709674886755 + }, + { + "name": "lex.50.50.slen.s2t.bin", + "schema": 1709574608450, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "sl", + "attachment": { + "hash": "1b7f14de28e3e840fe1e47092f688fb8d4396ea431f49492d78a25102cf0773a", + "size": 4052216, + "filename": "lex.50.50.slen.s2t.bin", + "location": "main-workspace/translations-models/4c44f34e-ad91-4d93-802c-e7d52b460e69.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "b7465f80-dff5-4ab0-af4c-554498dacc29", + "last_modified": 1709674886752 + }, + { + "name": "model.slen.intgemm.alphas.bin", + "schema": 1709574611235, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "sl", + "attachment": { + "hash": "48846d4029e51b449c642e1131f9224ede5ea7ffb08f415c848bbb58ddce7cd9", + "size": 17140836, + "filename": "model.slen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/e0e0b3dd-600e-41e7-b1bc-eb35bcf3090f.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "04e023e7-381c-4911-ac3e-86c78928d3b1", + "last_modified": 1709674886748 + }, + { + "name": "vocab.slen.spm", + "schema": 1709574617765, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "sl", + "attachment": { + "hash": "44f89161fd20329c5cfb0383c203b9e080839afe206436117695f21f7fd78e99", + "size": 817982, + "filename": "vocab.slen.spm", + "location": "main-workspace/translations-models/1991a715-dece-4ccb-bfd5-7df1ddd2826c.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "7c62ce9a-7d48-4b62-ba04-5918bea5f91e", + "last_modified": 1709674886744 + }, + { + "name": "vocab.tren.spm", + "schema": 1709574620805, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "tr", + "attachment": { + "hash": "ed328e589a3ccd70fd3ce7773fc3c01d8b7b18c687464cacf17fd40a8c0daadd", + "size": 811353, + "filename": "vocab.tren.spm", + "location": "main-workspace/translations-models/b99302c1-af69-4781-930f-d8dec82b8ec9.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "1859e7a1-dc43-4748-93a6-de27f23df681", + "last_modified": 1709674886740 + }, + { + "name": "lex.50.50.tren.s2t.bin", + "schema": 1709574623347, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "tr", + "attachment": { + "hash": "d3374ab5267a73ee1aa1d926a298bd349426835f40856991ee959bc5cd4f9fce", + "size": 4662492, + "filename": "lex.50.50.tren.s2t.bin", + "location": "main-workspace/translations-models/0c8e7a90-9172-4987-aab2-ed027e0ff3aa.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "a9684237-67b9-43d1-9be5-2e1c2de7d3e9", + "last_modified": 1709674886736 + }, + { + "name": "model.tren.intgemm.alphas.bin", + "schema": 1709574626093, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "tr", + "attachment": { + "hash": "bd18594ac5a7f1d9997e7ea5bd80272082219cf8b1ce604766e4f207eb86abbf", + "size": 17140836, + "filename": "model.tren.intgemm.alphas.bin", + "location": "main-workspace/translations-models/553d0a23-6940-49f5-8b2e-155b08897db7.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "9c403955-b977-4354-9e8c-f843212c8a9f", + "last_modified": 1709674886732 + }, + { + "name": "trgvocab.uken.spm", + "schema": 1709574633530, + "toLang": "en", + "version": "1.0", + "fileType": "trgvocab", + "fromLang": "uk", + "attachment": { + "hash": "d933cbf156c925ef42c064cbd6f85f18516f3ccac49bee7025b19a4a5c0ef711", + "size": 803064, + "filename": "trgvocab.uken.spm", + "location": "main-workspace/translations-models/293e0c1b-67a2-4297-99c2-8dff2ee37a33.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "8b6c5dc2-b09d-4845-849a-ebbccd414ccb", + "last_modified": 1709674886727 + }, + { + "name": "lex.uken.s2t.bin", + "schema": 1709574635392, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "uk", + "attachment": { + "hash": "763b9e0add9fd712305bc031ab86a58fb15f719dcad296046742176937b86841", + "size": 9761460, + "filename": "lex.uken.s2t.bin", + "location": "main-workspace/translations-models/f8e07d78-c8b0-487c-bdd4-5e9a293af34c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "2939b670-8eb4-4abe-bec9-24199e9c8218", + "last_modified": 1709674886724 + }, + { + "name": "model.uken.intgemm8.bin", + "schema": 1709574639574, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "uk", + "attachment": { + "hash": "90b6e21644af5bf5ce26442c724f55848a005d75e8bf688a51d2e64d6bc6b249", + "size": 25315747, + "filename": "model.uken.intgemm8.bin", + "location": "main-workspace/translations-models/26845b45-d3be-4e76-83db-c7f78a5f77da.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "c505f2ac-c6f6-4199-904b-6411551882c1", + "last_modified": 1709674886720 + }, + { + "name": "srcvocab.uken.spm", + "schema": 1709574645412, + "toLang": "en", + "version": "1.0", + "fileType": "srcvocab", + "fromLang": "uk", + "attachment": { + "hash": "797de9759ff722c124c64663f3b75538516a059cfce3e6cf9446f39d1063cb6d", + "size": 984214, + "filename": "srcvocab.uken.spm", + "location": "main-workspace/translations-models/47e4fc31-c065-4166-8a91-67a4c3b29853.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "8d1df651-038f-4b46-af7b-2926c6754bb0", + "last_modified": 1709674886715 + }, + { + "name": "lex.50.50.elen.s2t.bin", + "schema": 1706552704388, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "el", + "attachment": { + "hash": "be11aaabd9e63829bf3b050c17bb9e6f6b818e4ace9f0e4acac4e161d852b099", + "size": 4573592, + "filename": "lex.50.50.elen.s2t.bin", + "location": "main-workspace/translations-models/f826deac-0d28-4879-9087-99e1bbe09893.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "6c1903c4-b2dc-4216-9f82-dc68fe6afe0e", + "last_modified": 1706556926792 + }, + { + "name": "vocab.elen.spm", + "schema": 1706552727205, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "el", + "attachment": { + "hash": "d1b19ddb554741b3f707cb29eafca21c6ef42d205cdeb8aa7cb4202f61cbf518", + "size": 929962, + "filename": "vocab.elen.spm", + "location": "main-workspace/translations-models/f885aa65-edd5-4974-8698-df08fe9df5d1.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "5c1523c0-71f4-40bb-995d-d5c0b2b5c9b5", + "last_modified": 1706556926789 + }, + { + "name": "model.elen.intgemm.alphas.bin", + "schema": 1706552745311, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "el", + "attachment": { + "hash": "59bf496544b25de12c9c5cde2cfae9975fd55a50f5c3d4974335da8d6eb2cb91", + "size": 17140995, + "filename": "model.elen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/81df3b72-e0dc-4bd2-ab2b-d5e0edc35857.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "2482f24e-131c-4839-9f74-87b93e927b82", + "last_modified": 1706556926785 + }, + { + "name": "lex.50.50.tren.s2t.bin", + "schema": 1706554897118, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "tr", + "attachment": { + "hash": "d3374ab5267a73ee1aa1d926a298bd349426835f40856991ee959bc5cd4f9fce", + "size": 4662492, + "filename": "lex.50.50.tren.s2t.bin", + "location": "main-workspace/translations-models/b3603bc6-98d3-4d78-9df6-de8d50d09141.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "fc10daed-6664-4ab3-a0c5-01ac619ce809", + "last_modified": 1706556926782 + }, + { + "name": "model.tren.intgemm.alphas.bin", + "schema": 1706554903977, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "tr", + "attachment": { + "hash": "bd18594ac5a7f1d9997e7ea5bd80272082219cf8b1ce604766e4f207eb86abbf", + "size": 17140836, + "filename": "model.tren.intgemm.alphas.bin", + "location": "main-workspace/translations-models/f1e25c27-f952-4993-bb60-126ec25fe3db.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "3d18c554-8fe3-4379-a761-9d15d30e1cbe", + "last_modified": 1706556926779 + }, + { + "name": "vocab.tren.spm", + "schema": 1706554926730, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "tr", + "attachment": { + "hash": "ed328e589a3ccd70fd3ce7773fc3c01d8b7b18c687464cacf17fd40a8c0daadd", + "size": 811353, + "filename": "vocab.tren.spm", + "location": "main-workspace/translations-models/fa46434f-15f4-442a-bc02-b547bf4d8df7.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "33a6c88c-dedb-4321-ad56-6b6dd5d22e8f", + "last_modified": 1706556926776 + }, + { + "name": "lex.50.50.slen.s2t.bin", + "schema": 1706555033013, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "sl", + "attachment": { + "hash": "1b7f14de28e3e840fe1e47092f688fb8d4396ea431f49492d78a25102cf0773a", + "size": 4052216, + "filename": "lex.50.50.slen.s2t.bin", + "location": "main-workspace/translations-models/0d8a35e4-68c7-465c-a257-b67f09f98a4c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "431821b0-5b18-4cfc-a995-f57aed2608b1", + "last_modified": 1706556926773 + }, + { + "name": "model.slen.intgemm.alphas.bin", + "schema": 1706555045309, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "sl", + "attachment": { + "hash": "48846d4029e51b449c642e1131f9224ede5ea7ffb08f415c848bbb58ddce7cd9", + "size": 17140836, + "filename": "model.slen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/36daed48-c757-4d28-a25c-8e54986d5fc7.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "ba1592f3-fd05-4814-bfa0-432627613a36", + "last_modified": 1706556926770 + }, + { + "name": "vocab.slen.spm", + "schema": 1706555061692, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "sl", + "attachment": { + "hash": "44f89161fd20329c5cfb0383c203b9e080839afe206436117695f21f7fd78e99", + "size": 817982, + "filename": "vocab.slen.spm", + "location": "main-workspace/translations-models/f4419c45-ddf0-484d-b8b9-6d7f9da1d50c.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "6d317ebe-63f6-4f3c-9546-debc6886c912", + "last_modified": 1706556926767 + }, + { + "name": "lex.50.50.mten.s2t.bin", + "schema": 1706555130726, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "mt", + "attachment": { + "hash": "27fba2f530dc4e459e2acdcb0f575f20660bb18f20126a02327061dc5cd34ccd", + "size": 3000868, + "filename": "lex.50.50.mten.s2t.bin", + "location": "main-workspace/translations-models/09a47347-11d0-4ed6-b743-28f235e03d11.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "3431c52e-1fc1-4071-bcd1-980c898316b9", + "last_modified": 1706556926764 + }, + { + "name": "model.mten.intgemm.alphas.bin", + "schema": 1706555136908, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "mt", + "attachment": { + "hash": "7d86de7e2136b5ecf0b61f5cbd01418a1fdbb52f08b8eb9d7dfe2379bbdf2486", + "size": 17140836, + "filename": "model.mten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/2a8e2c88-2529-4cca-9be0-b9874044ffcb.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "30bcb4cd-50a9-45fe-8412-cf2d6c1f715c", + "last_modified": 1706556926761 + }, + { + "name": "vocab.mten.spm", + "schema": 1706555148838, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "mt", + "attachment": { + "hash": "01937c1cc6ea5c9d6ad93a42d3888a54bf12a7193411fed25327b698fdd4650d", + "size": 817199, + "filename": "vocab.mten.spm", + "location": "main-workspace/translations-models/1093d6d2-b50b-4eb2-8775-d7a9435e44f8.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "fdfc56f1-0e2e-4dbe-8296-d7b9f3796fc5", + "last_modified": 1706556926758 + }, + { + "name": "model.lten.intgemm.alphas.bin", + "schema": 1704822489191, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "lt", + "attachment": { + "hash": "0a03bd018d2b4890dbe8c26b41bd71bdc7a3e43c59959b3179d7b4d1a5137ae6", + "size": 17140898, + "filename": "model.lten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/cdff5528-68bb-4570-b87d-8bdae8baa65e.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "d44f0a7e-4b36-4b7b-aefd-f8bc13052cf1", + "last_modified": 1704823005815 + }, + { + "name": "lex.50.50.lten.s2t.bin", + "schema": 1704822494919, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "lt", + "attachment": { + "hash": "0989afc1d90d6172bedf85fa38e422c591ab39f78d8f4bcdaaf2a6b82fcfec25", + "size": 4363512, + "filename": "lex.50.50.lten.s2t.bin", + "location": "main-workspace/translations-models/b5b51638-8735-4ba9-96db-5de6221d87db.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "c0231cf0-01e0-401b-b2dc-eecdef5b2aa6", + "last_modified": 1704823005812 + }, + { + "name": "vocab.lten.spm", + "schema": 1704822498353, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "lt", + "attachment": { + "hash": "9cceabbb6212f930ba6c4e996d09eb6496e68312399beaca375fce34b3770069", + "size": 808325, + "filename": "vocab.lten.spm", + "location": "main-workspace/translations-models/30e22746-481c-43f1-bef1-e631d677cadd.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "bd9c7a7b-32b8-4e61-bf63-7d2fc464e7ec", + "last_modified": 1704823005809 + }, + { + "name": "model.nnen.intgemm.alphas.bin", + "schema": 1700619864067, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "nn", + "attachment": { + "hash": "17550943381bdfae30b1d8ee29b33b2da2c9ae4d4b9749e2e66b2946e27dce7d", + "size": 12980780, + "filename": "model.nnen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/8194e5c8-7083-44ea-8e9f-fa0984164bf9.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "b3e573c0-bff1-4193-a859-39cd0a2d2de2", + "last_modified": 1701186751747 + }, + { + "name": "lex.50.50.nnen.s2t.bin", + "schema": 1700619870669, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "nn", + "attachment": { + "hash": "b19abe7154430c1ee321e29391b14b2752c3350c3fbfcd1caf1ef7a0f9a30a97", + "size": 1506236, + "filename": "lex.50.50.nnen.s2t.bin", + "location": "main-workspace/translations-models/9bdab1d0-2dbf-471d-9a28-38ba1fd8fc57.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "8dbfe7f9-2443-43b4-9407-79d8d4cc1060", + "last_modified": 1701186751744 + }, + { + "name": "vocab.nnen.spm", + "schema": 1700619874357, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "nn", + "attachment": { + "hash": "14625622062046b7c44eb79652efb113518bf4b52fa1b98a949934b7a94b0273", + "size": 510942, + "filename": "vocab.nnen.spm", + "location": "main-workspace/translations-models/0cf58a28-e182-4592-abad-d25c096c551e.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "776444ed-bd6c-4609-911e-7c204ddd56b7", + "last_modified": 1701186751741 + }, + { + "name": "trgvocab.uken.spm", + "schema": 1700619877879, + "toLang": "en", + "version": "1.0a1", + "fileType": "trgvocab", + "fromLang": "uk", + "attachment": { + "hash": "d933cbf156c925ef42c064cbd6f85f18516f3ccac49bee7025b19a4a5c0ef711", + "size": 803064, + "filename": "trgvocab.uken.spm", + "location": "main-workspace/translations-models/8c7de6ad-2bd8-42e2-ab3f-4053648fe3e4.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "43645308-b36a-48e9-89b2-2f754ba51e83", + "last_modified": 1701186751738 + }, + { + "name": "lex.uken.s2t.bin", + "schema": 1700619881473, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "uk", + "attachment": { + "hash": "763b9e0add9fd712305bc031ab86a58fb15f719dcad296046742176937b86841", + "size": 9761460, + "filename": "lex.uken.s2t.bin", + "location": "main-workspace/translations-models/80e4921c-4cb1-4bd9-b917-de7f3dee86ae.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "4d6cd1b7-6318-49b1-8668-28c511fe4402", + "last_modified": 1701186751735 + }, + { + "name": "model.uken.intgemm8.bin", + "schema": 1700619886535, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "uk", + "attachment": { + "hash": "90b6e21644af5bf5ce26442c724f55848a005d75e8bf688a51d2e64d6bc6b249", + "size": 25315747, + "filename": "model.uken.intgemm8.bin", + "location": "main-workspace/translations-models/d7731f28-bf8d-454b-9c44-c62b059cb3bf.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "195d4e3d-a509-4ab9-8c7d-5fbf1ade990b", + "last_modified": 1701186751732 + }, + { + "name": "srcvocab.uken.spm", + "schema": 1700619894276, + "toLang": "en", + "version": "1.0a1", + "fileType": "srcvocab", + "fromLang": "uk", + "attachment": { + "hash": "797de9759ff722c124c64663f3b75538516a059cfce3e6cf9446f39d1063cb6d", + "size": 984214, + "filename": "srcvocab.uken.spm", + "location": "main-workspace/translations-models/6b41203e-2c43-4655-9f38-ca1c8bec8967.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "084c113f-7eed-4acf-afe0-00f99671cc6d", + "last_modified": 1701186751729 + }, + { + "name": "model.encs.intgemm.alphas.bin", + "schema": 1700619898096, + "toLang": "cs", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "9a2fe0588bd972accfc801e2f31c945de0557804a91666ae5ab43b94fb74ac4b", + "size": 17140756, + "filename": "model.encs.intgemm.alphas.bin", + "location": "main-workspace/translations-models/1e1acc3c-4a19-4a8d-808b-c17e2968c2b0.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "7ef8e77f-c4a9-4505-bf4d-5374afabba3e", + "last_modified": 1701186751726 + }, + { + "name": "lex.50.50.encs.s2t.bin", + "schema": 1700619903857, + "toLang": "cs", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "e19c77231bf977988e31ff8db15fe79966b5170564bd3e10613f239e7f461d97", + "size": 3556124, + "filename": "lex.50.50.encs.s2t.bin", + "location": "main-workspace/translations-models/85f4a85c-7056-4ebf-b650-44de2cd06af7.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "663f54f3-79ac-4a4e-971a-9eb17e0ca093", + "last_modified": 1701186751722 + }, + { + "name": "qualityModel.encs.bin", + "schema": 1700619908215, + "toLang": "cs", + "version": "1.0a1", + "fileType": "qualityModel", + "fromLang": "en", + "attachment": { + "hash": "d7eba90036a065e4a1e93e889befe09f93a7d9a3417f3edffdb09a0db88fe83a", + "size": 68, + "filename": "qualityModel.encs.bin", + "location": "main-workspace/translations-models/ef7dad4b-40b5-4f73-a72b-a98a465123c9.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "39dc5368-4d1f-4e83-9db4-fa4bdaf747ca", + "last_modified": 1701186751719 + }, + { + "name": "vocab.encs.spm", + "schema": 1700619911362, + "toLang": "cs", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "f71cc5d045e479607078e079884f44032f5a0b82547fb96eefa29cd1eb47c6f3", + "size": 769763, + "filename": "vocab.encs.spm", + "location": "main-workspace/translations-models/9e643f44-b0b2-4ec9-866f-73b54bc0b52c.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "caf36734-993d-4648-870e-66836e8ceaa7", + "last_modified": 1701186751716 + }, + { + "name": "lex.enuk.s2t.bin", + "schema": 1700619915077, + "toLang": "uk", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "2b07001be2cad9eca0a26dfb8cc8a9cc8f4f8a8359b53cc5c77474e54cb1f94a", + "size": 10294724, + "filename": "lex.enuk.s2t.bin", + "location": "main-workspace/translations-models/b9fa4cf9-eefd-4816-8a4d-be1572bfe947.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "8e0aec0a-ed4d-4c0c-bbbd-a1065b883a07", + "last_modified": 1701186751712 + }, + { + "name": "srcvocab.enuk.spm", + "schema": 1700619921415, + "toLang": "uk", + "version": "1.0a1", + "fileType": "srcvocab", + "fromLang": "en", + "attachment": { + "hash": "dd44ee771e3be2fce4986beb4f4386fa0a5b233dfb5602d3cb78461053a6a50e", + "size": 789110, + "filename": "srcvocab.enuk.spm", + "location": "main-workspace/translations-models/f1b20358-f3b5-40d1-96e1-d8a30db63a13.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "87237c1a-dd1b-47f9-9bb1-2b860894fb92", + "last_modified": 1701186751709 + }, + { + "name": "trgvocab.enuk.spm", + "schema": 1700619925292, + "toLang": "uk", + "version": "1.0a1", + "fileType": "trgvocab", + "fromLang": "en", + "attachment": { + "hash": "04f3110c139f80a4e72aeb2b6802a0be50b94e36aa89647cab53318a0917e442", + "size": 1003426, + "filename": "trgvocab.enuk.spm", + "location": "main-workspace/translations-models/02939ed9-cf6a-47cf-963f-2bf5ec6d27df.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "d2b25f4a-e470-4c57-bdb3-2d94cf1d9c1e", + "last_modified": 1701186751706 + }, + { + "name": "model.enuk.intgemm8.bin", + "schema": 1700619929025, + "toLang": "uk", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "326aa67032b19dfd979267ea88f066c8ca394b01bedece00e0bf6a722a42a099", + "size": 25315747, + "filename": "model.enuk.intgemm8.bin", + "location": "main-workspace/translations-models/a6106025-f0c2-40d4-8384-0686fedc340e.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "5c6a33ca-f1b3-495e-bc3c-b6242a0385db", + "last_modified": 1701186751703 + }, + { + "name": "vocab.enet.spm", + "schema": 1700619936219, + "toLang": "et", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "e3b66bc141f6123cd40746e2fb9b8ee4f89cbf324ab27d6bbf3782e52f15fa2d", + "size": 828426, + "filename": "vocab.enet.spm", + "location": "main-workspace/translations-models/521897d7-514e-4043-8723-a35a108507b4.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "05af2465-bb63-4fe1-8653-5476583cf9d6", + "last_modified": 1701186751700 + }, + { + "name": "qualityModel.enet.bin", + "schema": 1700619939863, + "toLang": "et", + "version": "1.0a1", + "fileType": "qualityModel", + "fromLang": "en", + "attachment": { + "hash": "bb9b9c449c705297fe6b83542d64406201960971f102787b9b6c733416406707", + "size": 68, + "filename": "qualityModel.enet.bin", + "location": "main-workspace/translations-models/49913732-7c8b-42b2-8824-bf11bd119ce4.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "fafbaa61-5049-40dc-8bb4-3a15d4d7f968", + "last_modified": 1701186751697 + }, + { + "name": "model.enet.intgemm.alphas.bin", + "schema": 1700619943016, + "toLang": "et", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "a28874a8b702a519a14dc71bcee726a5cb4b539eeaada2d06492f751469a1fd6", + "size": 17140754, + "filename": "model.enet.intgemm.alphas.bin", + "location": "main-workspace/translations-models/0037f9d4-4227-40cd-9b2a-b2546422f4fa.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "a5dcdf67-2fc5-476f-941a-052eb0238cff", + "last_modified": 1701186751694 + }, + { + "name": "lex.50.50.enet.s2t.bin", + "schema": 1700619949010, + "toLang": "et", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "3d1b40ff43ebef82cf98d416a88a1ea19eb325a85785eef102f59878a63a829d", + "size": 2700780, + "filename": "lex.50.50.enet.s2t.bin", + "location": "main-workspace/translations-models/5db3d517-45ce-40d0-924b-6a50da2c9dd3.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "591bfe15-4d78-4e45-ac42-cff5991f254a", + "last_modified": 1701186751691 + }, + { + "name": "lex.50.50.fien.s2t.bin", + "schema": 1700619952969, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "fi", + "attachment": { + "hash": "07110471bcdf71ce248a471adc00b6fbb4ead00591a2fbfd24c0d00bcb552595", + "size": 5046280, + "filename": "lex.50.50.fien.s2t.bin", + "location": "main-workspace/translations-models/003a2d6d-8d5a-4c03-b2b4-59b77b14c940.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "c2b95332-92b2-46bd-b90c-4879178bee0f", + "last_modified": 1701186751688 + }, + { + "name": "vocab.fien.spm", + "schema": 1700619957571, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "fi", + "attachment": { + "hash": "30feb41f8297548bd94addd2e3dda57ad1153deaf88870f13764029d673518e0", + "size": 821437, + "filename": "vocab.fien.spm", + "location": "main-workspace/translations-models/d2087e09-9410-4fa2-8fae-fdfaa59dd5ab.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "226a2619-1a08-4022-bffa-fdb99dea4ff3", + "last_modified": 1701186751684 + }, + { + "name": "model.fien.intgemm.alphas.bin", + "schema": 1700619961650, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "fi", + "attachment": { + "hash": "1bcc9d3b178ca8d6a99e4ed4263044308cc94381c27b46930252d92fd9445c67", + "size": 17140995, + "filename": "model.fien.intgemm.alphas.bin", + "location": "main-workspace/translations-models/a4653e7c-60d5-43f4-a286-770c150a6965.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "b17243c5-9881-4d6f-94e9-400acbbba9b3", + "last_modified": 1701186751681 + }, + { + "name": "vocab.huen.spm", + "schema": 1700619967298, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "hu", + "attachment": { + "hash": "0db772702235b02d1f29abafb7a49ed77e54c60245b3a46e90716e74263aedd6", + "size": 820746, + "filename": "vocab.huen.spm", + "location": "main-workspace/translations-models/ede1581b-859d-41dc-864a-2a3fc1ba2d9d.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "2209408a-caa1-4b7f-a9f3-9057bc87c32f", + "last_modified": 1701186751678 + }, + { + "name": "lex.50.50.huen.s2t.bin", + "schema": 1700619970951, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "hu", + "attachment": { + "hash": "fff56b2501258ec4c46a8fc715caee7aeb15d853f859cdfacd3ef9903ed2fff1", + "size": 5162428, + "filename": "lex.50.50.huen.s2t.bin", + "location": "main-workspace/translations-models/dfb544dd-f154-4244-b276-9de5de4923a8.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "9b411945-09e1-4be1-89b3-8865e1a79815", + "last_modified": 1701186751675 + }, + { + "name": "model.huen.intgemm.alphas.bin", + "schema": 1700619975309, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "hu", + "attachment": { + "hash": "518356dbb0c071739318601963a87580fb41732652f52bd3635246330c186d9e", + "size": 17140899, + "filename": "model.huen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/682156eb-87e7-4987-8c99-cbde74eafd88.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "ccd767af-f828-47c3-82ee-6902484c960f", + "last_modified": 1701186751672 + }, + { + "name": "model.caen.intgemm.alphas.bin", + "schema": 1700619981767, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "ca", + "attachment": { + "hash": "3a315266490d87f72adf9e5387ee567b2fb76a30018e51586b882b1d87bf5aed", + "size": 17140899, + "filename": "model.caen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/af0c1e43-2d39-4761-99fe-df4eb4c8f9f5.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "2c743b80-f06d-4113-878a-aa808ba86471", + "last_modified": 1701186751669 + }, + { + "name": "lex.50.50.caen.s2t.bin", + "schema": 1700619988106, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "ca", + "attachment": { + "hash": "a648be17d6f008feee687b455d00dbfaedba2ead8bee32658783c4325a8d3ece", + "size": 5244644, + "filename": "lex.50.50.caen.s2t.bin", + "location": "main-workspace/translations-models/131e044c-85ed-4287-9ee0-4aebe393b15f.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "a5922e2f-ec10-4ccc-8c84-019999861030", + "last_modified": 1701186751665 + }, + { + "name": "vocab.caen.spm", + "schema": 1700619992874, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "ca", + "attachment": { + "hash": "10a1f25e5640f596b547190082f87ba4994f8714693904c82a35d965b9cc7470", + "size": 811443, + "filename": "vocab.caen.spm", + "location": "main-workspace/translations-models/2c8f521d-17af-455b-a52f-b3236013f936.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "7471c959-6e86-457c-a698-0db1aea2e2e9", + "last_modified": 1701186751662 + }, + { + "name": "vocab.enfa.spm", + "schema": 1700619996576, + "toLang": "fa", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "ecf95b5cf04b86bf68113b3d6fdcf23c2728eb5929cebc0f5aa5c4d8b8330fa4", + "size": 794181, + "filename": "vocab.enfa.spm", + "location": "main-workspace/translations-models/62f0e92e-949f-4ce1-ae2b-3c2943c86ccb.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "990ec375-0f61-46cb-9e82-9506071937b2", + "last_modified": 1701186751659 + }, + { + "name": "model.enfa.intgemm.alphas.bin", + "schema": 1700620000244, + "toLang": "fa", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "74fa9694e56c9e0511229c6d247752132b4cf83bbf0574a601dceeac5b62f1b9", + "size": 17140835, + "filename": "model.enfa.intgemm.alphas.bin", + "location": "main-workspace/translations-models/6fdb5e77-f005-497c-8d8d-70772a080e4c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "7e354139-89c2-4605-8678-ce775ee99f7b", + "last_modified": 1701186751656 + }, + { + "name": "lex.50.50.enfa.s2t.bin", + "schema": 1700620006238, + "toLang": "fa", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "09db90a01fc53764db021c58f4935a60b693b90835039b5a27b7bb9f4709e3b7", + "size": 5097656, + "filename": "lex.50.50.enfa.s2t.bin", + "location": "main-workspace/translations-models/e16c5f86-2886-42b8-9f86-81a40936fd10.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "8367b30b-6620-4907-a7eb-3aa75d419dde", + "last_modified": 1701186751653 + }, + { + "name": "lex.50.50.enru.s2t.bin", + "schema": 1700620010545, + "toLang": "ru", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "7bd3e2c0a72286fe1f3da65c56c49a7cd77efa5f1d1a444e2a9e769480b96ff3", + "size": 3049096, + "filename": "lex.50.50.enru.s2t.bin", + "location": "main-workspace/translations-models/46f4af35-4a73-4dd0-b6d7-8e0441809fb4.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "55ff0652-a807-414b-bb82-09d91209c276", + "last_modified": 1701186751650 + }, + { + "name": "model.enru.intgemm.alphas.bin", + "schema": 1700620014556, + "toLang": "ru", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "4a45186a93b8a2dd9301c66a3b3dad580b1bcfa74aadda583ca383f9fe0dea93", + "size": 17140836, + "filename": "model.enru.intgemm.alphas.bin", + "location": "main-workspace/translations-models/add43920-64b1-49c1-96fc-b0d04f13a614.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "46e208cc-09a1-404c-8b0e-d944b1ffacd5", + "last_modified": 1701186751647 + }, + { + "name": "vocab.enru.spm", + "schema": 1700620020055, + "toLang": "ru", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "feca2d44f01b946c85faba3b15b5eb53344bec84cd14a1a4d4a82ddd774c5edd", + "size": 937157, + "filename": "vocab.enru.spm", + "location": "main-workspace/translations-models/d523bacf-a1d5-4c61-b2e3-106bd0377037.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "531feeb7-bad4-4f72-a533-7e9577e2a46a", + "last_modified": 1701186751644 + }, + { + "name": "model.eten.intgemm.alphas.bin", + "schema": 1700620023819, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "et", + "attachment": { + "hash": "aac98a2371e216ee2d4843cbe896c617f6687501e17225ac83482eba52fd0028", + "size": 17140754, + "filename": "model.eten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/55b5614c-3252-45e5-9007-9ccd24290b16.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "5765b270-0287-4e1b-95f7-caf2e87e7aba", + "last_modified": 1701186751641 + }, + { + "name": "vocab.eten.spm", + "schema": 1700620031408, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "et", + "attachment": { + "hash": "e3b66bc141f6123cd40746e2fb9b8ee4f89cbf324ab27d6bbf3782e52f15fa2d", + "size": 828426, + "filename": "vocab.eten.spm", + "location": "main-workspace/translations-models/317fa7aa-53e2-455e-a8e7-25cedb72e2d2.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "6f6aab4e-92a8-46a9-9889-478a2c83c0ce", + "last_modified": 1701186751637 + }, + { + "name": "lex.50.50.eten.s2t.bin", + "schema": 1700620035529, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "et", + "attachment": { + "hash": "6992bedc590e60e610a28129c80746fe5f33144a4520e2c5508d87db14ca54f8", + "size": 3974944, + "filename": "lex.50.50.eten.s2t.bin", + "location": "main-workspace/translations-models/152fa3a2-16a8-41f4-8eeb-8d6542fa7920.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "9b3df31e-b45d-4153-a785-6c866e38077b", + "last_modified": 1701186751634 + }, + { + "name": "model.ruen.intgemm.alphas.bin", + "schema": 1700620039611, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "ru", + "attachment": { + "hash": "3b6a0305e3d232fadd54f5a765365b7b96ad6d8f2e818cba594b02fbd8fadb3d", + "size": 17140836, + "filename": "model.ruen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/b66ae55e-13cc-473a-960d-b8d447bd1892.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "566883b4-bdfe-4674-98ed-11b0c1611682", + "last_modified": 1701186751631 + }, + { + "name": "lex.50.50.ruen.s2t.bin", + "schema": 1700620048093, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "ru", + "attachment": { + "hash": "e6667e22f5f86be4872e3768b7184727f5dd8c9f2ccfb0639baabcb1176f5d11", + "size": 5090836, + "filename": "lex.50.50.ruen.s2t.bin", + "location": "main-workspace/translations-models/bd019c16-ef1f-42a7-8efd-dd62b61a2cf3.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "d71e701d-c3e2-4ebf-9b0c-6551361f10bc", + "last_modified": 1701186751628 + }, + { + "name": "vocab.ruen.spm", + "schema": 1700620052265, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "ru", + "attachment": { + "hash": "aaf9a325c0a988c507d0312cb6ba1a02bac7a370bcd879aedee626a40bfbda78", + "size": 936576, + "filename": "vocab.ruen.spm", + "location": "main-workspace/translations-models/ad835b45-aea3-4b4c-86aa-75a18a781a9c.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "e79a7e53-a495-44ef-9e49-c1454d002722", + "last_modified": 1701186751624 + }, + { + "name": "vocab.faen.spm", + "schema": 1700620055979, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "fa", + "attachment": { + "hash": "c3a675794dcba12dde8e1a343a82d973b7c9584f9dce3dad37f99dfe64fd29de", + "size": 845020, + "filename": "vocab.faen.spm", + "location": "main-workspace/translations-models/c9570eb1-5ab2-4dd8-8d7a-950feef6c7a5.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "6fdca4ce-5617-4015-9622-73c587418d00", + "last_modified": 1701186751621 + }, + { + "name": "model.faen.intgemm.alphas.bin", + "schema": 1700620059753, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "fa", + "attachment": { + "hash": "f7a76cde1d66aa7d9ed017d7dd0f7e945608412e723e0d99dee293a6adae4572", + "size": 17140837, + "filename": "model.faen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/8b3b6b0f-0434-4d25-93ce-8e886c234461.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "d8332a39-b89d-43d8-aca6-463119ccaee8", + "last_modified": 1701186751618 + }, + { + "name": "lex.50.50.faen.s2t.bin", + "schema": 1700620066782, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "fa", + "attachment": { + "hash": "55fac2186b0c8c903d4ed958457f363b3a339a96b8ab7f73b62c7b7c9ac09b48", + "size": 6197320, + "filename": "lex.50.50.faen.s2t.bin", + "location": "main-workspace/translations-models/9b525804-dd24-49cb-98a2-eb9c8493f576.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "a2f5195c-e98c-4824-a643-810221e28d4c", + "last_modified": 1701186751614 + }, + { + "name": "lex.50.50.enhu.s2t.bin", + "schema": 1700620071185, + "toLang": "hu", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "cd5f4424ab156b158c57707a1491a17922e1b2498e6af47ee324264834e4f319", + "size": 4167800, + "filename": "lex.50.50.enhu.s2t.bin", + "location": "main-workspace/translations-models/19453b2f-68ff-477f-b2df-ddbca5c07e94.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "522f8a5e-c71a-4604-b707-6d60e69fca26", + "last_modified": 1701186751611 + }, + { + "name": "vocab.enhu.spm", + "schema": 1700620075353, + "toLang": "hu", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "7c29d374a73886fac74a205c6a30dcd3dd7b3199126bee48312c55481b2cf505", + "size": 822969, + "filename": "vocab.enhu.spm", + "location": "main-workspace/translations-models/696bd463-e35d-4d47-8aa6-7e96806df544.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "cb87a704-fabf-464d-bd9f-ed66fdb1bf7d", + "last_modified": 1701186751608 + }, + { + "name": "model.enhu.intgemm.alphas.bin", + "schema": 1700620079035, + "toLang": "hu", + "version": "1.0a1", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "3c639b9cf912049e4f6bc8cb612ae9d7a8e3a0e7879591b82c644a41dd527c45", + "size": 17140898, + "filename": "model.enhu.intgemm.alphas.bin", + "location": "main-workspace/translations-models/b1586767-e5da-42ef-8d92-6ca07782f365.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "b1714830-b251-427e-af8b-0e687dd398f4", + "last_modified": 1701186751605 + }, + { + "name": "model.csen.intgemm.alphas.bin", + "schema": 1700620085728, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "cs", + "attachment": { + "hash": "5b16661e2864dc50b2f4091a16bdd4ec8d8283e04271e602159ba348df5d6e2d", + "size": 17140756, + "filename": "model.csen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/50c22d42-fa38-49c0-abd6-55b929f73f2d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "0b14b346-0c8f-4d36-80a7-bb4c144dc6d3", + "last_modified": 1701186751601 + }, + { + "name": "vocab.csen.spm", + "schema": 1700620099538, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "cs", + "attachment": { + "hash": "f71cc5d045e479607078e079884f44032f5a0b82547fb96eefa29cd1eb47c6f3", + "size": 769763, + "filename": "vocab.csen.spm", + "location": "main-workspace/translations-models/7fc39790-957e-451e-8ae4-95e42f941aa0.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "5170948d-3d19-41b1-b488-12d4e2353993", + "last_modified": 1701186751598 + }, + { + "name": "lex.50.50.csen.s2t.bin", + "schema": 1700620103137, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "cs", + "attachment": { + "hash": "8228a3c3f7887759a62b7d7c674a7bef9b70161913f9b0939ab58f71186835c2", + "size": 4535788, + "filename": "lex.50.50.csen.s2t.bin", + "location": "main-workspace/translations-models/f0519aa5-cff6-4d57-aee4-061358a9ef00.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "f8c7b355-46b4-461f-8fa7-57be56b82e4e", + "last_modified": 1701186751595 + }, + { + "name": "vocab.isen.spm", + "schema": 1700620107365, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "is", + "attachment": { + "hash": "67020a6ce013f06a1fca8b1f4c1a65c4fb8c679b231a236e7c6988570cbc8e06", + "size": 820367, + "filename": "vocab.isen.spm", + "location": "main-workspace/translations-models/b6f7507f-c2e9-4954-bd4e-f9739a8d777b.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "28d43422-0333-44b7-bf9b-b426607e1ef9", + "last_modified": 1701186751592 + }, + { + "name": "model.isen.intgemm.alphas.bin", + "schema": 1700620111055, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "is", + "attachment": { + "hash": "29ff1cc6072372ee41f91d38dbf1cdfa4efb1a89a0c0c4200a443ee929e091b7", + "size": 17140780, + "filename": "model.isen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/3c65f862-4d77-435c-b6a5-182612df367e.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "8a7c00d1-2eea-439c-81b6-eae7be6fcc62", + "last_modified": 1701186751588 + }, + { + "name": "lex.50.50.isen.s2t.bin", + "schema": 1700620117179, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "is", + "attachment": { + "hash": "790a9dd90e07b105437df60d79cb30fcb34fdce918b4aa5e94992953bcc42520", + "size": 2926468, + "filename": "lex.50.50.isen.s2t.bin", + "location": "main-workspace/translations-models/f9c93aa6-a438-4469-947b-efbb896f3420.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "784a6b9a-8c17-4d8a-8082-b31bf9dfd1fc", + "last_modified": 1701186751585 + }, + { + "name": "vocab.nben.spm", + "schema": 1700620121116, + "toLang": "en", + "version": "1.0a1", + "fileType": "vocab", + "fromLang": "nb", + "attachment": { + "hash": "3cb4f4efc382d6e7242304e8649050da297469b69a604dd7480d8704fe4877b6", + "size": 511372, + "filename": "vocab.nben.spm", + "location": "main-workspace/translations-models/5a07cfd8-410e-40c9-b392-d968dcfe8ac2.spm", + "mimetype": "text/plain" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "698b61d3-b68e-4e64-b9c1-5d1828d82c25", + "last_modified": 1701186751582 + }, + { + "name": "lex.50.50.nben.s2t.bin", + "schema": 1700620124796, + "toLang": "en", + "version": "1.0a1", + "fileType": "lex", + "fromLang": "nb", + "attachment": { + "hash": "5ec9a1eb849cf8fde0908904808e65a0c5a3027f85b1b5b9944c6b15d424598d", + "size": 1487760, + "filename": "lex.50.50.nben.s2t.bin", + "location": "main-workspace/translations-models/a941c060-aa53-4fa1-9787-c1bc27df049d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "39bbbb8a-c9d2-4f60-8228-bf7b1d0b9143", + "last_modified": 1701186751577 + }, + { + "name": "model.nben.intgemm.alphas.bin", + "schema": 1700620128949, + "toLang": "en", + "version": "1.0a1", + "fileType": "model", + "fromLang": "nb", + "attachment": { + "hash": "03cef6aa909d2397420201551354c21010324fe155753d62b4eb190adca071be", + "size": 12980780, + "filename": "model.nben.intgemm.alphas.bin", + "location": "main-workspace/translations-models/9e3f743f-ec9e-4203-ac6d-a784ee5d2e82.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "env.channel == 'default' || env.channel == 'nightly'", + "id": "e7dad410-2ac4-49bb-8dea-fbc9f00b27c5", + "last_modified": 1701186751574 + }, + { + "name": "lex.50.50.plen.s2t.bin", + "schema": 1700620135283, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "pl", + "attachment": { + "hash": "863afade0ba058fb0173fedef3d1fb14d0dcabc24c3b4584cb1fed8f84d6d879", + "size": 4898024, + "filename": "lex.50.50.plen.s2t.bin", + "location": "main-workspace/translations-models/ce258bcd-2f27-42c4-8f46-4ccaac1158ae.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "3376b975-98b8-4045-80d1-9a4f332dfec8", + "last_modified": 1701186751571 + }, + { + "name": "vocab.plen.spm", + "schema": 1700620139432, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "pl", + "attachment": { + "hash": "a1d27e6f5c0d29f406364ebf0382949d1c0affc750cec4380f3173150552f43e", + "size": 822587, + "filename": "vocab.plen.spm", + "location": "main-workspace/translations-models/98b6b745-b695-4543-95d8-adf1996095ce.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "9f5b7167-4088-456e-9df3-92c707e8035f", + "last_modified": 1701186751568 + }, + { + "name": "model.plen.intgemm.alphas.bin", + "schema": 1700620143091, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "pl", + "attachment": { + "hash": "172a5f1d44bf8dd6a6eec3868b13b33ce265f3530e898fe11a80b739b821726e", + "size": 17140899, + "filename": "model.plen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/b26a428b-a136-469d-91dd-9389f72c053e.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "5216d9f7-34af-4bc7-b85f-06967ee463e9", + "last_modified": 1701186751565 + }, + { + "name": "lex.50.50.enpl.s2t.bin", + "schema": 1700620149347, + "toLang": "pl", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "409fbf5856cec372dffe0a3aa3c89462e2efbd783557272af84800a67195c38c", + "size": 3642112, + "filename": "lex.50.50.enpl.s2t.bin", + "location": "main-workspace/translations-models/6b14f949-6e9e-44ff-80e5-815a07615a37.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "8256b48e-d78c-4458-b24b-163ede0e9188", + "last_modified": 1701186751562 + }, + { + "name": "model.enpl.intgemm.alphas.bin", + "schema": 1700620153499, + "toLang": "pl", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "60d45f43a5ac869a80f899751d2d1f0f456da9815d26db70e4d2e0fd18ed4a8f", + "size": 17140899, + "filename": "model.enpl.intgemm.alphas.bin", + "location": "main-workspace/translations-models/62448fcb-464a-474b-b56a-37686dfe80bf.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "b08a1d14-e156-4736-8504-4526287e79a7", + "last_modified": 1701186751558 + }, + { + "name": "vocab.enpl.spm", + "schema": 1700620159408, + "toLang": "pl", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "a1d27e6f5c0d29f406364ebf0382949d1c0affc750cec4380f3173150552f43e", + "size": 822587, + "filename": "vocab.enpl.spm", + "location": "main-workspace/translations-models/e1fa6366-7675-4d1f-992b-5b1d88b9cb4d.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "5f5ba830-ca5c-45bb-b86a-ca69db18e85f", + "last_modified": 1701186751555 + }, + { + "name": "vocab.ende.spm", + "schema": 1700620163543, + "toLang": "de", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "417668f2ed297970febafb5b079a9d5ebc4ed0b3550ac8386d67a90473a09bd7", + "size": 784269, + "filename": "vocab.ende.spm", + "location": "main-workspace/translations-models/a5117ec6-0b87-4ae1-9c2e-5151de59af1d.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "0f4f6141-9210-4cf0-8f77-9b4f487276ee", + "last_modified": 1701186751552 + }, + { + "name": "model.ende.intgemm.alphas.bin", + "schema": 1700620167301, + "toLang": "de", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "b3e980d6602ab0bdfe8d9315cb5fc282a16bb1c8dccf38e70945c584551c4318", + "size": 17140835, + "filename": "model.ende.intgemm.alphas.bin", + "location": "main-workspace/translations-models/8a830d1c-e167-4238-8473-4065c65c02d2.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "0c2c30d6-42de-40b3-a488-a280e14db2f5", + "last_modified": 1701186751549 + }, + { + "name": "lex.50.50.ende.s2t.bin", + "schema": 1700620173502, + "toLang": "de", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "f03eb8245042feb7a5800815b8d0dc215d7a60691632405b65c461d250cedbe6", + "size": 3943644, + "filename": "lex.50.50.ende.s2t.bin", + "location": "main-workspace/translations-models/db204e54-8565-4bba-aa52-1e857bcc68a5.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "4fcec077-f61c-449a-92eb-33a0244fabba", + "last_modified": 1701186751545 + }, + { + "name": "model.bgen.intgemm.alphas.bin", + "schema": 1700620177631, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "bg", + "attachment": { + "hash": "71900847a98cf66bd1d05eaafc23a794c8c1285fb3f0e2ecd2849e6f81c79d53", + "size": 17140899, + "filename": "model.bgen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/7f0b6e9a-fa0f-44db-b5f2-337d76a22d8c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "209122fd-30b2-4789-b703-0a4a4a557f4d", + "last_modified": 1701186751542 + }, + { + "name": "lex.50.50.bgen.s2t.bin", + "schema": 1700620183858, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "bg", + "attachment": { + "hash": "71e8d040a2f63705bec232cd186f32e9f9a78e7968216516c4535589f6a828f9", + "size": 6182512, + "filename": "lex.50.50.bgen.s2t.bin", + "location": "main-workspace/translations-models/dc0f48bb-af79-4b7b-96ee-048fea3d21e0.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "269a8217-1f32-42d5-b363-a0fb360afcf3", + "last_modified": 1701186751539 + }, + { + "name": "vocab.bgen.spm", + "schema": 1700620188212, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "bg", + "attachment": { + "hash": "24ce87ba39216714f222ca6a105f30b1863a7ef8b58c9fafdc7a66184e9813a5", + "size": 920621, + "filename": "vocab.bgen.spm", + "location": "main-workspace/translations-models/6471c3e7-0c49-4615-93f0-6d7c1482f895.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "795900b7-1e54-4373-92c5-1b13a27528d8", + "last_modified": 1701186751535 + }, + { + "name": "vocab.fren.spm", + "schema": 1700620191895, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "fr", + "attachment": { + "hash": "4c84b95b62c930f0791466d73eb996841eef474c96d0c2f0e6c6d80640f2005a", + "size": 831382, + "filename": "vocab.fren.spm", + "location": "main-workspace/translations-models/3096e2ad-89fe-4f9b-aac7-57f011bd4c49.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "9cbcea61-5556-4a1f-8bbd-733ccc5d974c", + "last_modified": 1701186751532 + }, + { + "name": "model.fren.intgemm.alphas.bin", + "schema": 1700620195569, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "fr", + "attachment": { + "hash": "185f76d24c2d400fe4ea0cb2487df77722641b97a3ef10633872e8a7fdf40e09", + "size": 17140961, + "filename": "model.fren.intgemm.alphas.bin", + "location": "main-workspace/translations-models/e8e44aae-f20c-48c7-89c8-dade397a5858.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "0c582563-cd4b-439d-b885-226b761601e1", + "last_modified": 1701186751528 + }, + { + "name": "lex.50.50.fren.s2t.bin", + "schema": 1700620201934, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "fr", + "attachment": { + "hash": "3148abf21ea98a4d69d0e4504e0d68a6c060204a9b9a39b76855aee1d5b2c8ea", + "size": 8818768, + "filename": "lex.50.50.fren.s2t.bin", + "location": "main-workspace/translations-models/75287f2c-8f15-4045-a435-ec1b9394d973.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "68cfadab-bf33-4f78-829c-8bf862f359a9", + "last_modified": 1701186751525 + }, + { + "name": "model.deen.intgemm.alphas.bin", + "schema": 1700620206730, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "de", + "attachment": { + "hash": "1980225d00dc5645491777accff5b3c9d20b92eff67a25135f1cf8fe2ed8fb8f", + "size": 17140837, + "filename": "model.deen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/8e3df11a-c2fc-422a-bc2d-ae234dab44c9.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "f1228f72-31a7-400a-b64a-feb5ec58d2de", + "last_modified": 1701186751522 + }, + { + "name": "lex.50.50.deen.s2t.bin", + "schema": 1700620213986, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "de", + "attachment": { + "hash": "2f7c0f7bbce97ae5b52454074a892ba7b7610fb98e3c5d341e4ca79f0850c4de", + "size": 5047568, + "filename": "lex.50.50.deen.s2t.bin", + "location": "main-workspace/translations-models/e2f7beb4-b45d-49da-b83e-ff421ad1268c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "f26b8b40-8450-4e43-84e0-ced79b25581c", + "last_modified": 1701186751519 + }, + { + "name": "vocab.deen.spm", + "schema": 1700620218890, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "de", + "attachment": { + "hash": "417668f2ed297970febafb5b079a9d5ebc4ed0b3550ac8386d67a90473a09bd7", + "size": 784269, + "filename": "vocab.deen.spm", + "location": "main-workspace/translations-models/9fb51529-8473-4138-a9e0-0a588ab574f0.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "8030ef2b-0eb8-41d1-bce5-f3e0eb9de451", + "last_modified": 1701186751515 + }, + { + "name": "vocab.enfr.spm", + "schema": 1700620222551, + "toLang": "fr", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "4c84b95b62c930f0791466d73eb996841eef474c96d0c2f0e6c6d80640f2005a", + "size": 831382, + "filename": "vocab.enfr.spm", + "location": "main-workspace/translations-models/808114a9-eff2-47c3-a981-b7119105c40c.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "11b49e91-9ec5-4c04-ae50-82008ff843eb", + "last_modified": 1701186751512 + }, + { + "name": "lex.50.50.enfr.s2t.bin", + "schema": 1700620226577, + "toLang": "fr", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "38fb44bad1fd5f1e6bfdcf15cc8baa09d61aad2a4f9c587914e24e7b5c25c32c", + "size": 7886500, + "filename": "lex.50.50.enfr.s2t.bin", + "location": "main-workspace/translations-models/7178ceaa-3ae6-49cb-a7f0-a72bdded2d9b.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "14fb4efb-9248-4f99-8059-5ac2fa61334d", + "last_modified": 1701186751508 + }, + { + "name": "model.enfr.intgemm.alphas.bin", + "schema": 1700620231995, + "toLang": "fr", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "0678019c4d74c8c81d2de17e3e58d3aba5f5eb48f5595d9240c17f69d30461de", + "size": 17140961, + "filename": "model.enfr.intgemm.alphas.bin", + "location": "main-workspace/translations-models/762a23ff-317e-4f0d-95ff-e9d95674a521.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "63f0fb05-1ab4-4f1f-b08e-c0a6ce680d82", + "last_modified": 1701186751505 + }, + { + "name": "model.enes.intgemm.alphas.bin", + "schema": 1700620238065, + "toLang": "es", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "fa7460037a3163e03fe1d23602f964bff2331da6ee813637e092ddf37156ef53", + "size": 17140755, + "filename": "model.enes.intgemm.alphas.bin", + "location": "main-workspace/translations-models/50fbae09-5219-4393-ad75-28b23f44a17d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "d643e68a-e298-40b2-9a33-2b1abc5478fe", + "last_modified": 1701186751501 + }, + { + "name": "lex.50.50.enes.s2t.bin", + "schema": 1700620243934, + "toLang": "es", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "3a113d713dec3cf1d12bba5b138ae616e28bba4bbc7fe7fd39ba145e26b86d7f", + "size": 3347104, + "filename": "lex.50.50.enes.s2t.bin", + "location": "main-workspace/translations-models/d2d8de5b-b41e-4eb0-b978-dd48725e3e77.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "af020722-1e04-4a97-b98b-2dd5e01e3561", + "last_modified": 1701186751498 + }, + { + "name": "vocab.enes.spm", + "schema": 1700620248123, + "toLang": "es", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "909b1eea1face0d7f90a474fe29a8c0fef8d104b6e41e65616f864c964ba8845", + "size": 825463, + "filename": "vocab.enes.spm", + "location": "main-workspace/translations-models/e9c774ba-69ff-4951-8783-895aeeb05439.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "c153c235-817b-498d-b658-68b14257cf5a", + "last_modified": 1701186751494 + }, + { + "name": "qualityModel.enes.bin", + "schema": 1700620251765, + "toLang": "es", + "version": "1.0", + "fileType": "qualityModel", + "fromLang": "en", + "attachment": { + "hash": "ce141f8e9e50a5ef4d8e3243a274b1734dc532f6963794a8869dce35acb543c2", + "size": 68, + "filename": "qualityModel.enes.bin", + "location": "main-workspace/translations-models/bf47c1e1-b01e-40e4-ad4b-b16edec5510f.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "f0accb47-36ef-4996-98df-8c59de49bc0c", + "last_modified": 1701186751491 + }, + { + "name": "lex.50.50.esen.s2t.bin", + "schema": 1700620254899, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "es", + "attachment": { + "hash": "f11a2c23ef85ab1fee1c412b908d69bc20d66fd59faa8f7da5a5f0347eddf969", + "size": 3860888, + "filename": "lex.50.50.esen.s2t.bin", + "location": "main-workspace/translations-models/54c5d8e2-3900-4a31-860e-8a18f7d028b5.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "1354d6ea-621e-4345-92ed-ae59e9b93259", + "last_modified": 1701186751487 + }, + { + "name": "vocab.esen.spm", + "schema": 1700620258998, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "es", + "attachment": { + "hash": "909b1eea1face0d7f90a474fe29a8c0fef8d104b6e41e65616f864c964ba8845", + "size": 825463, + "filename": "vocab.esen.spm", + "location": "main-workspace/translations-models/cab5e093-7b55-47ea-a247-9747cc0109e3.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "ce27f34e-bcef-40d1-991e-b7f3ef967a6a", + "last_modified": 1701186751484 + }, + { + "name": "model.esen.intgemm.alphas.bin", + "schema": 1700620262672, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "es", + "attachment": { + "hash": "4b6b7f451094aaa447d012658af158ffc708fc8842dde2f871a58404f5457fe0", + "size": 17140755, + "filename": "model.esen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/9ee26e91-9b52-44ba-8d30-c0230dd587b2.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "b1ca7c9f-25ab-4a26-9998-0c74a3dbaec2", + "last_modified": 1701186751481 + }, + { + "name": "lex.50.50.enit.s2t.bin", + "schema": 1700620269589, + "toLang": "it", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "351ea80fb9f366f07533c7c4836248e72d9d4aa4eb7a05b5d74891a7abb4208c", + "size": 4495004, + "filename": "lex.50.50.enit.s2t.bin", + "location": "main-workspace/translations-models/d683f5a2-7a75-444c-a581-22b3c3cfaecb.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "c45e67c9-6e1a-45b8-adac-81066e178eab", + "last_modified": 1701186751477 + }, + { + "name": "model.enit.intgemm.alphas.bin", + "schema": 1700620273718, + "toLang": "it", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "3d7bbc4d7977e10b35f53faa79f5d5de8211f4f04baed9e7cd9dee1dcceda917", + "size": 17140899, + "filename": "model.enit.intgemm.alphas.bin", + "location": "main-workspace/translations-models/efb14526-aff4-4826-8cf6-12e2bc44ac7d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "3860d554-3be9-417e-acf8-7070db7cffae", + "last_modified": 1701186751474 + }, + { + "name": "vocab.enit.spm", + "schema": 1700620279728, + "toLang": "it", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "603f3349657c3deb9736a0c567452d102a5a03c377dfdf1d32c428608f2cff1b", + "size": 812781, + "filename": "vocab.enit.spm", + "location": "main-workspace/translations-models/8d28b170-7607-4834-a548-2a1244f35576.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "5948dfcd-ffa3-4d5d-af3b-6e8ab2651225", + "last_modified": 1701186751470 + }, + { + "name": "vocab.enbg.spm", + "schema": 1700620283359, + "toLang": "bg", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "b14e44beb653db924c826e1696bcfab23ca9fd3e479baf8bea67d0be77432192", + "size": 919745, + "filename": "vocab.enbg.spm", + "location": "main-workspace/translations-models/394361e9-26df-46d9-b092-91952b70350b.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "739552b2-5855-4113-b49f-9a1e02525453", + "last_modified": 1701186751467 + }, + { + "name": "lex.50.50.enbg.s2t.bin", + "schema": 1700620287115, + "toLang": "bg", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "0f9b794b6f8a9c4b5b781fde49391852b398184b730f89a09428cf562e8bede6", + "size": 5607608, + "filename": "lex.50.50.enbg.s2t.bin", + "location": "main-workspace/translations-models/793ae4c6-6689-40c6-ac76-dea8afac39b6.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "72434340-fd4c-4f6c-a2a2-269880d1f413", + "last_modified": 1701186751464 + }, + { + "name": "model.enbg.intgemm.alphas.bin", + "schema": 1700620291508, + "toLang": "bg", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "02715a7a81a610a37439d4f788a6f3efcc1ecb39618bc4184442a39378907dfe", + "size": 17140899, + "filename": "model.enbg.intgemm.alphas.bin", + "location": "main-workspace/translations-models/23faa0b3-fa97-4e8a-a1db-28b8f2f48ea8.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "259132ab-a24c-44f6-8b07-b1f701086e23", + "last_modified": 1701186751460 + }, + { + "name": "vocab.pten.spm", + "schema": 1700620297755, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "pt", + "attachment": { + "hash": "2326a27577b5ffa0b822c55fe850986961728097521de35fcc186f7d6dce72d4", + "size": 817234, + "filename": "vocab.pten.spm", + "location": "main-workspace/translations-models/75fa56af-540e-4a56-8a9f-1317ae7a9c61.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "0ee58420-3905-41dc-8a5e-2cd00b7e11b5", + "last_modified": 1701186751457 + }, + { + "name": "lex.50.50.pten.s2t.bin", + "schema": 1700620301416, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "pt", + "attachment": { + "hash": "702d9b09fbc020168998bb81afc927e74262f5a1455e9b443608a5d8efa4b52d", + "size": 4801740, + "filename": "lex.50.50.pten.s2t.bin", + "location": "main-workspace/translations-models/013e0ebf-3d6b-4723-b83e-0e00ed29477f.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "765cc652-9de8-4769-bff7-d1e3b734dc6e", + "last_modified": 1701186751453 + }, + { + "name": "model.pten.intgemm.alphas.bin", + "schema": 1700620305629, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "pt", + "attachment": { + "hash": "b4a1fd101f59ca258bfb051a3a4e5762497b9f91d7e74f28b7a2d68dc075f8fa", + "size": 17140899, + "filename": "model.pten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/dc4327ec-9ebc-4c12-8037-48cd30f3076d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "5ee67741-24f7-4ce6-9383-429358d3c9cb", + "last_modified": 1701186751450 + }, + { + "name": "vocab.ennl.spm", + "schema": 1700620312570, + "toLang": "nl", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "43ba3922c3bba2b76ca2e2124837c96518b0e31300b7d6d5ccce55ee10d86393", + "size": 807541, + "filename": "vocab.ennl.spm", + "location": "main-workspace/translations-models/18a0a000-37d5-45df-8952-6c19e0e65458.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "949dfa1f-999a-42e2-98ff-a6b7a0446f64", + "last_modified": 1701186751447 + }, + { + "name": "lex.50.50.ennl.s2t.bin", + "schema": 1700620316213, + "toLang": "nl", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "f780a6d74af4b141f551dcc0da56bab44a05a90ef53d63381269710f35eaa41b", + "size": 4494892, + "filename": "lex.50.50.ennl.s2t.bin", + "location": "main-workspace/translations-models/3055493f-e59d-41f8-a681-823e3ab5ef8d.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "35fba39c-bcec-4963-937d-9cdb7ad46eb4", + "last_modified": 1701186751444 + }, + { + "name": "model.ennl.intgemm.alphas.bin", + "schema": 1700620320391, + "toLang": "nl", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "906690a58a0d72aff28bd4b941cbd0984d1e0a62958c0b21aebae378a656d822", + "size": 17140899, + "filename": "model.ennl.intgemm.alphas.bin", + "location": "main-workspace/translations-models/668ec699-5377-452e-89b9-7195d58f0047.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "6f240eb1-186b-4a51-ab45-e74234ab7c49", + "last_modified": 1701186751441 + }, + { + "name": "lex.50.50.nlen.s2t.bin", + "schema": 1700620327542, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "nl", + "attachment": { + "hash": "15c473e6e5aef1fa3fe5cc804844e19a2c671d2e39169dcbc06f456ca4f9b2c0", + "size": 4940304, + "filename": "lex.50.50.nlen.s2t.bin", + "location": "main-workspace/translations-models/eeba1c98-e44a-4d97-8b5c-44c0a56edc3c.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "ad89adbb-eb3e-41ba-ac14-da6e541f1166", + "last_modified": 1701186751438 + }, + { + "name": "vocab.nlen.spm", + "schema": 1700620331918, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "nl", + "attachment": { + "hash": "48a6331cb8a69d21dd24c6c2a15de44a07bcfd4a4470eaf4616849d77945f6b9", + "size": 807730, + "filename": "vocab.nlen.spm", + "location": "main-workspace/translations-models/e16e52fe-1c21-48ab-9b7f-af1183a99d1a.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "3c746657-721b-4e87-b26d-b11fbe186036", + "last_modified": 1701186751434 + }, + { + "name": "model.nlen.intgemm.alphas.bin", + "schema": 1700620335563, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "nl", + "attachment": { + "hash": "187f9c8f1ffbcc8ecf0724b608ffed9f15bde262b9de5c4754f8d763b585969d", + "size": 17140899, + "filename": "model.nlen.intgemm.alphas.bin", + "location": "main-workspace/translations-models/1db60932-8656-41a8-a00f-191eb925cef8.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "90180eb5-ce3d-4d7c-827c-fd28f6b3a35e", + "last_modified": 1701186751431 + }, + { + "name": "lex.50.50.enpt.s2t.bin", + "schema": 1700620342874, + "toLang": "pt", + "version": "1.0", + "fileType": "lex", + "fromLang": "en", + "attachment": { + "hash": "dd0e64865277f0a1cf15a26a5b50e9e0570235afd8dab7c5e0249ad7057531d6", + "size": 4345620, + "filename": "lex.50.50.enpt.s2t.bin", + "location": "main-workspace/translations-models/be3a2e24-b12e-4c0e-b07a-c7b0ba6ab421.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "edc756e0-15d8-4c31-846e-9b0abd90004b", + "last_modified": 1701186751428 + }, + { + "name": "vocab.enpt.spm", + "schema": 1700620347099, + "toLang": "pt", + "version": "1.0", + "fileType": "vocab", + "fromLang": "en", + "attachment": { + "hash": "2326a27577b5ffa0b822c55fe850986961728097521de35fcc186f7d6dce72d4", + "size": 817234, + "filename": "vocab.enpt.spm", + "location": "main-workspace/translations-models/745bff57-f929-41d9-8f0f-913513cfd334.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "d5b514e5-8c8f-46c0-89dc-6a12dec9d5bc", + "last_modified": 1701186751425 + }, + { + "name": "model.enpt.intgemm.alphas.bin", + "schema": 1700620350781, + "toLang": "pt", + "version": "1.0", + "fileType": "model", + "fromLang": "en", + "attachment": { + "hash": "8fb05a27509bea3f67d2f59506485584d5cdbdcafa82b251576c27e91bd7011e", + "size": 17140899, + "filename": "model.enpt.intgemm.alphas.bin", + "location": "main-workspace/translations-models/b268bf87-94b6-4893-9da1-c4e75284ace7.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "f95b773e-a503-4af3-ac08-5ef89fc3a01d", + "last_modified": 1701186751422 + }, + { + "name": "model.iten.intgemm.alphas.bin", + "schema": 1700620356991, + "toLang": "en", + "version": "1.0", + "fileType": "model", + "fromLang": "it", + "attachment": { + "hash": "7dfdf189146d9353fdea264b9e4c8ac36441c770dc4353a8380b64e589dc035b", + "size": 17140899, + "filename": "model.iten.intgemm.alphas.bin", + "location": "main-workspace/translations-models/1cf6a2f6-f430-4119-98d5-1ec0617beb0f.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "71022281-b123-4ab9-8660-8aac392cba4d", + "last_modified": 1701186751419 + }, + { + "name": "lex.50.50.iten.s2t.bin", + "schema": 1700620363012, + "toLang": "en", + "version": "1.0", + "fileType": "lex", + "fromLang": "it", + "attachment": { + "hash": "e30ec549bd0da9ac42cccdcd3806d3be84d485f7fd329f90f6e40ee027e841d9", + "size": 4977500, + "filename": "lex.50.50.iten.s2t.bin", + "location": "main-workspace/translations-models/b38e032b-1b1a-4899-a0e5-80ef69a06814.bin", + "mimetype": "application/octet-stream" + }, + "filter_expression": "", + "id": "50b927ae-829e-49e0-aa74-25e72a76f651", + "last_modified": 1701186751415 + }, + { + "name": "vocab.iten.spm", + "schema": 1700620367366, + "toLang": "en", + "version": "1.0", + "fileType": "vocab", + "fromLang": "it", + "attachment": { + "hash": "603f3349657c3deb9736a0c567452d102a5a03c377dfdf1d32c428608f2cff1b", + "size": 812781, + "filename": "vocab.iten.spm", + "location": "main-workspace/translations-models/fbc18b06-35c1-47fa-b2db-5b792366471b.spm", + "mimetype": "text/plain" + }, + "filter_expression": "", + "id": "a4f8f941-8a34-4d9d-915c-9560ef0765ca", + "last_modified": 1701186751412 + } + ], + "timestamp": 1710173317976 +} diff --git a/services/settings/dumps/main/translations-wasm.json b/services/settings/dumps/main/translations-wasm.json new file mode 100644 index 0000000000..3375e13b10 --- /dev/null +++ b/services/settings/dumps/main/translations-wasm.json @@ -0,0 +1,78 @@ +{ + "data": [ + { + "name": "bergamot-translator", + "schema": 1705698520049, + "license": "MPL-2.0", + "release": "v0.4.5", + "version": "1.1", + "revision": "05a87784973b6e1cc591f1f1a9a05c5873d9971e", + "attachment": { + "hash": "9bfb4cc9dc176c7cbaa6b2890d9f6868c0ada6f44168a0bb0903dbfb88278d92", + "size": 5188872, + "filename": "bergamot-translator-worker.wasm", + "location": "main-workspace/translations-wasm/76ec99f8-04d5-47df-8c52-a3a38d864141.wasm", + "mimetype": "application/wasm" + }, + "fx_release": "122.0a1", + "filter_expression": "", + "id": "73c6f691-ba5e-4c98-9622-820a2cde16c0", + "last_modified": 1705701099603 + }, + { + "name": "fasttext-wasm", + "schema": 1681248641301, + "license": "MIT", + "release": "v0.9.2", + "version": "1.0", + "revision": "3697152e0fd772d9185697fdbd4a1d340ca5571d", + "attachment": { + "hash": "ef84129d43648c3f0bded0e7cc3cb03b8855f22c7a1ab9d5515474b62ff3110e", + "size": 953921, + "filename": "fasttext_wasm.wasm", + "location": "main-workspace/translations-wasm/5ee35b78-d0d4-43b0-8bee-e40fe57726b3.wasm", + "mimetype": "application/wasm" + }, + "filter_expression": "", + "id": "a51c66f6-d8af-11ed-a7b4-dbd50622f5f6", + "last_modified": 1681500422552 + }, + { + "name": "bergamot-translator", + "schema": 1681242478400, + "license": "MPL-2.0", + "release": "v0.4.4", + "version": "1.0", + "revision": "5ae1b1ebb3fa9a3eabed8a64ca6798154bd486eb", + "attachment": { + "hash": "dad40ec05e6e26f6df56aa37f0d3a208adcd5d35d3347e0f9a8a267c89d4f947", + "size": 5175863, + "filename": "bergamot-translator.wasm", + "location": "main-workspace/translations-wasm/3453d482-85f3-43f2-bdb9-cf86228b721b.wasm", + "mimetype": "application/wasm" + }, + "filter_expression": "", + "id": "9aaec48d-7855-4fd5-88d7-9213747e9ef5", + "last_modified": 1681500422546 + }, + { + "name": "fasttext-wasm", + "schema": 1681242495582, + "license": "MIT", + "release": "v0.9.2", + "version": "0.1", + "revision": "3697152e0fd772d9185697fdbd4a1d340ca5571d", + "attachment": { + "hash": "fa3146187eb393d2e3a2d00ac004133e6701136d132ea5124449d54a60358959", + "size": 875075, + "filename": "fasttext_wasm.wasm", + "location": "main-workspace/translations-wasm/a9bf0ece-6a35-40a7-9813-2e3ecaa7d7f9.wasm", + "mimetype": "application/wasm" + }, + "filter_expression": "", + "id": "ecf7c0f8-70e2-4057-a205-33bb160871f3", + "last_modified": 1681500422542 + } + ], + "timestamp": 1705701099603 +} diff --git a/services/settings/dumps/main/url-classifier-skip-urls.json b/services/settings/dumps/main/url-classifier-skip-urls.json new file mode 100644 index 0000000000..54e7371601 --- /dev/null +++ b/services/settings/dumps/main/url-classifier-skip-urls.json @@ -0,0 +1,20 @@ +{ + "data": [ + { + "schema": 1700179207170, + "feature": "fingerprinting-protection", + "pattern": "*.geetest.com", + "id": "f2a88631-c444-49d1-b377-c25608ef0018", + "last_modified": 1701090424142 + }, + { + "schema": 1701082018913, + "feature": "tracking-annotation", + "pattern": "d3vox9szr7t2nm.cloudfront.net", + "filter_expression": "env.version < '122'", + "id": "01eb9187-ab15-4851-a014-441381e3cd02", + "last_modified": 1701090424139 + } + ], + "timestamp": 1701090424142 +} diff --git a/services/settings/dumps/main/websites-with-shared-credential-backends.json b/services/settings/dumps/main/websites-with-shared-credential-backends.json new file mode 100644 index 0000000000..58eed99d19 --- /dev/null +++ b/services/settings/dumps/main/websites-with-shared-credential-backends.json @@ -0,0 +1,744 @@ +{ + "data": [ + { + "relatedRealms": [ + [ + "3docean.net", + "audiojungle.net", + "codecanyon.net", + "envato.com", + "graphicriver.net", + "photodune.net", + "placeit.net", + "themeforest.net", + "tutsplus.com", + "videohive.net" + ], + [ + "aa.com", + "americanairlines.com", + "americanairlines.jp" + ], + [ + "aetna.com", + "banneraetna.myplanportal.com" + ], + [ + "airbnb.com.ar", + "airbnb.com.au", + "airbnb.at", + "airbnb.be", + "airbnb.com.bz", + "airbnb.com.bo", + "airbnb.com.br", + "airbnb.ca", + "airbnb.cl", + "airbnb.com.co", + "airbnb.co.cr", + "airbnb.cz", + "airbnb.dk", + "airbnb.com.ec", + "airbnb.com.sv", + "airbnb.fi", + "airbnb.fr", + "airbnb.de", + "airbnb.gr", + "airbnb.com.gt", + "airbnb.gy", + "airbnb.com.hn", + "airbnb.com.hk", + "airbnb.hu", + "airbnb.is", + "airbnb.co.in", + "airbnb.co.id", + "airbnb.ie", + "airbnb.it", + "airbnb.jp", + "airbnb.com.my", + "airbnb.com.mt", + "airbnb.mx", + "airbnb.nl", + "airbnb.co.nz", + "airbnb.com.ni", + "airbnb.no", + "airbnb.com.pa", + "airbnb.com.py", + "airbnb.com.pe", + "airbnb.pl", + "airbnb.pt", + "airbnb.ru", + "airbnb.com.sg", + "airbnb.co.kr", + "airbnb.es", + "airbnb.se", + "airbnb.ch", + "airbnb.com.tw", + "airbnb.com.tr", + "airbnb.co.uk", + "airbnb.com", + "airbnb.co.ve" + ], + [ + "airfrance.com", + "klm.com", + "flyingblue.com" + ], + [ + "airnewzealand.co.nz", + "airnewzealand.com", + "airnewzealand.com.au" + ], + [ + "albertsons.com", + "acmemarkets.com", + "carrsqc.com", + "jewelosco.com", + "pavilions.com", + "randalls.com", + "safeway.com", + "shaws.com", + "starmarket.com", + "tomthumb.com", + "vons.com" + ], + [ + "alibaba.com", + "aliexpress.com" + ], + [ + "alltrails.com", + "alltrails.io" + ], + [ + "amazon.com", + "amazon.ae", + "amazon.com.au", + "amazon.com.br", + "amazon.ca", + "amazon.fr", + "amazon.de", + "amazon.in", + "amazon.it", + "amazon.com.mx", + "amazon.nl", + "amazon.es", + "amazon.com.tr", + "amazon.co.uk", + "amazon.sa", + "amazon.sg", + "amazon.se", + "amazon.pl", + "ring.com" + ], + [ + "amcrestcloud.com", + "amcrestview.com" + ], + [ + "americastestkitchen.com", + "cooksillustrated.com", + "cookscountry.com", + "onlinecookingschool.com" + ], + [ + "ameritrade.com", + "tdameritrade.com" + ], + [ + "anthem.com", + "sydneyhealth.com" + ], + [ + "anylist.com", + "anylistapp.com" + ], + [ + "appannie.com", + "data.ai" + ], + [ + "apple.com", + "icloud.com" + ], + [ + "atlassian.com", + "trello.com" + ], + [ + "att.com", + "att.net" + ], + [ + "audi.com", + "audiusa.com" + ], + [ + "bahn.de", + "bahn.com" + ], + [ + "battle.net", + "blizzard.com" + ], + [ + "beachbodyondemand.com", + "teambeachbody.com" + ], + [ + "beavercreek.com", + "breckenridge.com", + "epicpass.com", + "keystoneresort.com", + "kirkwood.com", + "mountsunapee.com", + "northstarcalifornia.com", + "okemo.com", + "parkcitymountain.com", + "skicb.com", + "skiheavenly.com", + "snow.com", + "stevenspass.com", + "stowe.com", + "vail.com", + "whistlerblackcomb.com" + ], + [ + "boingo.com", + "boingohotspot.com" + ], + [ + "bol.com", + "kobo.com" + ], + [ + "boudinbakery.com", + "boudincatering.com" + ], + [ + "braze.com", + "braze.eu" + ], + [ + "capitalone.com", + "capitalone360.com" + ], + [ + "cathaypacific.com", + "asiamiles.com" + ], + [ + "centralfcu.org", + "centralfcu.com" + ], + [ + "citi.com", + "citibank.com", + "citibankonline.com" + ], + [ + "comcast.net", + "xfinity.com" + ], + [ + "coolblue.nl", + "coolblue.be", + "coolblue.de" + ], + [ + "curbed.com", + "grubstreet.com", + "nymag.com", + "thecut.com", + "vulture.com" + ], + [ + "dinersclubnorthamerica.com", + "dinersclubus.com" + ], + [ + "discordapp.com", + "discord.com" + ], + [ + "discordmerch.com", + "discord.store" + ], + [ + "dish.com", + "mydish.com", + "dishnetwork.com" + ], + [ + "docusign.com", + "docusign.net" + ], + [ + "dropbox.com", + "getdropbox.com" + ], + [ + "eater.com", + "polygon.com", + "sbnation.com", + "theverge.com" + ], + [ + "ebay.at", + "ebay.be", + "ebay.ca", + "ebay.ch", + "ebay.cn", + "ebay.co.th", + "ebay.co.uk", + "ebay.com", + "ebay.com.au", + "ebay.com.hk", + "ebay.com.my", + "ebay.com.sg", + "ebay.com.tw", + "ebay.de", + "ebay.es", + "ebay.fr", + "ebay.ie", + "ebay.it", + "ebay.nl", + "ebay.ph", + "ebay.pl", + "ebay.vn" + ], + [ + "eurosport.no", + "eurosportplayer.com" + ], + [ + "eventbrite.at", + "eventbrite.be", + "eventbrite.ca", + "eventbrite.ch", + "eventbrite.cl", + "eventbrite.co", + "eventbrite.com", + "eventbrite.de", + "eventbrite.dk", + "eventbrite.es", + "eventbrite.fi", + "eventbrite.fr", + "eventbrite.hk", + "eventbrite.ie", + "eventbrite.in", + "eventbrite.it", + "eventbrite.my", + "eventbrite.nl", + "eventbrite.ph", + "eventbrite.pt", + "eventbrite.se", + "eventbrite.sg" + ], + [ + "facebook.com", + "messenger.com" + ], + [ + "fandangonow.com", + "fandango.com" + ], + [ + "fidelity.com", + "fidelityinvestments.com" + ], + [ + "flyblade.com", + "blade.com" + ], + [ + "fnac.com", + "fnacspectacles.com" + ], + [ + "fourleaf.net", + "fourleaf.cl" + ], + [ + "foursquare.com", + "swarmapp.com" + ], + [ + "glassdoor.ca", + "glassdoor.com", + "glassdoor.com.ar" + ], + [ + "gogoair.com", + "gogoinflight.com" + ], + [ + "hbo.com", + "hbomax.com", + "hbonow.com" + ], + [ + "igen.fr", + "watchgeneration.fr", + "macg.co" + ], + [ + "ikonpass.com", + "skilynx.com" + ], + [ + "ing.de", + "ing.com" + ], + [ + "intuit.com", + "mint.com" + ], + [ + "kaiserpermanente.org", + "kp.org" + ], + [ + "kclibrary.overdrive.com", + "kclibrary.bibliocommons.com" + ], + [ + "kcls.bibliocommons.com", + "kcls.overdrive.com", + "kcls.org" + ], + [ + "liebherr.com", + "myliebherr.com" + ], + [ + "logitech.com", + "logitechg.com", + "logi.com", + "astrogaming.com", + "ultimateears.com" + ], + [ + "lookmark.io", + "lookmark.link" + ], + [ + "lrz.de", + "mwn.de", + "mytum.de", + "tum.de" + ], + [ + "lufthansa.com", + "miles-and-more.com" + ], + [ + "marriott.com", + "marriottrewards.com", + "ritzcarlton.com", + "spg.com", + "starwoodhotels.com" + ], + [ + "microsoft.com", + "live.com", + "microsoftonline.com", + "office.com", + "skype.com", + "onenote.com", + "hotmail.com" + ], + [ + "minecraft.net", + "mojang.com" + ], + [ + "mytotalconnectcomfort.com", + "tccna.honeywell.com" + ], + [ + "myuhc.com", + "uhc.com", + "optum.com", + "optumrx.com" + ], + [ + "neatorama.com", + "neatoshop.com" + ], + [ + "newyorker.com", + "vanityfair.com" + ], + [ + "nintendolife.com", + "purexbox.com", + "pushsquare.com" + ], + [ + "nokia.com", + "alcatel-lucent.com", + "nsn-rdnet.net", + "nsn.com" + ], + [ + "nordvpn.com", + "nordpass.com", + "nordaccount.com" + ], + [ + "norwegian.com", + "norwegianreward.com" + ], + [ + "olo.com", + "olo.express" + ], + [ + "pinterest.com", + "pinterest.ca", + "pinterest.co.uk", + "pinterest.fr", + "pinterest.de", + "pinterest.es", + "pinterest.com.au", + "pinterest.se", + "pinterest.ph", + "pinterest.ch", + "pinterest.com.mx", + "pinterest.dk", + "pinterest.pt", + "pinterest.ru", + "pinterest.it", + "pinterest.at", + "pinterest.jp", + "pinterest.cl", + "pinterest.ie", + "pinterest.co.kr", + "pinterest.nz" + ], + [ + "pocket.com", + "getpocket.com" + ], + [ + "postnl.nl", + "postnl.be" + ], + [ + "probikeshop.fr", + "bikeshop.es", + "probikeshop.it", + "probikeshop.pt", + "probikeshop.com" + ], + [ + "protonmail.com", + "protonvpn.com" + ], + [ + "qnap.com", + "myqnapcloud.com" + ], + [ + "questdiagnostics.com", + "care360.com" + ], + [ + "rocketaccount.com", + "rocketmortgage.com" + ], + [ + "scholarshare529.com", + "secureaccountview.com" + ], + [ + "scoutingevent.com", + "campreservation.com" + ], + [ + "scribbr.com", + "scribbr.de", + "scribbr.dk", + "scribbr.es", + "scribbr.fi", + "scribbr.fr", + "scribbr.it", + "scribbr.nl", + "scribbr.no", + "scribbr.se" + ], + [ + "seattle.bibliocommons.com", + "spl.overdrive.com", + "spl.org" + ], + [ + "sfpl.bibliocommons.com", + "sfpl.overdrive.com" + ], + [ + "shopdisney.com", + "go.com", + "disneyplus.com", + "espn.com", + "disneystore.com" + ], + [ + "slcl.overdrive.com", + "slcl.org" + ], + [ + "slpl.bibliocommons.com", + "slpl.overdrive.com" + ], + [ + "sonyentertainmentnetwork.com", + "sony.com" + ], + [ + "spark.net", + "jdate.com" + ], + [ + "springfield.overdrive.com", + "coolcat.org" + ], + [ + "square.com", + "squareup.com" + ], + [ + "stackoverflow.com", + "askubuntu.com", + "serverfault.com", + "stackexchange.com", + "superuser.com" + ], + [ + "steampowered.com", + "steamcommunity.com" + ], + [ + "telekom-dienste.de", + "accounts.login.idm.telekom.com" + ], + [ + "tesla.com", + "teslamotors.com" + ], + [ + "ticketmaster.com", + "livenation.com" + ], + [ + "tp-link.com", + "tplinkcloud.com" + ], + [ + "tvnow.de", + "tvnow.at", + "tvnow.ch", + "auth.rtl.de", + "rtlplus.de", + "rtlplus.com" + ], + [ + "umsystem.edu", + "mst.edu", + "umkc.edu", + "umsl.edu", + "missouri.edu" + ], + [ + "united.com", + "unitedwifi.com" + ], + [ + "uspowerboating.com", + "ussailing.org" + ], + [ + "verizon.com", + "verizonwireless.com", + "vzw.com" + ], + [ + "wayfair.com", + "wayfair.ca", + "jossandmain.com", + "allmodern.com", + "perigold.com", + "birchlane.com" + ], + [ + "wellsfargo.com", + "wellsfargoadvisors.com" + ], + [ + "wiimmfi.de", + "wii-homebrew.com" + ], + [ + "wikipedia.org", + "mediawiki.org", + "wikibooks.org", + "wikidata.org", + "wikinews.org", + "wikiquote.org", + "wikisource.org", + "wikiversity.org", + "wikivoyage.org", + "wiktionary.org", + "commons.wikimedia.org", + "meta.wikimedia.org", + "incubator.wikimedia.org", + "outreach.wikimedia.org", + "species.wikimedia.org", + "wikimania.wikimedia.org" + ], + [ + "williams-sonoma.com", + "markandgraham.com", + "potterybarn.com", + "westelm.com" + ], + [ + "wilson.com", + "slugger.com", + "atecsports.com", + "demarini.com", + "evoshield.com", + "luxilon.com" + ], + [ + "worldlink.com.np", + "nettv.com.np" + ], + [ + "wsj.com", + "dowjones.com" + ], + [ + "www.seek.com.au", + "www.seek.co.nz", + "login.seek.com" + ], + [ + "www.vistaprint.ca", + "account.vistaprint.com" + ], + [ + "yahoo.com", + "flickr.com" + ], + [ + "zixmail.net", + "zixmessagecenter.com" + ] + ], + "id": "8c3d4151-8e68-4bb3-a3fd-babf4aba2cdc", + "last_modified": 1659924446436 + } + ], + "timestamp": 1659924446436 +} diff --git a/services/settings/dumps/moz.build b/services/settings/dumps/moz.build new file mode 100644 index 0000000000..f407580bfa --- /dev/null +++ b/services/settings/dumps/moz.build @@ -0,0 +1,16 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + "blocklists", + "main", + "security-state", +] + +dump_summary = "last_modified.json" +GeneratedFile(dump_summary, script="gen_last_modified.py") +FINAL_TARGET_FILES.defaults.settings += ["!%s" % dump_summary] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DIST_SUBDIR = "browser" diff --git a/services/settings/dumps/readme.md b/services/settings/dumps/readme.md new file mode 100644 index 0000000000..8daea50525 --- /dev/null +++ b/services/settings/dumps/readme.md @@ -0,0 +1,12 @@ +# Remote Settings Initial Data + +In order to reduce the amount of data to be downloaded on first synchronization, +a JSON dump from the records present on the remote server can be shipped with the +release. + +A bot will update the files automatically. For collections that should not be kept +in sync, put the JSON dumps in ../static-dumps/ instead. + +Dumps from dumps/ and static-dumps/ are packaged into the same resource path, +thus looking the same to the client code and also implying that filenames must +be unique between the two directories. diff --git a/services/settings/dumps/security-state/intermediates.json b/services/settings/dumps/security-state/intermediates.json new file mode 100644 index 0000000000..752d8fb270 --- /dev/null +++ b/services/settings/dumps/security-state/intermediates.json @@ -0,0 +1,30569 @@ +{ + "data": [ + { + "schema": 1710946446035, + "derHash": "8DZVz+hbOj8q8v03yrnqKDs6KKm2FX/pvrdcF8BhGzo=", + "subject": "CN=Keysec GR3 DV TLS CA 2024,O=KEYSEC LTDA,C=BR", + "subjectDN": "MEcxCzAJBgNVBAYTAkJSMRQwEgYDVQQKEwtLRVlTRUMgTFREQTEiMCAGA1UEAxMZS2V5c2VjIEdSMyBEViBUTFMgQ0EgMjAyNA==", + "whitelist": false, + "attachment": { + "hash": "fe495e9179292f67d50ca37e6347b3171d4a7cc6716d0ce1b782e000cedf5097", + "size": 1618, + "filename": "re4iHDQVFeEgZFfakl2IhF85P-DgLTbzfOV36u4F3FU=.pem", + "location": "security-state-staging/intermediates/3df9d148-6d1d-494e-8f77-d641a7d78124.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "re4iHDQVFeEgZFfakl2IhF85P+DgLTbzfOV36u4F3FU=", + "crlite_enrolled": false, + "id": "244051c7-07f4-4e57-9854-ab624aa55021", + "last_modified": 1710946623378 + }, + { + "schema": 1710946445643, + "derHash": "kXPvrqTmGFXrhY0QfbqUoc0YyuAOr5x+21cgGtmam/0=", + "subject": "CN=Keysec GR3 EV TLS CA 2024,O=KEYSEC LTDA,C=BR", + "subjectDN": "MEcxCzAJBgNVBAYTAkJSMRQwEgYDVQQKEwtLRVlTRUMgTFREQTEiMCAGA1UEAxMZS2V5c2VjIEdSMyBFViBUTFMgQ0EgMjAyNA==", + "whitelist": false, + "attachment": { + "hash": "9c2fd810de290c9602eaf84292356ccc60b5b9a86a64a6735918a60ce80152ad", + "size": 1634, + "filename": "5zd2ZxybEEZSlXlhcynmpWBuSSlSNb-L2hyf1iH-WVU=.pem", + "location": "security-state-staging/intermediates/09a3003d-6573-4bc0-8ed5-501a2960b9bb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5zd2ZxybEEZSlXlhcynmpWBuSSlSNb+L2hyf1iH+WVU=", + "crlite_enrolled": false, + "id": "361e1616-6414-4ae8-afca-b6e6a3ab544b", + "last_modified": 1710946623375 + }, + { + "schema": 1710946446503, + "derHash": "iv2h7XO/N13G4Jfbhxu/5M62jmE4HDm99qRXbcFxv7I=", + "subject": "CN=Keysec GR3 OV TLS CA 2024,O=KEYSEC LTDA,C=BR", + "subjectDN": "MEcxCzAJBgNVBAYTAkJSMRQwEgYDVQQKEwtLRVlTRUMgTFREQTEiMCAGA1UEAxMZS2V5c2VjIEdSMyBPViBUTFMgQ0EgMjAyNA==", + "whitelist": false, + "attachment": { + "hash": "5f922fcec6e5bdeeb7c72bf6cb4d7cb9712affcd3ca7b2edf439390a7bdceee1", + "size": 1618, + "filename": "ffyswDTRpdykWIYlQPDpRA5VFTJ6omaHxk5W69jO8JA=.pem", + "location": "security-state-staging/intermediates/59adb38b-7ec7-4ac1-afd9-e42cd392cf81.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ffyswDTRpdykWIYlQPDpRA5VFTJ6omaHxk5W69jO8JA=", + "crlite_enrolled": false, + "id": "662042eb-44f1-427f-b3a4-46109e55908c", + "last_modified": 1710946623372 + }, + { + "schema": 1710795242451, + "derHash": "la3INQ6EjZEg93vn1fpYdQhVO2Ua2jpCo/DIrck2PdI=", + "subject": "CN=MarketWare RSA Extended Validation Secure Server CA 3,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,C=PT", + "subjectDN": "MIGMMQswCQYDVQQGEwJQVDE9MDsGA1UECgw0TWFya2V0V2FyZSAtIFNvbHXDp8O1ZXMgcGFyYSBNZXJjYWRvcyBEaWdpdGFpcywgTGRhLjE+MDwGA1UEAxM1TWFya2V0V2FyZSBSU0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "2d24d3da735acff32c0c688d7156d6772dc4cddd374c7ed9401fc73faba750de", + "size": 2292, + "filename": "Es9zrpvheH6jWlnnG5GZKf4OGJttFUqxTOsCFLZlhko=.pem", + "location": "security-state-staging/intermediates/b9bcb2bc-23b6-4d66-afda-8f70b1181a4c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Es9zrpvheH6jWlnnG5GZKf4OGJttFUqxTOsCFLZlhko=", + "crlite_enrolled": false, + "id": "314cdc34-8ab8-4717-ad45-fa803357b3c6", + "last_modified": 1710795423423 + }, + { + "schema": 1710795242856, + "derHash": "yJWZUIAJLkaFrm1OHvp8QZExroD9yuH8qD2i+PmVEjY=", + "subject": "CN=K Software RSA Domain Validation Secure Server CA 3,O=K Software LLC,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5LIFNvZnR3YXJlIExMQzE8MDoGA1UEAxMzSyBTb2Z0d2FyZSBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "0069cb355fa82da3d0bbdf596110250a0d5e89b460a37831381349ecb5dcbc79", + "size": 2239, + "filename": "d7RXpz4TO5w7He2jqcJhDOe93JbF318mPnR2dLjAU3Q=.pem", + "location": "security-state-staging/intermediates/57b04cf4-27ff-42a9-8b4f-839f2b0f2eae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "d7RXpz4TO5w7He2jqcJhDOe93JbF318mPnR2dLjAU3Q=", + "crlite_enrolled": false, + "id": "7800aa03-e9e9-4949-a479-ca6be3172496", + "last_modified": 1710795423420 + }, + { + "schema": 1710795243216, + "derHash": "N53ZNUfsvhz2f32UrS/D3GLPFuZ31xITVl0KyZJ+5V4=", + "subject": "CN=MarketWare RSA Domain Validation Secure Server CA 3,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,C=PT", + "subjectDN": "MIGKMQswCQYDVQQGEwJQVDE9MDsGA1UECgw0TWFya2V0V2FyZSAtIFNvbHXDp8O1ZXMgcGFyYSBNZXJjYWRvcyBEaWdpdGFpcywgTGRhLjE8MDoGA1UEAxMzTWFya2V0V2FyZSBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "20ff6348a25369a532d720e964d6d0705902f3aba8e88dc10132304da34f25ba", + "size": 2292, + "filename": "cPk3iWChPK4OBkAMXZkVl0nHVot2WEoaEc4JShoOgXk=.pem", + "location": "security-state-staging/intermediates/fd4cdd62-8c22-4f42-b396-b7eb3ca60553.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cPk3iWChPK4OBkAMXZkVl0nHVot2WEoaEc4JShoOgXk=", + "crlite_enrolled": false, + "id": "1ff8e545-a74d-4ec5-b7a2-f3fbaf355c06", + "last_modified": 1710795423416 + }, + { + "schema": 1710557651981, + "derHash": "54jRSwQ2tRILvuPxXBW63wjBQH/nJWik8W+RUcOA4eM=", + "subject": "CN=E5,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNQ==", + "whitelist": false, + "attachment": { + "hash": "badc62e1392ffc5e5df2409ec05a71b1334be3bb5a0a5d42b3c1cdb7d4834386", + "size": 996, + "filename": "NYbU7PBwV4y9J67c4guWTki8FJ-uudrXL0a4V4aRcrg=.pem", + "location": "security-state-staging/intermediates/f46b0b36-6ce3-41a8-8c05-6fe451f74273.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NYbU7PBwV4y9J67c4guWTki8FJ+uudrXL0a4V4aRcrg=", + "crlite_enrolled": false, + "id": "57a05de3-2ab5-463d-b51a-a451c946031a", + "last_modified": 1710557823264 + }, + { + "schema": 1710557651622, + "derHash": "g2JP0zjI2bAjwYpny3qcBRnaQ9EXdbTGy9rUXD2ZfFI=", + "subject": "CN=E8,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFOA==", + "whitelist": false, + "attachment": { + "hash": "55a1393645174b530ee0bc29e53a01df5802e864e568402988f0a4b03b25063a", + "size": 1565, + "filename": "iFvwVyJSxnQdyaUvUERIf-8qk7gRze3612JMwoO3zdU=.pem", + "location": "security-state-staging/intermediates/08e1a7b8-7f72-4887-bcff-e14095cb1921.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iFvwVyJSxnQdyaUvUERIf+8qk7gRze3612JMwoO3zdU=", + "crlite_enrolled": false, + "id": "126f7654-6ee4-4022-bba6-595372f9e7e9", + "last_modified": 1710557823261 + }, + { + "schema": 1710557650551, + "derHash": "Xf2zzzGybyPYfAnzoM72QvZAaan7fP4pJwu13A8eFrs=", + "subject": "CN=E5,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNQ==", + "whitelist": false, + "attachment": { + "hash": "9b0bc663a1b51bce1c95a42b86e234cd0f9d724eff86544bc703307ca8192cab", + "size": 1565, + "filename": "NYbU7PBwV4y9J67c4guWTki8FJ-uudrXL0a4V4aRcrg=.pem", + "location": "security-state-staging/intermediates/abe14e76-5c32-4ec0-8b94-c1a2ab5451fe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NYbU7PBwV4y9J67c4guWTki8FJ+uudrXL0a4V4aRcrg=", + "crlite_enrolled": false, + "id": "28e14e4f-c8a2-45e2-afe1-1c62a1afe3c1", + "last_modified": 1710557823258 + }, + { + "schema": 1710557652358, + "derHash": "QYXfl4BsK6dvHXmCPxEv+mOaSczcmQkIECBnq2QSuIY=", + "subject": "CN=E9,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFOQ==", + "whitelist": false, + "attachment": { + "hash": "dd0e77f937de5f95d9b2bc38a9508960d8ca86838ef36c489bc9a7c9e5c94492", + "size": 996, + "filename": "8UQKm3bh5B5TpMtGEym_Yze0GXJr5RPkLhnxxpHF1LI=.pem", + "location": "security-state-staging/intermediates/c9f44b8b-4670-440c-9987-bcee06fda84a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8UQKm3bh5B5TpMtGEym/Yze0GXJr5RPkLhnxxpHF1LI=", + "crlite_enrolled": false, + "id": "04914e40-9ad6-4efe-96f9-6858e21f0bc4", + "last_modified": 1710557823255 + }, + { + "schema": 1710557649379, + "derHash": "duniiKr8Djf0OQy/lGqtmX1cHJAbPOUT09j626viq4U=", + "subject": "CN=E6,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNg==", + "whitelist": false, + "attachment": { + "hash": "3ebd77749d7810d81e7b5330f2432d81311cb84798964a819ae7098326312cfb", + "size": 1565, + "filename": "0Bbh_jEZSKymTy3kTOhsmlHKBB32EDu1KojrP3YfV9c=.pem", + "location": "security-state-staging/intermediates/724bdee9-0f01-4959-858a-d38b832f352f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0Bbh/jEZSKymTy3kTOhsmlHKBB32EDu1KojrP3YfV9c=", + "crlite_enrolled": false, + "id": "3ee6f34f-6f4d-4fa3-a105-c1403c0d5d87", + "last_modified": 1710557823252 + }, + { + "schema": 1710557651259, + "derHash": "VHFUICJMW2W+7QGNw5QNczjFd+Mi1UiPYz2Mao/tYbI=", + "subject": "CN=E7,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNw==", + "whitelist": false, + "attachment": { + "hash": "454fbabf72a59356f1d08e89a1c7f7b2c839b88d77a5215be7f2f126d408586e", + "size": 1000, + "filename": "y7xVm0TVJNahMr2sZydE2jQH8SquXV9yLF9seROHHHU=.pem", + "location": "security-state-staging/intermediates/a90ebad7-4e4e-48af-ad7e-0f916a648c2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "y7xVm0TVJNahMr2sZydE2jQH8SquXV9yLF9seROHHHU=", + "crlite_enrolled": false, + "id": "9f201f58-e8b8-4590-8c7d-b355adddf50d", + "last_modified": 1710557823250 + }, + { + "schema": 1710557649005, + "derHash": "Blq30qBQ+UdYcSF2XY0HDA4TMNV5j6pCwgcnSe0pN2I=", + "subject": "CN=E6,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNg==", + "whitelist": false, + "attachment": { + "hash": "be5ca6ac557668f57ea85588340ee76ac3a237a359f221406e815dc5665bd3b7", + "size": 1000, + "filename": "0Bbh_jEZSKymTy3kTOhsmlHKBB32EDu1KojrP3YfV9c=.pem", + "location": "security-state-staging/intermediates/3c307724-fa1f-46be-b729-f131ae30d8cd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0Bbh/jEZSKymTy3kTOhsmlHKBB32EDu1KojrP3YfV9c=", + "crlite_enrolled": false, + "id": "c34d8f1d-8cbe-47a7-ba7e-7e317b07f27a", + "last_modified": 1710557823247 + }, + { + "schema": 1710557652725, + "derHash": "rBJ0VCJn8XtSVTW1Vjv3Mf67GCUztGqC3Iactk61KMA=", + "subject": "CN=E8,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFOA==", + "whitelist": false, + "attachment": { + "hash": "8c5710eda1b3c27dd74d73141a1a8fe0323f4a45533aac37d07398d1fe1ba809", + "size": 1000, + "filename": "iFvwVyJSxnQdyaUvUERIf-8qk7gRze3612JMwoO3zdU=.pem", + "location": "security-state-staging/intermediates/3654e458-bec6-4580-8663-7d02c432873e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iFvwVyJSxnQdyaUvUERIf+8qk7gRze3612JMwoO3zdU=", + "crlite_enrolled": false, + "id": "6cbf3471-29d3-4c71-a08c-172017a3cbaf", + "last_modified": 1710557823244 + }, + { + "schema": 1710557648653, + "derHash": "nXw/GqatKy7A1c8eJG+NmubLyf0HVa03u5dLHy+2A/M=", + "subject": "CN=R10,O=Let's Encrypt,C=US", + "subjectDN": "MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNSMTA=", + "whitelist": false, + "attachment": { + "hash": "c81d1bd2445e83c128c7d552399b6488c0aa07373b79cf7e6ff4bcf9fbf6ada4", + "size": 1800, + "filename": "K7rZOrXHknnsEhUH8nLL4MZkejquUuIvOIr6tCa0rbo=.pem", + "location": "security-state-staging/intermediates/9f6c1f8c-f285-4901-982b-1b77f1fecfbf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "K7rZOrXHknnsEhUH8nLL4MZkejquUuIvOIr6tCa0rbo=", + "crlite_enrolled": false, + "id": "3911db43-1240-493b-8579-eb57a02d958f", + "last_modified": 1710557823241 + }, + { + "schema": 1710557647945, + "derHash": "MpUgMLlH2AhYqHQleIUYvfbZSmMdVeIBF1hcieuykZc=", + "subject": "CN=Tesla Public RSA TLS Issuing CA 2024,O=Tesla\\, Inc.,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRQwEgYDVQQKEwtUZXNsYSwgSW5jLjEtMCsGA1UEAxMkVGVzbGEgUHVibGljIFJTQSBUTFMgSXNzdWluZyBDQSAyMDI0", + "whitelist": false, + "attachment": { + "hash": "a9031e9af0574759f114729defe476ad7f5029c11c6848bef2cc5a21cac69f5d", + "size": 2324, + "filename": "9P49cufpvzdZXLi1rCWs-JDJ-OYFAqYkiOGdsQ8Y9EM=.pem", + "location": "security-state-staging/intermediates/d2e210e5-05e2-4c69-bfdf-ef3f59e3cd70.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9P49cufpvzdZXLi1rCWs+JDJ+OYFAqYkiOGdsQ8Y9EM=", + "crlite_enrolled": false, + "id": "7805c87e-4397-4d4d-abea-8e4b832c2944", + "last_modified": 1710557823239 + }, + { + "schema": 1710557648300, + "derHash": "d1TKt8Ja0mBaaB46BBh8Hjs6RvGIqCR9yG4CoPwzb5E=", + "subject": "CN=Tesla China Public RSA TLS Issuing CA 2024,O=Tesla\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRQwEgYDVQQKEwtUZXNsYSwgSW5jLjEzMDEGA1UEAxMqVGVzbGEgQ2hpbmEgUHVibGljIFJTQSBUTFMgSXNzdWluZyBDQSAyMDI0", + "whitelist": false, + "attachment": { + "hash": "1ebc266e5393c8914867037b06e2f36582ae5ba4d30caac2627367872a217b92", + "size": 2328, + "filename": "OTunEZ8hph3f2IjPSz6uBOtO1dr9gSAT6QkdsRjLvIc=.pem", + "location": "security-state-staging/intermediates/fdf0eed3-116e-4072-84ba-a4db196264d2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OTunEZ8hph3f2IjPSz6uBOtO1dr9gSAT6QkdsRjLvIc=", + "crlite_enrolled": false, + "id": "38d93572-6ab8-45ba-885a-08ffb780251e", + "last_modified": 1710557823236 + }, + { + "schema": 1710557647597, + "derHash": "WR6c5shj06B56fq+FHjHM5omshJp3eeVIRNhAkrjGkQ=", + "subject": "CN=R11,O=Let's Encrypt,C=US", + "subjectDN": "MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNSMTE=", + "whitelist": false, + "attachment": { + "hash": "4015249779032694e5dd8f5c9dd09836a0d664d07ad96267ab63effc1b4df6e1", + "size": 1800, + "filename": "bdrBhpj38ffhxpubzkINl0rG-UyossdhcBYj-Zx2fcc=.pem", + "location": "security-state-staging/intermediates/9b0f44f1-8d97-453b-812d-f86c94550ee8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bdrBhpj38ffhxpubzkINl0rG+UyossdhcBYj+Zx2fcc=", + "crlite_enrolled": false, + "id": "167f2ab2-d72b-467d-a4ae-5a77eb903142", + "last_modified": 1710557823233 + }, + { + "schema": 1710557646962, + "derHash": "rrH9dBDoO8lvXaPGp8LBu4NtH6XLhucIUViQ5Ciodws=", + "subject": "CN=E7,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFNw==", + "whitelist": false, + "attachment": { + "hash": "a3e3761ac2290e44f21dd83a3dee7616beb0fae40a86b7614df5aff5cfe0fbe2", + "size": 1565, + "filename": "y7xVm0TVJNahMr2sZydE2jQH8SquXV9yLF9seROHHHU=.pem", + "location": "security-state-staging/intermediates/d709b849-5593-4c34-8c01-6c2667055f27.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "y7xVm0TVJNahMr2sZydE2jQH8SquXV9yLF9seROHHHU=", + "crlite_enrolled": false, + "id": "5ec48a39-b128-4c18-ac18-78717882cf7a", + "last_modified": 1710557823230 + }, + { + "schema": 1710557650889, + "derHash": "07EoIWqEP47xMhUB9d9Spd9Sk57iwZKXcSzT3k1Bk1Q=", + "subject": "CN=R13,O=Let's Encrypt,C=US", + "subjectDN": "MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNSMTM=", + "whitelist": false, + "attachment": { + "hash": "8efd63a1350e6eced60418673bc51aa42d82242a553e6bddee30765207732a84", + "size": 1800, + "filename": "AlSQhgtJirc8ahLyekmtX-Iw-v46yPYRLJt9Cq1GlB0=.pem", + "location": "security-state-staging/intermediates/00e94c9e-039c-4ac0-afb5-95fb4af9247a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AlSQhgtJirc8ahLyekmtX+Iw+v46yPYRLJt9Cq1GlB0=", + "crlite_enrolled": false, + "id": "740850ff-e7cb-484e-a7a5-fa7b77642099", + "last_modified": 1710557823228 + }, + { + "schema": 1710557653101, + "derHash": "Ex/Od4QBaJmloAIDqe/IDxjrvXVYBxftwVU1gJMINuw=", + "subject": "CN=R12,O=Let's Encrypt,C=US", + "subjectDN": "MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNSMTI=", + "whitelist": false, + "attachment": { + "hash": "f5c1e03c9d4d299af1ec42ade782748f4cab780f708979e1f6be00a18f420591", + "size": 1800, + "filename": "kZwN96eHtZftBWrOZUsd6cA4es80n3NzSk_XtYz2EqQ=.pem", + "location": "security-state-staging/intermediates/b27616f6-d705-4101-99c7-225fc36c5276.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kZwN96eHtZftBWrOZUsd6cA4es80n3NzSk/XtYz2EqQ=", + "crlite_enrolled": false, + "id": "1224b785-8a96-4bdc-bd99-cf9cf1a91fe8", + "last_modified": 1710557823225 + }, + { + "schema": 1710557650106, + "derHash": "/eiPLU+JE9PcFmTV+N5R4H/iq/7ZO0WsrVopv+uqI/s=", + "subject": "CN=E9,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFOQ==", + "whitelist": false, + "attachment": { + "hash": "e174bd7700981b8496d947ddfeaae0d4c5386d116f6c55cf4d678bc56e54f8a8", + "size": 1565, + "filename": "8UQKm3bh5B5TpMtGEym_Yze0GXJr5RPkLhnxxpHF1LI=.pem", + "location": "security-state-staging/intermediates/75c016fa-42c2-4278-9fd8-420ef53ceea1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8UQKm3bh5B5TpMtGEym/Yze0GXJr5RPkLhnxxpHF1LI=", + "crlite_enrolled": false, + "id": "6e708fe9-bef1-4e04-a62c-b5f2df61674a", + "last_modified": 1710557823222 + }, + { + "schema": 1710557649732, + "derHash": "JNRaqbjWBT0oHzhCyMwMbBr3zN/ULdXBL2p0+pMj96I=", + "subject": "CN=R14,O=Let's Encrypt,C=US", + "subjectDN": "MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNSMTQ=", + "whitelist": false, + "attachment": { + "hash": "4bf1193ec7761636410d2f274e618ad669f85b537c9c5b1a93f3e84bd76ad96b", + "size": 1800, + "filename": "8WR6XuPvrFTIkukwWE_keXm3rNHHbBJxvKHFB22GmIg=.pem", + "location": "security-state-staging/intermediates/7e6c46a5-df71-419d-ada0-694612e63c1d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8WR6XuPvrFTIkukwWE/keXm3rNHHbBJxvKHFB22GmIg=", + "crlite_enrolled": false, + "id": "69343a2a-10ab-428d-9412-9a7986910578", + "last_modified": 1710557823219 + }, + { + "schema": 1710539501295, + "derHash": "kPqg3vqxPQmsAJdRwMO2BqR81X3oAi5od0gZm0RHSyM=", + "subject": "CN=Certigna Server Authentication ACME CA,O=Certigna,C=FR", + "subjectDN": "MHAxCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0aWduYTEdMBsGA1UEYQwUTlRSRlItNDgxNDYzMDgxMDAwMzYxLzAtBgNVBAMMJkNlcnRpZ25hIFNlcnZlciBBdXRoZW50aWNhdGlvbiBBQ01FIENB", + "whitelist": false, + "attachment": { + "hash": "49c8ef6d8a7b25da0bf8610a646bed6d9d0eac47d63eec405c40c5e71ce8208f", + "size": 2458, + "filename": "g96_QpC6c95hPe0mJuHADgpPz6RuVaqjHoRSjOyuL7E=.pem", + "location": "security-state-staging/intermediates/7b7f68ae-bfd3-4840-b207-9ab77f4232f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "g96/QpC6c95hPe0mJuHADgpPz6RuVaqjHoRSjOyuL7E=", + "crlite_enrolled": false, + "id": "38d23199-bfbc-46eb-bc42-70a370fbac24", + "last_modified": 1710539831556 + }, + { + "schema": 1710539500206, + "derHash": "WMKULEG5CPAJ6QD+YtETC76npaiDbyCJkImnqVHGQyc=", + "subject": "CN=Certigna Server Authentication ACME FR CA 2024,O=Certigna,C=FR", + "subjectDN": "MHgxCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0aWduYTEdMBsGA1UEYQwUTlRSRlItNDgxNDYzMDgxMDAwMzYxNzA1BgNVBAMMLkNlcnRpZ25hIFNlcnZlciBBdXRoZW50aWNhdGlvbiBBQ01FIEZSIENBIDIwMjQ=", + "whitelist": false, + "attachment": { + "hash": "b2210ca67012f7e0ba1fde2dc3546563fa2c16b17753d83a23cf28772fdc78d4", + "size": 2052, + "filename": "DoTA9i3cZ3sCoyIkBYv0KN9rxjve3-F1LOL0hMTEjp8=.pem", + "location": "security-state-staging/intermediates/bcda003c-3561-469c-97ec-e1268e4edb50.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DoTA9i3cZ3sCoyIkBYv0KN9rxjve3+F1LOL0hMTEjp8=", + "crlite_enrolled": false, + "id": "506528ab-12d0-4986-8355-f72a795f5311", + "last_modified": 1710539831553 + }, + { + "schema": 1710539500937, + "derHash": "4FD1W1Vj5DXdNgI6OjtKy+q0TDlk8YBIecwxEeKH2Bs=", + "subject": "CN=Certigna Server Authentication ACME FR CA,O=Certigna,C=FR", + "subjectDN": "MHMxCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0aWduYTEdMBsGA1UEYQwUTlRSRlItNDgxNDYzMDgxMDAwMzYxMjAwBgNVBAMMKUNlcnRpZ25hIFNlcnZlciBBdXRoZW50aWNhdGlvbiBBQ01FIEZSIENB", + "whitelist": false, + "attachment": { + "hash": "bbe52c415c1b8f00ea91967db06b87b829f88f35160ca7e8ff01d6b9acaa0e63", + "size": 2463, + "filename": "zkN9UXAo8i_VmU2mjpCUOtef-5jkt-KPl32fIqB0tNE=.pem", + "location": "security-state-staging/intermediates/4c8342ae-ea33-4964-bac7-4ef7d2d40537.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zkN9UXAo8i/VmU2mjpCUOtef+5jkt+KPl32fIqB0tNE=", + "crlite_enrolled": false, + "id": "14b7e624-637a-4380-95e3-3b432fc6aac2", + "last_modified": 1710539831550 + }, + { + "schema": 1710539500588, + "derHash": "qaY+A/DUmOxyQwJqeo8r5wXQTyhF1J9j2InJ3QtKOUQ=", + "subject": "CN=Certigna Server Authentication ACME CA 2024,O=Certigna,C=FR", + "subjectDN": "MHUxCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0aWduYTEdMBsGA1UEYQwUTlRSRlItNDgxNDYzMDgxMDAwMzYxNDAyBgNVBAMMK0NlcnRpZ25hIFNlcnZlciBBdXRoZW50aWNhdGlvbiBBQ01FIENBIDIwMjQ=", + "whitelist": false, + "attachment": { + "hash": "161dc34976ab4cfc42bd69ec0b38ae28178722e6243ee46a916e76743d135d0e", + "size": 2048, + "filename": "5t6bUEJAtYeRqDL4wc0-BLSX3Cy-W80bNcuO4zpx08Q=.pem", + "location": "security-state-staging/intermediates/fce8c500-791e-481c-8225-5faf94b2f6e3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5t6bUEJAtYeRqDL4wc0+BLSX3Cy+W80bNcuO4zpx08Q=", + "crlite_enrolled": false, + "id": "08355b95-d61d-4aa6-8185-4c6a16379fae", + "last_modified": 1710539831545 + }, + { + "schema": 1709780043992, + "derHash": "6CGxESkRBWfxYH8LDzaOx42BFBjwxIzEJUUPY+uICV0=", + "subject": "CN=Entrust 4K EV TLS Root CA - 2022,O=Entrust\\, Inc.,C=US", + "subjectDN": "MFAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSkwJwYDVQQDEyBFbnRydXN0IDRLIEVWIFRMUyBSb290IENBIC0gMjAyMg==", + "whitelist": false, + "attachment": { + "hash": "d5d1224d52aeeff3fa819a835cf5693d219ded634ea68df06af892a481a09373", + "size": 2064, + "filename": "qtbKnoRh2GMf_QeXEIiJfMm-n4sx1kgOUifxXyU-tyg=.pem", + "location": "security-state-staging/intermediates/fc1a0f62-1577-4d38-b489-39b75b9303a4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qtbKnoRh2GMf/QeXEIiJfMm+n4sx1kgOUifxXyU+tyg=", + "crlite_enrolled": false, + "id": "e2798fee-994b-49ad-bcb5-9fad36b266bf", + "last_modified": 1709780223348 + }, + { + "schema": 1709780043461, + "derHash": "sKF44Gt6cnTwsqZA9qA78AOrHt6Gk3cNXSdBstLwGYA=", + "subject": "CN=AffirmTrust 4K TLS Root CA - 2022,O=AffirmTrust,C=CA", + "subjectDN": "ME8xCzAJBgNVBAYTAkNBMRQwEgYDVQQKEwtBZmZpcm1UcnVzdDEqMCgGA1UEAxMhQWZmaXJtVHJ1c3QgNEsgVExTIFJvb3QgQ0EgLSAyMDIy", + "whitelist": false, + "attachment": { + "hash": "d68d5318a7ba2e9a85397e85f05d1f9fc36013ccdee30a7fff3c05e997f7ce91", + "size": 2288, + "filename": "PO-4Mc0JEYVGctI3huFt_Mbvj0EA2N9qQo9dg4I1_cw=.pem", + "location": "security-state-staging/intermediates/c6ba6944-757d-422a-85a3-113db515beed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PO+4Mc0JEYVGctI3huFt/Mbvj0EA2N9qQo9dg4I1/cw=", + "crlite_enrolled": false, + "id": "83abb7ec-d98c-4b07-8ef3-c24056303b1a", + "last_modified": 1709780223345 + }, + { + "schema": 1709758443441, + "derHash": "ZfA1j8k5NM1arEMSLzeUjXvQQtFI5v2IsuZaqaw5nd0=", + "subject": "CN=Entrust Root Certification Authority - G2,OU=See www.entrust.net/legal-terms+OU=(c) 2009 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "whitelist": false, + "attachment": { + "hash": "8097be6a2869b37e933e0d7cd1c8b2fe3fe53e3fd1942f557271eee692a28f5a", + "size": 1853, + "filename": "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at_U=.pem", + "location": "security-state-staging/intermediates/470cc99c-a735-40d5-be7d-d35d395a9917.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U=", + "crlite_enrolled": false, + "id": "89c6906b-2f50-4f51-ae6c-29b661081f05", + "last_modified": 1709758623247 + }, + { + "schema": 1709758442871, + "derHash": "4lXaLVTs/Rb7ZCajioM5nn459qAeluZJzYUPsP3/VOs=", + "subject": "CN=Entrust 4K TLS Root CA - 2022,O=Entrust\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSYwJAYDVQQDEx1FbnRydXN0IDRLIFRMUyBSb290IENBIC0gMjAyMg==", + "whitelist": false, + "attachment": { + "hash": "38fbd31004e9e27ad9be5bc74d44558035bd507afeca61071f303289eb1039f2", + "size": 2060, + "filename": "w8sbVZHe29cRAbw0WtuFV3BsGDBZKehyOv_50FpLf7A=.pem", + "location": "security-state-staging/intermediates/b6658a24-c5c8-4cb7-bd99-029c9467e3ac.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "w8sbVZHe29cRAbw0WtuFV3BsGDBZKehyOv/50FpLf7A=", + "crlite_enrolled": false, + "id": "bb644eb4-afd2-4150-8fbb-4d0c76c48f3a", + "last_modified": 1709758623243 + }, + { + "schema": 1709322861611, + "derHash": "79BmhAYTDkM4LOre67F+YlzXf1KOKI6L7a6xQuGbzgI=", + "subject": "CN=CloudSecure RSA Domain Validation Secure Server CA 2,O=CloudSecure Corporation,C=JP", + "subjectDN": "MG4xCzAJBgNVBAYTAkpQMSAwHgYDVQQKExdDbG91ZFNlY3VyZSBDb3Jwb3JhdGlvbjE9MDsGA1UEAxM0Q2xvdWRTZWN1cmUgUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "fb2121b1bc1d493d64cc748ec193beeffd50410766565c2f7d3c188672d62b2f", + "size": 2296, + "filename": "ngVpmshTjI2mGNZdkmE0BseYAQxwUyN7hAFExDPDwsg=.pem", + "location": "security-state-staging/intermediates/3928980f-322a-4b2f-a6d4-060445ac1799.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ngVpmshTjI2mGNZdkmE0BseYAQxwUyN7hAFExDPDwsg=", + "crlite_enrolled": false, + "id": "a66aa7c5-abb2-49b5-b693-66dd992c24ab", + "last_modified": 1709323057665 + }, + { + "schema": 1709322861260, + "derHash": "wKHKNkddM7Rx5OWOVXTdkpTTVMRXKVlU21VbJOrSqks=", + "subject": "CN=COMODO/PKWARE Secure Email CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGAMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEmMCQGA1UEAxMdQ09NT0RPL1BLV0FSRSBTZWN1cmUgRW1haWwgQ0E=", + "whitelist": false, + "attachment": { + "hash": "536d387994d2fcf3c04c848acb66841104d87c9b0b1ed73ccec9768bd275d362", + "size": 1979, + "filename": "javoiDXitAWxKnEeWWJB_XeluETyAl8SAu8OHaB7Q0c=.pem", + "location": "security-state-staging/intermediates/a14785fd-f6c9-4f29-9d06-df68da0a3959.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "javoiDXitAWxKnEeWWJB/XeluETyAl8SAu8OHaB7Q0c=", + "crlite_enrolled": false, + "id": "da5c02a2-8457-4e22-87c0-9f6e49221efe", + "last_modified": 1709323057662 + }, + { + "schema": 1709322860874, + "derHash": "YmFumxG9Djo1g2DIO3jbxb7qV1yQuX5oJEgz+zBDfGs=", + "subject": "CN=TERENA Personal CA,O=TERENA,C=NL", + "subjectDN": "MDsxCzAJBgNVBAYTAk5MMQ8wDQYDVQQKEwZURVJFTkExGzAZBgNVBAMTElRFUkVOQSBQZXJzb25hbCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b540a4b8690142678f25aaa6f34207fd3c4298612d40cac2034f5049aa748c3b", + "size": 1711, + "filename": "GrQOOcgaiD1vs9vGDvRaVMQKNDQm2_Nj7lPXTk4s2F0=.pem", + "location": "security-state-staging/intermediates/4742da83-41bb-4761-b382-7a48eb3f8886.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GrQOOcgaiD1vs9vGDvRaVMQKNDQm2/Nj7lPXTk4s2F0=", + "crlite_enrolled": false, + "id": "d447cf4a-07e2-4ccf-b3c9-898267f291fe", + "last_modified": 1709323057659 + }, + { + "schema": 1709322860517, + "derHash": "mm/Eq02x6m9mY1B+3B0Ajwka6I+rbzrlaoSkCQUp71g=", + "subject": "CN=Telekom Security EV RSA CA 23,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJjAkBgNVBAMMHVRlbGVrb20gU2VjdXJpdHkgRVYgUlNBIENBIDIz", + "whitelist": false, + "attachment": { + "hash": "5a3bc003f09d44a9bff3a566fbb608a3615298fe57e81f80753e9ebb01cd61d0", + "size": 2463, + "filename": "KsE3RzpShimShZDxrrUx8vMnCMdGFk9o00YoUm8hq1Y=.pem", + "location": "security-state-staging/intermediates/c196ecd0-2b07-4708-adc4-b44bbf006d56.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KsE3RzpShimShZDxrrUx8vMnCMdGFk9o00YoUm8hq1Y=", + "crlite_enrolled": false, + "id": "63278116-4a77-4a8d-830b-2d7766217557", + "last_modified": 1709323057656 + }, + { + "schema": 1709322859788, + "derHash": "/QKa3j96gKmNb//JQpBGWF7x2MuBk6F/YhQxfQZS18M=", + "subject": "CN=COMODO/HP Secure Email CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSIwIAYDVQQDExlDT01PRE8vSFAgU2VjdXJlIEVtYWlsIENB", + "whitelist": false, + "attachment": { + "hash": "adb2966a8c1fc1a877edbb79b2d43109f804829872e34bd8667f85ba432153e5", + "size": 1975, + "filename": "VQkNSqdqPoVan53-i1N5YrjNCMnJRtvNFB7icXr9un8=.pem", + "location": "security-state-staging/intermediates/dde31d19-c833-41ae-a61a-48529e89c711.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VQkNSqdqPoVan53+i1N5YrjNCMnJRtvNFB7icXr9un8=", + "crlite_enrolled": false, + "id": "a53b33af-6b45-402a-abac-9e6140dc28ec", + "last_modified": 1709323057653 + }, + { + "schema": 1709322862713, + "derHash": "oCHY53TGozxza9tcF2klrd9EbJlnm83NguW9LqX+RE0=", + "subject": "CN=TrustSign BR RSA EV SSL CA 3,O=TrustSign Certificadora Dig. & Solucoes Seguranca da Inf. Ltda.,C=BR", + "subjectDN": "MH4xCzAJBgNVBAYTAkJSMUgwRgYDVQQKDD9UcnVzdFNpZ24gQ2VydGlmaWNhZG9yYSBEaWcuICYgU29sdWNvZXMgU2VndXJhbmNhIGRhIEluZi4gTHRkYS4xJTAjBgNVBAMTHFRydXN0U2lnbiBCUiBSU0EgRVYgU1NMIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "2c84d91b393a7a753c19a6e8ecdd10deed6ab11cc24bd6bcf492eb38842b2450", + "size": 2296, + "filename": "LpohGU7zxrv6t1u_v9DhN6Y9n4Aonq2iqejOjCP0Ux8=.pem", + "location": "security-state-staging/intermediates/0794fa6c-acbb-45c4-9663-0efec7af694d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LpohGU7zxrv6t1u/v9DhN6Y9n4Aonq2iqejOjCP0Ux8=", + "crlite_enrolled": false, + "id": "b05dd25e-5a3a-4951-878c-54fb55073a71", + "last_modified": 1709323057650 + }, + { + "schema": 1709322859425, + "derHash": "Ub2MI/MSaUY4WcHVugmaJrqWXZSg9q9XP5Xhl28cZ+A=", + "subject": "CN=cPanel ECC Domain Validation Secure Server CA 3,O=cPanel\\, LLC,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRQwEgYDVQQKEwtjUGFuZWwsIExMQzE4MDYGA1UEAxMvY1BhbmVsIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "81d34a8d92722fab651fb8abaa7727686649d8a058bb61606f47ad5e942adc0b", + "size": 1219, + "filename": "x0Qkm6r8fuJiMuEHBtboy-jaDQ3y8uK8pyXK_03E2Ts=.pem", + "location": "security-state-staging/intermediates/9d8007dd-056d-4251-9d4f-2714d98e77de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x0Qkm6r8fuJiMuEHBtboy+jaDQ3y8uK8pyXK/03E2Ts=", + "crlite_enrolled": false, + "id": "7e069ef5-0b0c-46a0-bbdf-4d8e728423c0", + "last_modified": 1709323057647 + }, + { + "schema": 1709322858581, + "derHash": "ofN4S+qZZBNozTVcHkWvLeMVdHMTmuhqHsUV4gMabD8=", + "subject": "CN=cPanel RSA Domain Validation Secure Server CA 3,O=cPanel\\, LLC,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRQwEgYDVQQKEwtjUGFuZWwsIExMQzE4MDYGA1UEAxMvY1BhbmVsIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "6b080a2a80781e71ae161fbe4fff3ba30970e406aeb5c28c3bd1fe0613fdb7cc", + "size": 2227, + "filename": "1s-ZmXiKh7-UASkQQs1U4zOhS4YqerP9kEO5w54wsbw=.pem", + "location": "security-state-staging/intermediates/a5842fa3-5423-4b6e-90b9-5a4fc110a319.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1s+ZmXiKh7+UASkQQs1U4zOhS4YqerP9kEO5w54wsbw=", + "crlite_enrolled": false, + "id": "6435e6ca-cabc-415c-8a36-8afbecb70b12", + "last_modified": 1709323057644 + }, + { + "schema": 1709322856729, + "derHash": "j3JA8WCTbx/UBr8CizW11gm2SzvQ4x4TzG3Zq3yhLm8=", + "subject": "CN=GLOBE SSL SECURE EMAIL CA,O=GLOBE HOSTING CERTIFICATION AUTHORITY,C=RO", + "subjectDN": "MGExCzAJBgNVBAYTAlJPMS4wLAYDVQQKEyVHTE9CRSBIT1NUSU5HIENFUlRJRklDQVRJT04gQVVUSE9SSVRZMSIwIAYDVQQDExlHTE9CRSBTU0wgU0VDVVJFIEVNQUlMIENB", + "whitelist": false, + "attachment": { + "hash": "1cf3286ce0babb75314968445c3959acec4c2fb01d7342d74be3d80efd77125a", + "size": 1764, + "filename": "_eRFHz8wxXnTOa1eOct617RUUVoCS6wHVpWc2LKjvVg=.pem", + "location": "security-state-staging/intermediates/4c03d7f3-2876-42e2-8e63-1770d52bb8dd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/eRFHz8wxXnTOa1eOct617RUUVoCS6wHVpWc2LKjvVg=", + "crlite_enrolled": false, + "id": "22c0373b-2dd6-46fd-a8f5-948c6627d4ea", + "last_modified": 1709323057641 + }, + { + "schema": 1709322858961, + "derHash": "6bmYDIo/WT9Nnn2j9Jev+Ptm4kLGy6DXMOIGVZUT+l0=", + "subject": "CN=CloudSecure RSA Extended Validation Secure Server CA 2,O=CloudSecure Corporation,C=JP", + "subjectDN": "MHAxCzAJBgNVBAYTAkpQMSAwHgYDVQQKExdDbG91ZFNlY3VyZSBDb3Jwb3JhdGlvbjE/MD0GA1UEAxM2Q2xvdWRTZWN1cmUgUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAy", + "whitelist": false, + "attachment": { + "hash": "f70f60394839abcacf961991bfa4a022936ba4e50648e62886dbf535cd906593", + "size": 2276, + "filename": "1Ul-LolMUZyXeMV_wwOQF62lnEb7N8nJxkwmRmRRcYI=.pem", + "location": "security-state-staging/intermediates/f7c81653-1126-40e7-8768-021015877551.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1Ul+LolMUZyXeMV/wwOQF62lnEb7N8nJxkwmRmRRcYI=", + "crlite_enrolled": false, + "id": "7ff7cab8-de89-4883-9ba7-e72ab7f9adcb", + "last_modified": 1709323057638 + }, + { + "schema": 1709322861976, + "derHash": "1mqdGWzSwn101xnKKCXEWEa1Q1AjdpgTeFZ3l7hXHTo=", + "subject": "CN=TrustSign BR RSA OV SSL CA 3,O=TrustSign Certificadora Dig. & Solucoes Seguranca da Inf. Ltda.,C=BR", + "subjectDN": "MH4xCzAJBgNVBAYTAkJSMUgwRgYDVQQKDD9UcnVzdFNpZ24gQ2VydGlmaWNhZG9yYSBEaWcuICYgU29sdWNvZXMgU2VndXJhbmNhIGRhIEluZi4gTHRkYS4xJTAjBgNVBAMTHFRydXN0U2lnbiBCUiBSU0EgT1YgU1NMIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "4ffe538c0d7f0a5da3bb5331a42d56f1c9d607ee091deb7f1da0e1ceebee4ac0", + "size": 2316, + "filename": "5M4nqfeUxAKYvMXWPZxoI7Ohyf9GkQYIHHOMYGpbCmQ=.pem", + "location": "security-state-staging/intermediates/9e1bb80c-e7d7-4de1-9daf-27241124cf46.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5M4nqfeUxAKYvMXWPZxoI7Ohyf9GkQYIHHOMYGpbCmQ=", + "crlite_enrolled": false, + "id": "bc576a0c-04f0-41df-aa49-9759354a9cd0", + "last_modified": 1709323057634 + }, + { + "schema": 1709322857466, + "derHash": "6QSgV9g5vL5jEJK0IV3YzE+ISbqHtRqvJyi3HmSs51E=", + "subject": "CN=DREAMHOST SECURE EMAIL CA,O=DREAMHOST CERTIFICATION AUTHORITY,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMSowKAYDVQQKEyFEUkVBTUhPU1QgQ0VSVElGSUNBVElPTiBBVVRIT1JJVFkxIjAgBgNVBAMTGURSRUFNSE9TVCBTRUNVUkUgRU1BSUwgQ0E=", + "whitelist": false, + "attachment": { + "hash": "bd46e6b3068ecccbabe7da0273be8a3d35a413fdfcca05bc18847e7402db16fa", + "size": 1756, + "filename": "63NWnCfq1JadpotgYBS_ORAIQ4nhhoHBm9FjkLxi1nA=.pem", + "location": "security-state-staging/intermediates/6ba6c581-5ba3-48f8-8ccc-e38734c2de49.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "63NWnCfq1JadpotgYBS/ORAIQ4nhhoHBm9FjkLxi1nA=", + "crlite_enrolled": false, + "id": "ad597bfa-50d0-4288-95d2-d15db698ffba", + "last_modified": 1709323057631 + }, + { + "schema": 1709322858187, + "derHash": "Xwo1uk+Epl9osW1KM/ZRfAl3CDk+qv0oVvuak0GHGfQ=", + "subject": "CN=SGTRUST EMAIL AND CLIENT CA,O=SGssl,C=KR", + "subjectDN": "MEMxCzAJBgNVBAYTAktSMQ4wDAYDVQQKEwVTR3NzbDEkMCIGA1UEAxMbU0dUUlVTVCBFTUFJTCBBTkQgQ0xJRU5UIENB", + "whitelist": false, + "attachment": { + "hash": "17a9b911f686f237aa390ea2ff06319ae420ccb51198aeed267a5518db5715c4", + "size": 1723, + "filename": "gQN5MDXBIRQe70cFfEaNKKTh3AELpmBrtzu-04X41Po=.pem", + "location": "security-state-staging/intermediates/460d3cb1-d178-47f3-b286-99a9adab7c6a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gQN5MDXBIRQe70cFfEaNKKTh3AELpmBrtzu+04X41Po=", + "crlite_enrolled": false, + "id": "5a347462-e209-4a4f-bba5-902bb7cdd29b", + "last_modified": 1709323057628 + }, + { + "schema": 1709322860157, + "derHash": "JhjEFcFGupil8K1+pJiqWHgLpV+o47By0TwzDUEBBxc=", + "subject": "CN=EuropeanSSL Client CA,O=EUNETIC GmbH,C=DE", + "subjectDN": "MEQxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxFVU5FVElDIEdtYkgxHjAcBgNVBAMTFUV1cm9wZWFuU1NMIENsaWVudCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "939382e857302d9c045c2918ffbab03973b2a29f5c566cfdd7e53388636562f6", + "size": 1723, + "filename": "6rE_4fpPwsenurNcVvOyU8xh7Vp9kamSGTFA4_9ffGI=.pem", + "location": "security-state-staging/intermediates/a5c74b09-42b9-4fbe-ab76-8aac990547e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6rE/4fpPwsenurNcVvOyU8xh7Vp9kamSGTFA4/9ffGI=", + "crlite_enrolled": false, + "id": "21c33409-b6a8-429b-bddf-96d8eb9b5e6d", + "last_modified": 1709323057625 + }, + { + "schema": 1709322856380, + "derHash": "sF4Fz8v4GBPsMPo/dJIKoj/tNn4UfMgeESH2RphEnQ8=", + "subject": "CN=WISeKey CertifyID SSL GC CA 1,O=WISeKey,C=CH", + "subjectDN": "MEcxCzAJBgNVBAYTAkNIMRAwDgYDVQQKDAdXSVNlS2V5MSYwJAYDVQQDDB1XSVNlS2V5IENlcnRpZnlJRCBTU0wgR0MgQ0EgMQ==", + "whitelist": false, + "attachment": { + "hash": "d3ef1dcec7ca6721c8f698c1c8f6d88f5264f9596c6842ced7c780c3cbe4b79b", + "size": 1130, + "filename": "IUntA2TGvGsJRd1XrGg2Y7dxJ73p4cADKYPamKiIAPs=.pem", + "location": "security-state-staging/intermediates/338dc342-323f-41a6-8c50-062822784a9a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IUntA2TGvGsJRd1XrGg2Y7dxJ73p4cADKYPamKiIAPs=", + "crlite_enrolled": false, + "id": "1a94aa13-0606-4858-923c-41f0aceb9941", + "last_modified": 1709323057622 + }, + { + "schema": 1709322855255, + "derHash": "9AUHfHF0cDuXgceVRKe+mUJjhU0VFz5hqNGaKM96SrE=", + "subject": "CN=TERENA eScience Personal CA,O=TERENA,C=NL", + "subjectDN": "MEQxCzAJBgNVBAYTAk5MMQ8wDQYDVQQKEwZURVJFTkExJDAiBgNVBAMTG1RFUkVOQSBlU2NpZW5jZSBQZXJzb25hbCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0ff25cb8ace98409f31247ecb59b650b17e31d145ec2485517081e2ac1dfa4fb", + "size": 1743, + "filename": "rfAIoQEXwIYxHVXIaa2COA8XlDmE8FsumvzEWCU7MfU=.pem", + "location": "security-state-staging/intermediates/572cc780-7a7f-4edb-bb11-5177e420e4ee.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rfAIoQEXwIYxHVXIaa2COA8XlDmE8FsumvzEWCU7MfU=", + "crlite_enrolled": false, + "id": "df33d49d-be3f-4bfc-9291-d978b63c2fb5", + "last_modified": 1709323057619 + }, + { + "schema": 1709322855622, + "derHash": "Jk2YC0i7HaMvuxR7gQQ0RaxMhWOVlLoVrZoriDai2sA=", + "subject": "CN=TrustSign BR RSA DV SSL CA 3,O=TrustSign Certificadora Dig. & Solucoes Seguranca da Inf. Ltda.,C=BR", + "subjectDN": "MH4xCzAJBgNVBAYTAkJSMUgwRgYDVQQKDD9UcnVzdFNpZ24gQ2VydGlmaWNhZG9yYSBEaWcuICYgU29sdWNvZXMgU2VndXJhbmNhIGRhIEluZi4gTHRkYS4xJTAjBgNVBAMTHFRydXN0U2lnbiBCUiBSU0EgRFYgU1NMIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "27dacc48d530261d12816b8afab129e30ad60c13ca68d8ab480e84fd376aeedc", + "size": 2316, + "filename": "84TjjwMOkswfNmR4vhxNna7mdofPfiyWViNPR3qjzL8=.pem", + "location": "security-state-staging/intermediates/1b06b71c-6ace-446e-aac0-2d4fb31bfe45.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "84TjjwMOkswfNmR4vhxNna7mdofPfiyWViNPR3qjzL8=", + "crlite_enrolled": false, + "id": "6cd1a68d-ab85-4064-856c-0fdab449d7bf", + "last_modified": 1709323057615 + }, + { + "schema": 1709322854820, + "derHash": "TywMFwG94Btr7tptF8M2BCQhr3MRcGM0f36Xt2rYl/U=", + "subject": "CN=GlobalSSL Secure E-Mail and Client Authentication CA,O=GlobalSSL,C=DE", + "subjectDN": "MGAxCzAJBgNVBAYTAkRFMRIwEAYDVQQKEwlHbG9iYWxTU0wxPTA7BgNVBAMTNEdsb2JhbFNTTCBTZWN1cmUgRS1NYWlsIGFuZCBDbGllbnQgQXV0aGVudGljYXRpb24gQ0E=", + "whitelist": false, + "attachment": { + "hash": "b2019acf4321d57d1f4034c278f7761779e2fc130bbf2ca87aabbe5a7047c614", + "size": 1760, + "filename": "rvxRPOOErynHJj6lYghacyWHWiAPuUDhnQ7bzVQe7nA=.pem", + "location": "security-state-staging/intermediates/cacd7664-e756-4647-96f8-682e65e01ae0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rvxRPOOErynHJj6lYghacyWHWiAPuUDhnQ7bzVQe7nA=", + "crlite_enrolled": false, + "id": "9d2ed0cf-a2d4-430d-ae23-724baec635e4", + "last_modified": 1709323057612 + }, + { + "schema": 1709322857831, + "derHash": "16iplHwxgGwbRiX4L8vMp8wgkOWNshW45NiLqcYNMWY=", + "subject": "CN=Telekom Security EV ECC CA 21,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJjAkBgNVBAMMHVRlbGVrb20gU2VjdXJpdHkgRVYgRUNDIENBIDIx", + "whitelist": false, + "attachment": { + "hash": "4f4daf4500b9bacbea50c20917d19002ef1208e32742fccb32f02df7db43b2a5", + "size": 1288, + "filename": "wKQk6Q6vo3QumTIMdfN6uOOZ9KiK-cNGuthhTM680bg=.pem", + "location": "security-state-staging/intermediates/db242b85-b8f1-4409-a396-8322b80fd901.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wKQk6Q6vo3QumTIMdfN6uOOZ9KiK+cNGuthhTM680bg=", + "crlite_enrolled": false, + "id": "cfda4980-a121-4cd5-83e7-7686f5c693ff", + "last_modified": 1709323057609 + }, + { + "schema": 1709322862349, + "derHash": "ckJBfbqtFKmzf6nNYL6lZU8gw7XvbeiD6V3cWNSFR6c=", + "subject": "CN=CloudSecure RSA Organization Validation Secure Server CA 2,O=CloudSecure Corporation,C=JP", + "subjectDN": "MHQxCzAJBgNVBAYTAkpQMSAwHgYDVQQKExdDbG91ZFNlY3VyZSBDb3Jwb3JhdGlvbjFDMEEGA1UEAxM6Q2xvdWRTZWN1cmUgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "7a4f3f78fee45b90c27ad40e93823927b437902e6e57b223874fe3f383445197", + "size": 2304, + "filename": "Iu5dk2QcXxmoM5qhlUdD0pxpGFJGscbvGtxX_B-p5Kg=.pem", + "location": "security-state-staging/intermediates/59b4453f-c97e-4f86-8f39-cfa809c124ce.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Iu5dk2QcXxmoM5qhlUdD0pxpGFJGscbvGtxX/B+p5Kg=", + "crlite_enrolled": false, + "id": "14b4ae02-05a1-4223-a420-35328f2932ae", + "last_modified": 1709323057606 + }, + { + "schema": 1709322857090, + "derHash": "8BflEJGLd9pn5NAg4rdqQkYYdugi9iDFVzvLw7uzdAY=", + "subject": "CN=Gandi Secure Email CA,O=GANDI SAS,C=FR", + "subjectDN": "MEExCzAJBgNVBAYTAkZSMRIwEAYDVQQKEwlHQU5ESSBTQVMxHjAcBgNVBAMTFUdhbmRpIFNlY3VyZSBFbWFpbCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "7374bb117a04dbe56b2af679a65a85a2c734afce7e88acb81917ecd528ba648e", + "size": 1719, + "filename": "iFIEeQ06wWLDY4tmZC6npViJr_VFbFCHUdRkqmIc7OY=.pem", + "location": "security-state-staging/intermediates/80641d57-3847-4459-9d66-f653dd7bfadc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iFIEeQ06wWLDY4tmZC6npViJr/VFbFCHUdRkqmIc7OY=", + "crlite_enrolled": false, + "id": "61df2979-bddb-4586-94f1-15e23fe3c076", + "last_modified": 1709323057603 + }, + { + "schema": 1709322855985, + "derHash": "9ooj65c3qMdE7mcRLy/AJKrcUDu7sE8lze1PJXbJNIU=", + "subject": "CN=The Code Project Secure Email (S/mime) CA,O=The Code Project,C=CA", + "subjectDN": "MFwxCzAJBgNVBAYTAkNBMRkwFwYDVQQKExBUaGUgQ29kZSBQcm9qZWN0MTIwMAYDVQQDEylUaGUgQ29kZSBQcm9qZWN0IFNlY3VyZSBFbWFpbCAoUy9taW1lKSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "e34a2353b9ea221ccf98cccc3d39c3562f1daceb776f42298bbc37afdd4fc54a", + "size": 1752, + "filename": "xtYGt02m4umLJCyy0kfaczCboF12WILjsGOb1umDfX0=.pem", + "location": "security-state-staging/intermediates/53d673f8-b2c7-4c5c-9730-8965f2ec1dcb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xtYGt02m4umLJCyy0kfaczCboF12WILjsGOb1umDfX0=", + "crlite_enrolled": false, + "id": "81d6dcac-df61-4042-bd32-3070f7a85340", + "last_modified": 1709323057600 + }, + { + "schema": 1705981670999, + "derHash": "edV7Fd+mXChw6v4Rtjd2WQnP6Te0nBXOfxlAMMqzla0=", + "subject": "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "whitelist": false, + "attachment": { + "hash": "f2e183bb8adad66608733a2817b33625fa77430aedac090ac258cb9b32b84bab", + "size": 1618, + "filename": "i7WTqTvh0OioIruIfFR4kMPnBqrS2rdiVPl_s2uC_CY=.pem", + "location": "security-state-staging/intermediates/938b215a-776f-42e1-8cba-efd54169311d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i7WTqTvh0OioIruIfFR4kMPnBqrS2rdiVPl/s2uC/CY=", + "crlite_enrolled": false, + "id": "57b18913-6deb-4a87-b1e2-3379aeecfc35", + "last_modified": 1705982223073 + }, + { + "schema": 1705503186346, + "derHash": "BOgIYDvkayU0Vi494jf0rnq+K14xQdjexHCoIjcNPbc=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "3536d13c9902bfb62e34f8554dc470da81f30b5c0fd377fec2bf2ba505ec29c7", + "size": 1199, + "filename": "660nVcvNRcFsR3s7oHScFMybbR6KiQzPeKvSK--DK_k=.pem", + "location": "security-state-staging/intermediates/e8b772ed-1da9-4055-a9a2-9c31cd486a1d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "660nVcvNRcFsR3s7oHScFMybbR6KiQzPeKvSK++DK/k=", + "crlite_enrolled": false, + "id": "e6083412-2f4f-451a-874b-5a62037d7f8a", + "last_modified": 1705503423155 + }, + { + "schema": 1705503185461, + "derHash": "TeYXXzsPyjciFZ27waeNDElfbtryMwQKr8JVmIjBdqU=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDI0IFEy", + "whitelist": false, + "attachment": { + "hash": "97421ca04b528adadb2bbdf3bc52173447be4eda67b9f4108d27b36bf946b337", + "size": 1642, + "filename": "X5G5wFKv3dVearZKW5IcSCUd4PodgUWV3pEScHg27Cw=.pem", + "location": "security-state-staging/intermediates/a0d2ec79-24e8-4f5c-ad15-87901c0d4a05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "X5G5wFKv3dVearZKW5IcSCUd4PodgUWV3pEScHg27Cw=", + "crlite_enrolled": false, + "id": "a977f162-c8b1-41f2-b9e4-8b906e543559", + "last_modified": 1705503423152 + }, + { + "schema": 1705503185156, + "derHash": "0+cioFIOcvdW/em93z1WSfaoWkDKvcyTf3dwCnxF4vw=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDI0IFEy", + "whitelist": false, + "attachment": { + "hash": "0a96038c9db62e0ed417b812459719d29b8655948ad62ac1a47684eab7e06c9a", + "size": 1199, + "filename": "2L1kc7V6u1WOJipF5zt2aOWEIvM_mpQgRFouJxsVPqM=.pem", + "location": "security-state-staging/intermediates/4d0c782c-a2d9-401c-a679-dbde994e9764.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2L1kc7V6u1WOJipF5zt2aOWEIvM/mpQgRFouJxsVPqM=", + "crlite_enrolled": false, + "id": "8d4f98e5-d355-45af-b9b0-c7fa7664889f", + "last_modified": 1705503423149 + }, + { + "schema": 1705503184874, + "derHash": "q0N8awz3bf4+1wAXQ2gKFRabjYiuwAqfcabCgkro/0E=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "910bf03cdf3fa3ae1c6d6d0e6fc94c87191aa74efcf445b57e3233a2cfa17c40", + "size": 1642, + "filename": "sTITjdyzbufqQcFMCyTHS_F6AxlzIWMLyJM8V8Bk_T4=.pem", + "location": "security-state-staging/intermediates/5b0e8b09-8b61-4587-8e16-ec4ffb6b7125.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sTITjdyzbufqQcFMCyTHS/F6AxlzIWMLyJM8V8Bk/T4=", + "crlite_enrolled": false, + "id": "eebd2808-c11c-42f2-a774-8c3603f2a033", + "last_modified": 1705503423147 + }, + { + "schema": 1705503184603, + "derHash": "O3TYFdCt2LpBKjdmyAXk8R+UaUO+jxip0Ze1JvtCBrw=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "f43a7319419277da02713d279b13c9c47c4b2c27cf274950950e869a2a80fdd2", + "size": 1199, + "filename": "Tqawg980bEmVmlcprDRzsWCQaVSSAyhXqTZKLixoUJY=.pem", + "location": "security-state-staging/intermediates/75bf6801-9feb-4ef7-a8ff-8ca9a44efb83.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Tqawg980bEmVmlcprDRzsWCQaVSSAyhXqTZKLixoUJY=", + "crlite_enrolled": false, + "id": "2c45c42f-5c83-4ab5-8e5f-a29a5186bdb1", + "last_modified": 1705503423144 + }, + { + "schema": 1705503184291, + "derHash": "zhGwCtONMJLmGASIZdKLXugtNn1ylt2DOJgYxVx32j4=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjQgUTI=", + "whitelist": false, + "attachment": { + "hash": "2549757a47de6df222ba832a4e2dc80ddc101798aecca4ba08115212fbefb8ea", + "size": 1646, + "filename": "9c92ps9ViIvqXXEV20RON5_nuySPkzdOhK2H-pSFAMo=.pem", + "location": "security-state-staging/intermediates/8848d0fc-3773-482e-b90b-9b0e8793a0c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9c92ps9ViIvqXXEV20RON5/nuySPkzdOhK2H+pSFAMo=", + "crlite_enrolled": false, + "id": "54348587-ce92-452b-af71-eea27d062170", + "last_modified": 1705503423142 + }, + { + "schema": 1705503185739, + "derHash": "U/Ly08L5UlVUB7i7dwEiKcKk5H9zy0UTLclfbO3UOW8=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "7c02bd82b20db1ffcb06f5729b3f0a33e97dfd07279714141f1f7922f46816b3", + "size": 1642, + "filename": "FqaePH4NiGiiiCLz2DJLzJLm1kRkah4MGHua8wvYqdQ=.pem", + "location": "security-state-staging/intermediates/4df6fb56-e304-4c87-9919-0e50bcf8d887.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FqaePH4NiGiiiCLz2DJLzJLm1kRkah4MGHua8wvYqdQ=", + "crlite_enrolled": false, + "id": "21f97b0d-72c0-437a-9741-f1fbd8f04bd2", + "last_modified": 1705503423139 + }, + { + "schema": 1705503183954, + "derHash": "meY7Gp5MMkZkApw0AnXjCMYQGMvC6EzY6iNmVC6qMJw=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDI0IFEy", + "whitelist": false, + "attachment": { + "hash": "311cc1bba7123536e422149dbead2358daf0ac44b490f73530151d2f01a23cbe", + "size": 1199, + "filename": "TllTpSwnffvffqGBbCRqwBy-Y5hzCrkbySiflulE_PA=.pem", + "location": "security-state-staging/intermediates/9f00d1ae-c133-495d-96d0-628305873ab1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TllTpSwnffvffqGBbCRqwBy+Y5hzCrkbySiflulE/PA=", + "crlite_enrolled": false, + "id": "72c8b3ce-4055-4ae8-817f-119b691b2d87", + "last_modified": 1705503423137 + }, + { + "schema": 1705503183024, + "derHash": "DmxS8W/05adTLvOad2cpnw4oTMLGtkcTwfuoggmMXfU=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "d3bbde30e424be7fa55d0b64376efcd2c0b7e91ad4bb18e37d73a9039d1ccdc0", + "size": 1199, + "filename": "mwOXezCyOlYUkGDPtQ1qCaweeHsWLUN-TwW2E3CCkX0=.pem", + "location": "security-state-staging/intermediates/20e730f2-8925-4c16-ab19-78935db805dd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mwOXezCyOlYUkGDPtQ1qCaweeHsWLUN+TwW2E3CCkX0=", + "crlite_enrolled": false, + "id": "b48c7f1b-a756-4e92-83e4-e6f07074f699", + "last_modified": 1705503423134 + }, + { + "schema": 1705503183660, + "derHash": "1YFqf9X6GNPgxEx4Jz+oVr4KOpk0KIJnY+OogIYxGYY=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyNCBRMg==", + "whitelist": false, + "attachment": { + "hash": "25cf51c651ba51648dc30752317d3087872de9fe09f47fe666561ecc041e6578", + "size": 2345, + "filename": "r6Epwm7CLuojciskFB3GG7NjyZ764bd_d0-TRHGz-0Q=.pem", + "location": "security-state-staging/intermediates/cc45e7df-a37a-4b92-8ba4-59e1a3d6d3ff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "r6Epwm7CLuojciskFB3GG7NjyZ764bd/d0+TRHGz+0Q=", + "crlite_enrolled": false, + "id": "91436012-4de3-47ae-ab28-3d365774ad8f", + "last_modified": 1705503423131 + }, + { + "schema": 1705503183372, + "derHash": "kd3cuVyCWFo9hkX2Zc57i2BlafaUHTO9ndozil721MI=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDI0IFEy", + "whitelist": false, + "attachment": { + "hash": "414b7347871b9c265c22269427a1dd2358873f023cf2bdd96bf3935afdb4db31", + "size": 2353, + "filename": "8YpzQ5_S0kZObvnXnzFciK6491At9Pgmj8U9tMfTfLU=.pem", + "location": "security-state-staging/intermediates/935f48c1-a48e-4ce1-8733-b31354e1edba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8YpzQ5/S0kZObvnXnzFciK6491At9Pgmj8U9tMfTfLU=", + "crlite_enrolled": false, + "id": "ca348f59-2062-4859-b472-05d853b03fdc", + "last_modified": 1705503423129 + }, + { + "schema": 1705503186030, + "derHash": "HSBE4nvcRBHKP6NA0ILBzDKxNmNH1AY+iYRjr6w//wY=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2024 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDI0IFEy", + "whitelist": false, + "attachment": { + "hash": "2546a507b0a7567c5e95eede4e0d04fedc7bee333865b42e75f2061ff57338d8", + "size": 1642, + "filename": "Q8EnsLk8vTIWNzmsmTAWeaot9K_dGpLZbJMW-qEIQHE=.pem", + "location": "security-state-staging/intermediates/ec6453f9-5bf8-4ee9-8e17-c79cc8cb0c54.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Q8EnsLk8vTIWNzmsmTAWeaot9K/dGpLZbJMW+qEIQHE=", + "crlite_enrolled": false, + "id": "f96ed8cd-a04a-4851-9925-344181aeed2a", + "last_modified": 1705503423126 + }, + { + "schema": 1704923284170, + "derHash": "u2FAiu2fUwsuwFReU7osjr6qV9mXZEfbFmPO1GAM1rc=", + "subject": "CN=UCA Global G2 Root,O=UniTrust,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "whitelist": false, + "attachment": { + "hash": "8693b94fe853c85475023fa8be1792fd1f0f41c726cf590ac632829608d32dc3", + "size": 2036, + "filename": "ElXKvoFS-mTflC96R0F-KflsHOEb-MhOy-KBXMEoCBA=.pem", + "location": "security-state-staging/intermediates/f56c76d7-ae95-4ac4-9562-15e370ee8355.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ElXKvoFS+mTflC96R0F+KflsHOEb+MhOy+KBXMEoCBA=", + "crlite_enrolled": false, + "id": "8a3e5d82-5ae8-4e51-a5db-6b86500b155e", + "last_modified": 1704923823508 + }, + { + "schema": 1704923283383, + "derHash": "v6lcXfFktln6MvbRBWTXFw3eZhqFOnguarY2OUM7y0E=", + "subject": "CN=UCA Global G2 Root,O=UniTrust,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "whitelist": false, + "attachment": { + "hash": "5ceff937d1bce8a64fe718857c73f61b29830a24d45ac36cee4de5abec869491", + "size": 2040, + "filename": "ElXKvoFS-mTflC96R0F-KflsHOEb-MhOy-KBXMEoCBA=.pem", + "location": "security-state-staging/intermediates/7cd0dd1c-50ff-4045-8d42-130c062d2dff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ElXKvoFS+mTflC96R0F+KflsHOEb+MhOy+KBXMEoCBA=", + "crlite_enrolled": false, + "id": "2c1748b7-38bf-4495-91ea-1bd4dc918672", + "last_modified": 1704923823505 + }, + { + "schema": 1704469686367, + "derHash": "5LKgc2+VlQ0RuCyebDIDHhQg4GpmST5mXxKK5JlJj6s=", + "subject": "CN=LH.pl CA,O=LH.pl Sp. z o.o.,C=PL", + "subjectDN": "MDsxCzAJBgNVBAYTAlBMMRkwFwYDVQQKDBBMSC5wbCBTcC4geiBvLm8uMREwDwYDVQQDDAhMSC5wbCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1628c3d8568e4de0d51be81fe712f96e36711cc6b153eea5f3108e39a41b9d94", + "size": 1699, + "filename": "MJ-BRaw47gS1vebKWSZ_Y4etVrVSkjRdUN9KP4Ox3NM=.pem", + "location": "security-state-staging/intermediates/6bb2b968-d1dd-408d-bd15-bb2258ca0953.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MJ+BRaw47gS1vebKWSZ/Y4etVrVSkjRdUN9KP4Ox3NM=", + "crlite_enrolled": false, + "id": "9604f1d4-9179-4db7-a8ef-e1fca8de5e17", + "last_modified": 1704470223072 + }, + { + "schema": 1703537607315, + "derHash": "ctcW97tr0QVwT0K5UkkjUQ3LhbLYcMDpraWuuclpBRo=", + "subject": "CN=ePKI Root Certification Authority - G2,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MGMxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEvMC0GA1UEAwwmZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "aff986340dcc592a5eacd96a6044c07f55438a9b0214921ea5306a0ea997fed9", + "size": 2804, + "filename": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=.pem", + "location": "security-state-staging/intermediates/1cd7753f-d18c-4864-8129-0ea1a5092f75.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=", + "crlite_enrolled": false, + "id": "4e467304-4cae-4b24-8542-fa721e586cfb", + "last_modified": 1703537823229 + }, + { + "schema": 1703537607030, + "derHash": "aAfJcjXF7GCQJppLX+36tGmG5C9NZ9Lt3c9uRc8N+oA=", + "subject": "CN=HiPKI Root CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "ME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx", + "whitelist": false, + "attachment": { + "hash": "c94a4c47c3048f75024cf6c844a614b253f7ee3823a41c0e39e567effea001fc", + "size": 2328, + "filename": "ecqvU0fm5KlMjniphJb8dAIPgJ7eE_Ig-rYQTI3tMp8=.pem", + "location": "security-state-staging/intermediates/24d59f73-321d-498a-8fa9-3f4111529687.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ecqvU0fm5KlMjniphJb8dAIPgJ7eE/Ig+rYQTI3tMp8=", + "crlite_enrolled": false, + "id": "64de4f00-ad9d-48db-9d46-2c27c55f4337", + "last_modified": 1703537823226 + }, + { + "schema": 1702979286308, + "derHash": "hHQJ5jUm8WJ1OsSfdSGO+q+n1clK3pCVznLn9rbjrJk=", + "subject": "CN=WE5,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFNQ==", + "whitelist": false, + "attachment": { + "hash": "66a4d320985f57419f1d552144985648eced68840b7963cc0127e86b68ec7423", + "size": 968, + "filename": "8yZxLgpR8EbO8ANRrWKAzIzz9XdlSUgtYPkXKYUlGSY=.pem", + "location": "security-state-staging/intermediates/edf802d5-2d08-4318-97fa-19954fb14b9e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8yZxLgpR8EbO8ANRrWKAzIzz9XdlSUgtYPkXKYUlGSY=", + "crlite_enrolled": false, + "id": "78aa678c-c514-489b-a47f-d976a279011a", + "last_modified": 1702979823409 + }, + { + "schema": 1702979286002, + "derHash": "3JQWwvhVEm1t6XdndTjy+Wf/SZjpDfpDWhchm+B3/AY=", + "subject": "CN=WR4,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dSNA==", + "whitelist": false, + "attachment": { + "hash": "b9f64d659c6ad34b11345bc2d9a597d09b957f0f544c43f9f280e97de8f1f453", + "size": 1808, + "filename": "hZe1OerqJ1Pnq6F4N0gVjjpHqm037Ndf4aLLVpZZdAE=.pem", + "location": "security-state-staging/intermediates/e3958311-921c-432d-9c6d-8965c6365f95.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hZe1OerqJ1Pnq6F4N0gVjjpHqm037Ndf4aLLVpZZdAE=", + "crlite_enrolled": false, + "id": "a4bda45a-5b5d-4254-ad27-c56b8a3484ae", + "last_modified": 1702979823406 + }, + { + "schema": 1702979285359, + "derHash": "nV6GkGoWgKhr4njPduPStit3UYYQFGHTA87pENlM4To=", + "subject": "CN=WE4,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFNA==", + "whitelist": false, + "attachment": { + "hash": "c3f131b2230a3e9820ca964c47489ddaf7fd334dcda9afda8ea07512cc8a9bcd", + "size": 943, + "filename": "O5TQDB_wa4SkRjBrQL2Aq9CG317H9MDDgpTVcrpJDa4=.pem", + "location": "security-state-staging/intermediates/df749bb2-7318-4d17-9bd5-f5f71cc23b79.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "O5TQDB/wa4SkRjBrQL2Aq9CG317H9MDDgpTVcrpJDa4=", + "crlite_enrolled": false, + "id": "f8362afb-a2e5-4df9-8ef7-700eae6b88ec", + "last_modified": 1702979823403 + }, + { + "schema": 1702979285043, + "derHash": "rg/IUigPG4fO2vc8+4TPEG7+yI6ClCU681LtQDRGDXs=", + "subject": "CN=WR5,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dSNQ==", + "whitelist": false, + "attachment": { + "hash": "a06befc3d96cade86bed7e7b22a0bf465be67e57d58c636c2c27eb41b08c2350", + "size": 1808, + "filename": "_RoweMyX8j8_fAaaeAIwzhWXigZDrh_J2BBJaHrjX90=.pem", + "location": "security-state-staging/intermediates/f6b4c8a8-e26f-42cd-8cfd-622456e2cfb9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/RoweMyX8j8/fAaaeAIwzhWXigZDrh/J2BBJaHrjX90=", + "crlite_enrolled": false, + "id": "e8fb1277-f72f-4751-a48a-c0fd93c45a08", + "last_modified": 1702979823401 + }, + { + "schema": 1702979285679, + "derHash": "oof/q3Ysxpom1IIDft9wH2U86JkCXGKn5cuIu5tBnLs=", + "subject": "CN=WE1,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMQ==", + "whitelist": false, + "attachment": { + "hash": "76c8cd346304c20236963ef1d9a2632cb775d29be521315f940c7feb5b819428", + "size": 947, + "filename": "kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa_A4=.pem", + "location": "security-state-staging/intermediates/44091d85-b3e1-4a45-93b7-40094d3626b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=", + "crlite_enrolled": false, + "id": "b92b92e1-4d03-41fb-aea8-2ac1f6ccba1f", + "last_modified": 1702979823398 + }, + { + "schema": 1702979282537, + "derHash": "0Ml+VsewuoEtlErXcfd5m11BRKIyek5BZVT37iqgrq4=", + "subject": "CN=WE4,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFNA==", + "whitelist": false, + "attachment": { + "hash": "88a0f8993576590f88eb47c537e02214b477c44580eb23ac4233563fbc8833bb", + "size": 968, + "filename": "O5TQDB_wa4SkRjBrQL2Aq9CG317H9MDDgpTVcrpJDa4=.pem", + "location": "security-state-staging/intermediates/907554ce-bf54-4a19-8a6e-15fe19e6bb1c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "O5TQDB/wa4SkRjBrQL2Aq9CG317H9MDDgpTVcrpJDa4=", + "crlite_enrolled": false, + "id": "6585b4ef-414a-4d88-a7db-3912f1c07563", + "last_modified": 1702979823396 + }, + { + "schema": 1702979284073, + "derHash": "HfwWBfutNY2LyET3bRUgP6ycpcGnn9SFf/ryhk++v5Y=", + "subject": "CN=WE1,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMQ==", + "whitelist": false, + "attachment": { + "hash": "27d73fbd71e61bb97a5ee9eb30d07f7b8a6ae0b9eacb1ca9dc0bd7789a21117f", + "size": 968, + "filename": "kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa_A4=.pem", + "location": "security-state-staging/intermediates/22dd23ed-6558-4d33-884e-bb8c51dc3f70.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=", + "crlite_enrolled": false, + "id": "b925e54a-22e0-429e-a2a7-938e222fa9ae", + "last_modified": 1702979823394 + }, + { + "schema": 1702979282838, + "derHash": "gSwhLp5F3FAFx/R0ERg/X7L/G67hhNM1Sy6T14woAWQ=", + "subject": "CN=AE1,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA0FFMQ==", + "whitelist": false, + "attachment": { + "hash": "45a0fe1e15d4882a45910bffd62beab210d7ea87b00bbe4244ef70c872784700", + "size": 955, + "filename": "IGnQeIPjbxiR-JteR7AkUXl7pV-19BT4x3o15saZBNE=.pem", + "location": "security-state-staging/intermediates/fa74b311-65fe-4ff6-ab38-73f631970478.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IGnQeIPjbxiR+JteR7AkUXl7pV+19BT4x3o15saZBNE=", + "crlite_enrolled": false, + "id": "1100d9a6-f856-4661-8965-1420202593f3", + "last_modified": 1702979823391 + }, + { + "schema": 1702979282239, + "derHash": "n4GaTIduEtyE5v4ON8GmmxNwlLRT+phEk5j0tx9NAJI=", + "subject": "CN=WE3,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMw==", + "whitelist": false, + "attachment": { + "hash": "3096e62091f7005bfbb940f7c8d115868e2610909c6d3e3ff6708d0056b84eb2", + "size": 968, + "filename": "daBIAnKdRIX3bqM85I6We7wBUh0DPycNFBMvYkXGX2Q=.pem", + "location": "security-state-staging/intermediates/bfd5d12e-a763-4678-91e0-e15a14487bfa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "daBIAnKdRIX3bqM85I6We7wBUh0DPycNFBMvYkXGX2Q=", + "crlite_enrolled": false, + "id": "a7495087-922c-4003-8cac-93d2e522f9ab", + "last_modified": 1702979823388 + }, + { + "schema": 1702979283154, + "derHash": "5v4iv0Xk8NO4XFngLA9JVBjh640yEPeI1IzV4ctUfNQ=", + "subject": "CN=WR2,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dSMg==", + "whitelist": false, + "attachment": { + "hash": "209c8adefd267eac1bb057687118f475db2b88e0266ba7159a0da0420d4d9232", + "size": 1808, + "filename": "YPtHaftLw6_0vnc2BnNKGF54xiCA28WFcccjkA4ypCM=.pem", + "location": "security-state-staging/intermediates/30e866fe-25a8-40a5-aeb1-ac0f08756ce5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YPtHaftLw6/0vnc2BnNKGF54xiCA28WFcccjkA4ypCM=", + "crlite_enrolled": false, + "id": "87dfc3f3-2875-4c74-8f7d-90238d4926b5", + "last_modified": 1702979823385 + }, + { + "schema": 1702979283452, + "derHash": "L+NX2xN1H/kWDoc1SXWzQHSY9Byb0WpIZXhm5uWptMc=", + "subject": "CN=WR3,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dSMw==", + "whitelist": false, + "attachment": { + "hash": "2af156305a26830e0cab92f695d4fdd2a232114a81ddc36bea3137dc4b822115", + "size": 1808, + "filename": "OdSlmQD9NWJh4EbcOHBxkhygPwNSwA9Q91eounfbcoE=.pem", + "location": "security-state-staging/intermediates/b30ba518-de9f-42f8-8c99-0a3321df25b2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OdSlmQD9NWJh4EbcOHBxkhygPwNSwA9Q91eounfbcoE=", + "crlite_enrolled": false, + "id": "a9ea65b5-af3d-4de9-aacb-60c3915032fe", + "last_modified": 1702979823383 + }, + { + "schema": 1702979284438, + "derHash": "VMZg2inXX8gfB61tyLt67iJY4HHosQd1RPpWIv9EyZ0=", + "subject": "CN=WE3,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMw==", + "whitelist": false, + "attachment": { + "hash": "5f69b98f75457dd75b1fc0a89ec577a9ba7d8fdcfb3957bdc2a3d2c593652244", + "size": 943, + "filename": "daBIAnKdRIX3bqM85I6We7wBUh0DPycNFBMvYkXGX2Q=.pem", + "location": "security-state-staging/intermediates/7405438c-69c0-4704-a55b-babb5322df47.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "daBIAnKdRIX3bqM85I6We7wBUh0DPycNFBMvYkXGX2Q=", + "crlite_enrolled": false, + "id": "2341b1ef-a5b9-45d6-9333-5f146e9e60a8", + "last_modified": 1702979823380 + }, + { + "schema": 1702979283765, + "derHash": "sQtvAOYJUJ6HAPbTRoeiv8446gWo/fHNxAw6Kg0NDkU=", + "subject": "CN=WR1,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dSMQ==", + "whitelist": false, + "attachment": { + "hash": "f36fa7b73961008c46f560c5bb291bd2a76dae4e3fd4564ecc3f1d589f63bf3d", + "size": 1808, + "filename": "yDu9og255NN5GEf-Bwa9rTrqFQ0EydZ0r1FCh9TdAW4=.pem", + "location": "security-state-staging/intermediates/e8838a0d-c424-498b-900c-656973dce5d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yDu9og255NN5GEf+Bwa9rTrqFQ0EydZ0r1FCh9TdAW4=", + "crlite_enrolled": false, + "id": "54de915e-a0b4-4748-a565-3e96ab41d70b", + "last_modified": 1702979823377 + }, + { + "schema": 1702979281449, + "derHash": "nD8v0RxX18ZJrVoJMsDw0pdW9qChx0xD4eiaYtZM0yA=", + "subject": "CN=WE2,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMg==", + "whitelist": false, + "attachment": { + "hash": "d8e7fe5c7e6836f1fdc5ff81a1b9510c11c57d74b9323efa7a37531a88ca2cdf", + "size": 968, + "filename": "vh78KSg1Ry4NaqGDV10w_cTb9VH3BQUZoCWNa93W_EY=.pem", + "location": "security-state-staging/intermediates/1f80b11f-7e0a-48dd-a7a0-b3d2daf1588c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vh78KSg1Ry4NaqGDV10w/cTb9VH3BQUZoCWNa93W/EY=", + "crlite_enrolled": false, + "id": "6c362de1-6fa5-49fe-808a-92fea5ac91ff", + "last_modified": 1702979823374 + }, + { + "schema": 1702979284729, + "derHash": "VPjKhYvMdZHyjY3Ddy6bxYFxfzojooi/1AWTnDYgjeU=", + "subject": "CN=WE2,O=Google Trust Services,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxDDAKBgNVBAMTA1dFMg==", + "whitelist": false, + "attachment": { + "hash": "bbbc5386082e7cac318c5296b4d680d4bffbf8511c5798b5119a9473288a7784", + "size": 943, + "filename": "vh78KSg1Ry4NaqGDV10w_cTb9VH3BQUZoCWNa93W_EY=.pem", + "location": "security-state-staging/intermediates/50a88d6e-2070-4289-91ce-aff4d6d1ac66.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vh78KSg1Ry4NaqGDV10w/cTb9VH3BQUZoCWNa93W/EY=", + "crlite_enrolled": false, + "id": "32c0830e-d23a-448d-b04c-f1d9a5d6ec09", + "last_modified": 1702979823371 + }, + { + "schema": 1702698485861, + "derHash": "6muJ7WkHogn/kYhnb7Fk56ztiUuJlt++XOW7zCLeTd0=", + "subject": "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB", + "subjectDN": "MF8xCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNjA0BgNVBAMTLVNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBSb290IEU0Ng==", + "whitelist": false, + "attachment": { + "hash": "66ca4451c8bd1eb771a373c620d27ea8c021479b2a2d66fbc4bb82e32517c827", + "size": 1195, + "filename": "sLVjNUaFYfW7n6EtgBeEpjOlcnBdNPMrZDRF36iwBdE=.pem", + "location": "security-state-staging/intermediates/70f63da3-037d-416c-9aa3-f651cf4ddbf6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sLVjNUaFYfW7n6EtgBeEpjOlcnBdNPMrZDRF36iwBdE=", + "crlite_enrolled": false, + "id": "2f13933d-5bd4-4d26-982b-8557eab6a72a", + "last_modified": 1702699023180 + }, + { + "schema": 1702698486394, + "derHash": "kvNRvz1UFk36jdj54ROdMVA0l4ZIXSue7NAOKXHB5sU=", + "subject": "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB", + "subjectDN": "MF8xCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNjA0BgNVBAMTLVNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBSb290IFI0Ng==", + "whitelist": false, + "attachment": { + "hash": "1d6ba9f4a87acf2d3350366ab53367f2333574bf3944d21dcdd14afd0d3a17a8", + "size": 2341, + "filename": "Douxi77vs4G-Ib_BogbTFymEYq0QSFXwSgVCaZcI09Q=.pem", + "location": "security-state-staging/intermediates/c7eebf9a-39ad-4708-9361-d05032a25ded.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Douxi77vs4G+Ib/BogbTFymEYq0QSFXwSgVCaZcI09Q=", + "crlite_enrolled": false, + "id": "6220cf95-dd0c-4ef5-9595-7def8f40b4fd", + "last_modified": 1702699023177 + }, + { + "schema": 1702676885375, + "derHash": "aAJwHw/Qlg/ytR85qusgp3jYMmGpWa0Nf/C+VCQPZz0=", + "subject": "CN=Sectigo Public Server Authentication Root E46,O=Sectigo Limited,C=GB", + "subjectDN": "MF8xCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNjA0BgNVBAMTLVNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBSb290IEU0Ng==", + "whitelist": false, + "attachment": { + "hash": "daf33943ee6515c6f6685da58acad792f1d41238f045fcb457afbaf996102599", + "size": 1374, + "filename": "sLVjNUaFYfW7n6EtgBeEpjOlcnBdNPMrZDRF36iwBdE=.pem", + "location": "security-state-staging/intermediates/dfbbfdc5-3747-4cf5-b965-03ecde1435ac.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sLVjNUaFYfW7n6EtgBeEpjOlcnBdNPMrZDRF36iwBdE=", + "crlite_enrolled": false, + "id": "1e974b0f-b590-4131-8c61-60f2ec053a74", + "last_modified": 1702677423284 + }, + { + "schema": 1702676885859, + "derHash": "znVYC8Z5bTs+osPnQlnQ1AMHJAjpYMdxOcL7Wj2UGeQ=", + "subject": "CN=Sectigo Public Server Authentication Root R46,O=Sectigo Limited,C=GB", + "subjectDN": "MF8xCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNjA0BgNVBAMTLVNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBSb290IFI0Ng==", + "whitelist": false, + "attachment": { + "hash": "7b1997c27bf553e2790dae9518128f592d597f964bd542ba20ab323fec95ce3f", + "size": 1959, + "filename": "Douxi77vs4G-Ib_BogbTFymEYq0QSFXwSgVCaZcI09Q=.pem", + "location": "security-state-staging/intermediates/342d68ca-c5c7-4035-9b9e-605a2d14be23.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Douxi77vs4G+Ib/BogbTFymEYq0QSFXwSgVCaZcI09Q=", + "crlite_enrolled": false, + "id": "d658757e-f0e6-403c-add5-2d1b715ef254", + "last_modified": 1702677423281 + }, + { + "schema": 1702525683451, + "derHash": "kWPxkQ1JRL4y0NIkp5+fprQDbpCYeVrIHT3RTOT/JNI=", + "subject": "CN=DigiCert QuoVadis 2G3 TLS RSA4096 SHA384 2023 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MGwxCzAJBgNVBAYTAklFMSEwHwYDVQQKDBhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxOjA4BgNVBAMMMURpZ2lDZXJ0IFF1b1ZhZGlzIDJHMyBUTFMgUlNBNDA5NiBTSEEzODQgMjAyMyBDQTE=", + "whitelist": false, + "attachment": { + "hash": "066e7c017a39db9d21ea6c626a1fbd345b2241304c37c26cd55933353559cc74", + "size": 2337, + "filename": "ZPyxn3oNKP1K_sTUV8OfjfYB2niVatmowpkxgfZdTVA=.pem", + "location": "security-state-staging/intermediates/f818231c-ae72-4768-a4a5-5ae7765de037.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZPyxn3oNKP1K/sTUV8OfjfYB2niVatmowpkxgfZdTVA=", + "crlite_enrolled": false, + "id": "6a2998d4-e046-489f-9823-30f0aa88d1df", + "last_modified": 1702526223655 + }, + { + "schema": 1702486085377, + "derHash": "CoUHXdazguqzFEmCPbi+1rYaRBcUFl4z+spCzZyN7Co=", + "subject": "CN=vTrus DV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEUxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRgwFgYDVQQDEw92VHJ1cyBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "006851a10c762422e7e12ab4089a413ae73d2e8903dcf2b5f33339eb89028d4d", + "size": 2064, + "filename": "75pE_IZKhqgviWzR4qTd2zPqnbH6f4VSS4OvPeBjEjQ=.pem", + "location": "security-state-staging/intermediates/e1f0958b-86df-449d-b5f1-2757e988420c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "75pE/IZKhqgviWzR4qTd2zPqnbH6f4VSS4OvPeBjEjQ=", + "crlite_enrolled": false, + "id": "d6f08689-0364-4035-9f67-188ef9a50390", + "last_modified": 1702486623398 + }, + { + "schema": 1702486084746, + "derHash": "O4PrXXqaWvBidaChwbNb1WJiKlUh4mmfJVWTKLiCkFg=", + "subject": "CN=vTrus FastSSL CA G1,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRwwGgYDVQQDExN2VHJ1cyBGYXN0U1NMIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "21f6f414641686539d652061920c99da66eeac581d65caca2e1263ca27038b16", + "size": 2073, + "filename": "fpLQRtZkeoQDvEsHjbmGGp4fKzFQ1gW-e9kvlVQKjBY=.pem", + "location": "security-state-staging/intermediates/f44e1b60-b2f5-4240-a8cb-336596d983f3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fpLQRtZkeoQDvEsHjbmGGp4fKzFQ1gW+e9kvlVQKjBY=", + "crlite_enrolled": false, + "id": "c65114a4-315c-4f34-8e9e-0ef28fbdfdc5", + "last_modified": 1702486623395 + }, + { + "schema": 1702486083830, + "derHash": "p9coWEO4mxNPhSy1Km9DGTglfIJtaZqoBsiUoKHNuEc=", + "subject": "CN=vTrus YunSSL DV CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRswGQYDVQQDExJ2VHJ1cyBZdW5TU0wgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "ba59a36c91137df6649c2f760f9117e321c471b7573176c4f2332ec8bddde489", + "size": 2068, + "filename": "OfXp7s_mA-7Syh7zoom2IuL92dtJFWJQ4LjjCHnZCrk=.pem", + "location": "security-state-staging/intermediates/b4f3d401-59d8-4949-82ef-944da5336058.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OfXp7s/mA+7Syh7zoom2IuL92dtJFWJQ4LjjCHnZCrk=", + "crlite_enrolled": false, + "id": "28811891-e6f8-4c0f-bce3-5c665db2b118", + "last_modified": 1702486623391 + }, + { + "schema": 1702486085061, + "derHash": "LPVTkkmp44/AEOKf8+gEZljz0DC5MxBHNof6kfjaRMo=", + "subject": "CN=vTrus OV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEUxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRgwFgYDVQQDEw92VHJ1cyBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "d32a79724707e94c73675621bcd08858efa114f126cfd38d017df2d44b3177b1", + "size": 2064, + "filename": "_AQe5lWT9xIwgAiIHcAT-pRj70-ckw8xE4qArEfIyd0=.pem", + "location": "security-state-staging/intermediates/1ca4e475-3a37-4fba-b668-07c7f5b577dc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/AQe5lWT9xIwgAiIHcAT+pRj70+ckw8xE4qArEfIyd0=", + "crlite_enrolled": false, + "id": "e01d9a3b-493a-4396-9ca4-f3f2de493daa", + "last_modified": 1702486623388 + }, + { + "schema": 1702486085692, + "derHash": "g0aSLLhzC7aucasDv8QkYvQWBCPZB5vmQ4ViGsWHdnI=", + "subject": "CN=Cybertrust Japan SureServer CA G4,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MF4xCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEqMCgGA1UEAxMhQ3liZXJ0cnVzdCBKYXBhbiBTdXJlU2VydmVyIENBIEc0", + "whitelist": false, + "attachment": { + "hash": "1c24671ce668c95b4f55ebb766c3a802692898e52ea09312bec303f2d3ef9e46", + "size": 1792, + "filename": "rS4Ex7fMz9dQhgdB6qjxP-jJJQwjIeb-7RhvvdO6xy8=.pem", + "location": "security-state-staging/intermediates/bbaf0f46-8126-403d-b143-4e1eb27f9cd6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rS4Ex7fMz9dQhgdB6qjxP+jJJQwjIeb+7RhvvdO6xy8=", + "crlite_enrolled": false, + "id": "16279f1b-5776-48e4-9244-7efe39ec603d", + "last_modified": 1702486623385 + }, + { + "schema": 1702486084138, + "derHash": "tyMnOjUGxr7YXwg9pWJzS+CfLEet5HMXgx1jqovieKU=", + "subject": "CN=Cybertrust Japan SureServer EV CA G3,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MGExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEtMCsGA1UEAxMkQ3liZXJ0cnVzdCBKYXBhbiBTdXJlU2VydmVyIEVWIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "d764598a0c68977c0a59932194309f858c7c76dce31f38e15a84b22b718b49d2", + "size": 1796, + "filename": "zJoepoy-7XeJoRmjbR7_i8oZ1WEujOKGTD9NNM0uWHM=.pem", + "location": "security-state-staging/intermediates/7b0627e5-46dd-4a39-bfbf-9008ac22545a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zJoepoy+7XeJoRmjbR7/i8oZ1WEujOKGTD9NNM0uWHM=", + "crlite_enrolled": false, + "id": "241a27dc-6ea8-4c6a-9971-ee313af5e225", + "last_modified": 1702486623382 + }, + { + "schema": 1702486084441, + "derHash": "TTkfx5C9cCytPMvAsCXFzdiMb98nTc9fvT8CeoDCx+U=", + "subject": "CN=vTrus ECC DV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRwwGgYDVQQDExN2VHJ1cyBFQ0MgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "5d0d09acee96f60239874fa8cbd87dfee41ac77ccb2c2345645425c953b4c5c6", + "size": 1248, + "filename": "gJ1It3GyfR5y0xbHIcRJPwjI6151kRqrGSR_W5v6jek=.pem", + "location": "security-state-staging/intermediates/d7ce4c2f-7a54-4c3b-b5d0-e9a45a50d0ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gJ1It3GyfR5y0xbHIcRJPwjI6151kRqrGSR/W5v6jek=", + "crlite_enrolled": false, + "id": "4492cc7c-97f7-4600-9e46-da9cddcdec08", + "last_modified": 1702486623379 + }, + { + "schema": 1702486085984, + "derHash": "QuRsRIdFkShRdklzFFe2riAJnVQb0YLFSXsuZ+b/0PY=", + "subject": "CN=vTrus ECC OV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRwwGgYDVQQDExN2VHJ1cyBFQ0MgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "9a039a52d5e8d351f219305666a32564c917aa7d800fad7ae1c6f67c124b735f", + "size": 1248, + "filename": "atwg6ejpADR_jBbKnuSFzeAuVeHiQKJdUl2HSR0gLmU=.pem", + "location": "security-state-staging/intermediates/422a5983-0489-4cd2-8b21-9eed0ec2384a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "atwg6ejpADR/jBbKnuSFzeAuVeHiQKJdUl2HSR0gLmU=", + "crlite_enrolled": false, + "id": "5e3187ba-e500-4d22-865b-3ba0e069935c", + "last_modified": 1702486623376 + }, + { + "schema": 1702352884804, + "derHash": "mDgOQ34aRuasKqZNe+bXHylp5RCsr103hGzxK4r+ez4=", + "subject": "CN=CFCA EV RSA CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgRVYgUlNBIENB", + "whitelist": false, + "attachment": { + "hash": "02174d0773a22f7ddf45bfa6c9381a80dc9e3cea87a1c433797804f1cfa12be2", + "size": 1735, + "filename": "FostjSDLEiGEYgqs_X5gkmIzrdLVZOV7yvaKWmt947s=.pem", + "location": "security-state-staging/intermediates/8c73311a-b6ca-411f-9470-2052fdac35aa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FostjSDLEiGEYgqs/X5gkmIzrdLVZOV7yvaKWmt947s=", + "crlite_enrolled": false, + "id": "52b8fd61-3e6e-4cdc-b2d3-c70ab16f8fae", + "last_modified": 1702353423385 + }, + { + "schema": 1702352884534, + "derHash": "qSvx/fstoqHOYOdECZLlOkEM4vqoq3Hq10RPJIbl+p8=", + "subject": "CN=CFCA OV ECC CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgT1YgRUNDIENB", + "whitelist": false, + "attachment": { + "hash": "3da3c929f3dcc4dbfe6eaebe09c4fab613a57298f098f007d68254f442742b70", + "size": 1735, + "filename": "TN_N6Jf5rIbtK8zrR3O_l4jRaH_uNDW2uy7BpL6wRpI=.pem", + "location": "security-state-staging/intermediates/acefad73-b52a-43ed-b85d-8cab0869f2ac.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TN/N6Jf5rIbtK8zrR3O/l4jRaH/uNDW2uy7BpL6wRpI=", + "crlite_enrolled": false, + "id": "085e76be-1462-4bb5-a71b-13de680c2ff9", + "last_modified": 1702353423383 + }, + { + "schema": 1702352884224, + "derHash": "16Q6q1tZ9Yy2jMAc+Dijku6RP/Mw/nwzCe2jC4RUHQc=", + "subject": "CN=vTrus DV SSL CA G2,O=iTrusChina Co.\\, Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRpVHJ1c0NoaW5hIENvLiwgTHRkLjEbMBkGA1UEAwwSdlRydXMgRFYgU1NMIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "a28ebaa77124c4b2275a78789c6c65782052cf8a54da01edea3da85edb1d3437", + "size": 1715, + "filename": "6_JADGgIS7qnL3q5HjSoTOzc3odMNijcmursMnJhReQ=.pem", + "location": "security-state-staging/intermediates/f00f70b3-f28e-431f-8f71-a2f633499040.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6/JADGgIS7qnL3q5HjSoTOzc3odMNijcmursMnJhReQ=", + "crlite_enrolled": false, + "id": "dc84f6b9-ac89-467f-bb01-900748e40219", + "last_modified": 1702353423380 + }, + { + "schema": 1702352885755, + "derHash": "B26nNYOJQQ34XZEdJRuaWCxeb2hODwRu1DAYfwUzrvw=", + "subject": "CN=CFCA OV RSA CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgT1YgUlNBIENB", + "whitelist": false, + "attachment": { + "hash": "e326ed8724e104b43f7a73dadcafea08ad7d6fdd820d8a4558bea5ddd1072ce6", + "size": 1735, + "filename": "oDH56AcLYxtHRL0Vzs-6NObCXyFWaCqKf19VIrBXVKY=.pem", + "location": "security-state-staging/intermediates/dd558b1b-c233-4ce6-b488-e22e9314516b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oDH56AcLYxtHRL0Vzs+6NObCXyFWaCqKf19VIrBXVKY=", + "crlite_enrolled": false, + "id": "18d9efdc-6b6e-4d9f-ad58-bfd9db3909f6", + "last_modified": 1702353423378 + }, + { + "schema": 1702352885173, + "derHash": "7RJPR1qvTZjR5hjTvjLwA7PLuHPH0VsPGgCJc5y05Mw=", + "subject": "CN=CFCA DV RSA CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgRFYgUlNBIENB", + "whitelist": false, + "attachment": { + "hash": "1b74e4ff46433497155f6445c718c2b35401560cde64d863bb7afa29a28cb274", + "size": 1735, + "filename": "o8-G4ge4DP6REqP8nqEgIKpU1MYBfSTo2XXoAIlBhVA=.pem", + "location": "security-state-staging/intermediates/7f16a83f-64ff-4abe-81b4-4a97981da839.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o8+G4ge4DP6REqP8nqEgIKpU1MYBfSTo2XXoAIlBhVA=", + "crlite_enrolled": false, + "id": "0549e809-df97-4bac-8e1a-3c257d5b5c47", + "last_modified": 1702353423376 + }, + { + "schema": 1702352883540, + "derHash": "MpbrBgyIfo7A5ZkD6cyNX2MC7o+rqVYgrl3iNkMz91M=", + "subject": "CN=vTrus OV SSL CA G2,O=iTrusChina Co.\\, Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRpVHJ1c0NoaW5hIENvLiwgTHRkLjEbMBkGA1UEAwwSdlRydXMgT1YgU1NMIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "17d7817dc4df1312e08da6598409e25705300aaf63ac4de26dd9534ebf355212", + "size": 1715, + "filename": "M_T-ODly7zfdF_regqVPUCM52AYR2nAJSX2YFe_geDU=.pem", + "location": "security-state-staging/intermediates/efa4881e-b0ac-4ed6-8d4d-083540c8ea0e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M/T+ODly7zfdF/regqVPUCM52AYR2nAJSX2YFe/geDU=", + "crlite_enrolled": false, + "id": "21fdc757-5060-4551-964d-f884521c5212", + "last_modified": 1702353423373 + }, + { + "schema": 1702352885455, + "derHash": "1lgQ/9e/mg5Xed9nU7o4lDefhQsbdDrsVXhvmKwpAR8=", + "subject": "CN=CFCA EV ECC CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgRVYgRUNDIENB", + "whitelist": false, + "attachment": { + "hash": "2e785ab906f57de5b38277ddb2e235376abc6cdceff60668f1b20926a5bf7aaa", + "size": 1735, + "filename": "T_J7T2Xp0tXUxym7kWqj938wizi4IJKSuk2Cx_ei-a0=.pem", + "location": "security-state-staging/intermediates/219f5965-7274-4186-bb78-8e90c65398ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "T/J7T2Xp0tXUxym7kWqj938wizi4IJKSuk2Cx/ei+a0=", + "crlite_enrolled": false, + "id": "c5d19d1d-497a-4df6-a274-e1941058ddab", + "last_modified": 1702353423371 + }, + { + "schema": 1702352886056, + "derHash": "aQ3Vq2kACYEzObxmjG04ORRPNOF+W+fwlYh56angV/s=", + "subject": "CN=CFCA DV ECC CA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFzAVBgNVBAMMDkNGQ0EgRFYgRUNDIENB", + "whitelist": false, + "attachment": { + "hash": "de5c1276a142ce3215f26b9d34df3e6b9c947ebad516149c89a1a382807bc023", + "size": 1735, + "filename": "QCrNhyXiWTS-zTKMhcNGYuaF3Sh-qCy3Hr77ZyQ-XTA=.pem", + "location": "security-state-staging/intermediates/f004a08f-2c32-40e8-b2b9-9fded843766e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QCrNhyXiWTS+zTKMhcNGYuaF3Sh+qCy3Hr77ZyQ+XTA=", + "crlite_enrolled": false, + "id": "27a87848-2d12-4437-90f7-c2ddf5caf834", + "last_modified": 1702353423368 + }, + { + "schema": 1700646486489, + "derHash": "hA6N0d/JwMUNKcqFEpkc8u19zd8SQQN1/QpdR/j79XY=", + "subject": "CN=CommScope Public Trust RSA Sub-CA-02-01,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFN1Yi1DQS0wMi0wMQ==", + "whitelist": false, + "attachment": { + "hash": "762f4a00fafbf72cafcfbf2df28508633b74eab8d56655a38f34f22cda936dd0", + "size": 2560, + "filename": "KqL7He22QNw5xBoQbw-L4F7QQHXu2QEKXyV8S9IIM7Q=.pem", + "location": "security-state-staging/intermediates/4358f58e-e583-4cef-909e-09165bcf77e4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KqL7He22QNw5xBoQbw+L4F7QQHXu2QEKXyV8S9IIM7Q=", + "crlite_enrolled": false, + "id": "839cfa6e-a330-4c7a-9c3f-33e9a898e391", + "last_modified": 1700647023222 + }, + { + "schema": 1700646486205, + "derHash": "64nTCH12kS12FqyZxzuuusH7jkg1+LoVtUP3NEpMV/E=", + "subject": "CN=CommScope Public Trust ECC Sub-CA-01-01,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFN1Yi1DQS0wMS0wMQ==", + "whitelist": false, + "attachment": { + "hash": "2be311121ba10df25faf0d5302d6bfd11db4831fdab8d31c6d8306841c759de9", + "size": 1414, + "filename": "2eDPH26eDI6d3-Tsjd41zsuO9Klm9C0IFnwK1xslzpw=.pem", + "location": "security-state-staging/intermediates/0d83f23e-7f91-4581-8469-5546f6804616.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2eDPH26eDI6d3+Tsjd41zsuO9Klm9C0IFnwK1xslzpw=", + "crlite_enrolled": false, + "id": "4f712de1-16d2-4746-83e8-6e6dd3d87a4e", + "last_modified": 1700647023220 + }, + { + "schema": 1700646485601, + "derHash": "7ua69QsUvKBCArSWc35nb3E+dDH/1PyhMJ9mFIHsUg8=", + "subject": "CN=TrustAsia OV TLS ECC CA G4,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgT1YgVExTIEVDQyBDQSBHNA==", + "whitelist": false, + "attachment": { + "hash": "617ea9dd114bf829cd96b86bfde27f76e81a55711482fc7741ffb62c1037b958", + "size": 1317, + "filename": "H9C3IdVYXZbeqTf00x7ZWdOLTFS4QQJGl7WOOTEDfxI=.pem", + "location": "security-state-staging/intermediates/0a330041-2a71-4516-85f4-7586c3d2a439.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "H9C3IdVYXZbeqTf00x7ZWdOLTFS4QQJGl7WOOTEDfxI=", + "crlite_enrolled": false, + "id": "e6a2fdb2-02c5-4708-be6f-fa2591ac19d7", + "last_modified": 1700647023217 + }, + { + "schema": 1700646485912, + "derHash": "AhUxG8GC1ZcYgitF3fECE84mYak8PC0gPexlZ95Erjw=", + "subject": "CN=CommScope Public Trust RSA Sub-CA-01-02,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFN1Yi1DQS0wMS0wMg==", + "whitelist": false, + "attachment": { + "hash": "76fa134521bf3180c607f441ea511de8222ac07021bcb04f2b51434a869b6020", + "size": 2560, + "filename": "uUmm1vqY4WOAXX3tIc9qA2Ai5Yr0EmaZlBwLTelbnzw=.pem", + "location": "security-state-staging/intermediates/75bd4b66-445f-4c60-b998-2891565d084d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uUmm1vqY4WOAXX3tIc9qA2Ai5Yr0EmaZlBwLTelbnzw=", + "crlite_enrolled": false, + "id": "d2ed46e8-b5af-4401-a731-badd42ed9265", + "last_modified": 1700647023215 + }, + { + "schema": 1700646485300, + "derHash": "WY0hJQ3EAC/X8fwO9TBjPUsWh9hZfAp6hv2w6NcKQEs=", + "subject": "CN=CommScope Public Trust ECC Sub-CA-02-01,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFN1Yi1DQS0wMi0wMQ==", + "whitelist": false, + "attachment": { + "hash": "0861064b3940fe7c22d8860cbd8234786257b9ac114ebd823875c18db9e03559", + "size": 1414, + "filename": "FreBKmEXdNpyFMcG1o3nDNjwoZ5Vc924tuSbvWdZE64=.pem", + "location": "security-state-staging/intermediates/47074e7c-85a6-4995-b101-879fd3de19d0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FreBKmEXdNpyFMcG1o3nDNjwoZ5Vc924tuSbvWdZE64=", + "crlite_enrolled": false, + "id": "8f4225a5-796d-4d7a-8510-628d12fb66f6", + "last_modified": 1700647023212 + }, + { + "schema": 1700646484689, + "derHash": "woNIdA2jbPFN3EkoDPAwq5zjsKzgk7DhuhiUVvSRi5w=", + "subject": "CN=TrustAsia DV TLS ECC CA G4,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgRFYgVExTIEVDQyBDQSBHNA==", + "whitelist": false, + "attachment": { + "hash": "cd5019fcec123ec2fc8f4a53aebd1cbb131ebef82743dda28ee96724331751de", + "size": 1317, + "filename": "R-s0kdm8EzaFO3Liyw3bJqtHA3yG7-iGzhCMKglNFxY=.pem", + "location": "security-state-staging/intermediates/bef125ee-c7d1-49c8-9a1c-4e2d1cedf07d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "R+s0kdm8EzaFO3Liyw3bJqtHA3yG7+iGzhCMKglNFxY=", + "crlite_enrolled": false, + "id": "fe879c91-8b4f-4e67-ab92-9286422da37c", + "last_modified": 1700647023209 + }, + { + "schema": 1700646487143, + "derHash": "wmHuPUu9xFb/tnhoNj/tgMtcmtevP1pdrL4yvs3diKc=", + "subject": "CN=TrustAsia EV TLS ECC CA G4,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgRVYgVExTIEVDQyBDQSBHNA==", + "whitelist": false, + "attachment": { + "hash": "02ae542c9a65d7d5295ee961d24a8b2e609abde9cfec9675336097720aa12824", + "size": 1317, + "filename": "8yE4-1OesI1v8JXzpSp47XLFwrJh2fEhVnsstmEf3Kg=.pem", + "location": "security-state-staging/intermediates/f748aac3-8517-41fb-a04a-345c350c8c0b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8yE4+1OesI1v8JXzpSp47XLFwrJh2fEhVnsstmEf3Kg=", + "crlite_enrolled": false, + "id": "543e7451-9e2c-46e4-86e4-5bd8d7e912ba", + "last_modified": 1700647023207 + }, + { + "schema": 1700646485003, + "derHash": "gUc+GximiHdogYQbOuFgsq2TXOno1YMriiYlZEjbc3k=", + "subject": "CN=CommScope Public Trust ECC Sub-CA-01-02,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFN1Yi1DQS0wMS0wMg==", + "whitelist": false, + "attachment": { + "hash": "46cae718f26fd00c333165e2fbd250a12a9d38a8bc2a9a6dc942fa9edf99c12b", + "size": 1414, + "filename": "tBre6NvedLp1Nvk3FtFIxgjhtkXFJ7lgj8u07sRBDk0=.pem", + "location": "security-state-staging/intermediates/3948e9d2-7cc4-4136-9b3f-72bc70afb1aa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tBre6NvedLp1Nvk3FtFIxgjhtkXFJ7lgj8u07sRBDk0=", + "crlite_enrolled": false, + "id": "8fb9c242-9ed1-4e18-997b-dd05d7fae35a", + "last_modified": 1700647023204 + }, + { + "schema": 1700646486816, + "derHash": "E4Du1mssMWYS1fbn72jC00YFgv6pTgDGwnWV+Rk049k=", + "subject": "CN=CommScope Public Trust RSA Sub-CA-01-01,O=CommScope,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxMDAuBgNVBAMMJ0NvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFN1Yi1DQS0wMS0wMQ==", + "whitelist": false, + "attachment": { + "hash": "7c11c1b878f79f9e702f18c2a9340e77d2a3a26d92b6fbaefc47260195b201f2", + "size": 2560, + "filename": "PuzSWf62hnlXmDTvun4KqmESMLeim7gU8LRuyOQPieY=.pem", + "location": "security-state-staging/intermediates/8db659e4-ad44-4c01-a7d3-986d7aa7cd0c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PuzSWf62hnlXmDTvun4KqmESMLeim7gU8LRuyOQPieY=", + "crlite_enrolled": false, + "id": "24f99808-5e61-4042-87d3-feaa84f7b8ab", + "last_modified": 1700647023201 + }, + { + "schema": 1700621585126, + "derHash": "4N1nQ9bQjiqxiITtHn5Gs9lyb/+psYZmQq6/Jm/aJlQ=", + "subject": "CN=TrustAsia DV TLS RSA CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "e3a486ae0f27669283ac52e408e77393bbe7ba74eed3d7d6e60482865c1d290b", + "size": 2463, + "filename": "T21qUAje6E9fl90g8wWbialHnlVFqqtF6iOyY8qjamI=.pem", + "location": "security-state-staging/intermediates/e99ab652-0607-41fc-9d13-f5d01bdcf726.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "T21qUAje6E9fl90g8wWbialHnlVFqqtF6iOyY8qjamI=", + "crlite_enrolled": false, + "id": "4f319fc7-2f4d-4344-a756-115a452e2c76", + "last_modified": 1700621823177 + }, + { + "schema": 1700621585431, + "derHash": "Fd35jFfg9G5cXuACg7xK+P4OjSxCFRYw3v/6KF6mLQk=", + "subject": "CN=TrustAsia EV TLS RSA CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgRVYgVExTIFJTQSBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "efb23850edd4751aa963090b78590450e39d857bad481a85a589c4871b17e75c", + "size": 2463, + "filename": "W19nbGvl7OjWR0ODnprYtSlhjq_QnDddsFv6D2IGKmA=.pem", + "location": "security-state-staging/intermediates/ccbfc40d-6a02-4ee8-8271-412f1ffbde01.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W19nbGvl7OjWR0ODnprYtSlhjq/QnDddsFv6D2IGKmA=", + "crlite_enrolled": false, + "id": "4815157c-bb8a-4345-a1fa-cb97a45e1b33", + "last_modified": 1700621823174 + }, + { + "schema": 1700621584827, + "derHash": "Vz9feXSC9Wftx5AYeUV7xGyd8Kz9jOexqPt+Ihj6R3s=", + "subject": "CN=TrustAsia OV TLS RSA CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDDBpUcnVzdEFzaWEgT1YgVExTIFJTQSBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "b6d674772e49154ba8173896d98b4f9b7ed273e74285a1aecd0857fd3ad9fc34", + "size": 2463, + "filename": "3-85gBZqCqMtrR1W85eRq3_U5lvL2pUBz7X0SUQ41YI=.pem", + "location": "security-state-staging/intermediates/b328fee1-5c20-4662-b469-4cef9bb5d2c7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3+85gBZqCqMtrR1W85eRq3/U5lvL2pUBz7X0SUQ41YI=", + "crlite_enrolled": false, + "id": "b6460ed5-3bda-4f70-aa24-3ff159ca0a48", + "last_modified": 1700621823172 + }, + { + "schema": 1700495283327, + "derHash": "bdThK3UYSbo+GtkKxIZ0wkdMdyGC81AMhcLfTbf0iGY=", + "subject": "CN=Telia RSA TLS Root CA v3,O=Telia Company AB,C=SE", + "subjectDN": "MEsxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQDDBhUZWxpYSBSU0EgVExTIFJvb3QgQ0EgdjM=", + "whitelist": false, + "attachment": { + "hash": "282b8a19d9cf4422e5e3c92ee07a3992ce6b9c94e33ef8712c5f51387b9b5089", + "size": 2280, + "filename": "U2z9ropiV78UJmxDKFlY0Pm5N-MGjiohU3QXATOp_8w=.pem", + "location": "security-state-staging/intermediates/49050960-e0ab-4a87-a5bf-0e028259e8d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "U2z9ropiV78UJmxDKFlY0Pm5N+MGjiohU3QXATOp/8w=", + "crlite_enrolled": false, + "id": "2c791dca-18ac-468e-8584-ecf9a04b4f0a", + "last_modified": 1700495823241 + }, + { + "schema": 1700495284246, + "derHash": "B+WGYQfiSuaalnlmj5FqobOzncNVR4Hjrpq6pEtae8Q=", + "subject": "CN=Telia EC TLS Root CA v3,O=Telia Company AB,C=SE", + "subjectDN": "MEoxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2Mw==", + "whitelist": false, + "attachment": { + "hash": "0665acd9ebfe6d7e1d5003ea3eb0fda68d5eb71055a7a3e053ea4f09c0690e36", + "size": 1695, + "filename": "TIIdfYmt8IwelRySuEG0o8GCYbnOQm8zfMDjgFSZug4=.pem", + "location": "security-state-staging/intermediates/fb500b17-0b78-4b56-912a-09d9d6b1369d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TIIdfYmt8IwelRySuEG0o8GCYbnOQm8zfMDjgFSZug4=", + "crlite_enrolled": false, + "id": "87144287-1775-4362-bd74-80419dad9b83", + "last_modified": 1700495823238 + }, + { + "schema": 1700059676066, + "derHash": "drJ7gKWAJ9w88dpo2sFwEO2TmX0LYD4vrb6FASSTtac=", + "subject": "CN=GTS Root R4,O=Google Trust Services LLC,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSNA==", + "whitelist": false, + "attachment": { + "hash": "96c81d6de46d0742f372c8cbf2f2353fe3629988e8d65e26d3ab17a49b031a7e", + "size": 1264, + "filename": "mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=.pem", + "location": "security-state-staging/intermediates/65b14f50-6a3d-4b8e-937f-6be9603d8be5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=", + "crlite_enrolled": false, + "id": "d8d4a791-3336-422c-aa11-2915c1f0cf26", + "last_modified": 1700060223205 + }, + { + "schema": 1699976884812, + "derHash": "rCWGO6Q7lq6oZLoqolYCKvnTjck+l0N/PeYDzEhzBss=", + "subject": "CN=SSL.com TLS ECC Root CA 2022,O=SSL Corporation,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJTAjBgNVBAMMHFNTTC5jb20gVExTIEVDQyBSb290IENBIDIwMjI=", + "whitelist": false, + "attachment": { + "hash": "8d3a201dad112e4871960af5b7adb823efb7671b60479d17a18ec34364c6eac3", + "size": 1223, + "filename": "G_ANXI8TwJTdF-AFBM8IiIUPEv0Gf6H5LA_b9guG4yE=.pem", + "location": "security-state-staging/intermediates/484d2121-85a4-4993-a8c2-21df8d848fd0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G/ANXI8TwJTdF+AFBM8IiIUPEv0Gf6H5LA/b9guG4yE=", + "crlite_enrolled": false, + "id": "cb19b337-a3fb-43c0-9b35-2ebc139f80fe", + "last_modified": 1699977423180 + }, + { + "schema": 1699976885666, + "derHash": "a8sUfUHptXf0AAKlxLoIhGc3gFqv6FVN0OciBPYAe6c=", + "subject": "CN=SSL.com TLS RSA Root CA 2022,O=SSL Corporation,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJTAjBgNVBAMMHFNTTC5jb20gVExTIFJTQSBSb290IENBIDIwMjI=", + "whitelist": false, + "attachment": { + "hash": "7d4076e583d0c3f435a11e516b62cf496a5004671429c33f2f357f6d15c92f27", + "size": 2377, + "filename": "K89VOmb1cJAN3TK6bf4ezAbJGC1mLcG2Dh97dnwr3VQ=.pem", + "location": "security-state-staging/intermediates/aaa729fd-6eb1-4f1e-bdd4-46e7709a02ff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "K89VOmb1cJAN3TK6bf4ezAbJGC1mLcG2Dh97dnwr3VQ=", + "crlite_enrolled": false, + "id": "fda4fe85-c112-4330-bf7a-944c38636f92", + "last_modified": 1699977423177 + }, + { + "schema": 1699976885977, + "derHash": "y4m0tQXdL/Xt/OYYNZ/W4oiteQXCSGED/1b80dXeGFc=", + "subject": "CN=Cloudflare TLS Issuing RSA CA 1,O=CLOUDFLARE\\, INC.,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBDTE9VREZMQVJFLCBJTkMuMSgwJgYDVQQDDB9DbG91ZGZsYXJlIFRMUyBJc3N1aW5nIFJTQSBDQSAx", + "whitelist": false, + "attachment": { + "hash": "82c8afa86811c60cebea2655d5bc999c304f233d9e22ea76ba62f0d44cb33071", + "size": 1902, + "filename": "_mAMmbFbay0ESJ5nqjDg1sYjWZhtK5luMbycyTrFyUs=.pem", + "location": "security-state-staging/intermediates/b7f975e0-3287-4a8a-b4cf-e1fc67df1a0c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/mAMmbFbay0ESJ5nqjDg1sYjWZhtK5luMbycyTrFyUs=", + "crlite_enrolled": false, + "id": "54c765bd-67f4-41b9-9f3d-678e5940c31a", + "last_modified": 1699977423174 + }, + { + "schema": 1699976885370, + "derHash": "KWT9MhDqaPqitKhJs2JD0z90Qp0bQ84BnnsVTqx3Wbo=", + "subject": "CN=Cloudflare TLS Issuing ECC CA 1,O=CLOUDFLARE\\, INC.,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBDTE9VREZMQVJFLCBJTkMuMSgwJgYDVQQDDB9DbG91ZGZsYXJlIFRMUyBJc3N1aW5nIEVDQyBDQSAx", + "whitelist": false, + "attachment": { + "hash": "979c3cd0f714e6bcb3fb19bb59b91ee35e900864ab480f5f7729b9e8d57d1d23", + "size": 1061, + "filename": "Kt2bkYM55rPaGBFYxTLlq8AIJqapRcc1eKjai8GUPO0=.pem", + "location": "security-state-staging/intermediates/76961b66-d172-454b-b159-81bc0e44fbbf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Kt2bkYM55rPaGBFYxTLlq8AIJqapRcc1eKjai8GUPO0=", + "crlite_enrolled": false, + "id": "a74b6bfb-1ab6-4487-9af4-8e3632e1ab27", + "last_modified": 1699977423171 + }, + { + "schema": 1699393717540, + "derHash": "fgqMmETQ+Z7IQwdIPy4zGYB0n4inGFZJ1Nmfo8NYnlI=", + "subject": "CN=Nya Labs CA,O=NYA LABS LLC,C=US", + "subjectDN": "MDoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKDAxOWUEgTEFCUyBMTEMxFDASBgNVBAMMC055YSBMYWJzIENB", + "whitelist": false, + "attachment": { + "hash": "8dd9e0eaea313ca057fd25d7162b3677e82a2bd0366f1d4782cd40d4927b2ea5", + "size": 1695, + "filename": "XIN17Ax-J-06c6SQNYGeDokCHRH1HFMjAG-HQ0JRIXQ=.pem", + "location": "security-state-staging/intermediates/cc150b3e-701e-4b42-8c17-7e45893068dc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XIN17Ax+J+06c6SQNYGeDokCHRH1HFMjAG+HQ0JRIXQ=", + "crlite_enrolled": false, + "id": "c7b75907-142e-4576-bb48-80df34dc792f", + "last_modified": 1699394223213 + }, + { + "schema": 1698353584270, + "derHash": "+OEat/gzu+k70Tu+0+MKPqup40pFEONEq8k0BMJtGAE=", + "subject": "CN=ZDNS ECC EV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgRUNDIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d291fcf4b9c3dd7899d290b91c081c37d5f4f584a63641e836692513914462b9", + "size": 1264, + "filename": "E7KBGe5PvuRcg4hyZXb7k2MMDyymhBAYfQXlw7k4cAY=.pem", + "location": "security-state-staging/intermediates/bc6d3f60-6e18-45f1-9d40-ed6769c89232.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "E7KBGe5PvuRcg4hyZXb7k2MMDyymhBAYfQXlw7k4cAY=", + "crlite_enrolled": false, + "id": "11175cc2-4531-4e0d-8748-bd38f286ac37", + "last_modified": 1698353823411 + }, + { + "schema": 1698353583950, + "derHash": "PcJyve5ORgTUmHMsKMAmlj48sh3xmKKduUKFeLJEfaE=", + "subject": "CN=ZDNS ECC OV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgRUNDIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "9394a57b26e72b76fbcbe9bba73d69c34fc799c820b378b3b3082c3403dc143d", + "size": 1288, + "filename": "b4DdPS37e4mvyYbxFyRE322pWS7bjNGkMZCHChWzDz8=.pem", + "location": "security-state-staging/intermediates/f4423699-fdd3-4667-b927-945b76a6d94a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b4DdPS37e4mvyYbxFyRE322pWS7bjNGkMZCHChWzDz8=", + "crlite_enrolled": false, + "id": "c5399ab9-36c4-42d2-8a9b-9e7183badb83", + "last_modified": 1698353823408 + }, + { + "schema": 1698353583663, + "derHash": "QcKfgLkY75YFzdzr29smg8MQxc8Kp9jr9niDk1AekLk=", + "subject": "CN=ZDNS RSA EV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgUlNBIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ed6b1b93a3d3c74cf05119369cd7e778ff80f551bbf26b70eb5fc197558682d6", + "size": 2280, + "filename": "nsJxQPXGOMFqBzDqAsZFph87o95exDDQI8Fn87kP8ow=.pem", + "location": "security-state-staging/intermediates/a5f11967-825b-461c-ad33-dc72b92a6cec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nsJxQPXGOMFqBzDqAsZFph87o95exDDQI8Fn87kP8ow=", + "crlite_enrolled": false, + "id": "1cd3d46c-8877-43c9-a021-e649445e51b7", + "last_modified": 1698353823406 + }, + { + "schema": 1698353582759, + "derHash": "PMLBMvwjtaxnX4n4GXQQUgjR6qP7YbQGFf6ipoaOffk=", + "subject": "CN=ZDNS RSA DV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgUlNBIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "5c4b66bee6cee9f123e3a30f4f551b0d58d4b8d64d8f573b677153f55cf9ceea", + "size": 2300, + "filename": "wsmLMLMBzij4PyD0NLtZjfvmy8w2zz3tsKaERPVIO58=.pem", + "location": "security-state-staging/intermediates/5b3aac97-9e80-4021-9262-6bbc930f6dda.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wsmLMLMBzij4PyD0NLtZjfvmy8w2zz3tsKaERPVIO58=", + "crlite_enrolled": false, + "id": "2ef2f30a-3142-4813-994a-4c329c2bf3df", + "last_modified": 1698353823403 + }, + { + "schema": 1698353584579, + "derHash": "JNBy3qXvVP1qs6+18nujN3J95xKNAJ4H7m/T/xny2Js=", + "subject": "CN=ZDNS RSA OV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgUlNBIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "2f17bb4f9cb8e285224267a39b0f0ec978584fb37379675b04a8666a849399b8", + "size": 2300, + "filename": "N1qCCBXJIUydOQU7PWh2D2HOy0uJcolRGqd-CnBypX0=.pem", + "location": "security-state-staging/intermediates/a37a57d8-376e-4ccb-8626-d4a402aae5af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N1qCCBXJIUydOQU7PWh2D2HOy0uJcolRGqd+CnBypX0=", + "crlite_enrolled": false, + "id": "23a6f955-da53-45f9-bec5-04e95ba09a53", + "last_modified": 1698353823400 + }, + { + "schema": 1698353583372, + "derHash": "6V7yx0cUxPVXt27yz+vDd59QEc31cqIxbUM1fMvzSJY=", + "subject": "CN=ZDNS ECC DV SSL CA,O=互联网域名系统北京市工程研究中心有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMUUwQwYDVQQKDDzkupLogZTnvZHln5/lkI3ns7vnu5/ljJfkuqzluILlt6XnqIvnoJTnqbbkuK3lv4PmnInpmZDlhazlj7gxGzAZBgNVBAMTElpETlMgRUNDIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0f4a1a16129cf0e8fdae10260b682d7537819ef5ee23753a3c51f3b4152766be", + "size": 1288, + "filename": "hN8j8JnaY8WoonJHdr9CWMFJ_fEh8xq7ZeNGnaNUq0M=.pem", + "location": "security-state-staging/intermediates/0c76c999-ac7b-47dc-a29b-ec6dbb694cf1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hN8j8JnaY8WoonJHdr9CWMFJ/fEh8xq7ZeNGnaNUq0M=", + "crlite_enrolled": false, + "id": "58e90694-bb6f-4dbb-a80a-dcb0c5941377", + "last_modified": 1698353823396 + }, + { + "schema": 1697726891469, + "derHash": "Gix1/QluBJnp/2rHTlJvYequPt/IwupENv7gwk2LfQ4=", + "subject": "CN=TWCA Secure SSL Certification Authority,O=TAIWAN-CA,C=TW", + "subjectDN": "MFMxCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExMDAuBgNVBAMTJ1RXQ0EgU2VjdXJlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "6915db4e2c315f0ef561152eb43c24a83ff0b2a53b17f99b0016401498bd9a2d", + "size": 2060, + "filename": "9VZ7Yd685RTXsE6rL_puuMbnejYaXwaZasGL7c-Uolc=.pem", + "location": "security-state-staging/intermediates/47927f74-196f-42a4-a2a8-51f6c9298cd2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9VZ7Yd685RTXsE6rL/puuMbnejYaXwaZasGL7c+Uolc=", + "crlite_enrolled": false, + "id": "8fe8f35b-1038-4072-85b5-c2e317a0be57", + "last_modified": 1697727423154 + }, + { + "schema": 1697644096445, + "derHash": "Ag7smKPB7S1b4hGTNJrLl1HeBx1rRSpImmqGfOm2M+k=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDI0IFEx", + "whitelist": false, + "attachment": { + "hash": "8ee25b44d9dda10cd8ab2c66b203dd9b03f70107eb69ecb106769a08670a2a5d", + "size": 2349, + "filename": "vJ-m4dNdpz2VjjUXFAp-TfHe-LELgN7pTL_597hUwOY=.pem", + "location": "security-state-staging/intermediates/85164150-9fcd-4f4f-8da7-abf4506a6c44.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vJ+m4dNdpz2VjjUXFAp+TfHe+LELgN7pTL/597hUwOY=", + "crlite_enrolled": false, + "id": "809b6900-6ff4-404b-b18c-e51733cfd760", + "last_modified": 1697644623321 + }, + { + "schema": 1697644095794, + "derHash": "V6RNSmkCPZW+uJphpCZXn0U7wUbyqZiYfzxyFueKS50=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "4dc6ecbf99831b7c62dadeb270d2f4dab4c1fb09c65db1fc46797b6ccb934f9f", + "size": 1642, + "filename": "SQy-TLN02IERfG7c95byk9WP5It2JHmn3U5TfnQSFZE=.pem", + "location": "security-state-staging/intermediates/b4a30fa4-3327-4e69-a259-061f1c90359a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SQy+TLN02IERfG7c95byk9WP5It2JHmn3U5TfnQSFZE=", + "crlite_enrolled": false, + "id": "3e25e317-784c-4a26-9be8-1ab56ce7f424", + "last_modified": 1697644623318 + }, + { + "schema": 1697644095489, + "derHash": "S+a6QD1uz7XnOCD1yhlIJTIF2xhuOW13msa0G8ioJrQ=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "59ba1ed3554e8f053ae6bbb261ae017a5c7e3d1aeda4ada6e47509dfba615212", + "size": 1199, + "filename": "wRV8pz-T03eOYhVQXukc8adYX1LhUpuafMR0ZgqIFTY=.pem", + "location": "security-state-staging/intermediates/b7207461-77c8-4f18-9701-ca4fd658c6f0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wRV8pz+T03eOYhVQXukc8adYX1LhUpuafMR0ZgqIFTY=", + "crlite_enrolled": false, + "id": "a82c49b1-590a-4f35-9f76-47f4e336a97e", + "last_modified": 1697644623316 + }, + { + "schema": 1697644095179, + "derHash": "ELMQPS9JDSvy6V7M+ufWRm4ImSNIYZbklDHIj/Nh19c=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjQgUTE=", + "whitelist": false, + "attachment": { + "hash": "5dd34b46272d5787ea4fa4a1cee5b9c07bef31bb18d05dba9c767da3a3face4d", + "size": 1642, + "filename": "xj5Ul49ZHE5t22DWnWSKIHMaeuwPq_ikJmcuGVUkuOQ=.pem", + "location": "security-state-staging/intermediates/caa7196f-50c0-4e4c-816f-07d78ff71fec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xj5Ul49ZHE5t22DWnWSKIHMaeuwPq/ikJmcuGVUkuOQ=", + "crlite_enrolled": false, + "id": "e8db550c-54f3-492e-8df4-f56f6de8f169", + "last_modified": 1697644623313 + }, + { + "schema": 1697644094861, + "derHash": "earfuT8sRno37cg7XdVaIA6pwy09+2+uIBeix+Mwbc8=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDI0IFEx", + "whitelist": false, + "attachment": { + "hash": "0bc03324251953dc583b645dbecf441d8dfe10ab096925413b0c7f2c6d4a4305", + "size": 1642, + "filename": "wnwBVP_Dww2rgtYejr_KpE0AzeqKydioYikth99TF1U=.pem", + "location": "security-state-staging/intermediates/95f898b1-530b-423c-904b-f5887c5a5331.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wnwBVP/Dww2rgtYejr/KpE0AzeqKydioYikth99TF1U=", + "crlite_enrolled": false, + "id": "ee0331b9-7c8f-448c-8286-308d3886c6cb", + "last_modified": 1697644623311 + }, + { + "schema": 1697644094564, + "derHash": "p5gx0GsFUwpVM1JurR724Nf/orxS1Z7vtBuVCuzDHNs=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDI0IFEx", + "whitelist": false, + "attachment": { + "hash": "460152545bd2e306b482518bc898655f19af4e3c178eb5c0fb2a317a66c67be1", + "size": 1199, + "filename": "p8tYK3vj7LgX8YymjK9Xif-bnOIxqQyHpENjJu609JA=.pem", + "location": "security-state-staging/intermediates/097fd20f-11de-418f-878b-734cb4185807.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "p8tYK3vj7LgX8YymjK9Xif+bnOIxqQyHpENjJu609JA=", + "crlite_enrolled": false, + "id": "c50faf01-93a5-4378-96ad-cd4897cbfe93", + "last_modified": 1697644623308 + }, + { + "schema": 1697644094263, + "derHash": "14vgTNvjKNVJNIkGe43xxX5l6nTYiI8IxxHoFaahGhQ=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "0cafea7d70ecb626a3e4d61fae3df7515f2a9ed40de427e52e164d1b53bf1b0c", + "size": 1195, + "filename": "GuDgudI5am6HMMYjvHjAx2xafjBsIqLmlXlx9NzdV4U=.pem", + "location": "security-state-staging/intermediates/db6b408b-48d0-4d8d-acea-c60630c2bae8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GuDgudI5am6HMMYjvHjAx2xafjBsIqLmlXlx9NzdV4U=", + "crlite_enrolled": false, + "id": "cf230303-b818-4615-9892-e1e2075e4684", + "last_modified": 1697644623305 + }, + { + "schema": 1697644093954, + "derHash": "UUoUyCizqxYN2dEYYbvr5e76Kmygq4YAPNGggKo2qdQ=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDI0IFEx", + "whitelist": false, + "attachment": { + "hash": "ca73a39238772d4510166c287b756c3fd0cfa465602b791f5a0d05f6ca27cd85", + "size": 1195, + "filename": "I8BzKzF0nkRRs5UgZo00HnfGZ-7gXoNqbzviXSGQa1Y=.pem", + "location": "security-state-staging/intermediates/85c133f3-c82d-4cfd-a2e1-7099f93f70b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "I8BzKzF0nkRRs5UgZo00HnfGZ+7gXoNqbzviXSGQa1Y=", + "crlite_enrolled": false, + "id": "fcb72838-0ad1-4ea7-a1c1-4784b16e728b", + "last_modified": 1697644623303 + }, + { + "schema": 1697644093634, + "derHash": "PBsL7/BS7JCZenI1Ln09qvkHRrmjxNiYelu7nQNw5LA=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "c20e04f488396eb859e037c0b114c79cd733a2e868646584f32e0fa969eaca83", + "size": 1195, + "filename": "QyJnbwPolPO_92VzXtERZFIefCZbLKVQJ2ALitRnAYE=.pem", + "location": "security-state-staging/intermediates/0a0c5ce0-1e49-4125-a6b7-7ec7ced81db9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QyJnbwPolPO/92VzXtERZFIefCZbLKVQJ2ALitRnAYE=", + "crlite_enrolled": false, + "id": "8eb1e020-109d-4612-a4f5-f45481fafe03", + "last_modified": 1697644623300 + }, + { + "schema": 1697644093297, + "derHash": "AVL4Y1T8qVJbKAwjP32mz4tfI3PEJkRyMiauZyONsZA=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDI0IFEx", + "whitelist": false, + "attachment": { + "hash": "1147af9db4734d927b70704902746076c8d08e8702e7b25c62e0c2d16ae9577e", + "size": 1642, + "filename": "sIyusJrUkgaV-NuUk41j_lNmX1HYRzOm0UglcO11Dok=.pem", + "location": "security-state-staging/intermediates/1579d186-163c-419b-8695-4c3858526c8a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sIyusJrUkgaV+NuUk41j/lNmX1HYRzOm0UglcO11Dok=", + "crlite_enrolled": false, + "id": "488146a1-169b-4cc7-b9ab-7c08d6fcc79c", + "last_modified": 1697644623297 + }, + { + "schema": 1697644096137, + "derHash": "YLhGY4kiGQ3lLBqwRP0vqIqdbfOYmDHADIQpa423pe4=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "c80f4c418f70a550e668a18327367048b55139f6c9de68b4e61209d9487c3ae6", + "size": 2345, + "filename": "mJFodrtul3_GKzzln52r2AY63jna9WoaokP9dWJZaDI=.pem", + "location": "security-state-staging/intermediates/1c22fa37-315a-4532-a83a-883f962976fb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mJFodrtul3/GKzzln52r2AY63jna9WoaokP9dWJZaDI=", + "crlite_enrolled": false, + "id": "58b5dda4-f590-47be-8732-dee74829deb2", + "last_modified": 1697644623295 + }, + { + "schema": 1697644092971, + "derHash": "x6p72xTdXuL55uwjZ7ZNUA3kMue8Uemo5Ol5G4KM9l4=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2024 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyNCBRMQ==", + "whitelist": false, + "attachment": { + "hash": "cb5fd79970d5262b98c020ae5d91ca4964dc3437285e784cd225683a51967ef2", + "size": 1642, + "filename": "23i9VJIEQ4tpTAhFTgSk6Zg64p2kjQaoIhg5T13SyJU=.pem", + "location": "security-state-staging/intermediates/44f9a6e7-77bc-4dae-a832-c38d842930d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "23i9VJIEQ4tpTAhFTgSk6Zg64p2kjQaoIhg5T13SyJU=", + "crlite_enrolled": false, + "id": "c53229d0-32e8-41b1-b1cc-d8fb3a63c6e8", + "last_modified": 1697644623292 + }, + { + "schema": 1696474076344, + "derHash": "G6Av9KJWK75reZVjmQyIzFDhGCOVcZpOqoA5duL1CGA=", + "subject": "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "whitelist": false, + "attachment": { + "hash": "8ce67e5f0d98f4646648910cb7b465ec1bdd5157780a818fd142636047aa30c6", + "size": 1938, + "filename": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=.pem", + "location": "security-state-staging/intermediates/f7f528a3-e05c-4589-ada4-084ba7358310.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=", + "crlite_enrolled": false, + "id": "58ddd3a7-2cac-4974-bfee-675697c8069a", + "last_modified": 1696474622972 + }, + { + "schema": 1696474075886, + "derHash": "HIxwsmPLE6nm7j8JepZzGUzJ1oa8FKcsjNxwXywOaKc=", + "subject": "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "whitelist": false, + "attachment": { + "hash": "4eef7af9ec7616d0317629f5311712e74185b6a82ce44bb12511150c686ec626", + "size": 1938, + "filename": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=.pem", + "location": "security-state-staging/intermediates/789e7a06-11ab-4679-914b-702c4b17c653.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=", + "crlite_enrolled": false, + "id": "38901efd-9f46-42ce-8fd0-a44c1268ccc0", + "last_modified": 1696474622969 + }, + { + "schema": 1695307683026, + "derHash": "tyRQq/UEeor2PsnYfjMUhIULGEmiVQqCqG22tB7Th2A=", + "subject": "CN=Certum EC-384 CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL", + "subjectDN": "MHQxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEZMBcGA1UEAxMQQ2VydHVtIEVDLTM4NCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "9f6d0c980c92ac6a3e9e0a1e42717fa38645b16394d41c870163e37ad342f093", + "size": 1467, + "filename": "3ntpMunERYLODeB6vat-6pDHXW0qBzMd9XvVy4hVPRM=.pem", + "location": "security-state-staging/intermediates/f5b8aee1-184d-4879-a293-83a907ed9bb4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3ntpMunERYLODeB6vat+6pDHXW0qBzMd9XvVy4hVPRM=", + "crlite_enrolled": false, + "id": "5c606c94-7cdb-4580-a055-0f6251dbdadf", + "last_modified": 1695308223101 + }, + { + "schema": 1695307682604, + "derHash": "+xOJDHqxT/e5SycUUD4xEjv900D8TZeXQxZuBGm0eog=", + "subject": "CN=Certum Trusted Root CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL", + "subjectDN": "MHoxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEfMB0GA1UEAxMWQ2VydHVtIFRydXN0ZWQgUm9vdCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a586257fe96fc4e4a0d2e95d3c14a7d15541cc2858fa8f573dcac84fe1040205", + "size": 2060, + "filename": "aB3EgsKWyEAsbrsg5oMJo7yEZSOuNLmEqE7ml6MxLbc=.pem", + "location": "security-state-staging/intermediates/7a2c407a-2716-4f14-978e-9a399d955741.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aB3EgsKWyEAsbrsg5oMJo7yEZSOuNLmEqE7ml6MxLbc=", + "crlite_enrolled": false, + "id": "f655736e-e759-4498-8498-648772d8f63d", + "last_modified": 1695308223097 + }, + { + "schema": 1695221287209, + "derHash": "v7CIKOtpuZ7nzr3Wh8jog6H+B9P5mDR/ku6TVkFK0AI=", + "subject": "CN=GlobalSign Atlas R1 EV TLS CA 2023,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFUxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSswKQYDVQQDEyJHbG9iYWxTaWduIEF0bGFzIFIxIEVWIFRMUyBDQSAyMDIz", + "whitelist": false, + "attachment": { + "hash": "eb89274bd91cf84317c3b664e8a6e4e7ebdafe12f2d3f8bfd2047b9902c1805d", + "size": 1662, + "filename": "qN4R607e7UFOuFhqWN30zbutycDjfOym66glEPw88gQ=.pem", + "location": "security-state-staging/intermediates/41737f9c-d70d-4eea-b283-86c917b0b0ed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qN4R607e7UFOuFhqWN30zbutycDjfOym66glEPw88gQ=", + "crlite_enrolled": false, + "id": "5175aa15-4120-4a0a-a452-8c56e3b5ff2d", + "last_modified": 1695221823081 + }, + { + "schema": 1693406886101, + "derHash": "Obbjs4j3SVId8rNUGC60zYfUvzZDm/rwIC5Vls/CyqQ=", + "subject": "CN=SECOM Passport for Web EV 2.0 CA,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MF8xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSkwJwYDVQQDEyBTRUNPTSBQYXNzcG9ydCBmb3IgV2ViIEVWIDIuMCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "465f2f588af159270d8a966f6dac1d5c3cc9f88a6dfde6b136c9dcffe8523052", + "size": 1662, + "filename": "Wa2FjlVfGKwvkiH0LYWh-y-ihHlaTmVQ-gqZEsR3RwY=.pem", + "location": "security-state-staging/intermediates/9df0a300-6455-4e1f-9aa9-bb6d2f52f0a1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wa2FjlVfGKwvkiH0LYWh+y+ihHlaTmVQ+gqZEsR3RwY=", + "crlite_enrolled": false, + "id": "f3a57b4b-1087-4064-afdc-fd3d8fd73b7b", + "last_modified": 1693407423723 + }, + { + "schema": 1693342092773, + "derHash": "snT+vm68cYZsM58Bitkz581oBbQ7/ebSGNwhFHFp12s=", + "subject": "CN=e-Szigno Online SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIE9ubGluZSBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "72bc4f35ba6c3e85f36f856880dc364ba08e1ce42b81244f1ebf53e8514a8d5f", + "size": 1435, + "filename": "G_JwHP_ydSe7pufWcUyNckBrxBbnQ6Kmqw_OFPPiQI0=.pem", + "location": "security-state-staging/intermediates/9cda4ca9-3689-48fd-af2c-640bd25284ff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G/JwHP/ydSe7pufWcUyNckBrxBbnQ6Kmqw/OFPPiQI0=", + "crlite_enrolled": false, + "id": "df7a20cf-df80-4bde-8483-7d48af559d55", + "last_modified": 1693342624041 + }, + { + "schema": 1693342093437, + "derHash": "F0TXMTT5XOkWrevub3V0LEeTaGi2TSoMFi7xMpAPDuQ=", + "subject": "CN=e-Szigno Class3 SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIENsYXNzMyBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "f875b715f8027d390ccd47c7e62fa9a8cb805078002c5447fead8ec98441abbd", + "size": 1435, + "filename": "69Duo3nmlQnUEvqzlU27qTDaDY9K1yN0wfdopIs9Y7s=.pem", + "location": "security-state-staging/intermediates/1986ba70-a9d5-4c04-9f62-2c8f532bde42.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "69Duo3nmlQnUEvqzlU27qTDaDY9K1yN0wfdopIs9Y7s=", + "crlite_enrolled": false, + "id": "0b79b1b7-45eb-4167-b5a4-52c77a4ae693", + "last_modified": 1693342624038 + }, + { + "schema": 1693342093097, + "derHash": "/Y4MjMzbuuTB8HwkjRH+u7D7PaDNDYlKioDYBKjTmn0=", + "subject": "CN=e-Szigno Class2 SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIENsYXNzMiBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "eb233f6d83b210e52823cdff3145fe53c2632e2cf95904c5ac6d29368eb61943", + "size": 1435, + "filename": "HGXB7TIfcoLqLINF3LJD2A9t3V4VdHjcBv6LboViQMo=.pem", + "location": "security-state-staging/intermediates/36fd2d27-8c24-4aee-9ba5-feccce9483d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HGXB7TIfcoLqLINF3LJD2A9t3V4VdHjcBv6LboViQMo=", + "crlite_enrolled": false, + "id": "c2701cd6-d220-4744-a18b-3a3b323259ee", + "last_modified": 1693342624035 + }, + { + "schema": 1693342092400, + "derHash": "akjnNKxvBnFAySitu8xEkkadQW3i08mnoZfWI3DqwOI=", + "subject": "CN=e-Szigno Qualified TLS CA 2018,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHmUtU3ppZ25vIFF1YWxpZmllZCBUTFMgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "acb85909161220196625771f857155f74f662f21acb6efeab36c92266013233a", + "size": 1439, + "filename": "qd9EIyfp7CEtbkxafeyYAuC_8wQBWqGZflkLznwnuyc=.pem", + "location": "security-state-staging/intermediates/86baa78d-dbf7-44c9-b002-1a204379bad6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qd9EIyfp7CEtbkxafeyYAuC/8wQBWqGZflkLznwnuyc=", + "crlite_enrolled": false, + "id": "9cf88ec1-23c0-487b-9991-3fc43bbf71c0", + "last_modified": 1693342624031 + }, + { + "schema": 1693104479614, + "derHash": "8BBP8XJ0YI8aGKHh6r+OaKUfUAqH4u+iLstiJ2P+9M8=", + "subject": "CN=Gandi RSA Domain Validation Secure Server CA 3,O=Gandi,C=FR", + "subjectDN": "MFYxCzAJBgNVBAYTAkZSMQ4wDAYDVQQKEwVHYW5kaTE3MDUGA1UEAxMuR2FuZGkgUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "c57cc4329fa8b504ce063f97a8a15ffe28c6c989bbff9d66c2b1723fce07bf43", + "size": 2263, + "filename": "CFtSlX6OU4eENrKNiHtx6zY8UV7_SoMM63hXbHdXgjs=.pem", + "location": "security-state-staging/intermediates/0f4a0c2f-72a0-436a-ab5c-c6019c2d0993.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CFtSlX6OU4eENrKNiHtx6zY8UV7/SoMM63hXbHdXgjs=", + "crlite_enrolled": false, + "id": "9c147e1e-3e85-4edb-b057-0affdeff0587", + "last_modified": 1693105023362 + }, + { + "schema": 1693104480201, + "derHash": "DZkEhRHJDhZjGpf6dm8ore7cExhiTjdUsk/WD9WkvE0=", + "subject": "CN=McAfee RSA Organization Validation Secure Server CA 3,O=McAfee\\, Inc.,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxNY0FmZWUsIEluYy4xPjA8BgNVBAMTNU1jQWZlZSBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "8912a3bcb18bc8d4ae518cd66260d08a84acd4e56f04810c5345f4450ae2b969", + "size": 2284, + "filename": "gg9oIaRipMX5XuQCgBVsizhbKNuS7AJ0CmZZ5o0Jdb0=.pem", + "location": "security-state-staging/intermediates/db48f214-40d6-42b2-a070-27fd1bbe65a7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gg9oIaRipMX5XuQCgBVsizhbKNuS7AJ0CmZZ5o0Jdb0=", + "crlite_enrolled": false, + "id": "a6651c04-79b3-4cef-aa51-845d9c8544b7", + "last_modified": 1693105023359 + }, + { + "schema": 1693104479894, + "derHash": "+xKIZNrFbzq5PDcWkjANyjRcBP/lp/brJP5uRJs0iVA=", + "subject": "CN=Gandi RSA Organization Validation Secure Server CA 3,O=Gandi,C=FR", + "subjectDN": "MFwxCzAJBgNVBAYTAkZSMQ4wDAYDVQQKEwVHYW5kaTE9MDsGA1UEAxM0R2FuZGkgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "d026088b09a6e613ad8b5fcca9d59a8473a0692b01cbf76e773d881242d2cc27", + "size": 2272, + "filename": "rhYwjniKVh_GqP4xzEvEzVgdmxdxg_K1YyDK64M7rT4=.pem", + "location": "security-state-staging/intermediates/6675899f-be87-41eb-9b0d-656394edfaea.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rhYwjniKVh/GqP4xzEvEzVgdmxdxg/K1YyDK64M7rT4=", + "crlite_enrolled": false, + "id": "67147348-c740-4048-90e8-5e6ba1027579", + "last_modified": 1693105023357 + }, + { + "schema": 1692910086131, + "derHash": "AmCeiJefxoYuoVcfO8bfbHDy/pJ3Rz5D/gTDWXxDQx0=", + "subject": "CN=GTS CA 1D9,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMUQ5", + "whitelist": false, + "attachment": { + "hash": "d44fe24100e78a1699cb4766985db89ddd82c97eb1d2f7d5cbc6c7d0fa9bdf59", + "size": 1987, + "filename": "VhionU4ApKSODjkwxSzRGaien1cg3KhOAaTX0sEzqA4=.pem", + "location": "security-state-staging/intermediates/f02c6bb6-5014-4963-a200-4062712233bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VhionU4ApKSODjkwxSzRGaien1cg3KhOAaTX0sEzqA4=", + "crlite_enrolled": false, + "id": "f850e310-6786-40c0-86b7-56b717fc241a", + "last_modified": 1692910623870 + }, + { + "schema": 1692910086775, + "derHash": "7bzdAWmNg+r6Hj048BezrZay2NiOdGxYARzuDvEGk5w=", + "subject": "CN=GTS CA 2D5,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMkQ1", + "whitelist": false, + "attachment": { + "hash": "ceaac730634820c4595c9b174b9bf271cc07f2fd81a0105a5358e73c275ab68a", + "size": 1114, + "filename": "ahc0N7bGYyDMmxgBgeh3thoK90J-_LSNwxbG-EnqfU0=.pem", + "location": "security-state-staging/intermediates/86022f5c-03d1-41c9-91f6-6fd7edb68eb0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ahc0N7bGYyDMmxgBgeh3thoK90J+/LSNwxbG+EnqfU0=", + "crlite_enrolled": false, + "id": "ba3f498c-afb5-4773-9d62-c76498a15062", + "last_modified": 1692910623867 + }, + { + "schema": 1692910087070, + "derHash": "9dEkFaEsB/3pO9b55ORYjgPSBZbk+KXp0hOoM2S87nE=", + "subject": "CN=GTS CA 2D6,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMkQ2", + "whitelist": false, + "attachment": { + "hash": "4f5acb34ad2db0121e100e350ec7414b51fb7d8b4ab658accc1e30881d887d08", + "size": 1150, + "filename": "mGJygqQlaAdWTKB8W35Jg_xbYW_7zpL45PiiDd1w0qE=.pem", + "location": "security-state-staging/intermediates/4a0ac7d9-fa09-455a-bfca-a6ea4a6e575a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mGJygqQlaAdWTKB8W35Jg/xbYW/7zpL45PiiDd1w0qE=", + "crlite_enrolled": false, + "id": "377d9ef0-09a3-4298-947f-880fa1460cbf", + "last_modified": 1692910623864 + }, + { + "schema": 1692715688843, + "derHash": "xulqF0VwcJnwInlHL6KKmbrkR9d1EeGehrrzBHZRwes=", + "subject": "CN=TWCA Secure SSL Certification Authority,OU=Secure SSL Sub-CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MG8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExGjAYBgNVBAsTEVNlY3VyZSBTU0wgU3ViLUNBMTAwLgYDVQQDEydUV0NBIFNlY3VyZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "1102f22c76cebba017a34d47a919c003600a2fe0525dae669f48d49f619dea57", + "size": 2085, + "filename": "0ypLOVQVk99vHTj2x0rvkv-Dq5vQXLD6szOPfHlmXIE=.pem", + "location": "security-state-staging/intermediates/2b385c86-6fdd-4e4e-8d73-0bf5864afd50.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0ypLOVQVk99vHTj2x0rvkv+Dq5vQXLD6szOPfHlmXIE=", + "crlite_enrolled": false, + "id": "0f1ed81c-66c3-428c-b5d5-f9c8a4a3f5bb", + "last_modified": 1692716223578 + }, + { + "schema": 1692154077124, + "derHash": "ZSPDTx6Hmt12A8sgSKiYpeLwxsS1EsDSJ4K4XUOuM3E=", + "subject": "CN=DigiCert Global Root G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "whitelist": false, + "attachment": { + "hash": "1faa137dd2ee40cf50b2a051a7173d1010108a663e5440ea1fbac6c0031bf529", + "size": 1622, + "filename": "i7WTqTvh0OioIruIfFR4kMPnBqrS2rdiVPl_s2uC_CY=.pem", + "location": "security-state-staging/intermediates/104e770c-06a1-4448-b8d3-2f728cdcec56.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i7WTqTvh0OioIruIfFR4kMPnBqrS2rdiVPl/s2uC/CY=", + "crlite_enrolled": false, + "id": "44effbc9-9ad4-4720-a022-dae2b23ec1d9", + "last_modified": 1692154623711 + }, + { + "schema": 1692154076819, + "derHash": "7e3QBTy4RFkuZz7yoIHBWzjuPsvrWFbJpP6bcWn6/+c=", + "subject": "CN=CrowdStrike Federal EV RSA CA G1,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEpMCcGA1UEAxMgQ3Jvd2RTdHJpa2UgRmVkZXJhbCBFViBSU0EgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "c585aeefd429ffa446cafab9abe59c8d732dd69f53b70ea3fc57cbb15df590df", + "size": 2056, + "filename": "jlNmuWU1h89xgXejpUIIUrORzlvSrzWGeDUMnmVY_c0=.pem", + "location": "security-state-staging/intermediates/e8217047-9ec4-482d-a6b7-6f791422852c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jlNmuWU1h89xgXejpUIIUrORzlvSrzWGeDUMnmVY/c0=", + "crlite_enrolled": false, + "id": "970d042d-6df8-4a99-a686-c6a5f58570e2", + "last_modified": 1692154623708 + }, + { + "schema": 1691462897692, + "derHash": "hz8LqA46wiJlbf0EFYzBXCkn1C1dBfAd7kpH60OpFt8=", + "subject": "CN=Sectigo Public Server Authentication CA DV E36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBEViBFMzY=", + "whitelist": false, + "attachment": { + "hash": "06fac6b329e587905ce8e9269be181288e00ecf2d3126a91f7c00096ee958c9b", + "size": 1228, + "filename": "ZSagvDzjltLkewXEBuDxIzpW_dpVw1Juvvmd0hhkzdY=.pem", + "location": "security-state-staging/intermediates/085d3204-699c-4a11-9127-0229a9d6a5d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZSagvDzjltLkewXEBuDxIzpW/dpVw1Juvvmd0hhkzdY=", + "crlite_enrolled": false, + "id": "6153b2f9-41ef-4299-8f52-5d32deb66a1a", + "last_modified": 1691463423580 + }, + { + "schema": 1691462897375, + "derHash": "wF9udSJqb/30oHptdHPTxR5mpX/oETiBVTkVfdV8Wlk=", + "subject": "CN=SSL.com IV TLS Transit RSA CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gSVYgVExTIFRyYW5zaXQgUlNBIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "5f6f754b661265836cd289b5daa47726092c13e60cc454778f29627acdf29779", + "size": 2320, + "filename": "R_O5JJ9biXMtEnmiODpukBteFwfJM0MXwQWiSsjJALQ=.pem", + "location": "security-state-staging/intermediates/5767c8ec-709b-45c2-9547-e0c2270759ac.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "R/O5JJ9biXMtEnmiODpukBteFwfJM0MXwQWiSsjJALQ=", + "crlite_enrolled": false, + "id": "85c8e614-a0d7-4c3b-ba08-8a3e26645f04", + "last_modified": 1691463423577 + }, + { + "schema": 1691462897073, + "derHash": "V9YfCUSUgSTpt2hcfiybQ0TnXalYgyjs35NqyOgLQ44=", + "subject": "CN=SSL.com IV TLS Transit ECC CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gSVYgVExTIFRyYW5zaXQgRUNDIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "5bc1847a6c243c776a1d1dc4c8af9e7997a6c7169aab2a9bf414fdc6021bad82", + "size": 1171, + "filename": "Uujf2MPA35Lg50AhrhFGPERK_ZHDRJxc3Xmyj43PdEs=.pem", + "location": "security-state-staging/intermediates/0422e6ee-65a9-462e-85af-91e74551f5e4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Uujf2MPA35Lg50AhrhFGPERK/ZHDRJxc3Xmyj43PdEs=", + "crlite_enrolled": false, + "id": "9b582fdd-0007-40a3-a90e-68d29ccdd9a1", + "last_modified": 1691463423574 + }, + { + "schema": 1691462896755, + "derHash": "8XKNyvYaKAae6g+htILdYaRj91JV3XAM9gYH516Tyak=", + "subject": "CN=SSL.com OV TLS Issuing ECC CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gT1YgVExTIElzc3VpbmcgRUNDIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "8e740702df80f0076906b0f74f128450967945939025bb10357c389afab0db9c", + "size": 1171, + "filename": "_1gJkt0BlZX88iJllnik6BHg_MLbYbbnb-YQmvJxQak=.pem", + "location": "security-state-staging/intermediates/7f9e7738-5a40-4168-9ee0-d4c357f17a5e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/1gJkt0BlZX88iJllnik6BHg/MLbYbbnb+YQmvJxQak=", + "crlite_enrolled": false, + "id": "4f5a919b-791a-4c34-9424-075df260730f", + "last_modified": 1691463423571 + }, + { + "schema": 1691462896422, + "derHash": "/+kwRC3+tpzimpCLamObNwvWumB1+TGrg1Ydk4tFOiE=", + "subject": "CN=SSL.com TLS Transit RSA CA R2,O=SSL Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIFRyYW5zaXQgUlNBIENBIFIy", + "whitelist": false, + "attachment": { + "hash": "6bc6c2c92860ce808bd1cab7301a59d238c8a49125fdf02b9a409c4636af2866", + "size": 2316, + "filename": "vn0e1DEpYDnOWeNSH0IkuvrS8iIAE7x5aB7EolDUxS0=.pem", + "location": "security-state-staging/intermediates/3d5d12b4-1bb8-4b61-9d30-fb7c188d4305.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vn0e1DEpYDnOWeNSH0IkuvrS8iIAE7x5aB7EolDUxS0=", + "crlite_enrolled": false, + "id": "091d84c0-c607-4dbb-8ef6-4cab04674492", + "last_modified": 1691463423568 + }, + { + "schema": 1691462896110, + "derHash": "C53yb/h9+S7CZWYe7ILQn4p1MQBvPNpNb3KIlhsjsEU=", + "subject": "CN=SSL.com IV TLS Issuing RSA CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gSVYgVExTIElzc3VpbmcgUlNBIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "0f3eeeaeb3254359d65fa062cb898c3e08e7176beb1efc9b6c1950c2ba4b44c2", + "size": 2146, + "filename": "mhzhCo3EEEyXI3PieOhth0fVaJYvXtTu14HQEFJNQ18=.pem", + "location": "security-state-staging/intermediates/b1eb2577-4c07-4d79-8831-6bd8869c3f1c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mhzhCo3EEEyXI3PieOhth0fVaJYvXtTu14HQEFJNQ18=", + "crlite_enrolled": false, + "id": "f142e144-3fa0-479e-98f3-22d33b66dda1", + "last_modified": 1691463423565 + }, + { + "schema": 1691462895789, + "derHash": "TPdz+VX9t6l9sSUPL9e6NgdalhZrtcxBe1wCHQ9wrfI=", + "subject": "CN=SSL.com EV TLS Transit ECC CA R2,O=SSL Corporation,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKTAnBgNVBAMMIFNTTC5jb20gRVYgVExTIFRyYW5zaXQgRUNDIENBIFIy", + "whitelist": false, + "attachment": { + "hash": "deac762e51138bd3f8df0a48738b9f77c51aa66c6524b2fc50deadf591f0e63e", + "size": 1219, + "filename": "YhmwQZRxuMMLT6QK3cqSB3SCtkKBIgqOYUC1UCixGUM=.pem", + "location": "security-state-staging/intermediates/286b2242-3b97-4d62-a7f5-1264c34db11e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YhmwQZRxuMMLT6QK3cqSB3SCtkKBIgqOYUC1UCixGUM=", + "crlite_enrolled": false, + "id": "ef50d81e-347e-4ad0-8333-9060302ffe2c", + "last_modified": 1691463423563 + }, + { + "schema": 1691462895471, + "derHash": "ehokKF4l6U3yfOizRLG7azLwJU/w17WrboVq6Qz95Vw=", + "subject": "CN=Sectigo Public Server Authentication CA EV R36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBFViBSMzY=", + "whitelist": false, + "attachment": { + "hash": "cc4c5ca289ec16e68de34508a8bd2efc2b8088cc71f6faed778548e98984836e", + "size": 2243, + "filename": "0RfXDfccNqREsDzRXraedr-pPfaWA8fMplHCU30xYlo=.pem", + "location": "security-state-staging/intermediates/9558bcad-a4bf-4531-8bed-05440ff5d4c0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0RfXDfccNqREsDzRXraedr+pPfaWA8fMplHCU30xYlo=", + "crlite_enrolled": false, + "id": "f7cd88bf-ce57-412c-983e-91b2a50bd68b", + "last_modified": 1691463423560 + }, + { + "schema": 1691462898023, + "derHash": "KFjVGNB3QIjP571eUqr9FcRQtjxmfJ9XXrIsCE/CMYw=", + "subject": "CN=SSL.com OV TLS Transit RSA CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gT1YgVExTIFRyYW5zaXQgUlNBIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "ee69e4040883812c3e496cda16ba88fc84cde4eb9c901f10d1a9066143e4eae5", + "size": 2320, + "filename": "o0HYUK1eZnSZd9pJ4VJWzYwXhM_Kd77fUqkcbAgPyBQ=.pem", + "location": "security-state-staging/intermediates/5342b13e-7f08-493d-aeac-a600a97dbb13.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o0HYUK1eZnSZd9pJ4VJWzYwXhM/Kd77fUqkcbAgPyBQ=", + "crlite_enrolled": false, + "id": "8dac91d6-b1d0-4aea-a4fd-ef3d820ea5bc", + "last_modified": 1691463423557 + }, + { + "schema": 1691462894183, + "derHash": "bMldIdTHujlzbk5wPkJgt++23M2RQx7VxD1+CYazlVQ=", + "subject": "CN=SSL.com EV TLS Transit RSA CA R2,O=SSL Corporation,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKTAnBgNVBAMMIFNTTC5jb20gRVYgVExTIFRyYW5zaXQgUlNBIENBIFIy", + "whitelist": false, + "attachment": { + "hash": "35ab25d6626e936f4b53695dcd4292c39de30fd5d167d40303411153b4f1c622", + "size": 2365, + "filename": "BB0JZc-VhUu9ni64PNmCZWVFCZMOLGDKjfZ9-B9983Q=.pem", + "location": "security-state-staging/intermediates/9e050b69-fb2b-4938-9292-098ee191468d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BB0JZc+VhUu9ni64PNmCZWVFCZMOLGDKjfZ9+B9983Q=", + "crlite_enrolled": false, + "id": "424a8b02-3d88-4196-9f8f-6732bd62dd35", + "last_modified": 1691463423554 + }, + { + "schema": 1691462893216, + "derHash": "fz7mNmI27ebB98IcRDxkTTIVPtqpNXw6bah5zfKmsLY=", + "subject": "CN=SSL.com TLS Issuing ECC CA R2,O=SSL Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIElzc3VpbmcgRUNDIENBIFIy", + "whitelist": false, + "attachment": { + "hash": "d490c85e11163a1c928f0fe940d3d64fdcd93edc1e31c83c0c014173e8d6e073", + "size": 1167, + "filename": "NPRrBk96t64vay83a8Kf7kLV8Vmdi_17Qgpes328_Pk=.pem", + "location": "security-state-staging/intermediates/59307eda-d10c-48e4-a850-00d629c774d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NPRrBk96t64vay83a8Kf7kLV8Vmdi/17Qgpes328/Pk=", + "crlite_enrolled": false, + "id": "097b1472-eb8b-4033-905c-38fdd4914613", + "last_modified": 1691463423551 + }, + { + "schema": 1691462895158, + "derHash": "ZULRdr7VDxk8DOKXrkTs2KCoa+wu3mgnaTRAWbTnhTA=", + "subject": "CN=Sectigo Public Server Authentication CA OV R36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBPViBSMzY=", + "whitelist": false, + "attachment": { + "hash": "2b172262755cae69e5fc2de39639efcccb579ebbc8f91d3cddf2eb37a1f37659", + "size": 2243, + "filename": "KqkYYX5LYAYP7XGemqzbtPPIA8x7BS_BbOIcAXf3j2k=.pem", + "location": "security-state-staging/intermediates/6127baac-3535-499e-99bc-2c07d82d9e02.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KqkYYX5LYAYP7XGemqzbtPPIA8x7BS/BbOIcAXf3j2k=", + "crlite_enrolled": false, + "id": "22e4b9a9-bfd4-45dc-ba90-4d4862a70b48", + "last_modified": 1691463423549 + }, + { + "schema": 1691462891221, + "derHash": "DamZ32b9Icr/XhN2q603tP0siqabJaP2B5Tm86pH2Ys=", + "subject": "CN=SSL.com OV TLS Transit ECC CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gT1YgVExTIFRyYW5zaXQgRUNDIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "eaac4c30113f2417175090ad903230166d1bb00d86d1b6432c93557f075db52e", + "size": 1171, + "filename": "RFBEONmGnotBOM1HNtNx0nqeqPqncPrhxWZuEl0SvBg=.pem", + "location": "security-state-staging/intermediates/ee99c5c3-b365-4917-a21c-c860dc6e7f9e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RFBEONmGnotBOM1HNtNx0nqeqPqncPrhxWZuEl0SvBg=", + "crlite_enrolled": false, + "id": "c297c00b-1d0a-4e5e-a426-6443f0f157ff", + "last_modified": 1691463423546 + }, + { + "schema": 1691462892892, + "derHash": "/ZF7DQlPqk2xu6+e16xVvlFi2XHCJ7NOcIXKU3pKS0c=", + "subject": "CN=Sectigo Public Server Authentication CA EV E36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBFViBFMzY=", + "whitelist": false, + "attachment": { + "hash": "42f8d14c2b9050adc2aec0565b41e7ebcb6f79cd52a75aece52d1333f80f0dfa", + "size": 1228, + "filename": "lVUzVQgp60SlPl4E5_Nd7GLlg0qdQkgNz39YIr8YwFI=.pem", + "location": "security-state-staging/intermediates/3d7aa2ea-5e8c-456f-910f-1bcff8f69d68.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lVUzVQgp60SlPl4E5/Nd7GLlg0qdQkgNz39YIr8YwFI=", + "crlite_enrolled": false, + "id": "f877a2a4-c36c-491f-8d1d-a8fc31c090df", + "last_modified": 1691463423543 + }, + { + "schema": 1691462890504, + "derHash": "YEvdJgoo7dJW6dqnDAXNGKw87IJ/hCWQ1F6kK93GHkI=", + "subject": "CN=SSL.com OV TLS Issuing RSA CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gT1YgVExTIElzc3VpbmcgUlNBIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "86a2f9fd1a0e429dc8a2b2e2d745105dd4967d7644bfc0102521819da10625aa", + "size": 2146, + "filename": "hAjHe-3BykIlgjLNTOWLMDSaNA7iWDMBf9vMHQFxBfM=.pem", + "location": "security-state-staging/intermediates/add845c8-e88f-4d45-a826-fc828c90bfc8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hAjHe+3BykIlgjLNTOWLMDSaNA7iWDMBf9vMHQFxBfM=", + "crlite_enrolled": false, + "id": "e3737def-bfe1-468e-a56a-2e0927e3802e", + "last_modified": 1691463423540 + }, + { + "schema": 1691462892530, + "derHash": "aCVdXqUtLKmgsfjv+PRGzsyVqApdiA22gfFjDAtLIU0=", + "subject": "CN=SSL.com EV TLS Issuing ECC CA R1,O=SSL Corporation,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKTAnBgNVBAMMIFNTTC5jb20gRVYgVExTIElzc3VpbmcgRUNDIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "d25b900cb9b9db8f6eb5b5ad7c22c39a8da135237a2d7c79848c666a390a860e", + "size": 1219, + "filename": "cCqwzeMmcY9GAwoG1CFguEUHPI8MEGng1iWmfPgg6H4=.pem", + "location": "security-state-staging/intermediates/75085dec-bd2e-4583-b383-37b0eef56991.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cCqwzeMmcY9GAwoG1CFguEUHPI8MEGng1iWmfPgg6H4=", + "crlite_enrolled": false, + "id": "77d5b007-648b-4edd-b59e-0c781228df5b", + "last_modified": 1691463423537 + }, + { + "schema": 1691462891909, + "derHash": "XRvDmSdOZJ4ccml96RpUrXJQiMUiHLYeF+6cKQvEKpI=", + "subject": "CN=SSL.com TLS Transit ECC CA R2,O=SSL Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIFRyYW5zaXQgRUNDIENBIFIy", + "whitelist": false, + "attachment": { + "hash": "fdbf0743886b9b23d800d8c83737d68c6a8319aed67865a3dd1831b3b94a4f48", + "size": 1171, + "filename": "OXyj9ngbqO9cjLeO_-t9Ggl2EP4JTnVWHq4LEwhFM9w=.pem", + "location": "security-state-staging/intermediates/d713378e-9de0-49fc-bbe7-553927cb79b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OXyj9ngbqO9cjLeO/+t9Ggl2EP4JTnVWHq4LEwhFM9w=", + "crlite_enrolled": false, + "id": "f5aebbaf-2b63-4231-b9d9-3061ccf873db", + "last_modified": 1691463423534 + }, + { + "schema": 1691462894840, + "derHash": "Qe/vRv0OQ0tkS7R+biEYKo3vWbHm4fKVJYf6KL8QpKI=", + "subject": "CN=SSL.com IV TLS Issuing ECC CA 1,O=SSL Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKDAmBgNVBAMMH1NTTC5jb20gSVYgVExTIElzc3VpbmcgRUNDIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "e88acf9afc43d290a3fa794b905a8d4fe26e62824f31485ef5c85fb9d7ed63b7", + "size": 1171, + "filename": "Zd5CFIWdko8GdSBgv9xIY2vQLDplCKAQgT7bRrFKcVo=.pem", + "location": "security-state-staging/intermediates/4196e7ad-0298-4ef6-9184-b3efc81bde4e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Zd5CFIWdko8GdSBgv9xIY2vQLDplCKAQgT7bRrFKcVo=", + "crlite_enrolled": false, + "id": "2fcc29bf-5bfd-4b00-b60f-fc5dfc96d040", + "last_modified": 1691463423532 + }, + { + "schema": 1691462894516, + "derHash": "7L0gAvsV1pDrKcynNx2X+E+ufx/2e9A5Mjutr1ytkUg=", + "subject": "CN=SSL.com EV TLS Issuing RSA CA R1,O=SSL Corporation,C=US", + "subjectDN": "MFIxCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xKTAnBgNVBAMMIFNTTC5jb20gRVYgVExTIElzc3VpbmcgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "0734e18bf10838f54fe47fbda5e0ebd0fa71d4883469b39242df853bc49f19bc", + "size": 2194, + "filename": "3ebNWkR6qhSsVzk3WkbXqmW0areNxC--6UeU--EA9Io=.pem", + "location": "security-state-staging/intermediates/22b892be-5c86-4d06-964f-2cda61412a62.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3ebNWkR6qhSsVzk3WkbXqmW0areNxC++6UeU++EA9Io=", + "crlite_enrolled": false, + "id": "ffef345d-45e0-43e6-b6ca-ea643f8c844a", + "last_modified": 1691463423528 + }, + { + "schema": 1691462893863, + "derHash": "jFTDNLZrpOQmdyr0o/kTbBmhrscp/bKMU1wHpaTvIuA=", + "subject": "CN=Sectigo Public Server Authentication CA DV R36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBEViBSMzY=", + "whitelist": false, + "attachment": { + "hash": "e510e76c06a2dc90655e9f6416816f5f24cfff00922e56b65a73741f74d8cac9", + "size": 2243, + "filename": "a9khLOZJxlnJyrxstg_P-seiDCm-Yf3OsrXyFocBaI0=.pem", + "location": "security-state-staging/intermediates/c6c81ae5-2c39-42ac-b622-ba3e2a8773a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "a9khLOZJxlnJyrxstg/P+seiDCm+Yf3OsrXyFocBaI0=", + "crlite_enrolled": false, + "id": "40f5fc49-d136-4afe-ab06-5075807da2ab", + "last_modified": 1691463423525 + }, + { + "schema": 1691462893525, + "derHash": "v7w56forhMmpIzfiNE7iOB2NPTro6nXvTkjlgH7YDGk=", + "subject": "CN=SSL.com TLS Issuing RSA CA R1,O=SSL Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xJjAkBgNVBAMMHVNTTC5jb20gVExTIElzc3VpbmcgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "e64b192b57496f90dbe9f2ac9ad9722956d45711a7c7e790e2a3ef1dd8d552f0", + "size": 2142, + "filename": "0FKBVxnyd4Jq8v8ST-3sjO-WoB7PLBEpSdixbHE1L4g=.pem", + "location": "security-state-staging/intermediates/2e02ff28-d3dc-4d88-a3c7-227957c44bd6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0FKBVxnyd4Jq8v8ST+3sjO+WoB7PLBEpSdixbHE1L4g=", + "crlite_enrolled": false, + "id": "8727f373-05e0-4b49-9a20-3b11b8261915", + "last_modified": 1691463423522 + }, + { + "schema": 1691462892209, + "derHash": "n1mK8s7gMhgAukmsua3RRUXAj/5KBu+noJvDleNuXE0=", + "subject": "CN=Atos TrustedRoot Server CA ECC 2022,O=Atos,C=DE", + "subjectDN": "MEoxLDAqBgNVBAMMI0F0b3MgVHJ1c3RlZFJvb3QgU2VydmVyIENBIEVDQyAyMDIyMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERQ==", + "whitelist": false, + "attachment": { + "hash": "2510e6ad635cd96c21562beded62d3aa29f0f3ed10b5adb29c6eca9db9cd6dbe", + "size": 1593, + "filename": "8KEW23ySm9lo7J6pGuljKJ3cjwPhuVAWxBJIxXB8TgE=.pem", + "location": "security-state-staging/intermediates/61dd928f-9cbd-4e03-8549-9e9f094e0bc4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8KEW23ySm9lo7J6pGuljKJ3cjwPhuVAWxBJIxXB8TgE=", + "crlite_enrolled": false, + "id": "dd822a3c-beed-4df7-9158-a424398daa0f", + "last_modified": 1691463423519 + }, + { + "schema": 1691462891546, + "derHash": "QQK7CLfqGeVn62cQOExUCv2tmTrw2d+/kcpic3Vbi6A=", + "subject": "CN=Sectigo Public Server Authentication CA OV E36,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUHVibGljIFNlcnZlciBBdXRoZW50aWNhdGlvbiBDQSBPViBFMzY=", + "whitelist": false, + "attachment": { + "hash": "33731ef19fa7b496fdad56f77fa6e052e7dcb97dc2a530e9a9fe0f65ce8e3939", + "size": 1232, + "filename": "C0ruG6pqJqe-UDfJO02OWmh2qaGi8SKyJkfeZdqg0Nc=.pem", + "location": "security-state-staging/intermediates/ac0e5c69-93e1-4c98-a4db-7e18a588e589.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "C0ruG6pqJqe+UDfJO02OWmh2qaGi8SKyJkfeZdqg0Nc=", + "crlite_enrolled": false, + "id": "d34f4210-932c-4aa4-8cd3-284866d86ebc", + "last_modified": 1691463423516 + }, + { + "schema": 1691462890878, + "derHash": "hDED/cLgS1EVx4zHxH2AKr70JyFuW70JRtEFdRhow7U=", + "subject": "CN=Atos TrustedRoot Server CA RSA 2022,O=Atos,C=DE", + "subjectDN": "MEoxLDAqBgNVBAMMI0F0b3MgVHJ1c3RlZFJvb3QgU2VydmVyIENBIFJTQSAyMDIyMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERQ==", + "whitelist": false, + "attachment": { + "hash": "21999452086b1cfec572aaa2f8d523927f2198fee389f8701c8f9656df2d29d1", + "size": 2739, + "filename": "1-mHw9AHBDFOOcleNBxZiK9_XOFAmoN5RVkILbBi7b0=.pem", + "location": "security-state-staging/intermediates/d459e70a-a01b-48d8-8d63-3af1c0a04273.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1+mHw9AHBDFOOcleNBxZiK9/XOFAmoN5RVkILbBi7b0=", + "crlite_enrolled": false, + "id": "cd9d56e1-0df1-426e-9026-7510747fb824", + "last_modified": 1691463423513 + }, + { + "schema": 1691203695927, + "derHash": "zn/L66zdBZ5BDddiiyMm1zkZYLcTbMABNLk1z+i1l3E=", + "subject": "CN=Valid Certificadora RSA OV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBSU0EgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "b5fd4d0de12e785036bc8b57b7c4edaabfd12194899007826f6512fc344ce669", + "size": 2284, + "filename": "73hhXF9qC-ZSEptWzzZrPJ7cHW047z5Gf2ilv5WcsYw=.pem", + "location": "security-state-staging/intermediates/366dbeef-1e63-4f1b-9d4e-25a5385fde1e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "73hhXF9qC+ZSEptWzzZrPJ7cHW047z5Gf2ilv5WcsYw=", + "crlite_enrolled": false, + "id": "21e8026b-7faa-4abf-a9ad-582999b37350", + "last_modified": 1691204223483 + }, + { + "schema": 1691203695275, + "derHash": "q9zgbWYS89iHZc1Xn2hPjyglskxIMSHUrwpts4/cr0Q=", + "subject": "CN=时代互联 ECC EV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBFQ0MgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "6558e399b329394388049947e54723e644d58aec4ebfbaa7fdbc398de5f7e2ae", + "size": 1244, + "filename": "liigm1txiAcjWn1yZ3olhYNZOL4miZlxeejJOI9WB2o=.pem", + "location": "security-state-staging/intermediates/51dd6a22-71ca-44ff-bcc1-722f87d2a77b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "liigm1txiAcjWn1yZ3olhYNZOL4miZlxeejJOI9WB2o=", + "crlite_enrolled": false, + "id": "64eb36ba-5fdc-4ea2-8cad-3fd62a76d4ee", + "last_modified": 1691204223481 + }, + { + "schema": 1691203694260, + "derHash": "Jl+79eqGErlkvdIyACBA9eUyD8NYNZMY4hZmje3cRMo=", + "subject": "CN=Valid Certificadora ECC DV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBFQ0MgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "c21a56436edc6eb502ad8b10b543b0ff5560d7d140efd2f072c47d9049a9c9ef", + "size": 1272, + "filename": "tB0GG0pENi6tPYPDODNkdWRmTlCtoWmE5DV0ISTqGUM=.pem", + "location": "security-state-staging/intermediates/76aabb36-3c58-4c7c-bd3f-c7534d2a83ed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tB0GG0pENi6tPYPDODNkdWRmTlCtoWmE5DV0ISTqGUM=", + "crlite_enrolled": false, + "id": "fdbec5ad-abae-476c-80c0-b5587611db32", + "last_modified": 1691204223478 + }, + { + "schema": 1691203693535, + "derHash": "eaToBEoSycJcqlMDLPSbGsfO6vH4sK2TsZpbbXNRF9k=", + "subject": "CN=TBS ECC Organization Validation Secure Server CA 3,O=TBS Internet Ltd,C=GB", + "subjectDN": "MGUxCzAJBgNVBAYTAkdCMRkwFwYDVQQKExBUQlMgSW50ZXJuZXQgTHRkMTswOQYDVQQDEzJUQlMgRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "474b66bbe890de32addfd18e5158a9b218510bb9b88b49de950205b1cd7d1860", + "size": 1272, + "filename": "RIm3sUp38QcMpkuc0BxL_ND6ICnaLhVQt8Ss75Q8Stc=.pem", + "location": "security-state-staging/intermediates/4398a7a3-30a1-47a3-b806-ac96917dcf2b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RIm3sUp38QcMpkuc0BxL/ND6ICnaLhVQt8Ss75Q8Stc=", + "crlite_enrolled": false, + "id": "ee5b6664-2097-4b03-8542-34fd1a0a842e", + "last_modified": 1691204223476 + }, + { + "schema": 1691203694930, + "derHash": "GT1gCC8y5pXwCvjgOzeYeqkIrOi3jdDep+ACK4JdJXs=", + "subject": "CN=时代互联 RSA OV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBSU0EgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "c91c5353eb4ce5048adc074e9189f57e1fb0f085242dea6a1852838c908959a3", + "size": 2280, + "filename": "HuZK9BQxRghEaJE_3huFgo-OafS1aeyw0f89YoiR2So=.pem", + "location": "security-state-staging/intermediates/38a705cc-983a-4d40-8fcb-02720ac84ab8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HuZK9BQxRghEaJE/3huFgo+OafS1aeyw0f89YoiR2So=", + "crlite_enrolled": false, + "id": "24be2378-cf20-46b3-a475-2aa243ff8c28", + "last_modified": 1691204223473 + }, + { + "schema": 1691203695613, + "derHash": "Q1zGYD6c9Q524O0DQauAvCFTjjuK9oMk2lqhd+5IlTc=", + "subject": "CN=EUNETIC RSA Domain Validation Secure Server CA 3,O=EUNETIC GmbH,C=DE", + "subjectDN": "MF8xCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxFVU5FVElDIEdtYkgxOTA3BgNVBAMTMEVVTkVUSUMgUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "81a0bd1d0a43b7498e6a7324e38ed39c40b373302a25b2ffd50d49d6429e28b6", + "size": 2276, + "filename": "8P-6LHiOKbv8oyEpyGdFB_qfyTJUY7QIdmhlJtW8yzs=.pem", + "location": "security-state-staging/intermediates/22eb100b-99c5-4eb7-a934-fb847bfddc76.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8P+6LHiOKbv8oyEpyGdFB/qfyTJUY7QIdmhlJtW8yzs=", + "crlite_enrolled": false, + "id": "303fb12d-3172-4139-8d6c-bf4fa982e7f3", + "last_modified": 1691204223470 + }, + { + "schema": 1691203692568, + "derHash": "zeb9wTaN9xvxVrhrADFKVgdXjzdhLfs/l0sFhjvnyR4=", + "subject": "CN=RZTrust RSA DV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IFJTQSBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "bcc7eb3e401c9354781e4a111d9a66cabaaacd5571d8c57cc065588f9e3a9626", + "size": 2272, + "filename": "CvG_oJAQ1nLBEr_xZ4F2a1M9zKoziZs8AkFwzEMnDeE=.pem", + "location": "security-state-staging/intermediates/57031cca-d6df-442a-98c2-195889df0a95.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CvG/oJAQ1nLBEr/xZ4F2a1M9zKoziZs8AkFwzEMnDeE=", + "crlite_enrolled": false, + "id": "196740fe-c3b6-4752-87f5-1f50f2a38a13", + "last_modified": 1691204223468 + }, + { + "schema": 1691203693211, + "derHash": "6YaCx2sEhRCt5OcO3bRgEC68o75LihHyVxZ1YImxyBM=", + "subject": "CN=CTI ECC OV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIEVDQyBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "267372a7d1769c774dd5b41c4f35b1ac9a4fe36448e9d884a242a51d3bca22d8", + "size": 1264, + "filename": "Qqq7_u_cV5BMNMDveXsUgF1PfU8-KpkMKDnf5E77xcU=.pem", + "location": "security-state-staging/intermediates/7de6e193-0614-4857-b5a7-ccbcc0599acf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qqq7/u/cV5BMNMDveXsUgF1PfU8+KpkMKDnf5E77xcU=", + "crlite_enrolled": false, + "id": "2e0e989b-d0cc-40f6-bb5c-15a1963dc036", + "last_modified": 1691204223466 + }, + { + "schema": 1691203692287, + "derHash": "s4v9686retcvplaAbXvJi41ZvxFjmOWW4TybZu4HBAA=", + "subject": "CN=CTI RSA EV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIFJTQSBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "18b0818b354c85850a86a3f8987d0fe10dbf212e0ffb423eb2d56f5349c55ae5", + "size": 2255, + "filename": "4_7xuO7nc4aefMy8WTdJTgGnMcr_9RvGauYPaa71wYw=.pem", + "location": "security-state-staging/intermediates/604c3ba0-be79-4046-a0fa-8b293a5e035d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4/7xuO7nc4aefMy8WTdJTgGnMcr/9RvGauYPaa71wYw=", + "crlite_enrolled": false, + "id": "932ad223-728e-4130-add3-9d67b71dc331", + "last_modified": 1691204223463 + }, + { + "schema": 1691203691705, + "derHash": "VBJruYSz42NbkRl2M+y0qJivL/B4Gn8z6SlzZd9rOww=", + "subject": "CN=Valid Certificadora RSA EV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBSU0EgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "fb05facbf824af8e577f66c583acbd1939b37c654a8fb1c90afd6109952e3756", + "size": 2259, + "filename": "8aOVBHhdOKZbKIcdO0PeBzHxMQiJNtw47c5QN9-k6jc=.pem", + "location": "security-state-staging/intermediates/9780d155-5209-4fc0-9b97-0c38aaafa1dd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8aOVBHhdOKZbKIcdO0PeBzHxMQiJNtw47c5QN9+k6jc=", + "crlite_enrolled": false, + "id": "c98147ea-d745-4b97-8833-9ddae6ddacb2", + "last_modified": 1691204223460 + }, + { + "schema": 1691203692861, + "derHash": "z/1lRQ3ZKhZ5KXigX/I5stuet/SOnfzaL8N3GE8rumY=", + "subject": "CN=Site Blindado ECC Organization Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MHExCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xRTBDBgNVBAMTPFNpdGUgQmxpbmRhZG8gRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "78ea14874e3a5ee710306b273826dde58a736fd7fb9a8ec204be861fc29e3ba8", + "size": 1288, + "filename": "G8Vosi4orjbR2HvKm26KQP2M6OYZ164PCBbdCBekXCQ=.pem", + "location": "security-state-staging/intermediates/90bffd05-a52f-4246-968a-24c21a7c12aa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G8Vosi4orjbR2HvKm26KQP2M6OYZ164PCBbdCBekXCQ=", + "crlite_enrolled": false, + "id": "9bf24456-e43d-4fa0-8a98-e79dd1bb1f45", + "last_modified": 1691204223458 + }, + { + "schema": 1691203689492, + "derHash": "9hOJUN77cdLLFFZAHhvgDaFaDTs/Oau5VV28fD7FwuM=", + "subject": "CN=MarketWare RSA Organization Validation Secure Server CA 3,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,C=PT", + "subjectDN": "MIGQMQswCQYDVQQGEwJQVDE9MDsGA1UECgw0TWFya2V0V2FyZSAtIFNvbHXDp8O1ZXMgcGFyYSBNZXJjYWRvcyBEaWdpdGFpcywgTGRhLjFCMEAGA1UEAxM5TWFya2V0V2FyZSBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "800cc7f75d47656e75ba146518d0a696a6c42544c8e682cc29038713e6eab8a9", + "size": 2345, + "filename": "ZgpUesAlw4TN5wAGAhh7QjCge3rj-ybsQiTpN_7snnA=.pem", + "location": "security-state-staging/intermediates/e88c1c72-84a4-4877-bec7-75b129941af2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZgpUesAlw4TN5wAGAhh7QjCge3rj+ybsQiTpN/7snnA=", + "crlite_enrolled": false, + "id": "948c093d-d323-4b87-8b2a-050251bb7bea", + "last_modified": 1691204223455 + }, + { + "schema": 1691203690401, + "derHash": "SFR1x4z8/I59G/Tf+abEiiTkh6D6ABblwjZ2mYww2s8=", + "subject": "CN=EUNETIC RSA Extended Validation Secure Server CA 3,O=EUNETIC GmbH,C=DE", + "subjectDN": "MGExCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxFVU5FVElDIEdtYkgxOzA5BgNVBAMTMkVVTkVUSUMgUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "d5eade9b3522677576fa9c60fe2d338ab052b1d85b17e1276909cfbab78d93be", + "size": 2255, + "filename": "9Xo0XoHAnRSV-6TY_dALGkkUz2PBKBy9hqXeErnKBrg=.pem", + "location": "security-state-staging/intermediates/f10f9f7e-4a1e-410f-85a8-2e9970485f10.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9Xo0XoHAnRSV+6TY/dALGkkUz2PBKBy9hqXeErnKBrg=", + "crlite_enrolled": false, + "id": "f90bba9f-9505-4cba-ad07-fa4913a47b9f", + "last_modified": 1691204223453 + }, + { + "schema": 1691203694616, + "derHash": "c21ao9rDTHp5iM7g3r9PFo4+BmIX5ag46nJYCIl/czI=", + "subject": "CN=Valid Certificadora ECC OV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBFQ0MgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "c60052b20587c603bcb207cc7866f5e1cdb3931778ea007cf46037f13cc8895e", + "size": 1268, + "filename": "k1gQL8NRZ015m0nj4FFjeaLnS-4AUN73CZi59bdhe5I=.pem", + "location": "security-state-staging/intermediates/7f6bcffe-172e-4101-a799-fb4677cea328.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k1gQL8NRZ015m0nj4FFjeaLnS+4AUN73CZi59bdhe5I=", + "crlite_enrolled": false, + "id": "39b72183-df90-42cb-9df1-eb443b6ce8f7", + "last_modified": 1691204223450 + }, + { + "schema": 1691203688854, + "derHash": "ORGKEg59Nohq5jL2FmslnqlXXvHQmky+wOF9g2ww2wU=", + "subject": "CN=Valid Certificadora ECC EV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBFQ0MgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "3ee9085563b87f96a4f26bcc2ddd8867b117d256ee42ee7b44a8da47d8a95f28", + "size": 1248, + "filename": "961mseDsIKJetjdfJ0nTEen4aKBmS-wzq3-ZF1gBe0g=.pem", + "location": "security-state-staging/intermediates/27de2810-609a-46f0-9ea7-09ed8cb6fa3a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "961mseDsIKJetjdfJ0nTEen4aKBmS+wzq3+ZF1gBe0g=", + "crlite_enrolled": false, + "id": "d1bb8d95-a0c9-4d71-986a-5b90a62746ee", + "last_modified": 1691204223447 + }, + { + "schema": 1691203693964, + "derHash": "G6WQuauUkWYO10eflTmXyqccPbJR+h0q8ffvi1Ljb58=", + "subject": "CN=CTI RSA DV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIFJTQSBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2fc04beb98a1fac8ccb12fb099d14bce5af29e1fdcee8a88939733f7fa211016", + "size": 2276, + "filename": "ifZJTwazhcIvbMJvHrlemylPKtkwymbdh784dNtQeM4=.pem", + "location": "security-state-staging/intermediates/531f8ea9-9433-4940-b07d-e17fad731b92.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ifZJTwazhcIvbMJvHrlemylPKtkwymbdh784dNtQeM4=", + "crlite_enrolled": false, + "id": "0242013f-85c9-4861-937f-7512d7d5ca8b", + "last_modified": 1691204223444 + }, + { + "schema": 1691203691373, + "derHash": "uLJbZZVedePimDyye0Mcl7/pJBc7f3dnOtWffFI+qs8=", + "subject": "CN=Site Blindado ECC Domain Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MGsxCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xPzA9BgNVBAMTNlNpdGUgQmxpbmRhZG8gRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "fb480ea53a552e501774a5c2234bdbe957d22604c5c960c643facc65cba39f51", + "size": 1280, + "filename": "mvDk29Evac4Vaf03g4b8BtZ3RcKe3AmNzvM1VkYstOg=.pem", + "location": "security-state-staging/intermediates/803ba90f-3928-4de3-ba00-2a72869813df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mvDk29Evac4Vaf03g4b8BtZ3RcKe3AmNzvM1VkYstOg=", + "crlite_enrolled": false, + "id": "545653d3-c639-42d4-8b0b-ead9c3f8c634", + "last_modified": 1691204223442 + }, + { + "schema": 1691203687640, + "derHash": "AC6GOUskbBkcT5ME+CGxEDPX4Kdegc858mauR2iZgnE=", + "subject": "CN=Network Solutions RSA DV SSL CA 3,O=Network Solutions L.L.C.,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhOZXR3b3JrIFNvbHV0aW9ucyBMLkwuQy4xKjAoBgNVBAMTIU5ldHdvcmsgU29sdXRpb25zIFJTQSBEViBTU0wgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "27daa5e0b10b3ffab9b73021214dbbd5d4c430012d0eee3f650b97fcec5dd6d7", + "size": 2272, + "filename": "I8qCnUahEsK-qMy_wALVSUZfaX-FXmW8E5nnjn7lSok=.pem", + "location": "security-state-staging/intermediates/2eff95c6-a40b-451c-ad21-4a26226127a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "I8qCnUahEsK+qMy/wALVSUZfaX+FXmW8E5nnjn7lSok=", + "crlite_enrolled": false, + "id": "9a1f6f0e-a937-46f4-907b-314b1e753d3c", + "last_modified": 1691204223439 + }, + { + "schema": 1691203689187, + "derHash": "m3P0RoKSGniwsJYfewMbqo7F0GH1Te8yZVjATxzjs1Y=", + "subject": "CN=RZTrust ECC DV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IEVDQyBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "b26ccff276f28ce23f18d3d1004782a367f4f2dbdaffda68f47833f950659101", + "size": 1256, + "filename": "n9-nIppFPitMLMz8aPhtwnV6J26d8nIDdJeQt6Eubds=.pem", + "location": "security-state-staging/intermediates/7b07dc4c-f616-4139-ae1d-70adb90aac55.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "n9+nIppFPitMLMz8aPhtwnV6J26d8nIDdJeQt6Eubds=", + "crlite_enrolled": false, + "id": "db5eb429-79ee-4bb4-90df-836c89c0cd24", + "last_modified": 1691204223436 + }, + { + "schema": 1691203688231, + "derHash": "UurNFASvRHvJMkdnfTvoXGo2/yEUvo6VvBLpTARwoN8=", + "subject": "CN=时代互联 RSA DV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBSU0EgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "b88ea3cbbe54950bc956f07e32cdd0a4fd220820d0d0868d61eee1c028f69299", + "size": 2280, + "filename": "8d1XgrcRWTJMMsqmAisVCglKWebdUnzIOLlnCaAV8NQ=.pem", + "location": "security-state-staging/intermediates/132bf9a0-5292-494c-80ba-d81c9db3a386.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8d1XgrcRWTJMMsqmAisVCglKWebdUnzIOLlnCaAV8NQ=", + "crlite_enrolled": false, + "id": "21085995-2118-4ab7-882c-b3cbe8fbe760", + "last_modified": 1691204223434 + }, + { + "schema": 1691203688549, + "derHash": "FPdJAriY0ZmYnTozeSgJ2rs1DUTPA8nEmASLSG5EOAQ=", + "subject": "CN=JoySSL ECC Organization Validation Secure Server CA,O=JoySSL Limited,C=CN", + "subjectDN": "MGQxCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5Kb3lTU0wgTGltaXRlZDE8MDoGA1UEAxMzSm95U1NMIEVDQyBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "9353e4036e7d431968faa7821091d9eb1bd4f19d1b481ec0f4a5b1e0bff64405", + "size": 1268, + "filename": "9txvjDowBhhCEiGfjgacY-LBti-YanbxSA5WDXmiCBk=.pem", + "location": "security-state-staging/intermediates/34df1abb-6b9b-4956-a44c-142bfce0638c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9txvjDowBhhCEiGfjgacY+LBti+YanbxSA5WDXmiCBk=", + "crlite_enrolled": false, + "id": "fa529ce8-2e2a-469a-8742-5bfe6b8d7c46", + "last_modified": 1691204223431 + }, + { + "schema": 1691203685902, + "derHash": "xLqtr3MO/JNEHPGE1glRJwMVsGXdQgDY+hXTSbGUadw=", + "subject": "CN=CTI ECC EV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIEVDQyBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2e0c6c8cc5e2e3723413e1825c8d9883991694a0d6759776813c0bf229992915", + "size": 1240, + "filename": "jZbAhIZLY_VXaUg543ryM1yrtpNaoYhhKDYzFdgzYb4=.pem", + "location": "security-state-staging/intermediates/fd1c6c3b-3e2b-4d10-bfe3-a3b1d8143122.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jZbAhIZLY/VXaUg543ryM1yrtpNaoYhhKDYzFdgzYb4=", + "crlite_enrolled": false, + "id": "32a91b4a-a7d3-4d10-85c0-7f3f21249e80", + "last_modified": 1691204223428 + }, + { + "schema": 1691203687358, + "derHash": "0OkVdFam522Zy9lFhEfMMiFDRqW2xVhUhxoAXublAPg=", + "subject": "CN=时代互联 RSA EV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBSU0EgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "2c835a811964cfc1e6e9900166fd8f23422bd2896f37300ecc921b264e664622", + "size": 2255, + "filename": "Y5Hpl468XaN2R09gIr836gloFSaiwLhrfnUTz5RIvuc=.pem", + "location": "security-state-staging/intermediates/faf55c30-eea6-4936-b336-8df748b9d7c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y5Hpl468XaN2R09gIr836gloFSaiwLhrfnUTz5RIvuc=", + "crlite_enrolled": false, + "id": "a3362f43-e304-4063-a83f-7721605025a6", + "last_modified": 1691204223426 + }, + { + "schema": 1691203683938, + "derHash": "0fNa/aBIO5R4tNq7gZ+Yh+in+/a+GzizjO2RlWfD5y4=", + "subject": "CN=时代互联 ECC OV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBFQ0MgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "466607838f639b8aa9741da8300cb6e2e6f296c3d6be68b7ecd48d31f5332c13", + "size": 1264, + "filename": "5OizlTwuFA5LKHFxNOd07djxQh8ZMQjztp2VUPiUY-0=.pem", + "location": "security-state-staging/intermediates/b4a603c2-d2e8-401a-bd09-8a9344c34d3b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5OizlTwuFA5LKHFxNOd07djxQh8ZMQjztp2VUPiUY+0=", + "crlite_enrolled": false, + "id": "98dc0c39-26e7-42b8-b115-2ef9233e1226", + "last_modified": 1691204223423 + }, + { + "schema": 1691203691999, + "derHash": "WBRg1pWzgH+HEfJ3jcIuvdOxjd0yDd/NKONhnLKAsMQ=", + "subject": "CN=RZTrust ECC EV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IEVDQyBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "88709b4e5698fe972646101feb72d7a18087dd5ce4e1966a2e067b25e14669b9", + "size": 1236, + "filename": "iv8ucwdV1mKbwmO3yMGnK8cJ1ua3fq0Naok9_5g742Y=.pem", + "location": "security-state-staging/intermediates/72823fc9-14a1-40f0-b597-fa6d378b2f58.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iv8ucwdV1mKbwmO3yMGnK8cJ1ua3fq0Naok9/5g742Y=", + "crlite_enrolled": false, + "id": "d84c27a4-952d-4da6-bc98-de29493bbc4d", + "last_modified": 1691204223421 + }, + { + "schema": 1691203685236, + "derHash": "lgr9RR97VBZwjNsWGkce70OTnlJA6fQhjwpZuGaUa5c=", + "subject": "CN=Site Blindado RSA Organization Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MHExCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xRTBDBgNVBAMTPFNpdGUgQmxpbmRhZG8gUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "0c6f0ce1e88a7ad5224053d377732603f4e19298915274d042a4921f505eae41", + "size": 2300, + "filename": "usSzZviI45lU-cZkuxmTmt3D7aO-X3YED_xHpdc24cI=.pem", + "location": "security-state-staging/intermediates/ad3cba04-65cd-4209-97a4-948f591ebe07.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "usSzZviI45lU+cZkuxmTmt3D7aO+X3YED/xHpdc24cI=", + "crlite_enrolled": false, + "id": "efed4cd0-5f73-4b0f-a19f-6b5e12fb1707", + "last_modified": 1691204223418 + }, + { + "schema": 1691203690721, + "derHash": "seQuyuz8SylAjrpZg9vzki05QhxDtqYWVbA/muv+SiE=", + "subject": "CN=Site Blindado ECC Extended Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MG0xCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xQTA/BgNVBAMTOFNpdGUgQmxpbmRhZG8gRUNDIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "c2f274210ee5fdc8577a650f3746813e8b0b21590679ced6afd9f309767dfbeb", + "size": 1260, + "filename": "oL-edDYMNspTZ56qRMqXiuFGfPyMiVP3OWE0pkDfuMA=.pem", + "location": "security-state-staging/intermediates/02612b10-7e8e-4e6e-ae7a-688e9372cc57.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oL+edDYMNspTZ56qRMqXiuFGfPyMiVP3OWE0pkDfuMA=", + "crlite_enrolled": false, + "id": "81335996-a1d4-485c-b680-bc742c8bf82d", + "last_modified": 1691204223416 + }, + { + "schema": 1691203684900, + "derHash": "0FalaS8qdtqgpa6GnheZFJ1TAUKqi15v6tshxbgOEWk=", + "subject": "CN=Corporation Service Company RSA OV SSL CA,O=Corporation Service Company,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtDb3Jwb3JhdGlvbiBTZXJ2aWNlIENvbXBhbnkxMjAwBgNVBAMTKUNvcnBvcmF0aW9uIFNlcnZpY2UgQ29tcGFueSBSU0EgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "93822a7af2cbf4567b320db1e8d0723194b3e732f7a1c5dff0a94cde5563124e", + "size": 2288, + "filename": "eJFNz94QPdv8RexRcSa3nwty3nRqFlR7YXqKA5RGUGE=.pem", + "location": "security-state-staging/intermediates/cac9666b-1fb7-41c7-a880-4447ceda3c6b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "eJFNz94QPdv8RexRcSa3nwty3nRqFlR7YXqKA5RGUGE=", + "crlite_enrolled": false, + "id": "e5ad58f6-b1c7-46c9-b610-d6c8ac4bd222", + "last_modified": 1691204223413 + }, + { + "schema": 1691203690099, + "derHash": "VQhFPCBpzOgNzdFAcm0JfesIzuDJs1Z4Y9K1FqI11mY=", + "subject": "CN=RZTrust RSA EV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IFJTQSBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "3ea98e93d19c3f0dc77ff246cfb3bebd799456a619def694fc2bc2bcbf841c0a", + "size": 2247, + "filename": "LKHsYTSR_RFJrtmXsqYZ3GhXXG-_mwg7O0hjoH89uQM=.pem", + "location": "security-state-staging/intermediates/7e21ec9e-cadd-4f21-83ed-0f21a3ba67e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LKHsYTSR/RFJrtmXsqYZ3GhXXG+/mwg7O0hjoH89uQM=", + "crlite_enrolled": false, + "id": "06671dc7-b750-4b06-9852-e3b4a0ad0d70", + "last_modified": 1691204223411 + }, + { + "schema": 1691203686228, + "derHash": "N9SdltMtm/hoQz68vKcgAbF+9Zr2Wf8arzFuogyEqP8=", + "subject": "CN=Valid Certificadora RSA DV SSL CA,O=Valid Certificadora Digital LTDA,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMSkwJwYDVQQKEyBWYWxpZCBDZXJ0aWZpY2Fkb3JhIERpZ2l0YWwgTFREQTEqMCgGA1UEAxMhVmFsaWQgQ2VydGlmaWNhZG9yYSBSU0EgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "52844f190c4733bb7c8d7ea974500a870a824f5114ef0f1c7610b5819ccd31c5", + "size": 2284, + "filename": "uWexcmxZxKlsnSK1T1R1hgUnwi_ql_e2qRObuvkyMZo=.pem", + "location": "security-state-staging/intermediates/27d8e62b-f11b-4689-b012-57f48c2dec66.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uWexcmxZxKlsnSK1T1R1hgUnwi/ql/e2qRObuvkyMZo=", + "crlite_enrolled": false, + "id": "d17c69e8-cc94-432f-a67d-3c0ada497c9a", + "last_modified": 1691204223408 + }, + { + "schema": 1691203689782, + "derHash": "G1Lts5bqTjzJhqfdPhB+0qBwIFokpKmEmhiCCQDIuvE=", + "subject": "CN=Site Blindado RSA Extended Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MG0xCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xQTA/BgNVBAMTOFNpdGUgQmxpbmRhZG8gUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "efd3127b1085fca439e524dd4f4b5036c337585cf6ee4a37829b2f1ded08a612", + "size": 2272, + "filename": "VB96jR96ln4dfHF8w_x3MlN0NFqW04Q0nT_Pfm9aShE=.pem", + "location": "security-state-staging/intermediates/c4330474-a374-411f-81b9-65c842ce96c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VB96jR96ln4dfHF8w/x3MlN0NFqW04Q0nT/Pfm9aShE=", + "crlite_enrolled": false, + "id": "9e5b7eb5-2521-47c7-a8bc-90a31eec96eb", + "last_modified": 1691204223406 + }, + { + "schema": 1691203691074, + "derHash": "VRqpzj7f0TqRKz3JUT9LqmIwhdwioRUioWt3tHTxuzc=", + "subject": "CN=RZTrust RSA OV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IFJTQSBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "cbf676e8059148e9d55f12631d87d4ce630ff047aaa6a2cd77273f8d5fb837c7", + "size": 2268, + "filename": "Ea6Ae5V1aSWKlYUqOtbGxpsBC2xKzbemRjoHUAbPQWQ=.pem", + "location": "security-state-staging/intermediates/3e494c81-4098-401d-851d-4009f8e22894.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ea6Ae5V1aSWKlYUqOtbGxpsBC2xKzbemRjoHUAbPQWQ=", + "crlite_enrolled": false, + "id": "3761955e-9c47-46d8-8e33-6194f3496ad3", + "last_modified": 1691204223403 + }, + { + "schema": 1691203687925, + "derHash": "HxtpvzfT4iizoPTonsR4McmPOKhFORthT+1ZCHBcxd0=", + "subject": "CN=TBS RSA Organization Validation Secure Server CA 3,O=TBS Internet Ltd,C=GB", + "subjectDN": "MGUxCzAJBgNVBAYTAkdCMRkwFwYDVQQKExBUQlMgSW50ZXJuZXQgTHRkMTswOQYDVQQDEzJUQlMgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "f3537f467b3b6f59cc1e82b3b697ad0e60cf86302dd3bc5d5a7f8839c3c6afea", + "size": 2284, + "filename": "x2qLxRE1BSDybe8AFxypYqLIRvNxNLxNYLUnK71vGwE=.pem", + "location": "security-state-staging/intermediates/13c8bbf8-f6cf-43e0-8fb0-1842f6cf5be4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x2qLxRE1BSDybe8AFxypYqLIRvNxNLxNYLUnK71vGwE=", + "crlite_enrolled": false, + "id": "2b1a7fec-6fc1-48f4-a454-77cb671d5d3a", + "last_modified": 1691204223401 + }, + { + "schema": 1691203685597, + "derHash": "EupzvMCb20lREx3H7c0qSdL5AxRUunHfE58wcDN7Hpw=", + "subject": "CN=EUNETIC RSA Organization Validation Secure Server CA 3,O=EUNETIC GmbH,C=DE", + "subjectDN": "MGUxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxFVU5FVElDIEdtYkgxPzA9BgNVBAMTNkVVTkVUSUMgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "3ca8babf2b6c4cab0690bf452531104ddf05f13ff0bc632d57e0b0e13293ed65", + "size": 2284, + "filename": "yPlcIVsw9yfLtpwXsLrhxP1uo9ThTcJoZdZBhm_jKfk=.pem", + "location": "security-state-staging/intermediates/e34c8788-bdab-4086-8b9e-06c6b0080d4d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yPlcIVsw9yfLtpwXsLrhxP1uo9ThTcJoZdZBhm/jKfk=", + "crlite_enrolled": false, + "id": "bfedc183-dda6-46fc-a347-4231f3613ddb", + "last_modified": 1691204223398 + }, + { + "schema": 1691203683257, + "derHash": "nU8KvQpALHB0+hnv8nyW/tMG3CgKDvXvhkNzwWl6Aeg=", + "subject": "CN=Site Blindado RSA Domain Validation Secure Server CA 3,O=Site Blindado S.A.,C=BR", + "subjectDN": "MGsxCzAJBgNVBAYTAkJSMRswGQYDVQQKExJTaXRlIEJsaW5kYWRvIFMuQS4xPzA9BgNVBAMTNlNpdGUgQmxpbmRhZG8gUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "a2b0fcc0b23d03d42ee83d5528997bc342b9e52885f7a591f17f5015b20de0eb", + "size": 2292, + "filename": "BvSrgrowByfBfiz9Y5S3CODpBlJ1x-7Xpu5ufmyCmew=.pem", + "location": "security-state-staging/intermediates/f8b5c11a-e196-4ac6-8152-7001cc33995b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BvSrgrowByfBfiz9Y5S3CODpBlJ1x+7Xpu5ufmyCmew=", + "crlite_enrolled": false, + "id": "d10e400e-2479-4e9d-8e6b-6d91bf3cf0d7", + "last_modified": 1691204223396 + }, + { + "schema": 1691203686538, + "derHash": "AY9rNjtUOkOZRIoeurklDr/QcgAWsLQR/Ib35zZsZ4I=", + "subject": "CN=时代互联 ECC DV SSL CA,O=广东时代互联科技有限公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTlub/kuJzml7bku6PkupLogZTnp5HmioDmnInpmZDlhazlj7gxIzAhBgNVBAMMGuaXtuS7o+S6kuiBlCBFQ0MgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "daa2cf3af09259cfde6c1a1b22ae8d123f0bee08cce742d74690ad02573dd547", + "size": 1264, + "filename": "V_4C4-tqlkUohkfNixHEq6crrJBV-MASMk84K-xUpCY=.pem", + "location": "security-state-staging/intermediates/6b8a7d89-e9d8-4a90-b0f2-b68d122c5404.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "V/4C4+tqlkUohkfNixHEq6crrJBV+MASMk84K+xUpCY=", + "crlite_enrolled": false, + "id": "ca8098e0-49bd-4592-8dd6-c31f0ac1c024", + "last_modified": 1691204223393 + }, + { + "schema": 1691203684279, + "derHash": "r47RbT5DdDfKvWOw0mlY8CINidyccOFWbsKGmlSxIz0=", + "subject": "CN=CTI ECC DV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIEVDQyBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "11c53d8a168210d7a797e0a4702e102a3a441107c1525ce64d38f0f9a2e8cfe7", + "size": 1264, + "filename": "W3GhQPUDli3X3bC2rpWzAK5a9a4y6Oga3fEiMdSsWbc=.pem", + "location": "security-state-staging/intermediates/0f05c5b3-5cbc-476e-a8b6-0ece35dd3467.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W3GhQPUDli3X3bC2rpWzAK5a9a4y6Oga3fEiMdSsWbc=", + "crlite_enrolled": false, + "id": "1dec9ad7-9f3b-46ea-af8b-1a2a9823b005", + "last_modified": 1691204223391 + }, + { + "schema": 1691203683576, + "derHash": "CleC4EK66KM/Icbn349qRTRCrArOkxgMA5f4BQyvCME=", + "subject": "CN=CTI RSA OV SSL CA,O=Centre Testing International Group Co.\\, Ltd.,C=CN", + "subjectDN": "MGAxCzAJBgNVBAYTAkNOMTUwMwYDVQQKEyxDZW50cmUgVGVzdGluZyBJbnRlcm5hdGlvbmFsIEdyb3VwIENvLiwgTHRkLjEaMBgGA1UEAxMRQ1RJIFJTQSBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "588b2ee4526985bd7deca3fbbe508c06ff6e2b917377b07b98251d66b605c526", + "size": 2276, + "filename": "puZl_mcZtunCncJS3dmJ2OvShUNN9csPspHtJRBwosU=.pem", + "location": "security-state-staging/intermediates/8aa4992a-7bbb-4e84-a5b4-02f457b836d0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "puZl/mcZtunCncJS3dmJ2OvShUNN9csPspHtJRBwosU=", + "crlite_enrolled": false, + "id": "109af196-56bb-481d-9b55-86018ba4d73c", + "last_modified": 1691204223388 + }, + { + "schema": 1691203684605, + "derHash": "L5BDEKw1nwWy4Ah7JlHgJDfCfaV8/8klFR9fNnnKpRY=", + "subject": "CN=RZTrust ECC OV SSL CA,O=Zhongxiong Century Credit Co.\\, Ltd,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSswKQYDVQQKEyJaaG9uZ3hpb25nIENlbnR1cnkgQ3JlZGl0IENvLiwgTHRkMR4wHAYDVQQDExVSWlRydXN0IEVDQyBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "59a05847dcdf2315a9d0f8aa8f37578f9766ad5d7b8d8a1fac1a4b104a119246", + "size": 1256, + "filename": "BrilTykgcj-LFEJJkszef-dbFdlz0cJlB-DZUvymwlU=.pem", + "location": "security-state-staging/intermediates/ac569917-1487-40d1-8707-2129c0241fda.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BrilTykgcj+LFEJJkszef+dbFdlz0cJlB+DZUvymwlU=", + "crlite_enrolled": false, + "id": "bc787f17-4bed-4489-90c7-b40e4de7611a", + "last_modified": 1691204223386 + }, + { + "schema": 1691203686849, + "derHash": "kDM0UKOvH0LbWVT4SRSxhidx6rRmzlL90Z6qw1lO3Ts=", + "subject": "CN=Network Solutions RSA OV SSL CA 3,O=Network Solutions L.L.C.,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhOZXR3b3JrIFNvbHV0aW9ucyBMLkwuQy4xKjAoBgNVBAMTIU5ldHdvcmsgU29sdXRpb25zIFJTQSBPViBTU0wgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "1437cf1e0e4b90e2b36196fa15615057db4c3e00e6d4c015314006e6bf7c536d", + "size": 2272, + "filename": "MkbLAbYk3pBAD2BaO0EiRPvxV1qbyhJfFhc0skjkwfU=.pem", + "location": "security-state-staging/intermediates/2ceeae7b-8328-4537-a52b-5fe480a97e90.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MkbLAbYk3pBAD2BaO0EiRPvxV1qbyhJfFhc0skjkwfU=", + "crlite_enrolled": false, + "id": "ddc97932-2e16-48d1-84b0-3815961ddf1f", + "last_modified": 1691204223383 + }, + { + "schema": 1690296478698, + "derHash": "oRXsDXPC6KuxiDE0+i3w2YXnQYgWBKQIKQfXBeJAfHI=", + "subject": "CN=e-Szigno Qualified TLS CA 2023,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHmUtU3ppZ25vIFF1YWxpZmllZCBUTFMgQ0EgMjAyMw==", + "whitelist": false, + "attachment": { + "hash": "e5582687d546ac85a09c343f23ce9764cbc06e78844654f61940dde5034b145f", + "size": 1439, + "filename": "FfstIBJRQL_OSddFhkXVXxYXvlwpeV4N5QyzQSOMer4=.pem", + "location": "security-state-staging/intermediates/c3eaa351-c45f-4c58-b312-1a214832f8b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FfstIBJRQL/OSddFhkXVXxYXvlwpeV4N5QyzQSOMer4=", + "crlite_enrolled": false, + "id": "da49e20c-b2f2-4d31-b07b-1064e96e3b70", + "last_modified": 1690297023211 + }, + { + "schema": 1690296478406, + "derHash": "wEww5A3X6WmC+GBuvvNVSOXG9PeSpSpReM8koOn9c5Y=", + "subject": "CN=e-Szigno DV TLS CA 2023,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHMxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxIDAeBgNVBAMMF2UtU3ppZ25vIERWIFRMUyBDQSAyMDIz", + "whitelist": false, + "attachment": { + "hash": "e024d6f71f79e22a937ce5712dfbf1c17be250937f7a1a1b7aaa69d96607e27b", + "size": 1431, + "filename": "XxwXFeAhoV94cB2wpw1cfCsPS8BPvJevCyCobm4QKxg=.pem", + "location": "security-state-staging/intermediates/6e6e1fde-6155-4773-b392-6ee7800fcbdc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XxwXFeAhoV94cB2wpw1cfCsPS8BPvJevCyCobm4QKxg=", + "crlite_enrolled": false, + "id": "0d228708-baa8-45e3-a4ea-0bce48f43f86", + "last_modified": 1690297023208 + }, + { + "schema": 1690296478088, + "derHash": "EtRTenVH/2PDaSNiKiga/+lIESDbeBd2qvmBofm2aNg=", + "subject": "CN=e-Szigno OV TLS CA 2023,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHMxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxIDAeBgNVBAMMF2UtU3ppZ25vIE9WIFRMUyBDQSAyMDIz", + "whitelist": false, + "attachment": { + "hash": "ab5bf3cb8d3a60923137ac8e379030e9e4208f811155f11e65307099425c5d04", + "size": 1431, + "filename": "CBpKKUYnWuYNjyn6A4C6-fbIOhB5kbX1rkHpBJ-7g0Y=.pem", + "location": "security-state-staging/intermediates/0052bd62-f25b-4c75-b5e2-123cf02b80fe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CBpKKUYnWuYNjyn6A4C6+fbIOhB5kbX1rkHpBJ+7g0Y=", + "crlite_enrolled": false, + "id": "a206224b-c943-44b0-b74e-3c4bfb484c15", + "last_modified": 1690297023205 + }, + { + "schema": 1689929284314, + "derHash": "4O5LQh2QWSJPDNOte8kFCx3teHGKStJtjeP7QCEPH6E=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "8ad44e3b2a107c4708f63fa28c341b96ed2b6a01cd16c3ced0aee2858288515b", + "size": 1642, + "filename": "OB-rJj9cwenrStgWOxc0Kf8noe4X1ba9o2XQqsoa5jk=.pem", + "location": "security-state-staging/intermediates/6cea8a9d-7dc4-46ef-b07f-89fb4c77d86a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OB+rJj9cwenrStgWOxc0Kf8noe4X1ba9o2XQqsoa5jk=", + "crlite_enrolled": false, + "id": "f77f9ad4-d759-4cbd-920d-992d5b6cf0e8", + "last_modified": 1689929823491 + }, + { + "schema": 1689929282855, + "derHash": "5G+yp1CXo0XUJG3PRKENqnHZ/Q6/q2G6Z+bbhO5bbKs=", + "subject": "CN=GlobalSign GCC R6 AlphaSSL CA 2023,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFUxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSswKQYDVQQDEyJHbG9iYWxTaWduIEdDQyBSNiBBbHBoYVNTTCBDQSAyMDIz", + "whitelist": false, + "attachment": { + "hash": "57aa6c5c2a0c03f792955ce3cbac5583b5f8e93563fb617b3893efa641b68067", + "size": 1983, + "filename": "JdFERRONSeokpPRwHKoZgZPPGO-7YwoMHGHoe1BAq3c=.pem", + "location": "security-state-staging/intermediates/8ac97138-60ba-4e94-9568-ab722c6b3e5a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JdFERRONSeokpPRwHKoZgZPPGO+7YwoMHGHoe1BAq3c=", + "crlite_enrolled": false, + "id": "5fb08fd5-f7ee-41ca-8644-f594733c1895", + "last_modified": 1689929823489 + }, + { + "schema": 1689929282560, + "derHash": "uMVBjEPynSjM7mzCeVzqhNCyKUmxYll2ZveJMSbDzPU=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "1ccf59e6615d165b2170631b72253a798e6146661b5358e41cad12d64fd6e7a3", + "size": 1642, + "filename": "Wy0iIjIgLwqOCDhp9KhOtrT36mRLAjRtIhKru02-58Y=.pem", + "location": "security-state-staging/intermediates/fe842f07-42c4-4bfb-a0f5-71c9c698b366.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wy0iIjIgLwqOCDhp9KhOtrT36mRLAjRtIhKru02+58Y=", + "crlite_enrolled": false, + "id": "44ccbf06-3435-4f16-aaa3-b95248156eaa", + "last_modified": 1689929823486 + }, + { + "schema": 1689929282136, + "derHash": "Z6n3GigsStB1hsope5FJJsLh0zHF9kpGr719Y3jYmGg=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIzIFE0", + "whitelist": false, + "attachment": { + "hash": "17f521d76b4ad43d738b4b2bf4e955d1e914c141b237d0478f9e40efd8dca441", + "size": 1642, + "filename": "8y6mbhF5B8zzV4nMYCIe0Ql-uu5fT77-Au4Wf7VCOhA=.pem", + "location": "security-state-staging/intermediates/912f1794-1855-42ae-89fd-fdbcf439b265.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8y6mbhF5B8zzV4nMYCIe0Ql+uu5fT77+Au4Wf7VCOhA=", + "crlite_enrolled": false, + "id": "a056713a-fd76-406e-807e-8b7470ae43dd", + "last_modified": 1689929823483 + }, + { + "schema": 1689929280659, + "derHash": "uzK5BEFj1znoIjOuvH7zojVlt0gkLv4JcwmHSCh9nnY=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIzIFE0", + "whitelist": false, + "attachment": { + "hash": "6f88b74bde982f331b5e99aed97cff1caf4a2fa3a61df425417f9b5940a2045d", + "size": 1642, + "filename": "7d8PMwb2-WiKH-yPgprnp0lC28nA19XzSAWpGeJO254=.pem", + "location": "security-state-staging/intermediates/b2dfbd08-5fdf-45f7-815f-038138005eb7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7d8PMwb2+WiKH+yPgprnp0lC28nA19XzSAWpGeJO254=", + "crlite_enrolled": false, + "id": "61168bfc-8a2a-41b3-a899-998bb20267b3", + "last_modified": 1689929823481 + }, + { + "schema": 1689929281280, + "derHash": "yQXK/e0ZurV0Lzoy9sPN76TklWSjSOqEvi0DNhCBbsY=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "182f60a8831ce67d003b467607983ca3340ee80435a2a79087ed8f31e79942d2", + "size": 1199, + "filename": "WYAZh0EyGyBR7RhWjRK4md0Y-vp6X9snRCihQFSpyzI=.pem", + "location": "security-state-staging/intermediates/4d70acd8-7983-41ee-a91b-4c61613353fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WYAZh0EyGyBR7RhWjRK4md0Y+vp6X9snRCihQFSpyzI=", + "crlite_enrolled": false, + "id": "5fbce1b6-d222-468d-aaf0-12c492a56f55", + "last_modified": 1689929823478 + }, + { + "schema": 1689929280952, + "derHash": "P29X8f4ls9bwiPF3BPfjdq46kRUdzY4ZF8s6QXhbXw0=", + "subject": "CN=GlobalSign GCC R6 OV TLS CA 2023,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIEdDQyBSNiBPViBUTFMgQ0EgMjAyMw==", + "whitelist": false, + "attachment": { + "hash": "4bf721a0f15418f10880a0cf8ad328d2fb0367d1e1d37d0fa3174fe6dff043ec", + "size": 2328, + "filename": "Ngc8xKZRNBJ3tlPY8J83MIz187H94ti1R-xGzVtoRa8=.pem", + "location": "security-state-staging/intermediates/2b5741a2-2c92-45ba-92cf-07ce7ce0465b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ngc8xKZRNBJ3tlPY8J83MIz187H94ti1R+xGzVtoRa8=", + "crlite_enrolled": false, + "id": "709b3169-b97e-4d71-9fa2-836095db24eb", + "last_modified": 1689929823475 + }, + { + "schema": 1689929281847, + "derHash": "uX4QN+TNXAaScWAK/R/zRe6uG8LAI6X7qLFjJg/KXas=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "93fd5c1516af4e6774d20b252b76dee9908b24eba9e0e9cd2b35ba384699a7d4", + "size": 2345, + "filename": "uX3oDJ-7Djd7-44Ak5qYUVf02wuentlL6GZ1aw1YRYY=.pem", + "location": "security-state-staging/intermediates/f4c12995-d0ce-4838-976a-f97e989e87df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uX3oDJ+7Djd7+44Ak5qYUVf02wuentlL6GZ1aw1YRYY=", + "crlite_enrolled": false, + "id": "9e30af5c-6339-41dc-89bf-4fcd2f6b8d0b", + "last_modified": 1689929823473 + }, + { + "schema": 1689929284026, + "derHash": "14bSKnvWHQamhYY3bcEiYGNZL02GSsTnva5Ar7YiON0=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "77eaa752cf1c30582351fb7243578535da488502544c57e92e3b3f8143dd5dd9", + "size": 1199, + "filename": "cSKPSXoZl1cGh06RPXxurEKjGQq_K_dKQCAlMuCG4l0=.pem", + "location": "security-state-staging/intermediates/d750c111-7d23-416e-8068-905f41c7e342.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cSKPSXoZl1cGh06RPXxurEKjGQq/K/dKQCAlMuCG4l0=", + "crlite_enrolled": false, + "id": "d0ffc315-86cd-4808-be81-0b566d69392c", + "last_modified": 1689929823470 + }, + { + "schema": 1689929283421, + "derHash": "rU9O/t/oE3j9IW4WjuHraXK5zreWMD6Uv3bFr2NQvaM=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjMgUTQ=", + "whitelist": false, + "attachment": { + "hash": "95d4a021b1dcf98e8985e718cc9222e5ece6da54488091129f5ddf81edd7cfb9", + "size": 1642, + "filename": "Ue5JDkm1vKkgoe0owofi-323lQ57ZpDZ6Vl5Vcp4gw0=.pem", + "location": "security-state-staging/intermediates/a0cee078-bdda-427e-849d-e836836d9d2b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ue5JDkm1vKkgoe0owofi+323lQ57ZpDZ6Vl5Vcp4gw0=", + "crlite_enrolled": false, + "id": "73a2be3c-bc63-47d2-91ee-1feaafae1c5a", + "last_modified": 1689929823468 + }, + { + "schema": 1689929283140, + "derHash": "Hf/CrVtCPcxxXf5UqhF/JSWXTZVx/qgoGSMoB/St1O4=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMyBRNA==", + "whitelist": false, + "attachment": { + "hash": "e2da782c3fe2f0d5cb0a7d4b222c05af6dae3b3999e37e146324bb7e601bbf1b", + "size": 1195, + "filename": "iTrzCOTGjFPpfCYhj9X_Gett3pj5oPU6MHyfI5aK50M=.pem", + "location": "security-state-staging/intermediates/20a670fd-fde7-4163-acfe-e8a9c3f78736.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iTrzCOTGjFPpfCYhj9X/Gett3pj5oPU6MHyfI5aK50M=", + "crlite_enrolled": false, + "id": "64b33064-84fc-47ef-a270-93f8591bf116", + "last_modified": 1689929823465 + }, + { + "schema": 1689929283712, + "derHash": "+79Qcw6DenQ2CFySSDVQ/WXYXg5N/xIfd88PO3xAvUw=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIzIFE0", + "whitelist": false, + "attachment": { + "hash": "aa07e6585366939a0ee664715f610693fcebe885770e531776bfd3566cd643e6", + "size": 1195, + "filename": "GZFNSPIPKfNKrHiSXuWQzZ75qWd0SAHZLJ8h-n_FnlE=.pem", + "location": "security-state-staging/intermediates/b3ec6318-865f-4ab8-88c0-3a6eb9a5455f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GZFNSPIPKfNKrHiSXuWQzZ75qWd0SAHZLJ8h+n/FnlE=", + "crlite_enrolled": false, + "id": "322e6805-9106-4830-bbad-cdf4f7ff4c76", + "last_modified": 1689929823463 + }, + { + "schema": 1689929280374, + "derHash": "xayC1W89q7x9J5D/vUzWvgdXLOR63kVtsw/EjmA1LP0=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIzIFE0", + "whitelist": false, + "attachment": { + "hash": "c9a0fb7f0f68f8decb18ed5ea9d6ddfea81b562ffb01c0c7a465fd5e515111e0", + "size": 1199, + "filename": "Fy52G4lIb3umo5xiR8oga5RUXjqel5Lry4PvOdA58_c=.pem", + "location": "security-state-staging/intermediates/d992cc23-cd05-4cb8-8a23-c31049e5065a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Fy52G4lIb3umo5xiR8oga5RUXjqel5Lry4PvOdA58/c=", + "crlite_enrolled": false, + "id": "819e7f68-f9de-405e-bad6-8e6b23829b7b", + "last_modified": 1689929823460 + }, + { + "schema": 1689929281565, + "derHash": "SVkwa66MVI0xnRhVw0ST960Aubr4JL39lv8sPDK9ruo=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2023 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIzIFE0", + "whitelist": false, + "attachment": { + "hash": "22e73a22ef29b81de399719608ae4ad95852e691353a2b08dff7afb76d2c4346", + "size": 2349, + "filename": "NLdLGwnautRQRndsSeA-ENZvLXuVuwlCVRZvl5J69bo=.pem", + "location": "security-state-staging/intermediates/561aeab2-e7d3-4ade-ac89-74e25a7f9546.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NLdLGwnautRQRndsSeA+ENZvLXuVuwlCVRZvl5J69bo=", + "crlite_enrolled": false, + "id": "3a6faa8b-f53e-4ed7-a4fa-dcc6ea82aa56", + "last_modified": 1689929823457 + }, + { + "schema": 1689778080731, + "derHash": "NIXD3+mMXVQoZTR5u5BidYDuiSiQpi2g2avOOiAC6OQ=", + "subject": "CN=HARICA Qualified Web Authentication Certificates RSA,OU=Hellenic Academic and Research Institutions CA,O=Greek Universities Network (GUnet),C=GR", + "subjectDN": "MIHMMQswCQYDVQQGEwJHUjErMCkGA1UECgwiR3JlZWsgVW5pdmVyc2l0aWVzIE5ldHdvcmsgKEdVbmV0KTEYMBYGA1UEYQwPVkFUR1ItMDk5MDI4MjIwMTcwNQYDVQQLDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMT0wOwYDVQQDDDRIQVJJQ0EgUXVhbGlmaWVkIFdlYiBBdXRoZW50aWNhdGlvbiBDZXJ0aWZpY2F0ZXMgUlNB", + "whitelist": false, + "attachment": { + "hash": "71b428ec08883e3b36d19bde0bd55f5e6ed630fde1374b4a314e2c59b2beea94", + "size": 2536, + "filename": "kGxKeLInUl4Q9pvlALRV0c2jCDMtv5OZYa_lU99H6bo=.pem", + "location": "security-state-staging/intermediates/4b3fd075-b2f9-4f70-8d2c-a5227eb9ad5f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kGxKeLInUl4Q9pvlALRV0c2jCDMtv5OZYa/lU99H6bo=", + "crlite_enrolled": false, + "id": "ee77bf83-a860-409b-8ad9-fcaec423630a", + "last_modified": 1689778623464 + }, + { + "schema": 1689778081208, + "derHash": "J0F6Cfp0ELkZihsGRc3+woB5716BQ68r22l3MQRQJ84=", + "subject": "CN=HARICA Qualified Web Authentication Certificates ECC,OU=Hellenic Academic and Research Institutions CA,O=Greek Universities Network (GUnet),C=GR", + "subjectDN": "MIHMMQswCQYDVQQGEwJHUjErMCkGA1UECgwiR3JlZWsgVW5pdmVyc2l0aWVzIE5ldHdvcmsgKEdVbmV0KTEYMBYGA1UEYQwPVkFUR1ItMDk5MDI4MjIwMTcwNQYDVQQLDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMT0wOwYDVQQDDDRIQVJJQ0EgUXVhbGlmaWVkIFdlYiBBdXRoZW50aWNhdGlvbiBDZXJ0aWZpY2F0ZXMgRUND", + "whitelist": false, + "attachment": { + "hash": "de7ccd366b8a725ed931973a31321e2c69c977923e50579fbfce98196b48cc09", + "size": 1390, + "filename": "IiSR2mjIu_mybjYwsW8uyrO-e9dRjlyeV1EtKlNv1pA=.pem", + "location": "security-state-staging/intermediates/66dc257b-983f-42e3-b77c-c9c327f2c3b8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IiSR2mjIu/mybjYwsW8uyrO+e9dRjlyeV1EtKlNv1pA=", + "crlite_enrolled": false, + "id": "e35b1761-7442-4ac3-9a1a-68be549a244d", + "last_modified": 1689778623461 + }, + { + "schema": 1689691684212, + "derHash": "PAfX78jUWPZowQ1PBvkFA8zSXVnis/HVizKITZ5OOAk=", + "subject": "CN=Xinnet OV SSL,O=北京新网数码信息技术有限公司,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzmlrDnvZHmlbDnoIHkv6Hmga/mioDmnK/mnInpmZDlhazlj7gxFjAUBgNVBAMMDVhpbm5ldCBPViBTU0w=", + "whitelist": false, + "attachment": { + "hash": "5bc56588f2272b5e5fa493aa9f327168ddb2baa263b7064ea695ea8caaa8ba47", + "size": 2040, + "filename": "2KdOGXnlcULkcqXULv7vV5PeeErPEe6FeJY3L8Drx2w=.pem", + "location": "security-state-staging/intermediates/fd1e88e9-bb71-447d-9001-1717eec2f1a7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2KdOGXnlcULkcqXULv7vV5PeeErPEe6FeJY3L8Drx2w=", + "crlite_enrolled": false, + "id": "6744d4b9-1635-47c6-be96-76a90b26fbe6", + "last_modified": 1689692223153 + }, + { + "schema": 1689691683738, + "derHash": "nFOQL5UB9tiXZpmdvirRoUNkILZSU1zcLcUcz+L/7mg=", + "subject": "CN=Xinnet DV SSL,O=北京新网数码信息技术有限公司,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzmlrDnvZHmlbDnoIHkv6Hmga/mioDmnK/mnInpmZDlhazlj7gxFjAUBgNVBAMMDVhpbm5ldCBEViBTU0w=", + "whitelist": false, + "attachment": { + "hash": "d04017e5c9db416bd66646d549499c895e65f2db3a17c410dc2a3b2f403bf6be", + "size": 2040, + "filename": "WAY4atziUUIgmtn8Iz3QwpKNcfxhcE26-YjX14s13IA=.pem", + "location": "security-state-staging/intermediates/7b2e5ed3-4e3d-4288-93b5-5ed13256c768.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WAY4atziUUIgmtn8Iz3QwpKNcfxhcE26+YjX14s13IA=", + "crlite_enrolled": false, + "id": "b612a1b8-6da3-4822-8ecf-f6ae154d1d85", + "last_modified": 1689692223150 + }, + { + "schema": 1689216551728, + "derHash": "BI45u7axXvg1JfFjGSzqDfIdP/q6+rfGOQn7FVPuSWY=", + "subject": "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "whitelist": false, + "attachment": { + "hash": "848ebe136181541e5beff9a01c3d3daf731035aa92fea51a5187e18a79301512", + "size": 1943, + "filename": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=.pem", + "location": "security-state-staging/intermediates/3ca9a022-a475-4980-bbd9-c30239c7dceb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=", + "crlite_enrolled": false, + "id": "378b3536-a4c0-47ee-9864-eee566deb701", + "last_modified": 1689217066476 + }, + { + "schema": 1687877276036, + "derHash": "wKn7Ql0O3rxyvGxHrT06K2gkXtHVmliDvxnOn4xN7R8=", + "subject": "CN=Actalis DV Server ACME CA G1,O=Actalis S.p.A.,L=Ponte San Pietro,ST=Bergamo,C=IT", + "subjectDN": "MHoxCzAJBgNVBAYTAklUMRAwDgYDVQQIDAdCZXJnYW1vMRkwFwYDVQQHDBBQb250ZSBTYW4gUGlldHJvMRcwFQYDVQQKDA5BY3RhbGlzIFMucC5BLjElMCMGA1UEAwwcQWN0YWxpcyBEViBTZXJ2ZXIgQUNNRSBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "de56d1fe035c597187e0333871ac0a512392b087362ef7c5a411e358dc269822", + "size": 2495, + "filename": "V0cn4PMpbP5Kgy3YAtRYNPab9sGk1bTKtSfb8c3y94Y=.pem", + "location": "security-state-staging/intermediates/655eb17a-d709-45c1-819e-c6e6e2f4a1db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "V0cn4PMpbP5Kgy3YAtRYNPab9sGk1bTKtSfb8c3y94Y=", + "crlite_enrolled": false, + "id": "77e25342-e454-42ca-9d31-b555cf0068e0", + "last_modified": 1687877823463 + }, + { + "schema": 1687816081823, + "derHash": "U1XE+MbMMwsNgT1hlM8gaJXubzl3rAyZ5QkmBIjm1cg=", + "subject": "CN=LATSSL TLS Issuing RSA CA 1,O=LATSSL (Intradigital Soluciones Tecnologicas S. de R.L. de C.V),C=MX", + "subjectDN": "MH0xCzAJBgNVBAYTAk1YMUgwRgYDVQQKDD9MQVRTU0wgKEludHJhZGlnaXRhbCBTb2x1Y2lvbmVzIFRlY25vbG9naWNhcyBTLiBkZSBSLkwuIGRlIEMuVikxJDAiBgNVBAMMG0xBVFNTTCBUTFMgSXNzdWluZyBSU0EgQ0EgMQ==", + "whitelist": false, + "attachment": { + "hash": "a4cbd18d02c7966e268c1a4aea091c019b944a8605ec8f3e8f6a4526443460c4", + "size": 2328, + "filename": "puwKaFX3k90WcKWRSDiurWSEa9Hgs8O-94WEe_gawXY=.pem", + "location": "security-state-staging/intermediates/3b2e9ab4-d2e4-447c-a89b-fa73725d6eca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "puwKaFX3k90WcKWRSDiurWSEa9Hgs8O+94WEe/gawXY=", + "crlite_enrolled": false, + "id": "57ec0c70-a903-43ab-8a82-faee4c808be8", + "last_modified": 1687816623247 + }, + { + "schema": 1687816081505, + "derHash": "fxrXOHjg6nz9cFZ1xN2miMsl4SL2yFZP3fwUvb8EGDc=", + "subject": "CN=LATSSL EV TLS Issuing RSA CA 1,O=LATSSL (Intradigital Soluciones Tecnologicas S. de R.L. de C.V),C=MX", + "subjectDN": "MIGAMQswCQYDVQQGEwJNWDFIMEYGA1UECgw/TEFUU1NMIChJbnRyYWRpZ2l0YWwgU29sdWNpb25lcyBUZWNub2xvZ2ljYXMgUy4gZGUgUi5MLiBkZSBDLlYpMScwJQYDVQQDDB5MQVRTU0wgRVYgVExTIElzc3VpbmcgUlNBIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "9d0d9016e5744a3c0ac5fbf9c233299993b44f712424d692d3274df4c68dca3a", + "size": 2393, + "filename": "qf37enNi1OcQ1LDIDTLDgFICNwKh-fDECCAXU1KQbYU=.pem", + "location": "security-state-staging/intermediates/800aa953-54b2-4661-9185-868b7b9e345c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qf37enNi1OcQ1LDIDTLDgFICNwKh+fDECCAXU1KQbYU=", + "crlite_enrolled": false, + "id": "0c16b2ff-27ba-41f9-b609-cf6610888cc3", + "last_modified": 1687816623245 + }, + { + "schema": 1687384083870, + "derHash": "gvvoZdoi0fJa35S72AnT9RYSWEnnktt7sYRSMEwuzEM=", + "subject": "CN=Telekom Security EV RSA CA 23A,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF8xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJzAlBgNVBAMMHlRlbGVrb20gU2VjdXJpdHkgRVYgUlNBIENBIDIzQQ==", + "whitelist": false, + "attachment": { + "hash": "911f1771233497d7e316f4de00c0b5fdfc5e406d6efff4931b076aa9ca9696d3", + "size": 2125, + "filename": "76nkSZ_eTsD3NwWyC3ofmPuKCIU6w4MnAswQvOB8VKk=.pem", + "location": "security-state-staging/intermediates/4c7ea109-2e2a-44b0-9151-bd8ca790158b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "76nkSZ/eTsD3NwWyC3ofmPuKCIU6w4MnAswQvOB8VKk=", + "crlite_enrolled": false, + "id": "ba41b035-e588-4471-8a4b-9889d50b7682", + "last_modified": 1687384623199 + }, + { + "schema": 1686865674359, + "derHash": "V+CzNQTbr28poJXLiil6Sr7BIc4RZA76/BdZQJj2yqk=", + "subject": "CN=GeoTrust Global G3 TLS EUR ECC P384 SHA384 2023 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MG4xCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxPDA6BgNVBAMTM0dlb1RydXN0IEdsb2JhbCBHMyBUTFMgRVVSIEVDQyBQMzg0IFNIQTM4NCAyMDIzIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "78fc77d32d467cd24f9d8717785abbd462ce13fc39da01fcf054dec153d767b4", + "size": 1293, + "filename": "OARxnIfvxPIaORCkZB98Bydt_L4YbRHQaE4Ydz2AEwY=.pem", + "location": "security-state-staging/intermediates/5da833d4-65e3-4e3b-ae96-d00b6fc7aa60.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OARxnIfvxPIaORCkZB98Bydt/L4YbRHQaE4Ydz2AEwY=", + "crlite_enrolled": false, + "id": "525736bb-757a-4f47-962f-9346ce93913a", + "last_modified": 1686866223473 + }, + { + "schema": 1686865674778, + "derHash": "nn4xAwPtpob8xfVfdQaJWxX/BceA7ureq50BbbZy87o=", + "subject": "CN=GeoTrust Global G2 TLS EUR RSA4096 SHA384 2023 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MG0xCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxOzA5BgNVBAMTMkdlb1RydXN0IEdsb2JhbCBHMiBUTFMgRVVSIFJTQTQwOTYgU0hBMzg0IDIwMjMgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "caed1499c39787f250f8b35dfb3002a9c729eeebac8a56f3a2e9c16b972df6d7", + "size": 2093, + "filename": "t0Ep5M3U_19atoKfvx6RONUWKeAJxyn66OnfjuOeKl4=.pem", + "location": "security-state-staging/intermediates/15c14052-c343-49a2-a5e8-430b2ac2ca0b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t0Ep5M3U/19atoKfvx6RONUWKeAJxyn66OnfjuOeKl4=", + "crlite_enrolled": false, + "id": "6cf0041d-ca89-4b2c-88f6-36603395ff46", + "last_modified": 1686866223470 + }, + { + "schema": 1686304095720, + "derHash": "iarednt7pD+N3o6edKL8u+pA1XFV9+HyJZyIg1YB+u0=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 08,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDg=", + "whitelist": false, + "attachment": { + "hash": "590f39638eff5862ed7c54d9e5001224aba068979d894bcbe50ed2ba65f680e5", + "size": 1223, + "filename": "-9zTtVvIQc70tw3yG1y6divWnxbdAVfzbgea1f2WOtM=.pem", + "location": "security-state-staging/intermediates/be6b0f06-3178-4afa-92ae-8aaf0ea61109.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+9zTtVvIQc70tw3yG1y6divWnxbdAVfzbgea1f2WOtM=", + "crlite_enrolled": false, + "id": "916d5a07-136f-449e-92db-0a36b3bc3145", + "last_modified": 1686304623481 + }, + { + "schema": 1686304094777, + "derHash": "u9JxOcUwLGPZA/Vw8XOtTcBsl0uevikskP/Mq11vpU4=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 03,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDM=", + "whitelist": false, + "attachment": { + "hash": "1214f44b0866be82a3235d875298d7a99e6e70ce4823f87e84a4cbc050fca631", + "size": 1228, + "filename": "Z6NGNGtJqKusRdpHxcqa3PDNHEN8vStoX-WLg9xt4t4=.pem", + "location": "security-state-staging/intermediates/ef0510fe-2e21-476a-b5e0-60cd8bea4f43.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z6NGNGtJqKusRdpHxcqa3PDNHEN8vStoX+WLg9xt4t4=", + "crlite_enrolled": false, + "id": "85fe2fc0-1210-4a88-a7bf-c72e13dcea55", + "last_modified": 1686304623478 + }, + { + "schema": 1686304095070, + "derHash": "ckJHeUlRyT8+QXEWF+lc4UMmPjGWw0Wh2nj2Y5dJ7AM=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 07,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDc=", + "whitelist": false, + "attachment": { + "hash": "2525435a81e90f4d8476447a77f6d57b1e6ab865699460b7e30bac276b5f3368", + "size": 2028, + "filename": "Mfmoi2wKbxJCpI54JB7B-PPNkO8dRO51Bpbp-Gu4aFg=.pem", + "location": "security-state-staging/intermediates/9245329e-39ef-4780-b73f-ace0c160a550.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Mfmoi2wKbxJCpI54JB7B+PPNkO8dRO51Bpbp+Gu4aFg=", + "crlite_enrolled": false, + "id": "5111b189-cdbc-4f73-82f3-8a36a213433c", + "last_modified": 1686304623475 + }, + { + "schema": 1686304095377, + "derHash": "nRvF0t11v4tk815/kZ4lRsIlvoiMGoy+gsDpV5I0p+0=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 03,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDM=", + "whitelist": false, + "attachment": { + "hash": "d58791280901a97daa9ffff22f1bf37cb2d67263adb55af56adb6a0b7fb9b96e", + "size": 2028, + "filename": "ZkWBotC4nL-Ba_kXaVPx7TpoRSF9uwxEAuufz67J7sQ=.pem", + "location": "security-state-staging/intermediates/2ed04a92-1bb8-4065-9dc9-a830ba86c64b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZkWBotC4nL+Ba/kXaVPx7TpoRSF9uwxEAuufz67J7sQ=", + "crlite_enrolled": false, + "id": "6103ff73-bc2b-4d81-be8e-7f749a965e19", + "last_modified": 1686304623472 + }, + { + "schema": 1686304093822, + "derHash": "ejrk8Skg1agSm+EYP77ENw7xC4s61B6uSljVOFqpTTM=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 04,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDQ=", + "whitelist": false, + "attachment": { + "hash": "6c58af37d5879a05821fdd13b27fe1d039ddb8c51337f0ebc9016e69136961ff", + "size": 1223, + "filename": "EYyBlNxqiU35_S5DF_T1fpKZju6M94UCcLjRQ471fBY=.pem", + "location": "security-state-staging/intermediates/b260c80a-7dbc-4d40-891d-960244eb12a0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EYyBlNxqiU35/S5DF/T1fpKZju6M94UCcLjRQ471fBY=", + "crlite_enrolled": false, + "id": "e691a59a-6a63-4c31-a7ce-dd60add22cc9", + "last_modified": 1686304623470 + }, + { + "schema": 1686304094148, + "derHash": "viNBSkLnSIbnxyqGG6Ld2gF17YKSI9iUxdJyZR/AwYk=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 07,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDc=", + "whitelist": false, + "attachment": { + "hash": "61779bd43be72ef37cd407ade77e9490cc2c28a112f706ac442b9050362d34cc", + "size": 1228, + "filename": "VObRTDLlT_F-gUSuQacH62jRPzch1yNU6oGxRzgsyck=.pem", + "location": "security-state-staging/intermediates/bfe9c54c-7137-4211-acb3-64f772f4e354.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VObRTDLlT/F+gUSuQacH62jRPzch1yNU6oGxRzgsyck=", + "crlite_enrolled": false, + "id": "28ed4e80-5663-4a85-bc7b-7686ce53d127", + "last_modified": 1686304623467 + }, + { + "schema": 1686304094454, + "derHash": "M/lzG+kQpm3GrNB9nZyiEu6NCppceMi/Pom7dN+PuTY=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 04,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDQ=", + "whitelist": false, + "attachment": { + "hash": "ee6b096e81fae20b1080cd53c5530ada07582ad239f17d4a290c11baa670ebc7", + "size": 2028, + "filename": "E--ujdrbZMGIJ0eA9QXCCHs5D8ABeIzQ2mAqHPFN-gs=.pem", + "location": "security-state-staging/intermediates/37713e42-e61a-42d2-9d30-fe1a54f1b13f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "E++ujdrbZMGIJ0eA9QXCCHs5D8ABeIzQ2mAqHPFN+gs=", + "crlite_enrolled": false, + "id": "c0e81865-d3fc-4f54-be74-9590626f6821", + "last_modified": 1686304623464 + }, + { + "schema": 1686304093484, + "derHash": "URwcQct+sqEAeMMsgvF5Jbp4beRsYzkh0DjnQJ4Vpeo=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 08,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDg=", + "whitelist": false, + "attachment": { + "hash": "b2d8fd2a16d7cb07aff2f8d36716fe90e1e51766df23dc49d04930d152dced41", + "size": 2028, + "filename": "LjZtjXjXZ6xC52xYpbqo-7iCeMHHB3aNu04-PJtDyYg=.pem", + "location": "security-state-staging/intermediates/d0535e94-655d-4f27-8b45-ad5ac9f6cbe0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LjZtjXjXZ6xC52xYpbqo+7iCeMHHB3aNu04+PJtDyYg=", + "crlite_enrolled": false, + "id": "f41f9392-bf2c-41f9-9d5c-38154f217967", + "last_modified": 1686304623461 + }, + { + "schema": 1685177545543, + "derHash": "LJm5F7egaFePfvtPuOYLnLWg5zvzAODh3BEuVlTFrlI=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 08,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDg=", + "whitelist": false, + "attachment": { + "hash": "91646730acc556a2a24a0a0fe52da02b114c1b20e2979fd02f313cf2eda7e2e2", + "size": 1548, + "filename": "-9zTtVvIQc70tw3yG1y6divWnxbdAVfzbgea1f2WOtM=.pem", + "location": "security-state-staging/intermediates/279ad09c-5a6c-4694-9d25-d445c4b2d300.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+9zTtVvIQc70tw3yG1y6divWnxbdAVfzbgea1f2WOtM=", + "crlite_enrolled": false, + "id": "2ce90c9b-8702-4e9b-8bda-a8641ca98b4f", + "last_modified": 1685177823491 + }, + { + "schema": 1685177545240, + "derHash": "PT9LRA+TP/0mlWXtqeIOjfhjycvjZR07R2xbT0r1zig=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 03,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDM=", + "whitelist": false, + "attachment": { + "hash": "54a6b6063accaa1cef8092d8fcc1225da0dee42bf8798602862d1c2a7a67a60d", + "size": 2694, + "filename": "ZkWBotC4nL-Ba_kXaVPx7TpoRSF9uwxEAuufz67J7sQ=.pem", + "location": "security-state-staging/intermediates/9913da6f-e2c3-454e-8347-ec82cc9ec231.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZkWBotC4nL+Ba/kXaVPx7TpoRSF9uwxEAuufz67J7sQ=", + "crlite_enrolled": false, + "id": "a5b2128d-ca28-45c7-9649-f8a1745bb4b1", + "last_modified": 1685177823489 + }, + { + "schema": 1685177544696, + "derHash": "z90GH81M/zu54TMmTKf95Fykm3DPqpd64NxCK0MwqME=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 08,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDg=", + "whitelist": false, + "attachment": { + "hash": "46fef2b9c6e075a266f4adb2cd041b8a7dfff77af85e35d6b288a3658790f3b9", + "size": 2694, + "filename": "LjZtjXjXZ6xC52xYpbqo-7iCeMHHB3aNu04-PJtDyYg=.pem", + "location": "security-state-staging/intermediates/42b6dca2-a884-400a-86c1-b8029edc6aca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LjZtjXjXZ6xC52xYpbqo+7iCeMHHB3aNu04+PJtDyYg=", + "crlite_enrolled": false, + "id": "1c59021b-4ea7-4e2e-a4df-8d6d8e5a26ee", + "last_modified": 1685177823486 + }, + { + "schema": 1685177544409, + "derHash": "/Tn/xI8Ug1QmIWKi9V3UbcJWTPwUmTCa1T8JwQmB3Mo=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 04,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDQ=", + "whitelist": false, + "attachment": { + "hash": "975ca374184140b38ddd263c44d2912adb56b680ae0e82f74b6f5c476491b18c", + "size": 2694, + "filename": "E--ujdrbZMGIJ0eA9QXCCHs5D8ABeIzQ2mAqHPFN-gs=.pem", + "location": "security-state-staging/intermediates/33166e47-0914-4b86-925a-205d56c28869.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "E++ujdrbZMGIJ0eA9QXCCHs5D8ABeIzQ2mAqHPFN+gs=", + "crlite_enrolled": false, + "id": "c98cdd5b-c263-4578-b5b2-633ddf9bb7e7", + "last_modified": 1685177823481 + }, + { + "schema": 1685177544081, + "derHash": "+7eSakUbrfUWvlGGFKd+bjJeKYGZCHltgH9ZMg+RjuI=", + "subject": "CN=Microsoft Azure RSA TLS Issuing CA 07,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBSU0EgVExTIElzc3VpbmcgQ0EgMDc=", + "whitelist": false, + "attachment": { + "hash": "e61eb99a86811b06dc9ebfaf6aa45d93ad0e1e6f5e8c40ff86ac2782ae9329ed", + "size": 2694, + "filename": "Mfmoi2wKbxJCpI54JB7B-PPNkO8dRO51Bpbp-Gu4aFg=.pem", + "location": "security-state-staging/intermediates/88015f4e-1e50-41ef-b06f-69ce4d987a8a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Mfmoi2wKbxJCpI54JB7B+PPNkO8dRO51Bpbp+Gu4aFg=", + "crlite_enrolled": false, + "id": "602f8ff0-d290-44ea-9557-d3be1435a522", + "last_modified": 1685177823478 + }, + { + "schema": 1685155973485, + "derHash": "TQ9dojsJkgmwSOGHG0uxxLToEuP6Akm7jRngD/qekbw=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 04,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDQ=", + "whitelist": false, + "attachment": { + "hash": "7f5346bdb6f4bcfeda422b6c63a620a24cc8513c16e2843ded02c47188c618e2", + "size": 1548, + "filename": "EYyBlNxqiU35_S5DF_T1fpKZju6M94UCcLjRQ471fBY=.pem", + "location": "security-state-staging/intermediates/35d0a28f-f9e1-4414-855d-1be5a154c2ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EYyBlNxqiU35/S5DF/T1fpKZju6M94UCcLjRQ471fBY=", + "crlite_enrolled": false, + "id": "41c0f9dc-64f1-4422-b02f-089d8d66e0b3", + "last_modified": 1685156223318 + }, + { + "schema": 1685155973795, + "derHash": "vTgWQjVT7Zk/pEoC9VYkcMDPsNOwBTLjUmpKOuyHUi8=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 07,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDc=", + "whitelist": false, + "attachment": { + "hash": "a680760a267dd36c86922c2e4329f07226955be62171a80683b8ded5fc835d89", + "size": 1548, + "filename": "VObRTDLlT_F-gUSuQacH62jRPzch1yNU6oGxRzgsyck=.pem", + "location": "security-state-staging/intermediates/484b708f-9b55-42e3-b4b1-afa0eecd5726.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VObRTDLlT/F+gUSuQacH62jRPzch1yNU6oGxRzgsyck=", + "crlite_enrolled": false, + "id": "02a2e1ab-5560-486c-be00-69a4692fe69c", + "last_modified": 1685156223316 + }, + { + "schema": 1685155972855, + "derHash": "Lsmlumi2D4Hl+GYvdkV0PM4e3OBq9obHdUMfe7tpq9Q=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 03,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDM=", + "whitelist": false, + "attachment": { + "hash": "2514728cefb5b15be4f24444a41f0ac2d28c12f66be304ffe1a7816ee14bf9d4", + "size": 1548, + "filename": "Z6NGNGtJqKusRdpHxcqa3PDNHEN8vStoX-WLg9xt4t4=.pem", + "location": "security-state-staging/intermediates/0f176d85-9191-4d2c-8bd0-df7605a21da3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z6NGNGtJqKusRdpHxcqa3PDNHEN8vStoX+WLg9xt4t4=", + "crlite_enrolled": false, + "id": "2c31c9f3-2ac8-4b30-9eaf-4a767dc0a623", + "last_modified": 1685156223313 + }, + { + "schema": 1685137676528, + "derHash": "dVhaEddVHU7FB7iqnsBsBsR+QLelzt0YOzw0LsfrejQ=", + "subject": "CN=Domain The Net Technologies Ltd CA for TLS R3,O=Domain The Net Technologies Ltd,C=IL", + "subjectDN": "MG8xCzAJBgNVBAYTAklMMSgwJgYDVQQKDB9Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkMTYwNAYDVQQDDC1Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkIENBIGZvciBUTFMgUjM=", + "whitelist": false, + "attachment": { + "hash": "a230b7548d424355eea3091d0b3067c437e3c43a6072cb41b35156db574471de", + "size": 2308, + "filename": "ZEzTWkNjnCePaoNdGLycH6AiCbdlBAy0UK-QZLDbixA=.pem", + "location": "security-state-staging/intermediates/55a640d8-cd99-4d5f-be18-cb43885bd248.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZEzTWkNjnCePaoNdGLycH6AiCbdlBAy0UK+QZLDbixA=", + "crlite_enrolled": false, + "id": "416ce32f-5a4a-4fbc-a298-bcd6763d510f", + "last_modified": 1685138223258 + }, + { + "schema": 1685137676183, + "derHash": "ADIpWwucs4p3WvXsAa29LTmJIA2Lf1xzM80cBh+ILjI=", + "subject": "CN=Domain The Net Technologies Ltd CA for EV TLS R3,O=Domain The Net Technologies Ltd,C=IL", + "subjectDN": "MHIxCzAJBgNVBAYTAklMMSgwJgYDVQQKDB9Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkMTkwNwYDVQQDDDBEb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkIENBIGZvciBFViBUTFMgUjM=", + "whitelist": false, + "attachment": { + "hash": "05090c1f907741bf1a5b862f1f600374511ec6afbc902501c0db425cf3a1b963", + "size": 2373, + "filename": "0S29nbPcOwPdMAwVJXaUI5BQDk3P33opVyStTq_IY2I=.pem", + "location": "security-state-staging/intermediates/4772d4ec-0d5a-4474-8f75-fd02670d8abc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0S29nbPcOwPdMAwVJXaUI5BQDk3P33opVyStTq/IY2I=", + "crlite_enrolled": false, + "id": "b8fbe7a4-df7c-4cad-be28-acbe15b86802", + "last_modified": 1685138223255 + }, + { + "schema": 1684964877855, + "derHash": "59sAFElASPImpnPCi3JejduczUhnqFUteuhcJSO9JlU=", + "subject": "CN=纳网 ECC Extended Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHYxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxODA2BgNVBAMML+e6s+e9kSBFQ0MgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "e2e70967b158b51f6ebb9465975ef70c87a0a9609a9d9ddd7c2341695655a98b", + "size": 1284, + "filename": "VUzImrZskQ8zq13IGHYIDovNeG2p4c2S7V-A74FiJoQ=.pem", + "location": "security-state-staging/intermediates/bf49dba6-b832-4a06-89b8-c3809aa12d44.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VUzImrZskQ8zq13IGHYIDovNeG2p4c2S7V+A74FiJoQ=", + "crlite_enrolled": false, + "id": "3cd57864-be98-4256-9703-e37f9df3b04e", + "last_modified": 1684965423774 + }, + { + "schema": 1684964877536, + "derHash": "NH3x84ctFO7RTbOH5BvuvTm1zwtGjsCIBtBhCNVk9GM=", + "subject": "CN=纳网 RSA Domain Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHQxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxNjA0BgNVBAMMLee6s+e9kSBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b3a0afc057fa2821c5d6d9f4afa6cffe4da5ec6287b5c406a7f81649810dcae2", + "size": 2304, + "filename": "_6kdazY4w8jF61YK5px0MYqRlxf-uZ8ifGdnGXcoH7M=.pem", + "location": "security-state-staging/intermediates/8321c121-3e50-4524-9557-be1bf3aee59f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/6kdazY4w8jF61YK5px0MYqRlxf+uZ8ifGdnGXcoH7M=", + "crlite_enrolled": false, + "id": "e1977996-8566-4c1d-b2d1-55cab733f261", + "last_modified": 1684965423771 + }, + { + "schema": 1684964879073, + "derHash": "/nnZSRtAvePzL/y2UoI8heR4rFzmOiAn05lVun9qwjs=", + "subject": "CN=InCommon RSA IGTF Server CA 3,O=Internet2,C=US", + "subjectDN": "MEkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJbnRlcm5ldDIxJjAkBgNVBAMTHUluQ29tbW9uIFJTQSBJR1RGIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "dc30409c32d14961eb37ebd4a9cb465e3e2cfd2e243eab94bf1e97c36e93245e", + "size": 2268, + "filename": "0n5wv8lMh2y8--AjkmLotXDjTCsq_4X94TlUlSj90fo=.pem", + "location": "security-state-staging/intermediates/467b3a35-dbac-4d77-9bb9-9ed8fa280010.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0n5wv8lMh2y8++AjkmLotXDjTCsq/4X94TlUlSj90fo=", + "crlite_enrolled": false, + "id": "4a504b13-1f33-4101-beaf-45bec79f60bc", + "last_modified": 1684965423768 + }, + { + "schema": 1684964878772, + "derHash": "yss5RaLTCXpe/FT70uDuLh++pxHa1mW8f5UtqSrI1lc=", + "subject": "CN=纳网 ECC Organization Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHoxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxPDA6BgNVBAMMM+e6s+e9kSBFQ0MgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ca6ec03e4137e696772cd3f6da42519ac11bf0a9f3edff5df125e40632fa35c1", + "size": 1301, + "filename": "zXnZgoAPwPjgZnOjAfbxXWVY9Gptx_WI6N7kug6MbSE=.pem", + "location": "security-state-staging/intermediates/2d2ae0ad-9978-4ebb-82ec-c4f2594f4e68.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zXnZgoAPwPjgZnOjAfbxXWVY9Gptx/WI6N7kug6MbSE=", + "crlite_enrolled": false, + "id": "01b78d4a-7d1e-4771-ac35-4b829d70db1c", + "last_modified": 1684965423765 + }, + { + "schema": 1684964876862, + "derHash": "NxcmTWxdBJsa1Z9LV2HvyUq8MlQoXR5oEJEIQFCGxPI=", + "subject": "CN=纳网 ECC Domain Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHQxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxNjA0BgNVBAMMLee6s+e9kSBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "f53c7f18a6e86a68a11c6f7bab89513755179cc3f62806ad5cf9a774ffdd620e", + "size": 1293, + "filename": "O2mnRlU_q0zBEliHGyx4erLTBw5mfoMDZv8VW4itE4U=.pem", + "location": "security-state-staging/intermediates/932f58cc-f4c0-4d94-84c8-805d2d4895de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "O2mnRlU/q0zBEliHGyx4erLTBw5mfoMDZv8VW4itE4U=", + "crlite_enrolled": false, + "id": "c3168727-246e-4098-af73-d13bd659b60d", + "last_modified": 1684965423762 + }, + { + "schema": 1684964878171, + "derHash": "m3hknb12y14bu0OGNC5FL8h8mbxxGQXl9CkSblgAb7Y=", + "subject": "CN=纳网 RSA Extended Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHYxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxODA2BgNVBAMML+e6s+e9kSBSU0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "9dee6abfc6cde563ae3d9e6814558eddc86d12f136bfbc6f5ecc0f02f02c4307", + "size": 2296, + "filename": "eMGAhjVGwujojpRqRpVd5aaCAEjn4Q_NyGwdvcJuefc=.pem", + "location": "security-state-staging/intermediates/96bd7b14-f782-454e-819d-ee45f76e48a9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "eMGAhjVGwujojpRqRpVd5aaCAEjn4Q/NyGwdvcJuefc=", + "crlite_enrolled": false, + "id": "a5f26bbb-896d-42b5-9ce3-0175f7fd1d0c", + "last_modified": 1684965423759 + }, + { + "schema": 1684964878468, + "derHash": "HXs2NN457YCjxIPd1asjygvGusmyq6PbeqMyvA/LKlU=", + "subject": "CN=纳网 RSA Organization Validation Secure Server CA,O=厦门纳网科技股份有限公司,C=CN", + "subjectDN": "MHoxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTljqbpl6jnurPnvZHnp5HmioDogqHku73mnInpmZDlhazlj7gxPDA6BgNVBAMMM+e6s+e9kSBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "26b943b10363753fceb78009cf25d9b17cdbc7508df383d56b3eda5907af523b", + "size": 2312, + "filename": "HtvxJEbD34zPX-NkRiH3k8vGQFVm39Kx-DVWmdJnkUI=.pem", + "location": "security-state-staging/intermediates/6e46ff71-eb6a-45b0-9868-1f84e45b4efa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HtvxJEbD34zPX+NkRiH3k8vGQFVm39Kx+DVWmdJnkUI=", + "crlite_enrolled": false, + "id": "0306ad62-2908-4659-9e3b-b5639312c133", + "last_modified": 1684965423756 + }, + { + "schema": 1684702351141, + "derHash": "00pbmBqFygddtiy6xBXvZZ2VM5BAykdoaGJdSqI6mEk=", + "subject": "CN=HiPKI OV TLS CA - G1,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MFExCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEdMBsGA1UEAwwUSGlQS0kgT1YgVExTIENBIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "1f90782835d7d7fe81e6d1997c68f9805ae51d6fec679c756a1e0fcc381c8bbc", + "size": 2361, + "filename": "hWMpQ4xuO_I1s2fKIr56xHfWzraaOM5aZ0fGcwoT160=.pem", + "location": "security-state-staging/intermediates/7b957b37-100f-428a-8abe-fbd788e459da.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hWMpQ4xuO/I1s2fKIr56xHfWzraaOM5aZ0fGcwoT160=", + "crlite_enrolled": false, + "id": "14c7a35a-1bf0-4851-b69e-26a763d40c8d", + "last_modified": 1684702623265 + }, + { + "schema": 1684208871446, + "derHash": "v2TAbR1I2XegvC38kX6ODWNCvRFZBHFjbXoIBsyN8AM=", + "subject": "CN=Root Networks CA,O=Root Networks\\, LLC,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRswGQYDVQQKDBJSb290IE5ldHdvcmtzLCBMTEMxGTAXBgNVBAMMEFJvb3QgTmV0d29ya3MgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c0151985717258bcc0e50a2be4573b9396de5876dcc0cbc7c5b24e367f08dba3", + "size": 1687, + "filename": "aSd-MMsqlGu57jni9roFhyhvrMQMlaQVHgpMQZ3xAz8=.pem", + "location": "security-state-staging/intermediates/5e78b1fa-f64a-4b1f-b3dd-dde122a631c4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aSd+MMsqlGu57jni9roFhyhvrMQMlaQVHgpMQZ3xAz8=", + "crlite_enrolled": false, + "id": "9fd12cb8-6138-4e3c-9fe9-7f7cf09f79d6", + "last_modified": 1684209423740 + }, + { + "schema": 1684208871891, + "derHash": "NfjMiWEFCLwWvTMtpUOWRpjHLWeJX+tOBovqXgZoZFc=", + "subject": "CN=cyber_Folks,O=cyber_Folks S.A.,C=PL", + "subjectDN": "MD4xCzAJBgNVBAYTAlBMMRkwFwYDVQQKDBBjeWJlcl9Gb2xrcyBTLkEuMRQwEgYDVQQDDAtjeWJlcl9Gb2xrcw==", + "whitelist": false, + "attachment": { + "hash": "2c1b7dc7e000bf1a833bf34a23d0a4fa7b4a3c986ed17757b209ea41dbade460", + "size": 1703, + "filename": "kq5-Wv6p_Qk1dZ4-Cq4ScIsLfvt3RrBBE40y3Wwntkg=.pem", + "location": "security-state-staging/intermediates/faa069e8-f47c-4f98-a875-e0c3b31e05d9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kq5+Wv6p/Qk1dZ4+Cq4ScIsLfvt3RrBBE40y3Wwntkg=", + "crlite_enrolled": false, + "id": "5dc895a3-7c85-4dc4-966a-03d1b98dc2d8", + "last_modified": 1684209423738 + }, + { + "schema": 1683931970391, + "derHash": "udU/I1xfqMkusx3Pyt9JWbNKMq82rKHqEPB9ZVHDmKU=", + "subject": "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "whitelist": false, + "attachment": { + "hash": "e78e398403576ebf3e95e1feb965efad8ee75e60076714001a690c26a7873f3a", + "size": 1983, + "filename": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=.pem", + "location": "security-state-staging/intermediates/36de1839-8a27-4de6-b16b-bdecf75cf4df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=", + "crlite_enrolled": false, + "id": "d7ec40fc-6f59-4d54-980e-a2fb062c608c", + "last_modified": 1683932264447 + }, + { + "schema": 1683910139418, + "derHash": "WobLKX43z/HbAf6o4U/kxeBU+LIo6Xbkp4Ljrqcj/Bs=", + "subject": "CN=netartSSL,O=netart.com sp. z o.o.,C=PL", + "subjectDN": "MEExCzAJBgNVBAYTAlBMMR4wHAYDVQQKDBVuZXRhcnQuY29tIHNwLiB6IG8uby4xEjAQBgNVBAMMCW5ldGFydFNTTA==", + "whitelist": false, + "attachment": { + "hash": "b4b1330598e6adddef05930d5bad06ddb76b01aa1572f7b1603a56efb5bc620d", + "size": 1703, + "filename": "5aJfMT_Pu-r4SU6c4xiGnV6b8gTNvtSaSNsRcrO8l30=.pem", + "location": "security-state-staging/intermediates/a21bdc47-7a06-4bc6-bcaa-506951f37a0a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5aJfMT/Pu+r4SU6c4xiGnV6b8gTNvtSaSNsRcrO8l30=", + "crlite_enrolled": false, + "id": "6e6329ac-0c2c-4d80-87df-b7f134ff050e", + "last_modified": 1683910624095 + }, + { + "schema": 1683686879404, + "derHash": "EVoqRdtSA2GizfCjlcSkvYoYkC6qQDZ5KCX4RrvXaRc=", + "subject": "CN=BJCA EV SSL CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBFViBTU0wgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "ef6e2fdc75139a5894e7e1991dfab92762b020f37bf9b1cd5c27709a34eaca0a", + "size": 2032, + "filename": "sLkDzaX9Q8etOWTdHEANzOS3cnjp-yqD0coS9iaHRjM=.pem", + "location": "security-state-staging/intermediates/448193ac-ac7c-4a3f-a881-f91f0a4c424f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sLkDzaX9Q8etOWTdHEANzOS3cnjp+yqD0coS9iaHRjM=", + "crlite_enrolled": false, + "id": "0d85ff7b-cdd5-4f10-8831-533ba23464c4", + "last_modified": 1683687423842 + }, + { + "schema": 1683686879115, + "derHash": "L59BEU3K3DB4TkD+99buBjqb56Nj3lc36I+hEYZxUF4=", + "subject": "CN=BJCA IV SSL CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBJViBTU0wgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "0be887765dd949f54d98f598abb1933b7e5178628989923cf78985acd568b707", + "size": 1195, + "filename": "4kBf0Ckuf9X-B56JgI62Q9L5Nkj3Pk4NF5RmI71UvIE=.pem", + "location": "security-state-staging/intermediates/2c2a5067-06ef-4f7e-a569-bd57553b41d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4kBf0Ckuf9X+B56JgI62Q9L5Nkj3Pk4NF5RmI71UvIE=", + "crlite_enrolled": false, + "id": "a37caa64-097f-4b55-b097-44f7badfb88d", + "last_modified": 1683687423840 + }, + { + "schema": 1683686878274, + "derHash": "CmvD4gJKxGL11yvkNq5h0DOXjqjdtj1MXWIUkV5pBJs=", + "subject": "CN=BJCA OV SSL CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBPViBTU0wgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "37a37a29b7208fe2e18d6a5abe8df4814d6077cee5b92ef49884c42009067c2d", + "size": 2032, + "filename": "CBy_M0YoOsUoXP0Sn4rk371RA0hmwvpKTvZrj0Mc2LI=.pem", + "location": "security-state-staging/intermediates/fd9ed13a-147e-49c9-ae11-e62b80dbe726.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CBy/M0YoOsUoXP0Sn4rk371RA0hmwvpKTvZrj0Mc2LI=", + "crlite_enrolled": false, + "id": "dbd25493-c3ed-4d08-bef5-c8a9f009846e", + "last_modified": 1683687423837 + }, + { + "schema": 1683686878546, + "derHash": "5gFHdwU0EnD9EgBmu98mIj5pU8Tbj6fqGX6vW/g0OyU=", + "subject": "CN=BJCA EV SSL CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBFViBTU0wgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "1323214ca2300de22d31d3814369d58fa6ae9e62ad92d6cc7604beb534750fd5", + "size": 1195, + "filename": "izxAW1vFQ-UGDxZZLXNl_O7jFN6W3VRKExKXWDX4Ps4=.pem", + "location": "security-state-staging/intermediates/c4a02c21-2b81-4612-96ea-de52dd1333c0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "izxAW1vFQ+UGDxZZLXNl/O7jFN6W3VRKExKXWDX4Ps4=", + "crlite_enrolled": false, + "id": "17f83555-ab78-4fe7-908c-178e8b13c869", + "last_modified": 1683687423835 + }, + { + "schema": 1683686877665, + "derHash": "1wxZcAmvOjo3vfq+oMZBCMe4PNbCBC6P8Xij7o/gyug=", + "subject": "CN=BJCA IV SSL CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBJViBTU0wgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "4c482fc8f507db6f5c7d83db341cff5fc7e21125450d9bcd86f157d233d6d3e3", + "size": 2032, + "filename": "1A_YDK0Cvh0x4c9OrH6hqXq_RW1vH6qY-R_tqJ5qsog=.pem", + "location": "security-state-staging/intermediates/c63f496a-748b-420b-9347-234c8c835e67.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1A/YDK0Cvh0x4c9OrH6hqXq/RW1vH6qY+R/tqJ5qsog=", + "crlite_enrolled": false, + "id": "3ac3dfe5-e8a6-46b9-8130-d4483fef73f7", + "last_modified": 1683687423832 + }, + { + "schema": 1683686879698, + "derHash": "GdD+Zg28D6lIz0WRjkje+4OWxAJpA7wZ/k+RVS3/Tck=", + "subject": "CN=BJCA Generic CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "MFAxCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEZMBcGA1UEAwwQQkpDQSBHZW5lcmljIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "3adf773fce633198e611293a20e90991037ea4e69c8194041bda5d7113d367b4", + "size": 1991, + "filename": "q4LhFN_r1c3dNt9NyxWwOwKS1S9Dv2gJVKxNa57YdPI=.pem", + "location": "security-state-staging/intermediates/55e65fbf-a90b-4e4c-b9ce-7bd8ca55f86b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "q4LhFN/r1c3dNt9NyxWwOwKS1S9Dv2gJVKxNa57YdPI=", + "crlite_enrolled": false, + "id": "1afda35e-956c-46f3-85f2-2e84242c2017", + "last_modified": 1683687423830 + }, + { + "schema": 1683686877217, + "derHash": "P1yxUxyxIjqr+3CHLcQ9LdbMPSgj6WtFip+KfsAmWUY=", + "subject": "CN=BJCA DV SSL CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBEViBTU0wgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "0477b03fe42a51cf720c5f271dd8707de7df5cca4b939d75088a721f4054c716", + "size": 1195, + "filename": "8WeDV8VuEJrDjNNnUkEUBbECT6pr7d3VnZOwBO87fcA=.pem", + "location": "security-state-staging/intermediates/ba34206c-ebf7-4165-9dd0-c834d74a7240.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8WeDV8VuEJrDjNNnUkEUBbECT6pr7d3VnZOwBO87fcA=", + "crlite_enrolled": false, + "id": "4f059ec0-e82f-4017-974e-6f0c7a0d76d5", + "last_modified": 1683687423825 + }, + { + "schema": 1683686876910, + "derHash": "OhpL1qYkaFeNvJHcJHBbJ2qDfMGLa+8f8/btD+YyYwI=", + "subject": "CN=BJCA OV SSL CA2,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBPViBTU0wgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "8b27df18c2e866e7dfa85754cb29421b6f2e2ca998ff85edd3e912829175b711", + "size": 1195, + "filename": "tvJtJO4-3B8MoQ5C0ZwacldZCG-fJQKWXSdIedTP7wU=.pem", + "location": "security-state-staging/intermediates/070e5855-b176-4634-895a-2e70b31a0a09.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tvJtJO4+3B8MoQ5C0ZwacldZCG+fJQKWXSdIedTP7wU=", + "crlite_enrolled": false, + "id": "2188890a-7aaf-4ab5-813d-c7bb3659023d", + "last_modified": 1683687423823 + }, + { + "schema": 1683686877953, + "derHash": "tAjWyCCXEhaUubZUjFtJRFlMCBE082xb6I10+jR1nZE=", + "subject": "CN=BJCA DV SSL CA1,O=BEIJING CERTIFICATE AUTHORITY,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEYMBYGA1UEAwwPQkpDQSBEViBTU0wgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "9cb99347de0950999aa205bc399844ebfda2eed74f9e22b40e9f5d35fe2e692f", + "size": 2032, + "filename": "kuKda6ujweDL4dO-o3j75jUDxqs8YWcDK29LT2fc2yg=.pem", + "location": "security-state-staging/intermediates/46746f82-2bb6-4c4f-9930-8cc48fa32633.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kuKda6ujweDL4dO+o3j75jUDxqs8YWcDK29LT2fc2yg=", + "crlite_enrolled": false, + "id": "d35666a2-7f3f-4cf0-ae11-6f9581f65b30", + "last_modified": 1683687423820 + }, + { + "schema": 1681980513975, + "derHash": "L+Ta43D88rWPDHXyq8SUFLiB9VTs8hSF6uc/6qF7Hc0=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "e1b5719c859cdab8a474a0112ee858eb1e61037646e6cb396abb27ee8fba8715", + "size": 1642, + "filename": "Ve3lyti87rHZ-l3V1KUMRI_2aHITKUVn31T6H9mp9rE=.pem", + "location": "security-state-staging/intermediates/d40871a2-6299-47c5-aa84-8d02939633de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ve3lyti87rHZ+l3V1KUMRI/2aHITKUVn31T6H9mp9rE=", + "crlite_enrolled": false, + "id": "5b0aeab7-d877-4ec2-a63c-721cfa168c75", + "last_modified": 1681981023506 + }, + { + "schema": 1681980512041, + "derHash": "38buQuansznfF45WbvtUgqoaRgxYajLWfJqwU68r5lc=", + "subject": "CN=Alibaba Cloud GCC R3 AlphaSSL CA 2023,O=Alibaba Cloud Computing Ltd.,C=CN", + "subjectDN": "MGQxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxBbGliYWJhIENsb3VkIENvbXB1dGluZyBMdGQuMS4wLAYDVQQDEyVBbGliYWJhIENsb3VkIEdDQyBSMyBBbHBoYVNTTCBDQSAyMDIz", + "whitelist": false, + "attachment": { + "hash": "6268a2b6f41dd3a3d5badf9191690b02ecca64f3bc8a20726197820735f4b6d0", + "size": 1658, + "filename": "K1ELBBmlYY3TT6smnAcMLUz2cDFhaHu8VW8YwAf532U=.pem", + "location": "security-state-staging/intermediates/18b45473-4898-4bcf-b9b5-9ec4e349c5ef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "K1ELBBmlYY3TT6smnAcMLUz2cDFhaHu8VW8YwAf532U=", + "crlite_enrolled": false, + "id": "a7b4dc1a-14cf-4111-9682-02ef5f789021", + "last_modified": 1681981023496 + }, + { + "schema": 1681980512986, + "derHash": "czYGgPDA3ixULrPasrMHZNVFFgZQGVPHbVGi9uvRwps=", + "subject": "CN=Alibaba Cloud GCC R3 OV TLS CA 2023,O=Alibaba Cloud Computing Ltd.,C=CN", + "subjectDN": "MGIxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxBbGliYWJhIENsb3VkIENvbXB1dGluZyBMdGQuMSwwKgYDVQQDEyNBbGliYWJhIENsb3VkIEdDQyBSMyBPViBUTFMgQ0EgMjAyMw==", + "whitelist": false, + "attachment": { + "hash": "59f332a0297aef3220f57b479bbffe2b9109f4c4df0a99d1e1de017626827f7b", + "size": 1654, + "filename": "VRjaLQ7B-wtlRMwsaC3B7zF3mTX_1OVwGIVmlTjOSig=.pem", + "location": "security-state-staging/intermediates/4a4883c9-4cd1-451d-b6dc-30d24a83864e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VRjaLQ7B+wtlRMwsaC3B7zF3mTX/1OVwGIVmlTjOSig=", + "crlite_enrolled": false, + "id": "c60e5794-6ef5-4bb5-a662-8131355d39ca", + "last_modified": 1681981023486 + }, + { + "schema": 1681980509208, + "derHash": "yqs5Hkgu4Ta+dOP62eOhrFievgYLlVCFlIdik++H584=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIzIFEz", + "whitelist": false, + "attachment": { + "hash": "f1617b28b34e6ffacd50778141d8dfe9016f4398792e8c14a2df61abd2aa455e", + "size": 1642, + "filename": "q-_wkbAp7eqw63n_j5nXubRRmg1kkl4uJ2mY3n3FHa0=.pem", + "location": "security-state-staging/intermediates/4a06ca8a-5a25-4b2e-8a35-abc7a773b0e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "q+/wkbAp7eqw63n/j5nXubRRmg1kkl4uJ2mY3n3FHa0=", + "crlite_enrolled": false, + "id": "b2d3dd3e-e7f2-4503-a50f-65afa820d775", + "last_modified": 1681981023476 + }, + { + "schema": 1681980511082, + "derHash": "IzXV7fpQfrVrfawA9DTVW5Vlpvlo5M06wBusIxehCxY=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "d8d9ab70c9ebb5e6864ae0c4e423fef0751ca86b7d90edbc1517ea338cfde25b", + "size": 2345, + "filename": "yxLcFJnkRXGR-KVLCZl-CHkPbRF5y-RCN9MdEbtIONk=.pem", + "location": "security-state-staging/intermediates/02e19a30-4448-4bc7-bbdf-74e49557038f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yxLcFJnkRXGR+KVLCZl+CHkPbRF5y+RCN9MdEbtIONk=", + "crlite_enrolled": false, + "id": "0c01414e-7d79-4758-a084-a8323b20434e", + "last_modified": 1681981023466 + }, + { + "schema": 1681980510125, + "derHash": "1FRIf747LLwQwwI+c67ifz42QHJs57sDu0JEtQyy9MQ=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIzIFEz", + "whitelist": false, + "attachment": { + "hash": "d26cd5341cb8682557e082a1aee534cfc0b1ffb570c17d6560bc291d4c2f75fd", + "size": 2349, + "filename": "BCMEXqQktHWumt_p7oT22QTg5c6wtgNsno7RvlRIxNY=.pem", + "location": "security-state-staging/intermediates/55dc041c-857c-40a5-92a6-3e7da38fc6d3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BCMEXqQktHWumt/p7oT22QTg5c6wtgNsno7RvlRIxNY=", + "crlite_enrolled": false, + "id": "5e500c6c-44ae-4ada-b242-66f2f0d18d11", + "last_modified": 1681981023455 + }, + { + "schema": 1681980507254, + "derHash": "QFsei2y93xYFyC0wr1LwLYk4hzZ07KGKkqy6ziJhC94=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIzIFEz", + "whitelist": false, + "attachment": { + "hash": "0bff04fa4bd2a344c006a31b0136d7450808df8ad79f3e68074b2ee5f931b70a", + "size": 1195, + "filename": "kQVDsIkppqSaZy0YFOhBH7ArXz-RDRERKavzw_weJP4=.pem", + "location": "security-state-staging/intermediates/623a3e24-33d7-4b0f-bd18-ac77e2645b6e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kQVDsIkppqSaZy0YFOhBH7ArXz+RDRERKavzw/weJP4=", + "crlite_enrolled": false, + "id": "12c0c496-fc90-4b33-a6e0-f14dc91a9df8", + "last_modified": 1681981023445 + }, + { + "schema": 1681980505327, + "derHash": "l6/nw1N4w4aDdnahNNeh1HK3LvCR8iT5mGyCVqbXUpY=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "56b850e2b77d579b5a92f3a525476f17948c9b4d744fc1a3e9cab4bee8bebf14", + "size": 1195, + "filename": "uE-VijBf9sAK9f9J-ixUzyhG97LG1jXUB7_xspbX0HI=.pem", + "location": "security-state-staging/intermediates/febcd89f-543a-4705-ac69-2287d6c6550b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uE+VijBf9sAK9f9J+ixUzyhG97LG1jXUB7/xspbX0HI=", + "crlite_enrolled": false, + "id": "ce90f97c-60fc-43e0-b5c4-891615c697c5", + "last_modified": 1681981023435 + }, + { + "schema": 1681980506274, + "derHash": "9wnIIFGNbDhZXO+DVdvgLSQWO5+ENlK6LDEjD8WAYiA=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjMgUTM=", + "whitelist": false, + "attachment": { + "hash": "b08557e3048bb87c7b58bd83a423e39097c8b195de4dfdb4bfbfb886c9301335", + "size": 1642, + "filename": "b6DeMireWjqJis8u4iAqzA9R_m3RgX62hL8TasbQcYM=.pem", + "location": "security-state-staging/intermediates/057b2fee-ca76-47e1-a195-90bc4da49711.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b6DeMireWjqJis8u4iAqzA9R/m3RgX62hL8TasbQcYM=", + "crlite_enrolled": false, + "id": "51299d7b-7f89-421a-b3ec-6d813287372a", + "last_modified": 1681981023425 + }, + { + "schema": 1681980504411, + "derHash": "ZVejdAMJGYdIPWeJ7ZzkVJMSZ0uR9XVyOXqnaAbwCdU=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "5d7bfaf1e838e54448e2034e8270a8269f2685b93a2be1c03968ada01f72f734", + "size": 1642, + "filename": "ryuehCQIDQXNuTCk0mWODtnkXmQxZnG1DyfANmzzSjo=.pem", + "location": "security-state-staging/intermediates/a9a6eb82-b8a3-49ee-914f-5effa8811b60.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ryuehCQIDQXNuTCk0mWODtnkXmQxZnG1DyfANmzzSjo=", + "crlite_enrolled": false, + "id": "8a8af3c7-16df-4a83-81ff-1092d0a49d56", + "last_modified": 1681981023415 + }, + { + "schema": 1681980503457, + "derHash": "/CgRE33IbHmH1WZeIqxKJ0HOVLYj3FjoDOkO+GJvZ/w=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIzIFEz", + "whitelist": false, + "attachment": { + "hash": "0dbb962a4c2156af0cb5499b20a3aaeaef29b907f98a0599168dd63a0d88d8ad", + "size": 1195, + "filename": "Drs6CcFsFR__02Bl8sHuhlU7B5y6p74KhWkk96Y0fRA=.pem", + "location": "security-state-staging/intermediates/6139e7b6-151c-418e-a69f-43d72a66c327.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Drs6CcFsFR//02Bl8sHuhlU7B5y6p74KhWkk96Y0fRA=", + "crlite_enrolled": false, + "id": "af85d79b-b801-4b35-8880-953747188640", + "last_modified": 1681981023405 + }, + { + "schema": 1681980502509, + "derHash": "zaz0EuKSHDQrGUNnZDPmBIjkvTnxk6nCLBKqEbZ5ZZc=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "ba5d7250d955bf523dc908684ce179257f5cb8653d57e3d8e8c020cc04d401aa", + "size": 1199, + "filename": "yssnEIwbPEwUdOQJ6xO4F5nN25BJ-pOk6UOdQj-4jtQ=.pem", + "location": "security-state-staging/intermediates/997262b5-6dfe-4930-b7b0-0dc8ec45ce2e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yssnEIwbPEwUdOQJ6xO4F5nN25BJ+pOk6UOdQj+4jtQ=", + "crlite_enrolled": false, + "id": "895d974f-5ed4-4353-8d8c-e898887caf0b", + "last_modified": 1681981023395 + }, + { + "schema": 1681980501532, + "derHash": "OMu4cjHwYqgtftLvc/1OS4Q1LqfdfxVobeOpQUt64Ck=", + "subject": "CN=Alibaba Cloud GCC R3 DV TLS CA 2023,O=Alibaba Cloud Computing Ltd.,C=CN", + "subjectDN": "MGIxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxBbGliYWJhIENsb3VkIENvbXB1dGluZyBMdGQuMSwwKgYDVQQDEyNBbGliYWJhIENsb3VkIEdDQyBSMyBEViBUTFMgQ0EgMjAyMw==", + "whitelist": false, + "attachment": { + "hash": "53716598b91a9ecd78e2c6e8c46226571cf32d4f24d18b62a25953a60f208d91", + "size": 1654, + "filename": "Dt0LJTAS7xpXMDCnaxxwMfhDHjHWcQtjDnm7kVY_njA=.pem", + "location": "security-state-staging/intermediates/0a5d2763-d6c4-47d1-983c-19092741ed70.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Dt0LJTAS7xpXMDCnaxxwMfhDHjHWcQtjDnm7kVY/njA=", + "crlite_enrolled": false, + "id": "40ea2830-caee-40e7-829a-4b68b96ab2a4", + "last_modified": 1681981023385 + }, + { + "schema": 1681980508238, + "derHash": "TeX7IB2tHJzOpoZIwqmgPlPV85tZWdBgjnBzJvDGQyo=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIzIFEz", + "whitelist": false, + "attachment": { + "hash": "e1659007883bde145325f58be7140741a93d9a07e478560ce0ad3f188a75a415", + "size": 1642, + "filename": "FfScPkObVDnLRrFA2p_v5QNg34gJoH4A-Eacet8Akck=.pem", + "location": "security-state-staging/intermediates/a0d724f7-7f9a-448e-a916-77d21f79d8e7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FfScPkObVDnLRrFA2p/v5QNg34gJoH4A+Eacet8Akck=", + "crlite_enrolled": false, + "id": "e19fffa7-23b4-4fb6-a5a6-d8378b96a39b", + "last_modified": 1681981023375 + }, + { + "schema": 1681980500514, + "derHash": "QNRPoJomDo/NUnKTqpZfJHsx4hBPpRs20mOwR5+4KPI=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2023 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMyBRMw==", + "whitelist": false, + "attachment": { + "hash": "dc88ca4ce423e765198984f2c4f176c1486e8c451dee2471eb86c9380bad9156", + "size": 1199, + "filename": "VVcFkx148EzULQTkJ5rbQABu5ga4j_J9MKJrLSGDVLs=.pem", + "location": "security-state-staging/intermediates/3d8ecc6f-b94e-41ef-9b58-a93bb8b2dbd5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VVcFkx148EzULQTkJ5rbQABu5ga4j/J9MKJrLSGDVLs=", + "crlite_enrolled": false, + "id": "780797c6-c052-44e4-9fd1-8f856900d477", + "last_modified": 1681981023365 + }, + { + "schema": 1680511689525, + "derHash": "9vi81BPJczFm6FhDtGjdNucnFS2aN7FRKcDnZI7O5jk=", + "subject": "CN=SHECA OV Server CA G7,O=UniTrust,C=CN", + "subjectDN": "MEAxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEeMBwGA1UEAwwVU0hFQ0EgT1YgU2VydmVyIENBIEc3", + "whitelist": false, + "attachment": { + "hash": "74f8b64ebb878cae50637037ba71ed194776d06db80a0d99d9d7bb69aa72bd2c", + "size": 2003, + "filename": "Avi_QOpi59uZzfLmkrdh6vjem03c1I8chB3tK_OFL7M=.pem", + "location": "security-state-staging/intermediates/e61bfabc-0234-47bf-902a-9b8818738a56.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Avi/QOpi59uZzfLmkrdh6vjem03c1I8chB3tK/OFL7M=", + "crlite_enrolled": false, + "id": "b6aa83f8-f475-4f5c-8d7e-dcb466827c22", + "last_modified": 1680512223621 + }, + { + "schema": 1680270493771, + "derHash": "PdacW+Fw+UP4BNHTH+j5FsDAImzd166pqpoM39NHQ2E=", + "subject": "CN=UCA Global G2 Root,O=UniTrust,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "whitelist": false, + "attachment": { + "hash": "731abf0288a65056be76b95961168ac9cefd34f85f98777eeb686e5ba6c912a2", + "size": 2052, + "filename": "ElXKvoFS-mTflC96R0F-KflsHOEb-MhOy-KBXMEoCBA=.pem", + "location": "security-state-staging/intermediates/457d9552-723c-4b98-97ad-990c5a4bedd6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ElXKvoFS+mTflC96R0F+KflsHOEb+MhOy+KBXMEoCBA=", + "crlite_enrolled": false, + "id": "cafda296-6d89-431c-a92d-0bd54e535030", + "last_modified": 1680271023541 + }, + { + "schema": 1679518190665, + "derHash": "uLGLq6y2qU6r5pKMa6z09qLYQjXX+GZIa194RHO+cIw=", + "subject": "CN=CATrust ECC DV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IEVDQyBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2ab6599fd3c8df213b615d6d3adaac51b5dd5ab735e95d8920d82f75d82bafc7", + "size": 1268, + "filename": "3UyXjiYjrNvUOYfp8OLu2PTkIolV-fC0_CEKjYqPo8U=.pem", + "location": "security-state-staging/intermediates/42de34a5-6028-49ac-ad65-66dd57d6e252.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3UyXjiYjrNvUOYfp8OLu2PTkIolV+fC0/CEKjYqPo8U=", + "crlite_enrolled": false, + "id": "f446d9cb-98b5-4366-981c-641d30e22c9d", + "last_modified": 1679518623586 + }, + { + "schema": 1679518189726, + "derHash": "aZeihYStCsZhkTbivB+pxjHKMCS4KEKU4m/OM2vlQcM=", + "subject": "CN=CATrust RSA OV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IFJTQSBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c50626a2bf256c4f795c7546f7c51cc388150ec1253e5b17c5822c4e348a36d8", + "size": 2284, + "filename": "v6XOiEkxQzkn7SEb0Q2LQaZ0gtWNS8AnXawpcfDPS60=.pem", + "location": "security-state-staging/intermediates/95162acb-3cc8-4551-883d-e2e3116b7a77.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v6XOiEkxQzkn7SEb0Q2LQaZ0gtWNS8AnXawpcfDPS60=", + "crlite_enrolled": false, + "id": "6a472d8a-b1a2-4dea-a336-15de8f5e3e00", + "last_modified": 1679518623577 + }, + { + "schema": 1679518188768, + "derHash": "JuHhe1efUd4DIEUhttZzI8l7DzUemSPFqo5/Mz+atI4=", + "subject": "CN=CATrust RSA EV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IFJTQSBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "e0b60ed6235c7c1b2e6a29c91630c277016d26759c1fac2990ec71ed1f76c672", + "size": 2272, + "filename": "BtUPHcjEELm-QnFyE15TdkdlQOavUDnjajRQcWpYsl0=.pem", + "location": "security-state-staging/intermediates/27cd65f3-81c0-4ac3-a394-2ba20dda0ca1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BtUPHcjEELm+QnFyE15TdkdlQOavUDnjajRQcWpYsl0=", + "crlite_enrolled": false, + "id": "e33f5591-6c13-4223-a468-b530f7cd851e", + "last_modified": 1679518623567 + }, + { + "schema": 1679518187835, + "derHash": "iYxEvueLFjTI1YBwCjgUa59YCeswcDQivLNfQE5qEOw=", + "subject": "CN=CATrust ECC EV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IEVDQyBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "da5540304b11677882ea6ae0ec717e4b5e57af19c621241695957f1a715d3cfd", + "size": 1260, + "filename": "hIyspf1ZWyfmS7ZOEhBNhGHOU6RFe3xq6HrgMqSZ30U=.pem", + "location": "security-state-staging/intermediates/716326ca-2ca9-412a-a186-4908fab9e555.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hIyspf1ZWyfmS7ZOEhBNhGHOU6RFe3xq6HrgMqSZ30U=", + "crlite_enrolled": false, + "id": "198baa50-bb6f-4745-b605-6b4e0d4d3282", + "last_modified": 1679518623557 + }, + { + "schema": 1679518186919, + "derHash": "wfeN/0sT1kyxCe6yrfxLFkAM9Y6MCF+pVHLKUWVZBNw=", + "subject": "CN=CATrust ECC OV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IEVDQyBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "3bece7a6b82fa63353dc9b8f92d4246e6b4aa212220b446d2f0e1600baf82e04", + "size": 1268, + "filename": "dTVBbobQ2Ok-sZMKOKt0n9iYoBl2bqIUEkZMIKpowiU=.pem", + "location": "security-state-staging/intermediates/a0db7a14-88b2-49fb-8ce0-fad4acb30896.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dTVBbobQ2Ok+sZMKOKt0n9iYoBl2bqIUEkZMIKpowiU=", + "crlite_enrolled": false, + "id": "2fd47a56-dee1-4852-bb55-c48b9c9dca1a", + "last_modified": 1679518623547 + }, + { + "schema": 1679518185972, + "derHash": "KSX5m593b4QdC0wJoH8KNsEH8JN2Eeg+AHpxLkt+xzU=", + "subject": "CN=GZCA ECC DV SSL CA,O=Guizhou Electronic Certification Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGoxCzAJBgNVBAYTAkNOMT4wPAYDVQQKEzVHdWl6aG91IEVsZWN0cm9uaWMgQ2VydGlmaWNhdGlvbiBUZWNobm9sb2d5IENvLiwgTHRkLjEbMBkGA1UEAxMSR1pDQSBFQ0MgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "20ba25c049bb6275e2df1411c3b57caf370ace54bbb113a9009b68ce9c14fa39", + "size": 1276, + "filename": "b_IMNNNv_FTbv-C7MMHsmjCkXCfwDWOhmqEFl5il9W0=.pem", + "location": "security-state-staging/intermediates/8d552c15-b5f4-449d-b02a-a6b7154bb03c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b/IMNNNv/FTbv+C7MMHsmjCkXCfwDWOhmqEFl5il9W0=", + "crlite_enrolled": false, + "id": "40c3d218-9465-4338-9925-2c0c2b467776", + "last_modified": 1679518623537 + }, + { + "schema": 1679518185013, + "derHash": "UPFoRh2Kvz0E5Uvs0AlkXQtxxJ/fVh4NjUOtkAXH+MQ=", + "subject": "CN=CATrust RSA DV SSL CA,O=Zhejiang Xinya Network Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTQwMgYDVQQKEytaaGVqaWFuZyBYaW55YSBOZXR3b3JrIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR4wHAYDVQQDExVDQVRydXN0IFJTQSBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "acd1f4f34a78e8e0484ee2f961ef7598795e43a6bfe7c316a4a21d194123ad17", + "size": 2280, + "filename": "N7vnsahv1k49T5Q2aUOGwPQQO7vC3fd1R6bOSKLtzco=.pem", + "location": "security-state-staging/intermediates/9c173173-2fdc-4b67-b5c8-1b8d4afbcc07.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N7vnsahv1k49T5Q2aUOGwPQQO7vC3fd1R6bOSKLtzco=", + "crlite_enrolled": false, + "id": "2014a9a5-bf50-429b-92e1-751d01acc3e9", + "last_modified": 1679518623527 + }, + { + "schema": 1679348884403, + "derHash": "/jy+2DjTC6uQAYTB8hpLJ9MhHLXJJX1+mFwq5DrGqJ8=", + "subject": "CN=DigiCert QuoVadis G3 Qualified TLS RSA4096 SHA256 2023 CA1,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MIGNMQswCQYDVQQGEwJOTDEgMB4GA1UECgwXUXVvVmFkaXMgVHJ1c3RsaW5rIEIuVi4xFzAVBgNVBGEMDk5UUk5MLTMwMjM3NDU5MUMwQQYDVQQDDDpEaWdpQ2VydCBRdW9WYWRpcyBHMyBRdWFsaWZpZWQgVExTIFJTQTQwOTYgU0hBMjU2IDIwMjMgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "86aebbb76a7e4b04ea41677c2df013545eb98d7452e87bb68bcb48b1c1fbf0a1", + "size": 2548, + "filename": "YubuyclU_SovUNMJEaROc1k7BuMker87y_5vmVN7WpI=.pem", + "location": "security-state-staging/intermediates/acc32d68-3a01-4913-aebb-4ab3cd246e71.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YubuyclU/SovUNMJEaROc1k7BuMker87y/5vmVN7WpI=", + "crlite_enrolled": false, + "id": "c0049371-5f14-47b1-8279-402e553cb17c", + "last_modified": 1679349423384 + }, + { + "schema": 1677228491917, + "derHash": "KuBib5pD7bKwoi1BDrBsSC75glY6cXokcofFBNeb+YA=", + "subject": "CN=Vitalwerks Internet Solutions\\, No-IP TLS ICA,O=Vitalwerks Internet Solutions\\, LLC,C=US", + "subjectDN": "MHExCzAJBgNVBAYTAlVTMSswKQYDVQQKEyJWaXRhbHdlcmtzIEludGVybmV0IFNvbHV0aW9ucywgTExDMTUwMwYDVQQDEyxWaXRhbHdlcmtzIEludGVybmV0IFNvbHV0aW9ucywgTm8tSVAgVExTIElDQQ==", + "whitelist": false, + "attachment": { + "hash": "278a462ecdf4df1a609a6367ba3729e2ba033f164e121bcb23da609e65f9abdf", + "size": 2097, + "filename": "N6n5EY_gXyOOq-YM483B1KNDkNWuz0gGk0ykPaFW6fc=.pem", + "location": "security-state-staging/intermediates/952bd502-791e-4261-b4e5-03edaa55f023.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N6n5EY/gXyOOq+YM483B1KNDkNWuz0gGk0ykPaFW6fc=", + "crlite_enrolled": false, + "id": "63a47a6b-3034-4b86-a9eb-5ee45a852017", + "last_modified": 1677229023629 + }, + { + "schema": 1677077287265, + "derHash": "I9Xvv7Qc57HzC6uKj2Svhe27XXpJwI1hbeTEfK2wNZc=", + "subject": "CN=AC Defesa GR3 OV TLS CA 2023,O=Ministerio da Defesa,C=BR", + "subjectDN": "MFMxCzAJBgNVBAYTAkJSMR0wGwYDVQQKExRNaW5pc3RlcmlvIGRhIERlZmVzYTElMCMGA1UEAxMcQUMgRGVmZXNhIEdSMyBPViBUTFMgQ0EgMjAyMw==", + "whitelist": false, + "attachment": { + "hash": "070ca649687ff8d8d65eb2fc849039d78d53c35aa858fd6834dfabafd0e4926d", + "size": 1979, + "filename": "JPQfz4gF4tV93sa-MJsA3c25wQwVyzqUpnntaX6CmPw=.pem", + "location": "security-state-staging/intermediates/ca789bde-0e50-4330-beb7-cf5e9a10080a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JPQfz4gF4tV93sa+MJsA3c25wQwVyzqUpnntaX6CmPw=", + "crlite_enrolled": false, + "id": "23216a63-e4b0-428f-9b42-8b677af4ef7b", + "last_modified": 1677077823452 + }, + { + "schema": 1675997293717, + "derHash": "9OJr6wJ5Io2W1HsF33RK5s5qrYiKO3V9JJ6z0i0n9MY=", + "subject": "CN=Namirial OV SSL CA 2023,O=Namirial S.p.A,C=IT", + "subjectDN": "MEgxCzAJBgNVBAYTAklUMRcwFQYDVQQKEw5OYW1pcmlhbCBTLnAuQTEgMB4GA1UEAxMXTmFtaXJpYWwgT1YgU1NMIENBIDIwMjM=", + "whitelist": false, + "attachment": { + "hash": "729128c4afbb50cea67b02caf56f09bef7eb6469a5f7df8956336ba9538b8324", + "size": 1707, + "filename": "GhBA08YZ8vp_Xes0LrygZD8ksGEubqU9Lay8MPHlSmc=.pem", + "location": "security-state-staging/intermediates/19849d6e-4747-43ee-8681-ec845c5cc334.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GhBA08YZ8vp/Xes0LrygZD8ksGEubqU9Lay8MPHlSmc=", + "crlite_enrolled": false, + "id": "5c37fcd9-61bf-4a7a-b0f4-074e7f39c7f4", + "last_modified": 1675997823498 + }, + { + "schema": 1675997292858, + "derHash": "Nm3WHs5J72in4HBZFezn7nuqPF1xuTY81Ifg/gJCpjQ=", + "subject": "CN=Namirial EV SSL CA 2023,O=Namirial S.p.A,C=IT", + "subjectDN": "MEgxCzAJBgNVBAYTAklUMRcwFQYDVQQKEw5OYW1pcmlhbCBTLnAuQTEgMB4GA1UEAxMXTmFtaXJpYWwgRVYgU1NMIENBIDIwMjM=", + "whitelist": false, + "attachment": { + "hash": "67364967a3577cdd142e29b9d2aec50384f1d310ab4b97e8c2ac04683dc6688a", + "size": 1707, + "filename": "myEH9rFEfLuM-7OFEGOGt8yzAJoO4ynKNAdhB4SZjHA=.pem", + "location": "security-state-staging/intermediates/a24d726b-c5e0-44b0-ad89-2a3a333460f3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "myEH9rFEfLuM+7OFEGOGt8yzAJoO4ynKNAdhB4SZjHA=", + "crlite_enrolled": false, + "id": "847f09f1-f458-4197-81f2-5992fd609725", + "last_modified": 1675997823490 + }, + { + "schema": 1675392490034, + "derHash": "2h98vzIuVcwIInb4UZFH36HoDLr31Xmf93E1iDLtFxI=", + "subject": "CN=Encryption Everywhere G3 TLS ECC P384 SHA384 2023 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE+MDwGA1UEAxM1RW5jcnlwdGlvbiBFdmVyeXdoZXJlIEczIFRMUyBFQ0MgUDM4NCBTSEEzODQgMjAyMyBDQTE=", + "whitelist": false, + "attachment": { + "hash": "c366c089312f3269094359b365076562e4cbf44a2d19a93327648eafb3aa4f90", + "size": 1284, + "filename": "zQa89-rnQxJd-XNtaDbJXJNQA1ihuhjqN0aVRPKw48Q=.pem", + "location": "security-state-staging/intermediates/7ac964c9-10e6-4b7e-80d8-1c63c5cbea2c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zQa89+rnQxJd+XNtaDbJXJNQA1ihuhjqN0aVRPKw48Q=", + "crlite_enrolled": false, + "id": "440561ba-1927-4680-8db4-f7079da8e2c1", + "last_modified": 1675393023146 + }, + { + "schema": 1674831022888, + "derHash": "z48+Opk8nQIAaLerIArJQgB0hzGCL9WzJ2ciai7k76U=", + "subject": "CN=Sooma Digital Trust Validation,O=Sooma.com Web Services Lda,C=PT", + "subjectDN": "MFsxCzAJBgNVBAYTAlBUMSMwIQYDVQQKDBpTb29tYS5jb20gV2ViIFNlcnZpY2VzIExkYTEnMCUGA1UEAwweU29vbWEgRGlnaXRhbCBUcnVzdCBWYWxpZGF0aW9u", + "whitelist": false, + "attachment": { + "hash": "851f49a7a7218844b557e0b3aaf654647f2f8fe9e6205b4f1f2ff80d3a34f4c7", + "size": 1739, + "filename": "LATZ1J06AHqrk9VsNHj2eLFLEFlkqAd2SQ4y3hqzzN0=.pem", + "location": "security-state-staging/intermediates/d593074a-d73e-4e7a-b819-4800085c2f9f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LATZ1J06AHqrk9VsNHj2eLFLEFlkqAd2SQ4y3hqzzN0=", + "crlite_enrolled": false, + "id": "eddcc056-16e1-46d1-b6c3-4a86ea59aa4a", + "last_modified": 1674831423067 + }, + { + "schema": 1674787701762, + "derHash": "I+ywPuwXM4xOM6a0ikHcPNoSKBu8P/gTwFidbMI4dSI=", + "subject": "CN=GTS CA 1C3,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMUMz", + "whitelist": false, + "attachment": { + "hash": "88a6ecb70d888765711d7b8ea76f793d4b228fedad48e215e01fc948196b7baf", + "size": 1995, + "filename": "zCTnfLwLKbS9S2sbp-uFz4KZOocFvXxkV06Ce9O5M2w=.pem", + "location": "security-state-staging/intermediates/eec3a261-928a-474b-a9db-297e4d855111.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=", + "crlite_enrolled": false, + "id": "92f275a2-aac5-4159-bb64-fa9324986d7a", + "last_modified": 1674788223239 + }, + { + "schema": 1674787700915, + "derHash": "ZOKGt2BjYCo3Lv1gzejbJlaknuFehCVLPW61/jj0KIs=", + "subject": "CN=GTS CA 1D4,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMUQ0", + "whitelist": false, + "attachment": { + "hash": "3eeab8b593f9b526e5e441336b3c99ef183d6618c02890e5bab1825e1a95744b", + "size": 1983, + "filename": "cXjPgKdVe6iojP8s0YQJ3rtmDFHTnYZxcYvmYGFiYME=.pem", + "location": "security-state-staging/intermediates/2ff09e25-9b47-4a3a-9759-af5ca75012da.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cXjPgKdVe6iojP8s0YQJ3rtmDFHTnYZxcYvmYGFiYME=", + "crlite_enrolled": false, + "id": "ea3a64a9-15b0-4050-98b9-d20a4a168573", + "last_modified": 1674788223231 + }, + { + "schema": 1674787698322, + "derHash": "l9QgA+EyVSlGCX8g75VfWxzVcKpDcteAAzpl775pdY0=", + "subject": "CN=GTS CA 1P5,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMVA1", + "whitelist": false, + "attachment": { + "hash": "6d39d26fa45c2aea9f3ecfb72ba0887e152ffd6f6a6dc6e197c57cefd4030fa0", + "size": 1983, + "filename": "81Wf12bcLlFHQAfJluxnzZ6Frg-oJ9PWY_Wrwur8viQ=.pem", + "location": "security-state-staging/intermediates/bceea9fa-e4b3-4241-b6a6-400d7eff3735.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "81Wf12bcLlFHQAfJluxnzZ6Frg+oJ9PWY/Wrwur8viQ=", + "crlite_enrolled": false, + "id": "532667c0-63b1-489e-b811-2addd1bc4e5d", + "last_modified": 1674788223200 + }, + { + "schema": 1674787700063, + "derHash": "EcaXh4cyBW3hfB2hNOnSttI88d6Vs/sKTRilF6tjIwo=", + "subject": "CN=GTS CA 2A1,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMkEx", + "whitelist": false, + "attachment": { + "hash": "66c35c0df4a124a571526bb96130bec322a6f7500769403d0c068726f5bf3b3c", + "size": 1130, + "filename": "dEp_Gc3Me0g2QnAtpHNcs692NFByRfF7Od0K2TuwG1Y=.pem", + "location": "security-state-staging/intermediates/607f27bf-2819-4878-b856-10593b911305.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dEp/Gc3Me0g2QnAtpHNcs692NFByRfF7Od0K2TuwG1Y=", + "crlite_enrolled": false, + "id": "7e85fc12-49ef-46b5-b15d-3fce25360ce3", + "last_modified": 1674788223192 + }, + { + "schema": 1674182897507, + "derHash": "KQ5piTmiT3tjqxTQSQ3pK+vvbBwtO+cX83dbccGrYm0=", + "subject": "CN=DigiCert Secure Site Pro EV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MHAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFIMEYGA1UEAxM/RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEVWIEc1IFRMUyBDTiBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "e7281a5cc6b8ad939617a94a997a9d4f8e60101945ab0c6a2064d606f464aebd", + "size": 1272, + "filename": "eaAynczvJkYEkjTwg_ILts_B3O7ARwBKF0ub7873vVM=.pem", + "location": "security-state-staging/intermediates/83c1b12e-a743-4a04-91c3-770e33065b74.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "eaAynczvJkYEkjTwg/ILts/B3O7ARwBKF0ub7873vVM=", + "crlite_enrolled": false, + "id": "23afcb2c-5fad-450a-bf51-1b8775e39f55", + "last_modified": 1674183423088 + }, + { + "schema": 1674182899250, + "derHash": "B/VaEF6IbRkfvSJTKD53sfwczcyfJqPmx+aXBqdZP+8=", + "subject": "CN=GeoTrust EV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE4MDYGA1UEAxMvR2VvVHJ1c3QgRVYgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "4e7d5787c48a69aecf8754f51666658f6ba679c85810cf7a9e76c8b7e6d74020", + "size": 1252, + "filename": "hjn_zL9duyFlCwJVMDWpZeXWqrF0eR2hde5cLK6Xvh0=.pem", + "location": "security-state-staging/intermediates/0ce3adb0-b82e-42b3-85b6-70713ba66e1f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hjn/zL9duyFlCwJVMDWpZeXWqrF0eR2hde5cLK6Xvh0=", + "crlite_enrolled": false, + "id": "a8dfed55-8274-47e6-8ff8-8575d9461d93", + "last_modified": 1674183423080 + }, + { + "schema": 1674182898405, + "derHash": "nlYaS0hgdsIZ88lTh78RZw1y9sTw3Nm7tH9Z9cFC74g=", + "subject": "CN=DigiCert Secure Site OV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFEMEIGA1UEAxM7RGlnaUNlcnQgU2VjdXJlIFNpdGUgT1YgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "6fef87f0a78395da32d0436f9980bd232d2069fa4379f28b443bf28f831291e2", + "size": 1268, + "filename": "-bKsY1_ebGdh706aKSpuQG2wjpqVTRyv5l5GKLMAsN8=.pem", + "location": "security-state-staging/intermediates/500a052e-f9d0-436f-a97d-8d8a81cfbd65.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+bKsY1/ebGdh706aKSpuQG2wjpqVTRyv5l5GKLMAsN8=", + "crlite_enrolled": false, + "id": "e6075397-e203-45dc-8f90-252857f402b2", + "last_modified": 1674183423072 + }, + { + "schema": 1674182896623, + "derHash": "HXWgs3tK4R6IPJfT/w3F2E2T/hKcEt14CGxKeNrz9wk=", + "subject": "CN=DigiCert Basic OV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE+MDwGA1UEAxM1RGlnaUNlcnQgQmFzaWMgT1YgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "7a5b159d88726a07412283a5ff3c3a09e10eb0426af96c313264eb793d07111d", + "size": 1260, + "filename": "4YfxE5kGg9f6-I9qqArY8w3vHxgVrg4p4hX5bjloYcs=.pem", + "location": "security-state-staging/intermediates/f502395c-c61b-4579-8603-18bbb1f4a448.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4YfxE5kGg9f6+I9qqArY8w3vHxgVrg4p4hX5bjloYcs=", + "crlite_enrolled": false, + "id": "cf5c07ee-1ea4-468a-87eb-777dd91007ba", + "last_modified": 1674183423064 + }, + { + "schema": 1674182900174, + "derHash": "/bdueKRQwPOvCT1RhsOyihKAVnoOxTHKWXf/TmFrNeM=", + "subject": "CN=DigiCert Basic EV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE+MDwGA1UEAxM1RGlnaUNlcnQgQmFzaWMgRVYgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "cbcc3f4565b240aaecb93498a8781f9703fa293fbc6547545c92da602a6d9406", + "size": 1260, + "filename": "HbhL5NSlVqU1eh1D7iUq1b7A4F-fPzRviUPz4YSnvWI=.pem", + "location": "security-state-staging/intermediates/65a21908-6d48-49ff-ba25-552cd3741a4a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HbhL5NSlVqU1eh1D7iUq1b7A4F+fPzRviUPz4YSnvWI=", + "crlite_enrolled": false, + "id": "e93ab04a-9ddd-461e-a523-4a7af2101ff8", + "last_modified": 1674183423056 + }, + { + "schema": 1674182894905, + "derHash": "1Ds/vC5nFbM9PXA3IA+p1Bgfl0ksRNvrqPuuloDWTNA=", + "subject": "CN=DigiCert Secure Site Pro G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MG0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFFMEMGA1UEAxM8RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEc1IFRMUyBDTiBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "25fb1b289af8f75a357fe21eccb42da287893c642aa7f3586f7404e8f6124c66", + "size": 1272, + "filename": "1XQ_a9PQAwNYJCt4Vu5FJDvAa2SS0jm309aXwhfEKQU=.pem", + "location": "security-state-staging/intermediates/64f6535c-a700-4c21-b5cc-0cbc50967ee8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1XQ/a9PQAwNYJCt4Vu5FJDvAa2SS0jm309aXwhfEKQU=", + "crlite_enrolled": false, + "id": "6684d072-74a1-4660-bb0a-4c1cf025b1f0", + "last_modified": 1674183423048 + }, + { + "schema": 1674182895794, + "derHash": "ScHyWoi1sVqAwaLaEViREcWtjiIhBP3EkCL9au8c9U0=", + "subject": "CN=DigiCert Secure Site EV G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFEMEIGA1UEAxM7RGlnaUNlcnQgU2VjdXJlIFNpdGUgRVYgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "b766a9772a8f22b658c23425163dd19d89e906f71fcd49a48721a6612359d82b", + "size": 1268, + "filename": "3oRtcRQOa5w2ww2itGpyEVFhTkjFjKvPeJvy3pvOnZk=.pem", + "location": "security-state-staging/intermediates/ac21d77c-c219-4555-a8aa-7223e66109f6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3oRtcRQOa5w2ww2itGpyEVFhTkjFjKvPeJvy3pvOnZk=", + "crlite_enrolled": false, + "id": "be93a29a-9e5f-4baa-a79a-01ea2608e676", + "last_modified": 1674183423040 + }, + { + "schema": 1674118097821, + "derHash": "66aICm2WCiaSoFqlOe8cjsA7PEVTCN7loPak3AUO3qM=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIzIFEy", + "whitelist": false, + "attachment": { + "hash": "8fb5d3f160c5ccae8157b7c4f32bf3e3171a0f76551e34727c34a45f33179983", + "size": 1642, + "filename": "hs4Hh-BNgf9DKcdRXkRbYnDYDkI4lkVh3r1gHqYCUqA=.pem", + "location": "security-state-staging/intermediates/ef0a807b-5d79-4801-8b20-fe50e59b66c4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hs4Hh+BNgf9DKcdRXkRbYnDYDkI4lkVh3r1gHqYCUqA=", + "crlite_enrolled": false, + "id": "2d128000-e3ba-466f-b5e8-a86084e7072d", + "last_modified": 1674118623211 + }, + { + "schema": 1674118096109, + "derHash": "j/lPOQ7z1LRyxTOZ4l2Mp0Y29oJk9Fb/XxEc2MucD9g=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjMgUTI=", + "whitelist": false, + "attachment": { + "hash": "c49143de6de931e48cf59e7becaddaf8f39afcccc12937817e41e221daecc105", + "size": 1642, + "filename": "xSQAedRmdmoANrEJn7lw5f_gpX8c6qMJ06j-7sLhSzQ=.pem", + "location": "security-state-staging/intermediates/6ba9c906-5413-4b4e-bd79-8ad68741303c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xSQAedRmdmoANrEJn7lw5f/gpX8c6qMJ06j+7sLhSzQ=", + "crlite_enrolled": false, + "id": "c82c929a-921d-4f56-93ee-601e2659643b", + "last_modified": 1674118623204 + }, + { + "schema": 1674118096970, + "derHash": "ug3y0SGnOeAAKqGbWyQA9CBKMk8oZpNWzrCxstrfi4Y=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "f502d7edef2b04cedc5ebfdfd6fb8a1bb624d45a8f9091b0af20e6fa09b6d2af", + "size": 2345, + "filename": "yM_wbYLv1Lo-hEOUa117pzEdqmmxj2vLiFnOWKr9JIY=.pem", + "location": "security-state-staging/intermediates/3d919236-dbd4-467c-a532-17002991ff44.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yM/wbYLv1Lo+hEOUa117pzEdqmmxj2vLiFnOWKr9JIY=", + "crlite_enrolled": false, + "id": "d1856ed8-4812-42d5-804c-1f518d0628f8", + "last_modified": 1674118623196 + }, + { + "schema": 1674118093517, + "derHash": "+kH5nQhOGUOBRNT6dFfImrobuhUL+53Ev51Nc+ZiPG8=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIzIFEy", + "whitelist": false, + "attachment": { + "hash": "86dc8a152285502ab0b93dd35511722b6d57774ee3982bcd87755f8d5f6f3e7e", + "size": 1642, + "filename": "GV2qRaW2TJf9hPIuI3wnYYupPxBlGCae56HBH9pWnOc=.pem", + "location": "security-state-staging/intermediates/c67085e3-7aaf-4619-8123-8ca3775d7894.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GV2qRaW2TJf9hPIuI3wnYYupPxBlGCae56HBH9pWnOc=", + "crlite_enrolled": false, + "id": "8bcb6956-6ca9-45b8-b19f-d4a94f19e590", + "last_modified": 1674118623189 + }, + { + "schema": 1674118098698, + "derHash": "huWBGvZnjCP+cTPMkqJ+k8+4VoK+DClUV156TQYoVZg=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIzIFEy", + "whitelist": false, + "attachment": { + "hash": "a84bf80d888003717917a3cc7409f945ae1e6ac8f159c701dd3004c63758e2eb", + "size": 1195, + "filename": "IUY9OV_EDF6gubHe5IkRYke-8QQdcJ6oveu1U-MyCTk=.pem", + "location": "security-state-staging/intermediates/48974134-7573-45f7-9da0-8430c6879c58.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IUY9OV/EDF6gubHe5IkRYke+8QQdcJ6oveu1U+MyCTk=", + "crlite_enrolled": false, + "id": "c199eadd-0d2a-4c73-abb9-f4167f5bc998", + "last_modified": 1674118623181 + }, + { + "schema": 1674118094357, + "derHash": "9cpT2HkNKsXhJ10uEN2RSpJPIASVEUT7jHNVYWOiZS4=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "32ce311eb3554628250ffa5fa16a13d69f97784546ae300516a2b7da67ec45dd", + "size": 1199, + "filename": "OzRqS-hhLXb10irCqkREGjgoZCi_L1AkmofkYd707-Q=.pem", + "location": "security-state-staging/intermediates/ccadb0ee-c33c-4d70-9b0a-d503079c3bfe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OzRqS+hhLXb10irCqkREGjgoZCi/L1AkmofkYd707+Q=", + "crlite_enrolled": false, + "id": "b8209172-f62d-44b1-b99a-d803840080e1", + "last_modified": 1674118623174 + }, + { + "schema": 1674118095245, + "derHash": "ISfbsoCispYDnRfi5kLrk0bDOZCrQpimZK3KAC/2f/0=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "b625cf9d102cb623390207571bf0486f4b06d2a6a37461af52223cd3646943ed", + "size": 1642, + "filename": "Rcdacmchnci_I8P-lH8QD8OwczzPmG3FnbFvKnvczvM=.pem", + "location": "security-state-staging/intermediates/2764d817-d7a8-404f-bbd7-a23a0a330fb0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Rcdacmchnci/I8P+lH8QD8OwczzPmG3FnbFvKnvczvM=", + "crlite_enrolled": false, + "id": "16d65aef-e0ef-4cca-ad82-864a63d5f6e0", + "last_modified": 1674118623166 + }, + { + "schema": 1674118091740, + "derHash": "rtzB28tE2W9pK9RR12oyKYORUdW+gv6NDRsjAw9I+xk=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIzIFEy", + "whitelist": false, + "attachment": { + "hash": "7f70ed97de2fb06f4f4adbcfabdc871943af4dde11749cc7e5f12f5996775a1f", + "size": 2349, + "filename": "WKagdxaH2x53QylFI6o6LazgcoUKUvvjZ9zbeORLE28=.pem", + "location": "security-state-staging/intermediates/a07e9f96-26e6-4b28-9b49-09c8ca08a912.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WKagdxaH2x53QylFI6o6LazgcoUKUvvjZ9zbeORLE28=", + "crlite_enrolled": false, + "id": "85879eab-5ff7-40c5-83d2-e405ac33c22e", + "last_modified": 1674118623158 + }, + { + "schema": 1674118089985, + "derHash": "dxyuUf2wYYXwEbdo9kaZGIVOnvxZktYmOZ+K7z/BDbM=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "abfebcf60f8b595f5b13ebc708e7a4eec60dd77d3bc7bdb52834f1fc73f0b8fc", + "size": 1642, + "filename": "0JrPGnQO3ysJLES4Vu3jfnAEvtg4lOIbOY_kuEQpZyA=.pem", + "location": "security-state-staging/intermediates/78ef431a-cb96-4ffc-86c7-68123d21a46c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0JrPGnQO3ysJLES4Vu3jfnAEvtg4lOIbOY/kuEQpZyA=", + "crlite_enrolled": false, + "id": "1ff4e892-b2b4-44ba-9ca1-de704efca285", + "last_modified": 1674118623150 + }, + { + "schema": 1674118090855, + "derHash": "ZWS/UPNSA3WxHypb1DK7ROgQrcfrLErSL5y+DDK0YWI=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "4d56f99cb062e607e22db41624451768cd9d6ef4c3b5bfa60f3662c88daafcfb", + "size": 1199, + "filename": "LiNoX4j15fdXsD-Wp-mF7A69MHEOrM0W4UfhTNmuGdc=.pem", + "location": "security-state-staging/intermediates/3b5083ab-175b-4c5f-a509-40cc6f618768.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LiNoX4j15fdXsD+Wp+mF7A69MHEOrM0W4UfhTNmuGdc=", + "crlite_enrolled": false, + "id": "9a4d19fa-c269-4d6c-b5cf-2ee11cdb4bae", + "last_modified": 1674118623142 + }, + { + "schema": 1674118089106, + "derHash": "fPtiQUDv1V1EoyTRByHK9I3GR1XbbRGSD40rUS9+tw8=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIzIFEy", + "whitelist": false, + "attachment": { + "hash": "98ad6f857f493dcbccd164a2d4612bcd7041032bfd6a1dd489f898f3d9635d00", + "size": 1195, + "filename": "t5xfk_LNC3ysfmfrjPGQlYyli61IoE2PdK3n4sZeanU=.pem", + "location": "security-state-staging/intermediates/47ad26f1-5450-4308-a8aa-e05b2b922835.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t5xfk/LNC3ysfmfrjPGQlYyli61IoE2PdK3n4sZeanU=", + "crlite_enrolled": false, + "id": "c295e515-c21a-4df4-b5f5-13309f079227", + "last_modified": 1674118623134 + }, + { + "schema": 1674118092598, + "derHash": "uV0LUaq3UE0Ph4AXO4w5XdcYNxumGt4wwRsbd7M+wBs=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2023 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMyBRMg==", + "whitelist": false, + "attachment": { + "hash": "3874642ef418625fc93dc7cbfc3862d9ee6d457a66fd6624ffb5b22d48ffd167", + "size": 1195, + "filename": "29vPFx0_yd9icbhlvHV5H19leYnTYB28ADRzf6yqqbo=.pem", + "location": "security-state-staging/intermediates/8cef6be9-b00b-4aaf-ba42-08890642c13e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "29vPFx0/yd9icbhlvHV5H19leYnTYB28ADRzf6yqqbo=", + "crlite_enrolled": false, + "id": "0f015e53-2078-4f12-868f-c646ec19a53c", + "last_modified": 1674118623126 + }, + { + "schema": 1674096494466, + "derHash": "OSWDVDuTsQ4FBt511pOZ/LvBRpyN45YGbHVgiLkiQdo=", + "subject": "CN=Apple Public Server RSA CA 1 - G1,O=Apple Inc.,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSowKAYDVQQDEyFBcHBsZSBQdWJsaWMgU2VydmVyIFJTQSBDQSAxIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "35b8ea0b8b208776616acd154fabbd1d3abade4a57f7a5e656ec072d1b474f8a", + "size": 1654, + "filename": "9Vw5Oz-M36bapAlDoz-njgwaF-AaQLrKSmhcZZnxAcY=.pem", + "location": "security-state-staging/intermediates/3b3e6534-12f9-4116-b098-a1615312a950.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9Vw5Oz+M36bapAlDoz+njgwaF+AaQLrKSmhcZZnxAcY=", + "crlite_enrolled": false, + "id": "3f76b3f1-038f-407b-a950-741feea3c520", + "last_modified": 1674097023181 + }, + { + "schema": 1674096495364, + "derHash": "hxHuU550IT9fQS60oYqYw7WNpiC01D51sFQq/Dn8YDM=", + "subject": "CN=Apple IST CA 8 - G1,O=Apple Inc.,C=US", + "subjectDN": "MEAxHDAaBgNVBAMME0FwcGxlIElTVCBDQSA4IC0gRzExEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "2cd6b163c464a522511c510d8e9b798eae4d6dbe7d90880114a485a3a0e1f2a7", + "size": 1142, + "filename": "4k-OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=.pem", + "location": "security-state-staging/intermediates/5f8c8a43-dde5-4198-b41b-c7927bc414b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4k+OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=", + "crlite_enrolled": false, + "id": "d0b18316-bb49-4cec-b297-2a24c5a5e2e2", + "last_modified": 1674097023173 + }, + { + "schema": 1674096493557, + "derHash": "khi6uU59XR+B1i0Pwj4xyLvL7jVF0dfp0/0pswvBiMg=", + "subject": "CN=Apple IST CA 8 - G1,OU=Certification Authority,O=Apple Inc.,C=US", + "subjectDN": "MGIxHDAaBgNVBAMME0FwcGxlIElTVCBDQSA4IC0gRzExIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "0be6834a2b742f4b697c05091ee7071ce8419abe4ed8eed05d569d2e0029ffc6", + "size": 1187, + "filename": "4k-OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=.pem", + "location": "security-state-staging/intermediates/a3476927-7931-4452-8215-9be907c01a5d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4k+OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=", + "crlite_enrolled": false, + "id": "2cf92b30-6231-40cc-8aa7-532cddbda587", + "last_modified": 1674097023165 + }, + { + "schema": 1674096492687, + "derHash": "KvmI8m9u8Nq5BVaX8JQftOXEIkfKmCgmiV7ymYXTDNY=", + "subject": "CN=Apple Public Server ECC CA 1 - G1,O=Apple Inc.,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSowKAYDVQQDEyFBcHBsZSBQdWJsaWMgU2VydmVyIEVDQyBDQSAxIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "9965d9b518d900e8daeab6fe08b6e6227a1114bb092251f35ffa9176cc753453", + "size": 1163, + "filename": "jT8fGVx6BA9z4arxOCBTK2xfhH6Uhco7diQx-k5Yo38=.pem", + "location": "security-state-staging/intermediates/d596ae35-7fd8-42ba-be66-58552ee1a68c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jT8fGVx6BA9z4arxOCBTK2xfhH6Uhco7diQx+k5Yo38=", + "crlite_enrolled": false, + "id": "702fc2b4-8a5a-4ef1-a6ee-ecc543c98009", + "last_modified": 1674097023157 + }, + { + "schema": 1673664491599, + "derHash": "JQJv6+UyTurKiTdrE+25aZFZQRLDERINV5Nk48wEQvQ=", + "subject": "CN=GeoTrust EV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE2MDQGA1UEAxMtR2VvVHJ1c3QgRVYgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "1c0df32bf701d6081a572b51b9e610c73d5536b4a8715af33bc065132b616800", + "size": 2398, + "filename": "7qiDszt41IUUdFUYhm3NkXicySldTp3SYqd5jj2Z2jI=.pem", + "location": "security-state-staging/intermediates/b4fa2b95-9f96-4c4f-9712-adfe22b57aff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7qiDszt41IUUdFUYhm3NkXicySldTp3SYqd5jj2Z2jI=", + "crlite_enrolled": false, + "id": "9b80d9d1-7be9-40b9-bc8c-efa250a05204", + "last_modified": 1673665023062 + }, + { + "schema": 1673664489839, + "derHash": "qqYn7trEZtvEx3UMh9mZWIq0sD50ub/c2ikC3Tba4Pg=", + "subject": "CN=DigiCert Secure Site Pro G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFDMEEGA1UEAxM6RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEc1IFRMUyBDTiBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "51f608361ceddddb1a3e4ef71c7db8843d057eac6993dfbb7092619e16d6f3ff", + "size": 2414, + "filename": "xJqQupneQCTdPArbnsiIyxl0X7lH0cIJncgQzYV_rCo=.pem", + "location": "security-state-staging/intermediates/28791f25-59a0-434d-abae-4cf8c1d41dd0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xJqQupneQCTdPArbnsiIyxl0X7lH0cIJncgQzYV/rCo=", + "crlite_enrolled": false, + "id": "d2a025f2-88cd-4fb2-ac29-23fa0beaa0e3", + "last_modified": 1673665023055 + }, + { + "schema": 1673664488120, + "derHash": "e60eveOMUia1zhGOOGd1+vvEyKVFWY2WXNAel0kd748=", + "subject": "CN=DigiCert Basic EV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE8MDoGA1UEAxMzRGlnaUNlcnQgQmFzaWMgRVYgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "adf0a88b598d534fc91008a2cbc15eb4c61c4e9b85ae5b44b4fa821a309d49e6", + "size": 2406, + "filename": "yABwDbrf93-I4iN3W8B8i_vv3U4HayU_9ljjZNpiRhM=.pem", + "location": "security-state-staging/intermediates/00d9d020-a4fb-4c1f-92f7-986a11343266.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yABwDbrf93+I4iN3W8B8i/vv3U4HayU/9ljjZNpiRhM=", + "crlite_enrolled": false, + "id": "668bc802-9636-4836-86e8-82aac0e0f575", + "last_modified": 1673665023047 + }, + { + "schema": 1673664492441, + "derHash": "r+0GLoffmBgq2b736xPA4vK2aYq04/M5MqEOKG4CTp0=", + "subject": "CN=DigiCert Basic OV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE8MDoGA1UEAxMzRGlnaUNlcnQgQmFzaWMgT1YgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "86e54b3da64217f7cbbd8444f258c818e0b6bb6902c2e97fce6b7c1803a4afeb", + "size": 2406, + "filename": "dhG7VhAvJm68DhewGNt0X68MahDfp2VaRCTME9Kvnqs=.pem", + "location": "security-state-staging/intermediates/2af209b3-67f2-4e8b-bf10-68d3849277ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dhG7VhAvJm68DhewGNt0X68MahDfp2VaRCTME9Kvnqs=", + "crlite_enrolled": false, + "id": "f9ea6ae1-1ce5-4d73-8504-cb467c9d6a11", + "last_modified": 1673665023039 + }, + { + "schema": 1673664490718, + "derHash": "ZSSPqGbIVJCNDbBF+IzavJRSVTuYKuzmsNhFhmtCa38=", + "subject": "CN=DigiCert Secure Site EV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFCMEAGA1UEAxM5RGlnaUNlcnQgU2VjdXJlIFNpdGUgRVYgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "f75b8501249ec0f47ee547c6d117383fc002fd5b6bd64856609e9b5f8fbee1db", + "size": 2414, + "filename": "dnfn1GWnChOOKOTgq38bPYRhDWZNOBTicogtBje6O3E=.pem", + "location": "security-state-staging/intermediates/c7b62543-c78a-44ee-9efc-f216e5bfd056.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dnfn1GWnChOOKOTgq38bPYRhDWZNOBTicogtBje6O3E=", + "crlite_enrolled": false, + "id": "51ca5aaf-bc87-4a66-a1fd-bc5d6dbeaad7", + "last_modified": 1673665023031 + }, + { + "schema": 1673664489006, + "derHash": "ejg51Lh5Cxl0QMlNpofc6xpawH2Tb4Gs9AsWM2SwqVE=", + "subject": "CN=DigiCert Secure Site Pro EV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFGMEQGA1UEAxM9RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEVWIEc1IFRMUyBDTiBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "7ddea0065899bee970a228ba580b863a6360364b7a3bac351d180ee2dc6183f2", + "size": 2418, + "filename": "anVdxE-C1TqV5AT7innXnbEV78Jf-BHpbwJ_-aGSlc4=.pem", + "location": "security-state-staging/intermediates/ae043b15-1e73-4ed4-be69-01e93286b1b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "anVdxE+C1TqV5AT7innXnbEV78Jf+BHpbwJ/+aGSlc4=", + "crlite_enrolled": false, + "id": "73893ab9-964f-4576-97c9-4174c5f2d5a4", + "last_modified": 1673665023024 + }, + { + "schema": 1673664487209, + "derHash": "VTQSfwVkmLTPHJZzZbHcLUTzMQmrz6ZFTyW+5PK2VIE=", + "subject": "CN=DigiCert Secure Site OV G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFCMEAGA1UEAxM5RGlnaUNlcnQgU2VjdXJlIFNpdGUgT1YgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "eab62f406fab419cfe4053781698abe505966d1752b2c03bb62995766097e00b", + "size": 2414, + "filename": "731AZkcNdxw9L154ZN3auYetdnXwhN81UrZZTmrgK8M=.pem", + "location": "security-state-staging/intermediates/3b225b39-85bc-455e-953a-a319eddbd5e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "731AZkcNdxw9L154ZN3auYetdnXwhN81UrZZTmrgK8M=", + "crlite_enrolled": false, + "id": "78cc356b-bbbe-4758-8bd3-28706bc0ebf1", + "last_modified": 1673665023015 + }, + { + "schema": 1671601697348, + "derHash": "tVKPSkOVfhq74IxOvy2Kz0wsGRTWNUEXSSyMP+Az/Zc=", + "subject": "CN=DigiCert Basic OV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE+MDwGA1UEAxM1RGlnaUNlcnQgQmFzaWMgT1YgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "dff1af54a4380caa0cf453b7a581615ab85076062f1d454ea9aaf813b3189dd0", + "size": 1276, + "filename": "ERgsTxZRQki8NPIvyX4rwpZThwUO-QdY-n9ePzg5Dxg=.pem", + "location": "security-state-staging/intermediates/035448b0-49bd-4b36-9997-deaa1fc3b42c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ERgsTxZRQki8NPIvyX4rwpZThwUO+QdY+n9ePzg5Dxg=", + "crlite_enrolled": false, + "id": "e0f91205-7746-4cfb-864d-50af98e55fd9", + "last_modified": 1671602223107 + }, + { + "schema": 1671601696499, + "derHash": "NM3GfYjSUhetyKJTSOEUy+xjFIORz6R104KHRpUgLSw=", + "subject": "CN=DigiCert Secure Site Pro G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MG0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFFMEMGA1UEAxM8RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEczIFRMUyBDTiBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "b4bb637adacc089d7b3110c6ec879c449eb63d506148f61b3d6a6de4453cefa4", + "size": 1288, + "filename": "-A8nTSBTv94a3mE7wo66UD1eF9E9oo7dZos3reSXkoI=.pem", + "location": "security-state-staging/intermediates/7252f984-96a1-455c-925d-2a27de9058ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+A8nTSBTv94a3mE7wo66UD1eF9E9oo7dZos3reSXkoI=", + "crlite_enrolled": false, + "id": "8e992dd0-900d-4a80-956d-d118f98b1c4c", + "last_modified": 1671602223099 + }, + { + "schema": 1671601698224, + "derHash": "lfa5Iw8n+1/AyylEPb5zABaLWZLHVY8/Y9Q1LzK4ycs=", + "subject": "CN=DigiCert Secure Site Pro EV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MHAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFIMEYGA1UEAxM/RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEVWIEczIFRMUyBDTiBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "a71eaf249c9409ae361366abad28454bd2d8773174082eb7b07a3de8d0677604", + "size": 1293, + "filename": "UwABA_jg1ktL5jL23DywiBLU-fYaE5LcsQA7OUZAn1M=.pem", + "location": "security-state-staging/intermediates/c89b46ba-6440-4252-8a78-01ab1f7fa2b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UwABA/jg1ktL5jL23DywiBLU+fYaE5LcsQA7OUZAn1M=", + "crlite_enrolled": false, + "id": "33dfa616-530a-4850-b816-a829a16043d5", + "last_modified": 1671602223091 + }, + { + "schema": 1671601694771, + "derHash": "lDvEBrC0Ws+IQTBFInJlcD8ayLIdb7wrjL3lnXzlG4E=", + "subject": "CN=DigiCert Secure Site EV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFEMEIGA1UEAxM7RGlnaUNlcnQgU2VjdXJlIFNpdGUgRVYgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "ffc843d97035686729a70749fa1f05ffae741bdf1cb10ade8d50c8d88f45b925", + "size": 1284, + "filename": "x1LcScRT0-fNvUAOtrP-Ysoh-0DYta2s1j2MYXUkMSQ=.pem", + "location": "security-state-staging/intermediates/5c55f052-6c38-43b5-bf21-f95cce990a4c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x1LcScRT0+fNvUAOtrP+Ysoh+0DYta2s1j2MYXUkMSQ=", + "crlite_enrolled": false, + "id": "e911067b-f347-48fd-a63c-9f720b7242c5", + "last_modified": 1671602223083 + }, + { + "schema": 1671601692983, + "derHash": "HKV+8VmBK/vi2BBvYVLlHd0KWkyUic0JdZW3l8s3hD4=", + "subject": "CN=DigiCert Basic EV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE+MDwGA1UEAxM1RGlnaUNlcnQgQmFzaWMgRVYgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "19eaa49fae33c503700b348015aca5d15220e606a93c094e4b59b0c89ac7e1e1", + "size": 1276, + "filename": "loWqnyOzHqCDfDJqkVv6-5z2ZZUVkM5Ka546x6D-dVE=.pem", + "location": "security-state-staging/intermediates/6071fae3-719d-47d3-affc-1e568f9365b0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "loWqnyOzHqCDfDJqkVv6+5z2ZZUVkM5Ka546x6D+dVE=", + "crlite_enrolled": false, + "id": "88a4a9a1-9389-4832-8350-e5c3cc020166", + "last_modified": 1671602223075 + }, + { + "schema": 1671601692080, + "derHash": "ONUw+xcAU2F+R6ps7/IcLbQ8nZZ0GvFYRWYHIJFHS7M=", + "subject": "CN=DigiCert Secure Site OV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFEMEIGA1UEAxM7RGlnaUNlcnQgU2VjdXJlIFNpdGUgT1YgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "5afc7bf47bfe77e5d8f07aebaab599e7bc5634d3dc95bd29190fbc8849f9d414", + "size": 1284, + "filename": "d-uUQdn-NjWX1fSzFjY7sNuWCNa9bqt3MNbCLd-fUP0=.pem", + "location": "security-state-staging/intermediates/877fe714-023f-4bf5-af37-35516011ddb3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "d+uUQdn+NjWX1fSzFjY7sNuWCNa9bqt3MNbCLd+fUP0=", + "crlite_enrolled": false, + "id": "3086d047-5f2c-4023-b2a9-5ed337324bf3", + "last_modified": 1671602223067 + }, + { + "schema": 1671601693879, + "derHash": "AvQCOLo3Lp31HXVA4E7f7atUOTI/mgLfWzjAdMp9QG4=", + "subject": "CN=GeoTrust EV G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE4MDYGA1UEAxMvR2VvVHJ1c3QgRVYgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "993fdac6bd1cb42a1c5571e8cb7ee32bfde36c3cc81575154979b8102247b4c2", + "size": 1268, + "filename": "Vro7q_Mn-MafVHn7eAx-dnZCebTlKXgc-1MRajEFc58=.pem", + "location": "security-state-staging/intermediates/80f0e6c5-6f1e-4efc-8339-183aa3b77b6d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vro7q/Mn+MafVHn7eAx+dnZCebTlKXgc+1MRajEFc58=", + "crlite_enrolled": false, + "id": "75f50592-a139-4346-a8f6-05781f382fad", + "last_modified": 1671602223058 + }, + { + "schema": 1671601695625, + "derHash": "ZihxyA9OD9IZ6mmq/E0orXtB6TgVYL7nh4SNTu8cvyQ=", + "subject": "CN=GeoTrust G3 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE1MDMGA1UEAxMsR2VvVHJ1c3QgRzMgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "eec25c9df85a7631ebbcb1b26ac67fdece214330b820fd6318f83e441187d5d9", + "size": 1264, + "filename": "pP-gM-LKhuAMiwV6v--SS39_RCHU_0McW6EqGFt2m7Y=.pem", + "location": "security-state-staging/intermediates/fe51dc93-d5c4-458a-8743-22361c5b1b36.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pP+gM+LKhuAMiwV6v++SS39/RCHU/0McW6EqGFt2m7Y=", + "crlite_enrolled": false, + "id": "8294eb1c-58e3-4af5-96e3-a5bdbdf652c0", + "last_modified": 1671602223050 + }, + { + "schema": 1671245299685, + "derHash": "gm69kOlGwl098Fq/uRGsCkJsepoGO3IkRkcQvsWljOE=", + "subject": "CN=DigiCert Secure Site Pro G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFDMEEGA1UEAxM6RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEcyIFRMUyBDTiBSU0E0MDk2IFNIQTI1NiAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "bd0c8ad3a70b00963d65cf57248efe2e0f8babfea9b94bca4b410259d25063d5", + "size": 2085, + "filename": "w3dW1QXYCswRHG5CogJA-prBNavQbaKuo2FBex0QYJQ=.pem", + "location": "security-state-staging/intermediates/0b4e57ec-48c8-45a6-99df-37694b240e4b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "w3dW1QXYCswRHG5CogJA+prBNavQbaKuo2FBex0QYJQ=", + "crlite_enrolled": false, + "id": "44bc91f1-55af-456c-9b0b-dd9384f7458d", + "last_modified": 1671245823143 + }, + { + "schema": 1671245298808, + "derHash": "b+JPv/u/+9w75ppHLb917zDKz4rqMZ84k2VMkbq5EUM=", + "subject": "CN=GeoTrust EV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE2MDQGA1UEAxMtR2VvVHJ1c3QgRVYgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "71817ff3e70b1153eb11094f9ffef3aab48055dc2d9a82ea7af4ca8c61d10889", + "size": 2064, + "filename": "tB2cb7z-R5e_hH_OpqL9kr-sSb7WEYTKENsIXEz4WHw=.pem", + "location": "security-state-staging/intermediates/5d7f0cdf-8ae0-4695-be17-b0ea9ac61479.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tB2cb7z+R5e/hH/OpqL9kr+sSb7WEYTKENsIXEz4WHw=", + "crlite_enrolled": false, + "id": "a52b64c1-f11d-40ed-8a28-2e5a3744432c", + "last_modified": 1671245823134 + }, + { + "schema": 1671245296200, + "derHash": "hTQ0XnH1RQtr0nWM74SVVHAIpuMCrI2sYlNhyyTca9U=", + "subject": "CN=DigiCert Basic EV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE8MDoGA1UEAxMzRGlnaUNlcnQgQmFzaWMgRVYgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "c5ef0e6700f7b94991eacd324f7f9a48fa12fe4ef44ff439c0477a617ccee4b2", + "size": 2073, + "filename": "sHTcOD11LxBnz764CQwvNEeksH0Giu9hdnvwm7angAI=.pem", + "location": "security-state-staging/intermediates/d6f8520c-1d5c-4cd2-b48c-302f3520b3de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sHTcOD11LxBnz764CQwvNEeksH0Giu9hdnvwm7angAI=", + "crlite_enrolled": false, + "id": "45cdfff0-77d8-498a-b078-76775ea30fbb", + "last_modified": 1671245823126 + }, + { + "schema": 1671245295320, + "derHash": "Bdye3A/d+pdaFDLvgG7HgAeLU2KtRa922xXJB2MNsl0=", + "subject": "CN=GeoTrust G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEzMDEGA1UEAxMqR2VvVHJ1c3QgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "7c86f73832738c029bd6a6896a27b080010f875bf336ba42bb1b945a1a32f826", + "size": 2060, + "filename": "udvVO5HfT3B83McRDgEhj2WVTnsk-Sb9NBCszsRWAqY=.pem", + "location": "security-state-staging/intermediates/89df84d8-7359-4947-98c1-803412735fca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "udvVO5HfT3B83McRDgEhj2WVTnsk+Sb9NBCszsRWAqY=", + "crlite_enrolled": false, + "id": "ce187fcd-4deb-47b2-b985-e0ca913d6062", + "last_modified": 1671245823118 + }, + { + "schema": 1671245297077, + "derHash": "tIDhGmvQwrxTixx9scY7QGlOXe3ZN1LQgtn9Qi/LP70=", + "subject": "CN=DigiCert Secure Site Pro EV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFGMEQGA1UEAxM9RGlnaUNlcnQgU2VjdXJlIFNpdGUgUHJvIEVWIEcyIFRMUyBDTiBSU0E0MDk2IFNIQTI1NiAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "98171ba50b727ff633a8ada50d1b3b25da3c44dbd9f7f1d331391ea9dcca78a0", + "size": 2089, + "filename": "XtpPRwFel7bEhBGUrcYRMJmNOS7_xC0jcvlXfLpsFPc=.pem", + "location": "security-state-staging/intermediates/67308735-928a-4ba6-b085-0a377579df5a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XtpPRwFel7bEhBGUrcYRMJmNOS7/xC0jcvlXfLpsFPc=", + "crlite_enrolled": false, + "id": "dde3cab0-712f-47e8-add0-415389e5ca63", + "last_modified": 1671245823110 + }, + { + "schema": 1671245294403, + "derHash": "8WyZzeGe0azotPvHAOBefv8hFv1qIkJ3ZrJRxui7POc=", + "subject": "CN=DigiCert Basic OV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE8MDoGA1UEAxMzRGlnaUNlcnQgQmFzaWMgT1YgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "c020c4f7ca9ff68c8bd41639621ec4bce4873762a08ca517b62125464de6eb83", + "size": 2073, + "filename": "2tJ8M_9cRsT1fURyr1gOdW2fbIxW49qcEvw7Zfco3hM=.pem", + "location": "security-state-staging/intermediates/b6301c39-8f95-443a-90be-9c4c5e2b190d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2tJ8M/9cRsT1fURyr1gOdW2fbIxW49qcEvw7Zfco3hM=", + "crlite_enrolled": false, + "id": "b9c0d175-dc62-4b1c-9934-bb66eb408ad9", + "last_modified": 1671245823102 + }, + { + "schema": 1671245297936, + "derHash": "fNbN0l7uJRKq8UGa/UTBRsQ6oQk9WmDU7TnvvdqBWtQ=", + "subject": "CN=DigiCert Secure Site OV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFCMEAGA1UEAxM5RGlnaUNlcnQgU2VjdXJlIFNpdGUgT1YgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "229ba8e0e951f42ec8862a2074cb2d96e9ebf32f5ba132daade7119e5546718e", + "size": 2081, + "filename": "iRVt6vFAG0-gB4h7KFntBfxhDjw2OHuU0tsCfL5BCW8=.pem", + "location": "security-state-staging/intermediates/75239002-e947-480f-b624-9c6abb705ba7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iRVt6vFAG0+gB4h7KFntBfxhDjw2OHuU0tsCfL5BCW8=", + "crlite_enrolled": false, + "id": "17d34e92-6577-48e1-909c-f8b1526c6827", + "last_modified": 1671245823093 + }, + { + "schema": 1671245293501, + "derHash": "j8ibc2iikSlzVndEBHaQhxHajz8EwKtqYdbenU600cI=", + "subject": "CN=DigiCert Secure Site EV G2 TLS CN RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFCMEAGA1UEAxM5RGlnaUNlcnQgU2VjdXJlIFNpdGUgRVYgRzIgVExTIENOIFJTQTQwOTYgU0hBMjU2IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "06eac73503785f8d0c1ba0d9bb9b62c0f57273c7fdbae31f18dd45681986af8c", + "size": 2081, + "filename": "qI021aPCdwZoUbslqwFNaDgfoStti1_gERbafd-pHK4=.pem", + "location": "security-state-staging/intermediates/831f5da6-81fb-438f-85e6-306964518bbc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qI021aPCdwZoUbslqwFNaDgfoStti1/gERbafd+pHK4=", + "crlite_enrolled": false, + "id": "05d7bc1d-f977-4ada-9a6b-7914b00d7330", + "last_modified": 1671245823085 + }, + { + "schema": 1670863687245, + "derHash": "1Tv0lop9s8jE4zZvLH92rWG3BB3+/GTBkCxJmm//8kE=", + "subject": "CN=TWCA Global Root CA G2,OU=Root CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MFQxCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHzAdBgNVBAMTFlRXQ0EgR2xvYmFsIFJvb3QgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "98eb759b45ae7a8b4c2aa6949f77f6eca2b524c383ace6b4f33b26bffc4260fe", + "size": 2255, + "filename": "Binh5u18FNN7FdJhefHXYDXjDUk_g97m9xr3tLQ08WY=.pem", + "location": "security-state-staging/intermediates/0777eb68-c3da-4328-a0df-95dee9a0da10.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Binh5u18FNN7FdJhefHXYDXjDUk/g97m9xr3tLQ08WY=", + "crlite_enrolled": false, + "id": "02bbd7f1-bbba-4d0d-bf32-8fc655502ab5", + "last_modified": 1670864222956 + }, + { + "schema": 1670863688123, + "derHash": "xhn05vexuqemxvJECSo/guRqbWe+4mM3+68CVG8zEz8=", + "subject": "CN=TWCA CYBER Root CA,OU=Root CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTElRXQ0EgQ1lCRVIgUm9vdCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "5f3fc9163fe2e721c2aa420f3cee55276d56ee77858d4fbf09275009f74f2591", + "size": 2251, + "filename": "BmALlMAxi7aWfwx3h8yKEDKhecTpXjxXYLMuKQ9_7Js=.pem", + "location": "security-state-staging/intermediates/f2ef287e-d43a-4588-b648-7a0fedb4cb47.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BmALlMAxi7aWfwx3h8yKEDKhecTpXjxXYLMuKQ9/7Js=", + "crlite_enrolled": false, + "id": "679c7db9-5c10-4aa8-b2e1-b2d6201b209f", + "last_modified": 1670864222947 + }, + { + "schema": 1669870099271, + "derHash": "f0MlzCQQejlEFVLyf9w0GFgCSC4WTReUqkFe8eQga6c=", + "subject": "CN=Entrust Certification Authority - L1K,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFL", + "whitelist": false, + "attachment": { + "hash": "63de4667ecc52b8c03324efb20d2d3bba8bf5d65f33fe9d75bfeb4b0fe70c3f7", + "size": 1849, + "filename": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=.pem", + "location": "security-state-staging/intermediates/469a331b-ff21-4a17-9718-0553715c4ec5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=", + "crlite_enrolled": false, + "id": "d359e000-3422-4d3d-8d45-096408e95930", + "last_modified": 1669870624944 + }, + { + "schema": 1669668499137, + "derHash": "jVPipHelLDjQe6/9+3F5WSo0TcuOy3Xhr4ljyaL+nIM=", + "subject": "CN=GeoSSL RSA Extended Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxODA2BgNVBAMTL0dlb1NTTCBSU0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "362e44cc1be38d034d48d013358849c7dcbaa96f2d0edf1bf7ec5b3033a816c4", + "size": 2255, + "filename": "ddQlhc5XifO6R-WuMtb7cM6ZbG6W0Q5mXmmhlTXiRFs=.pem", + "location": "security-state-staging/intermediates/e0735db7-bb34-4323-9b8f-081433bd0fd3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ddQlhc5XifO6R+WuMtb7cM6ZbG6W0Q5mXmmhlTXiRFs=", + "crlite_enrolled": false, + "id": "2e9187ce-4bdc-49e6-a5ab-8b541d7a79fe", + "last_modified": 1669669023713 + }, + { + "schema": 1669668498274, + "derHash": "2gC1Rrp74isUa3S8I9rKOWBGSoR3mwEWWDv9i3kdQf0=", + "subject": "CN=GeoSSL RSA Organization Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFwxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxPDA6BgNVBAMTM0dlb1NTTCBSU0EgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "2c14ef3c5c0fbdfa9a9242de8c0c98f3166aeb1e68d03d4f96876bffcb051206", + "size": 2272, + "filename": "RafzbiIbXcWhJv01Vws7hbCUu7Ob10WNi-q9yUxtkwo=.pem", + "location": "security-state-staging/intermediates/5a10e7b1-dd0c-4576-b009-e5dd34a527d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RafzbiIbXcWhJv01Vws7hbCUu7Ob10WNi+q9yUxtkwo=", + "crlite_enrolled": false, + "id": "07f623d4-3aab-44c3-b605-4c3012f68505", + "last_modified": 1669669023706 + }, + { + "schema": 1669668497390, + "derHash": "/cHiV085fYuwvENusC+cwH7rCL2QODqcMhc8iJvdjpY=", + "subject": "CN=GeoSSL RSA Domain Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxNjA0BgNVBAMTLUdlb1NTTCBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "56169197b112f9eb1bcf28ded2643c28e5d4510b70349503b76f0c26956cc0e7", + "size": 2263, + "filename": "6od8LbsRZ70mucCJmlXZL66jtDof8GjuIP3z_M3XTvY=.pem", + "location": "security-state-staging/intermediates/64a6bfb6-19c9-4c2e-8a37-5f5bab5a2bde.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6od8LbsRZ70mucCJmlXZL66jtDof8GjuIP3z/M3XTvY=", + "crlite_enrolled": false, + "id": "0f5281c0-74db-47b3-b081-775cbe600d7c", + "last_modified": 1669669023698 + }, + { + "schema": 1669668496514, + "derHash": "r/j0d1shXMXzVLh7AA3vdosY2o+PMS09f+AceeyIIKU=", + "subject": "CN=InCommon RSA IGTF Server CA 2,O=Internet2,C=US", + "subjectDN": "MEkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJbnRlcm5ldDIxJjAkBgNVBAMTHUluQ29tbW9uIFJTQSBJR1RGIFNlcnZlciBDQSAy", + "whitelist": false, + "attachment": { + "hash": "14384bb9410dc2b39095f97f6c6c4c2125783bd77983fe7629f19c8d422fdb5b", + "size": 2263, + "filename": "61zhBAEXtJMcKuCk0kOoYENOt_iXEB-vTTnrezeFhtE=.pem", + "location": "security-state-staging/intermediates/42580e0c-abd4-4f8d-a88f-f7cbd167e898.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "61zhBAEXtJMcKuCk0kOoYENOt/iXEB+vTTnrezeFhtE=", + "crlite_enrolled": false, + "id": "59fcf8a4-56b9-4841-9195-79a77910be5c", + "last_modified": 1669669023690 + }, + { + "schema": 1669668494795, + "derHash": "t42Os0f4pnd+v5/SO4OACryIzbPPG0BwaxGwXx+gb7w=", + "subject": "CN=InCommon ECC Server CA 2,O=Internet2,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJbnRlcm5ldDIxITAfBgNVBAMTGEluQ29tbW9uIEVDQyBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "bfead90e57e7a127a44c053789beeae99d48c7c7ea910aff44caa548ce4d4cb0", + "size": 1228, + "filename": "UJd7EKNaKP0I5AOmPVjTsmFeym78buyJ_UWoURYuvQA=.pem", + "location": "security-state-staging/intermediates/b46b3137-3b00-4aea-beb6-c0aae11f0db2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UJd7EKNaKP0I5AOmPVjTsmFeym78buyJ/UWoURYuvQA=", + "crlite_enrolled": false, + "id": "50ab7885-78eb-456e-a407-3f4b39c36ba4", + "last_modified": 1669669023682 + }, + { + "schema": 1669668495677, + "derHash": "N53iqm70sX4ebA2WJlMfeS5llZF/stKKnnXquezp50M=", + "subject": "CN=GeoSSL ECC Extended Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFgxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxODA2BgNVBAMTL0dlb1NTTCBFQ0MgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "8835b8bf4becd4b3a5647d717c97879d42b4a2f498c272f1192b9fd78dbdd119", + "size": 1244, + "filename": "dWFnLHXuYjRMKQ9l9oYZaLE_FVNqQpqWS-WNxUo8yZQ=.pem", + "location": "security-state-staging/intermediates/73b48642-48c6-4e2b-8618-80c484befb9e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dWFnLHXuYjRMKQ9l9oYZaLE/FVNqQpqWS+WNxUo8yZQ=", + "crlite_enrolled": false, + "id": "5b379d75-a415-481c-b3a1-f0f504f9fded", + "last_modified": 1669669023674 + }, + { + "schema": 1669668493911, + "derHash": "JSm6MGTKmky94uREoch9BG9pFaHv40FN9b22p33GIKc=", + "subject": "CN=GeoSSL ECC Organization Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFwxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxPDA6BgNVBAMTM0dlb1NTTCBFQ0MgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "4eab674f0368219ed00f909dffb732ca61faeaef0bb29330dda822f461eee9f9", + "size": 1256, + "filename": "s2MUPnrFQWqodG9MAwrEqGZip-I0MUwxn8ap-jQjfUM=.pem", + "location": "security-state-staging/intermediates/0d2326ab-8cce-4ae2-9610-1bc4cb2cb474.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "s2MUPnrFQWqodG9MAwrEqGZip+I0MUwxn8ap+jQjfUM=", + "crlite_enrolled": false, + "id": "d8a74877-2eb5-45b9-9a1c-ad7f0dba0785", + "last_modified": 1669669023666 + }, + { + "schema": 1669668492998, + "derHash": "h+AcxN0MnZKj29SQkv8T+c04dEXNxX5bmE4bdyG1sCk=", + "subject": "CN=InCommon RSA Server CA 2,O=Internet2,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJbnRlcm5ldDIxITAfBgNVBAMTGEluQ29tbW9uIFJTQSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "71d4c087fe91778af395f778b86b825ce955d146490ab94540d3fcdf1ec74ee2", + "size": 2239, + "filename": "nIUvrOVzCyKOqY-U4sofEeIMk94Dt_WuMgaesi8NITk=.pem", + "location": "security-state-staging/intermediates/8d2e464a-9995-4b44-ac8b-55f3656ad646.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nIUvrOVzCyKOqY+U4sofEeIMk94Dt/WuMgaesi8NITk=", + "crlite_enrolled": false, + "id": "53e44ea8-81c7-4e30-8262-90b1e227e875", + "last_modified": 1669669023658 + }, + { + "schema": 1669668492093, + "derHash": "gqWKnkiWBsMBVi/FnaKxNmA44zw+c1FvroS48X+l5PU=", + "subject": "CN=GeoSSL ECC Domain Validation Secure Server CA,O=GeoSSL,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMQ8wDQYDVQQKEwZHZW9TU0wxNjA0BgNVBAMTLUdlb1NTTCBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "af362825be8a508c9d869d98b310db008dfd11676462921211d4ad9241dc7031", + "size": 1252, + "filename": "FVfl1r7A_nbUaPI8gp_6mC1lvxpub3NNCfyJOg0q9oA=.pem", + "location": "security-state-staging/intermediates/beb9c58d-fd8a-41b9-bb23-1a8e57cdf630.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FVfl1r7A/nbUaPI8gp/6mC1lvxpub3NNCfyJOg0q9oA=", + "crlite_enrolled": false, + "id": "96db0a85-59b3-4892-aca4-6aa83b0b0b3e", + "last_modified": 1669669023650 + }, + { + "schema": 1668674893607, + "derHash": "AEgjQbEEoN5uDx1QjbhMtRT3SU/gSYITOlx1ATbFXcg=", + "subject": "CN=Hongkong Post Root CA 3,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK", + "subjectDN": "MG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9uZ2tvbmcgUG9zdCBSb290IENBIDM=", + "whitelist": false, + "attachment": { + "hash": "1769ef49c13db2ca1f82506a6c4aaa6ea94918c140d50b6096ee7f81ff82cf46", + "size": 2024, + "filename": "JUHlO6WzsHrL5wl6xKA-BAwRz3ptSmfLIT1Vi1AWegY=.pem", + "location": "security-state-staging/intermediates/569425e5-70f5-4d17-9375-130302d7faca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JUHlO6WzsHrL5wl6xKA+BAwRz3ptSmfLIT1Vi1AWegY=", + "crlite_enrolled": false, + "id": "3aa0463f-d538-4149-9aab-0811ed205212", + "last_modified": 1668675423131 + }, + { + "schema": 1668566894507, + "derHash": "LErWS06GLX1GQk2foT6pqXSmL3xLYIrhqHFCTMmmhz0=", + "subject": "CN=CrowdStrike TLS CA 2022,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "MEsxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEgMB4GA1UEAxMXQ3Jvd2RTdHJpa2UgVExTIENBIDIwMjI=", + "whitelist": false, + "attachment": { + "hash": "33ddc8825dd673ff7f560e7853a5e0d291bb6cb6cf90aad4c4593390e46269f1", + "size": 1711, + "filename": "o1stY0JqCx4_MuMRqWJETN_Heofxx39-E3XvVnnzWwo=.pem", + "location": "security-state-staging/intermediates/87cebe8e-a76e-4af9-b874-8a495d8ce5f7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o1stY0JqCx4/MuMRqWJETN/Heofxx39+E3XvVnnzWwo=", + "crlite_enrolled": false, + "id": "b535d0e1-ea0e-4159-ac70-c6114f51be54", + "last_modified": 1668567423462 + }, + { + "schema": 1667530084667, + "derHash": "rX9Xqj/F6vQxAiSkdQqXzYed4ZidS4arCLeig3O8tIk=", + "subject": "CN=Encryption Everywhere G5 TLS ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE/MD0GA1UEAxM2RW5jcnlwdGlvbiBFdmVyeXdoZXJlIEc1IFRMUyBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "300ee1328c6efdbb22764cea6242768d04fd6cb08707ddc287ac51fa68d34545", + "size": 1268, + "filename": "_htyD_PQCXTNGgD95EXyXJmf38uObunt_fGtg5kILvE=.pem", + "location": "security-state-staging/intermediates/4b1a0173-87b5-4e1b-a14d-e8174f0a493a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/htyD/PQCXTNGgD95EXyXJmf38uObunt/fGtg5kILvE=", + "crlite_enrolled": false, + "id": "0201da13-72e6-449d-8ddd-2b8bef52de00", + "last_modified": 1667530623289 + }, + { + "schema": 1667530085711, + "derHash": "ZUv3OyY5CR+hjylnVbcElZ9S9n/femWq8cMVRBIBoJ0=", + "subject": "CN=Encryption Everywhere G5 TLS RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGUxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE9MDsGA1UEAxM0RW5jcnlwdGlvbiBFdmVyeXdoZXJlIEc1IFRMUyBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "96cd92ce7e2c6183e6d7449ef364b27eb95a68b8b2c3192acd483834d467ebb5", + "size": 2410, + "filename": "sgTlkT0_dg29ozZ3tlfIrDLv3tupAog0j75Ke03BNJM=.pem", + "location": "security-state-staging/intermediates/bf9934cb-4818-4294-b61f-54a5e7f73501.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sgTlkT0/dg29ozZ3tlfIrDLv3tupAog0j75Ke03BNJM=", + "crlite_enrolled": false, + "id": "78793fc6-6e9d-4a6b-88ad-6e8e779862e8", + "last_modified": 1667530623279 + }, + { + "schema": 1666727451807, + "derHash": "eBbHsFZrRng7HBXYoo2LDSDP6yCz0T95RG4VxKUckd8=", + "subject": "CN=Certum Extended Validation CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGCMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQDEx1DZXJ0dW0gRXh0ZW5kZWQgVmFsaWRhdGlvbiBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0705626e2fe60aaf3f2b067419aa1d9994ec7c33fd5519e98851d2477d71ba8e", + "size": 2430, + "filename": "rxmDSIbuiNS8fzkHrqwb5gM8fGXYXY7Egq5fLYyEn4s=.pem", + "location": "security-state-staging/intermediates/ad1b09ec-5983-4577-b17f-3bcdc6f7349d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rxmDSIbuiNS8fzkHrqwb5gM8fGXYXY7Egq5fLYyEn4s=", + "crlite_enrolled": false, + "id": "a6162e7a-0ad4-4d74-9ccf-7528c89121b2", + "last_modified": 1666727875396 + }, + { + "schema": 1666727384249, + "derHash": "aZ1Ut0gqXTKTMeoEFcwu3NYP2gHRnnHQVBlrzgZ3c1w=", + "subject": "CN=GlobalSign Organization Validation CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MGYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYDVQQDEzNHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gU0hBMjU2IC0gRzM=", + "whitelist": false, + "attachment": { + "hash": "2503aecef7682dc86265404bb3212730e5bfa9316dbc827331274d72deee9383", + "size": 1634, + "filename": "19e9lxa6ojidVP5dVZZuTl4VDK62HtqIUx9aA4Kc3aw=.pem", + "location": "security-state-staging/intermediates/d382ff16-ea91-4cd3-a050-90c8d6613505.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "19e9lxa6ojidVP5dVZZuTl4VDK62HtqIUx9aA4Kc3aw=", + "crlite_enrolled": false, + "id": "018fd818-2416-4ecf-bd00-0eae97797a59", + "last_modified": 1666727875382 + }, + { + "schema": 1666727358783, + "derHash": "WzErfhG3DQfBTgq5nwjQB0iWYJjFKqhaBqCCK75ZoCw=", + "subject": "CN=Telia Domain Validation CA v2,O=Telia Finland Oyj,C=FI", + "subjectDN": "MFExCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEmMCQGA1UEAwwdVGVsaWEgRG9tYWluIFZhbGlkYXRpb24gQ0EgdjI=", + "whitelist": false, + "attachment": { + "hash": "e50f92a6a1126e1974b698dc74b5324cb74a1bf0a656d8fa21ae230568387c3f", + "size": 2328, + "filename": "gB4lDqs5uw6KjxweLgOqWfxNkncJhFCg7rB3hEJ4fvo=.pem", + "location": "security-state-staging/intermediates/8258df94-c3d1-471b-9fb1-34f2157ac923.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gB4lDqs5uw6KjxweLgOqWfxNkncJhFCg7rB3hEJ4fvo=", + "crlite_enrolled": false, + "id": "52f5910f-78f9-4bc2-b611-367c69c58d8d", + "last_modified": 1666727875368 + }, + { + "schema": 1666727380330, + "derHash": "aXDjlmIUF9zuMVqsKLteoxYfF1Ag9/Tpuhfa3ljbsI0=", + "subject": "CN=WoTrus DV SSL CA,O=WoTrus CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1RydXMgQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29UcnVzIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "c00640d76f2191d5d2fd5624662ed82ab5430015987a69f81f883cdc4d5a7b43", + "size": 1691, + "filename": "qo1QyzYCUCM6TTpkflyWle2ERuNQ8q7_99oCt1RmDgk=.pem", + "location": "security-state-staging/intermediates/ac244e93-fe89-4324-8196-d9da2b7bf0b9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qo1QyzYCUCM6TTpkflyWle2ERuNQ8q7/99oCt1RmDgk=", + "crlite_enrolled": false, + "id": "b97451e0-61f4-46a5-908d-5fd947861af6", + "last_modified": 1666727875348 + }, + { + "schema": 1666727336675, + "derHash": "2uNDT2lvyfD2UuGypvabXpJz0J9DvTvdRxfWFB+M0sI=", + "subject": "OU=Public Certification Authority - G2,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MGAxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEsMCoGA1UECwwjUHVibGljIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "d35775690c65ebee8113249b23f19a293655d0ed08e999f64243089ed009db1b", + "size": 2129, + "filename": "tnFqrstaexEa6Zz-CVGGozhI8IF1FafM6uzX3zzHrUI=.pem", + "location": "security-state-staging/intermediates/07e9d719-440e-4db5-9e24-7f15cd5cee54.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tnFqrstaexEa6Zz+CVGGozhI8IF1FafM6uzX3zzHrUI=", + "crlite_enrolled": false, + "id": "9bae286e-930a-4285-8a27-54c6509f3077", + "last_modified": 1666727875331 + }, + { + "schema": 1666727377394, + "derHash": "GDWw5ILqZVNvwBDkvBPAYPZWaBZfupfi9ULOlspt/vw=", + "subject": "CN=Entrust Certification Authority - L1F,OU=See www.entrust.net/legal-terms+OU=(c) 2016 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTYgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFG", + "whitelist": false, + "attachment": { + "hash": "b215df35bd16df20092886331099193a624ea14d00398b12325bb57279cf4181", + "size": 1410, + "filename": "45FtWowsHe863G4x3FzC8qT3R9tgrLIVwZGjN5hwd80=.pem", + "location": "security-state-staging/intermediates/60dd4560-425b-4a39-9729-7bc1fc60adbc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "45FtWowsHe863G4x3FzC8qT3R9tgrLIVwZGjN5hwd80=", + "crlite_enrolled": false, + "id": "7cb091bb-e197-494b-8caa-a6f3725b7737", + "last_modified": 1666727875317 + }, + { + "schema": 1666727357096, + "derHash": "yW8kxFET/ZGuL55A4QZlO/oP+8+gfiCVJMhE58jaQUg=", + "subject": "CN=CA Disig R2I2 Certification Service,O=Disig a.s.,L=Bratislava,C=SK", + "subjectDN": "MGUxCzAJBgNVBAYTAlNLMRMwEQYDVQQHDApCcmF0aXNsYXZhMRMwEQYDVQQKDApEaXNpZyBhLnMuMSwwKgYDVQQDDCNDQSBEaXNpZyBSMkkyIENlcnRpZmljYXRpb24gU2VydmljZQ==", + "whitelist": false, + "attachment": { + "hash": "f15a1b6085ecf56bcc317ea1be8002f3c662d6a3a7e4bb6320daa95d1ef02dee", + "size": 2142, + "filename": "v1zylIDkrVcStoBb1ajeFz3xSjTmFzgYYMJDQsfaIxo=.pem", + "location": "security-state-staging/intermediates/74dc56b9-2278-47d0-9591-8771a0e9d98a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v1zylIDkrVcStoBb1ajeFz3xSjTmFzgYYMJDQsfaIxo=", + "crlite_enrolled": false, + "id": "bf754a3f-1f3e-4515-933a-fcb359cafde5", + "last_modified": 1666727875303 + }, + { + "schema": 1666727393770, + "derHash": "9r+Isqjc1P60AVmE7C8UJ8gBfRQM9B8i7mieDWcYAS8=", + "subject": "CN=WoTrus OV SSL CA,O=WoTrus CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1RydXMgQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29UcnVzIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ce4ba30cb7d9fab70ae32267594148013d61f09cd075d7e3e07f4792e46d517c", + "size": 1691, + "filename": "xxDKJ6WsrIZ1X_E1XI1IQgUNGc1SquSk5UkyOBHKq4I=.pem", + "location": "security-state-staging/intermediates/b8d761f5-e513-43c4-ba77-4138f539938e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xxDKJ6WsrIZ1X/E1XI1IQgUNGc1SquSk5UkyOBHKq4I=", + "crlite_enrolled": false, + "id": "773ec055-4ebc-48c9-84a6-04ffd1e4b40d", + "last_modified": 1666727875288 + }, + { + "schema": 1666727409093, + "derHash": "6ppDUV4Tj/d4L8rNucfo4aYc/R0XCW7V3dH0ANArA/U=", + "subject": "CN=certSIGN SSL DV CA Class 3 G2,OU=certSIGN SSL DV CA Class 3 G2,O=certSIGN,C=RO", + "subjectDN": "MHAxCzAJBgNVBAYTAlJPMREwDwYDVQQKDAhjZXJ0U0lHTjEmMCQGA1UECwwdY2VydFNJR04gU1NMIERWIENBIENsYXNzIDMgRzIxJjAkBgNVBAMMHWNlcnRTSUdOIFNTTCBEViBDQSBDbGFzcyAzIEcy", + "whitelist": false, + "attachment": { + "hash": "27c0349500f0a3bb1a05e55a47d5c9fb8216d028bb4be566fb5acd95419f5850", + "size": 1626, + "filename": "FZ9KA10aaA0Pq9Q4xOxYK99jgxb3tQy47R4dIFgxFUo=.pem", + "location": "security-state-staging/intermediates/24cf3b35-2f29-40e6-9bca-d871a307ae78.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FZ9KA10aaA0Pq9Q4xOxYK99jgxb3tQy47R4dIFgxFUo=", + "crlite_enrolled": false, + "id": "d4028f7a-433e-4118-9af5-f556af86871c", + "last_modified": 1666727875274 + }, + { + "schema": 1666727409802, + "derHash": "Vtpu/vHVBBNMcu7cOuRKp/oRuEiCDb+qhsqONdYO2wQ=", + "subject": "CN=FujiSSL Public Validation Authority - G3,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MGcxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMTEwLwYDVQQDEyhGdWppU1NMIFB1YmxpYyBWYWxpZGF0aW9uIEF1dGhvcml0eSAtIEcz", + "whitelist": false, + "attachment": { + "hash": "bbb89301ab3ea4fd79d1448f5c852927551328811eee186c33c831d20d5e0ae0", + "size": 1674, + "filename": "dzjq2willdWoAe9M1lz4AuLGmmFYgxAD2zYIXMhbzjo=.pem", + "location": "security-state-staging/intermediates/f2374571-0b80-42e0-8883-fda71075451e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dzjq2willdWoAe9M1lz4AuLGmmFYgxAD2zYIXMhbzjo=", + "crlite_enrolled": false, + "id": "77918a2e-3fdb-4dcf-8e32-cbe1b39f74f4", + "last_modified": 1666727875260 + }, + { + "schema": 1666727367562, + "derHash": "GPQ2j+k7PK4CUjC85+rTQP2Q+yf5oQ42/uifxFTyJ4g=", + "subject": "CN=CrossTrust DV CA5,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFAxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMRowGAYDVQQDExFDcm9zc1RydXN0IERWIENBNQ==", + "whitelist": false, + "attachment": { + "hash": "2095edba6d54418256b1c82c2499bd9f75e39608c3dc0e3b395dac7a8870a119", + "size": 1642, + "filename": "mfydQPGtsWNlYaYdaD2eprRgxX0MdeoAw0G537kLXzk=.pem", + "location": "security-state-staging/intermediates/779275d4-b98f-4e5c-9606-7d98a85e2d32.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mfydQPGtsWNlYaYdaD2eprRgxX0MdeoAw0G537kLXzk=", + "crlite_enrolled": false, + "id": "70cd95b5-008a-41bf-b38e-525ca51982fb", + "last_modified": 1666727875246 + }, + { + "schema": 1666727335669, + "derHash": "NEe3S15QClSZg/os7XOlZC5qrseIKVRhWEN99m10Nbg=", + "subject": "CN=Entrust Certification Authority - L1J,OU=See www.entrust.net/legal-terms+OU=(c) 2016 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTYgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFK", + "whitelist": false, + "attachment": { + "hash": "2e07048ca86219c4ca3243524ce82f92bd4d6ee5a455e2f0788fa0719d8fec57", + "size": 1414, + "filename": "jI4Ykls9hjddSkzQ6huOlUndX7rjGTHuF5_d41IfoQg=.pem", + "location": "security-state-staging/intermediates/4cf5d4e3-3abb-470a-8f2f-a06b53c04f64.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jI4Ykls9hjddSkzQ6huOlUndX7rjGTHuF5/d41IfoQg=", + "crlite_enrolled": false, + "id": "b9f11e18-5206-4146-9bc4-ddabac60444f", + "last_modified": 1666727875232 + }, + { + "schema": 1666727392740, + "derHash": "E++zmi9mVOjGe9BPTG1MkM1sq1CRvO3HN4f2t309P+c=", + "subject": "CN=Entrust Certification Authority - L1K,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFL", + "whitelist": false, + "attachment": { + "hash": "2317985189faf07f31bc3c665a12f23068191e798992ad37652b74c99ac9bb73", + "size": 1813, + "filename": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=.pem", + "location": "security-state-staging/intermediates/18eafa54-0261-4c50-a26c-e36ab1883d27.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=", + "crlite_enrolled": false, + "id": "760c1e85-d14f-45de-9089-5346d069bb23", + "last_modified": 1666727875218 + }, + { + "schema": 1666727433546, + "derHash": "Ep+13lAeJAQc0UqBB1/RzeJXQI1KNT5jaRLji92i0/s=", + "subject": "CN=Certum Domain Validation CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGFMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSkwJwYDVQQDEyBDZXJ0dW0gRG9tYWluIFZhbGlkYXRpb24gQ0EgU0hBMg==", + "whitelist": false, + "attachment": { + "hash": "e25bdf92d0be8655e107328d6904320e57365813d9a19f81ea387cb5426c4296", + "size": 1727, + "filename": "S4AbJNGvyS57nzJwv8sPMUML8VHSqH1vbiBftdPcErI=.pem", + "location": "security-state-staging/intermediates/4ebda4af-1b27-4b52-bf3e-baac3a47d395.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S4AbJNGvyS57nzJwv8sPMUML8VHSqH1vbiBftdPcErI=", + "crlite_enrolled": false, + "id": "61249918-c227-4afe-9de6-22348a31422d", + "last_modified": 1666727875202 + }, + { + "schema": 1666727424955, + "derHash": "ybBswIMYYiBhjmGodyZA+CTfadVhrVa9wVrVbQzghgg=", + "subject": "CN=Apple IST CA 2 - G1,OU=Certification Authority,O=Apple Inc.,C=US", + "subjectDN": "MGIxHDAaBgNVBAMTE0FwcGxlIElTVCBDQSAyIC0gRzExIDAeBgNVBAsTF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKEwpBcHBsZSBJbmMuMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "d84962e6ad0e174b509f0b27d7aca095081588308e85148f0e798c3dcccfd511", + "size": 1662, + "filename": "tc-C1H75gj-ap48SMYbFLoh56oSw-CLJHYPgQnm3j9U=.pem", + "location": "security-state-staging/intermediates/811c650d-5339-4109-9f5c-e9b038422338.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tc+C1H75gj+ap48SMYbFLoh56oSw+CLJHYPgQnm3j9U=", + "crlite_enrolled": false, + "id": "acc298cc-782a-430e-96f7-3ba5d0bc27cf", + "last_modified": 1666727875188 + }, + { + "schema": 1666727354885, + "derHash": "bN+dy/NRCju0AnYdYtDF5OevxR2c/wHwK9UyVtxWet8=", + "subject": "CN=GDCA TrustAUTH R4 DV SSL CA G2,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJzAlBgNVBAMMHkdEQ0EgVHJ1c3RBVVRIIFI0IERWIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "6649636f2813ad008e8133da4658e92771065ef07c503f24fb1bf0e9efebc92c", + "size": 1727, + "filename": "cfsQj68ZkIybk1gX6hizWacoE7hvqJNujNu2E03n_3g=.pem", + "location": "security-state-staging/intermediates/38a31f82-83ee-41b4-80ac-dd89b54a7ece.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cfsQj68ZkIybk1gX6hizWacoE7hvqJNujNu2E03n/3g=", + "crlite_enrolled": false, + "id": "93bffb7c-4c34-4987-80a0-4fcbf72ce0d6", + "last_modified": 1666727875160 + }, + { + "schema": 1666727410152, + "derHash": "/QI2IkTzEmbK/wBYGNEATsTrCPsjmq+qr/9HSX1gBdY=", + "subject": "CN=Certum Organization Validation CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGLMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS8wLQYDVQQDEyZDZXJ0dW0gT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gQ0EgU0hBMg==", + "whitelist": false, + "attachment": { + "hash": "ee9903c59449e4d0acc7b6208d9d0397e473dfb4bdf3dca7397b4cf26911831b", + "size": 1735, + "filename": "51GveKNrpJjtGpXY5QDx03s3YTQwaQic6dWBqo3zX6s=.pem", + "location": "security-state-staging/intermediates/aec69216-92cf-49fb-ad18-7c895d138dbb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "51GveKNrpJjtGpXY5QDx03s3YTQwaQic6dWBqo3zX6s=", + "crlite_enrolled": false, + "id": "c76e2118-173c-4a95-bf4e-159a6ad00ee9", + "last_modified": 1666727875146 + }, + { + "schema": 1666727376525, + "derHash": "7TyZFGbLxFtf0dooECj5WHuCGVI2R+DKG0fyxSfSkg8=", + "subject": "CN=AffirmTrust Extended Validation CA - EV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGDMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKEFmZmlybVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBFVjE=", + "whitelist": false, + "attachment": { + "hash": "22bb58450c6234f294cba177f1dbd93e6d193576017408e8cdc292f4bc6a925e", + "size": 1674, + "filename": "xAD7ZgT8h1RQOQsz_6DxwKe17_oiwt-Jy6y1G1Akw00=.pem", + "location": "security-state-staging/intermediates/cfde6906-9b1c-47ff-a5d9-15c5809705bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xAD7ZgT8h1RQOQsz/6DxwKe17/oiwt+Jy6y1G1Akw00=", + "crlite_enrolled": false, + "id": "ee7fb2cb-357d-4c77-afde-598161638058", + "last_modified": 1666727875132 + }, + { + "schema": 1666727443308, + "derHash": "vzLalUVxZZqvcVwT7nA+NkPfy67uLYIRDKaOtXy2fOA=", + "subject": "CN=TUBITAK Kamu SM SSL Sertifika Hizmet Saglayicisi - Surum 1,OU=Kamu Sertifikasyon Merkezi - Kamu SM,O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK,L=Gebze - Kocaeli,C=TR", + "subjectDN": "MIHfMQswCQYDVQQGEwJUUjEYMBYGA1UEBwwPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKDDlUdXJraXllIEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsMJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTFDMEEGA1UEAww6VFVCSVRBSyBLYW11IFNNIFNTTCBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpIC0gU3VydW0gMQ==", + "whitelist": false, + "attachment": { + "hash": "ab10fd30609bc30f75d3ede708f810cf2fdb009802424fd6ed3e4c6bf308db7c", + "size": 2296, + "filename": "FUrqo5K2woGDo0xUxDmAoLHpCJRt3-d67a7xGdVm1s8=.pem", + "location": "security-state-staging/intermediates/56f71156-d839-4560-9fe4-299ea364f331.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FUrqo5K2woGDo0xUxDmAoLHpCJRt3+d67a7xGdVm1s8=", + "crlite_enrolled": false, + "id": "461c30ef-db33-4e0c-bcf1-c4fb264c2a61", + "last_modified": 1666727875111 + }, + { + "schema": 1666727379977, + "derHash": "ecQJGwWxXBaDEot6NV4KrWLhu7w+Xzc1NwwGzE0a+0Q=", + "subject": "CN=CrossTrust OV CA5,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFAxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMRowGAYDVQQDExFDcm9zc1RydXN0IE9WIENBNQ==", + "whitelist": false, + "attachment": { + "hash": "bd3caa1f305a9f0b5a55466e00fb389199886a78055b526ee4a8fd5a2c317e51", + "size": 1642, + "filename": "9QmSs2TRmBuBXXpRPhom-2y54_RQ3KsK4te_JED6g0Q=.pem", + "location": "security-state-staging/intermediates/0cd79666-328f-4d26-a58d-65b8203303df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9QmSs2TRmBuBXXpRPhom+2y54/RQ3KsK4te/JED6g0Q=", + "crlite_enrolled": false, + "id": "b6b1d83f-6615-4c21-bf97-c5dba8c7d925", + "last_modified": 1666727875063 + }, + { + "schema": 1666727339898, + "derHash": "rtXdmlM5aF37Ap9tiaFDNallEsPKzFKymUr4trN/pNI=", + "subject": "CN=GlobalSign Extended Validation CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MGIxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTgwNgYDVQQDEy9HbG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBTSEEyNTYgLSBHMw==", + "whitelist": false, + "attachment": { + "hash": "bbc372d9ecdf9fa392a04587c4d036f042f3f09c6f0914cc81e10d9e8e8a3a02", + "size": 1577, + "filename": "86fLIetopQLDNxFZ0uMI66Xpl1pFgLlHHn9v6kT0i4I=.pem", + "location": "security-state-staging/intermediates/a42b26ca-6177-4156-b30c-df67c447df2e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "86fLIetopQLDNxFZ0uMI66Xpl1pFgLlHHn9v6kT0i4I=", + "crlite_enrolled": false, + "id": "6faa42a8-09b8-46c4-9167-2568ddb696c8", + "last_modified": 1666727875046 + }, + { + "schema": 1666727449880, + "derHash": "CP1BixGIU0hP0bBm8ZIqgPVx2P/3Jo1Ziwht8YtYCtg=", + "subject": "CN=Atos TrustedRoot Server-CA 2019,O=Atos,C=DE", + "subjectDN": "MEYxKDAmBgNVBAMMH0F0b3MgVHJ1c3RlZFJvb3QgU2VydmVyLUNBIDIwMTkxDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRF", + "whitelist": false, + "attachment": { + "hash": "e9026c05fca12f79a992b5c8a7d90af2ccafac750d96e6d2a420590a55a54e3c", + "size": 1922, + "filename": "SD8O2olCwqFt3wL6_lvBiVzprjUyo7vWr7bIS3B3am8=.pem", + "location": "security-state-staging/intermediates/c310d05d-c0b0-4149-9d40-04e0f46a1fab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SD8O2olCwqFt3wL6/lvBiVzprjUyo7vWr7bIS3B3am8=", + "crlite_enrolled": false, + "id": "4f5ba09a-4228-4ae3-b9fb-e261273f7b68", + "last_modified": 1666727875018 + }, + { + "schema": 1666727404592, + "derHash": "x/VG8Mdsmg2pmS6YhOYki3yiu2reg4vXoDqMQe4qMGQ=", + "subject": "CN=WoSign OV SSL CA,O=WoSign CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1NpZ24gQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29TaWduIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "048dba35914e23e206772fb60d80b4ca80d87cf337af50dd6dc2287381e3e76a", + "size": 1691, + "filename": "MZKD2c60FHEBoj-uZNlegylIGvCafu0tBZ2eUHao0-0=.pem", + "location": "security-state-staging/intermediates/b60c5c46-1be6-40b6-8d56-cd82c562287f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MZKD2c60FHEBoj+uZNlegylIGvCafu0tBZ2eUHao0+0=", + "crlite_enrolled": false, + "id": "2a6ed91a-06ad-49ae-94d2-9fd509326fda", + "last_modified": 1666727874989 + }, + { + "schema": 1666727399772, + "derHash": "tf1vgAM09WUDawmZ+DELWwvXJoOV2LJnAFaXr3MBxeg=", + "subject": "CN=AffirmTrust Certificate Authority - OV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGCMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0FmZmlybVRydXN0IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIE9WMQ==", + "whitelist": false, + "attachment": { + "hash": "0444cfdbccd513e2a46e37f1768b21669333660cb0c9e2802b9e5041b414be02", + "size": 1674, + "filename": "RM5J-l4IeDRKjL4ZuWGG2Jt3Wae0v4pWKttrQv-DsGM=.pem", + "location": "security-state-staging/intermediates/eb61a73a-5fdb-41c0-848b-e07b9e3a24e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RM5J+l4IeDRKjL4ZuWGG2Jt3Wae0v4pWKttrQv+DsGM=", + "crlite_enrolled": false, + "id": "be2ceaf1-8ba0-4cab-bd2a-0cdb7f118523", + "last_modified": 1666727874974 + }, + { + "schema": 1666727432688, + "derHash": "S3M04diZmCK6+o/2iIElOJsYpOWrJv+mJMf2j9yB8Ms=", + "subject": "CN=GlobalSign CloudSSL CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENsb3VkU1NMIENBIC0gU0hBMjU2IC0gRzM=", + "whitelist": false, + "attachment": { + "hash": "2d52ef3ad1bf79d364b9204631f8b1fedcd6e4e13aadf92c4b6af52d751e96dd", + "size": 1634, + "filename": "-VZJxHgrOOiVyUxgMRbfoo-GIWrMKd4aellBBHtBcKg=.pem", + "location": "security-state-staging/intermediates/67867c84-32d8-4600-aa2f-a51493938ab0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+VZJxHgrOOiVyUxgMRbfoo+GIWrMKd4aellBBHtBcKg=", + "crlite_enrolled": false, + "id": "8f1617bd-0e32-4828-b630-2a3c510a24ef", + "last_modified": 1666727874943 + }, + { + "schema": 1666727402699, + "derHash": "MdqiXRQtCLkOZA1LxQsknw/jl4XJjV5T4jMlnA+uk5g=", + "subject": "CN=Online e-Szigno SSL CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHwxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEcMBoGA1UEYQwTVkFUSFUtMjM1ODQ0OTctMi00MTEkMCIGA1UEAwwbT25saW5lIGUtU3ppZ25vIFNTTCBDQSAyMDE2", + "whitelist": false, + "attachment": { + "hash": "a70c67eb66b82e4075e34248c87882934b44064980cd89b1e783b12797290157", + "size": 2580, + "filename": "x0t02qhpw-MVPvpI409gZ7KlO9C8TZ6yqNYTYIDmB98=.pem", + "location": "security-state-staging/intermediates/168326d2-eb32-4449-8388-890b59e9496f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x0t02qhpw+MVPvpI409gZ7KlO9C8TZ6yqNYTYIDmB98=", + "crlite_enrolled": false, + "id": "2f2ec8eb-d0d3-4ccb-bfe9-6771b302aa0e", + "last_modified": 1666727874929 + }, + { + "schema": 1666727356431, + "derHash": "O8UYVgQK1/9mg6qFoNNPnqaAzSPDfLigQjsPiaJEBbk=", + "subject": "CN=ACCVCA-120,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMjAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "5c48e95a58a4d10758544929296f57524a51140212f8479daff1c315fe338caf", + "size": 2686, + "filename": "XH3Wh5FuH9ZeZcCdLZM0VbsyQg3aeFV1wujvNEYNRQ8=.pem", + "location": "security-state-staging/intermediates/fd767b1f-850e-4779-ad1c-fcb1520983b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XH3Wh5FuH9ZeZcCdLZM0VbsyQg3aeFV1wujvNEYNRQ8=", + "crlite_enrolled": false, + "id": "ea1bd9a0-1903-47bb-b8fb-ecb8955b6d38", + "last_modified": 1666727874915 + }, + { + "schema": 1666727387621, + "derHash": "ORLFhecn8rB3iI9njwQ/2N3O6ekeZiimJFsbjrvMORI=", + "subject": "CN=Class2 e-Szigno SSL CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHwxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEcMBoGA1UEYQwTVkFUSFUtMjM1ODQ0OTctMi00MTEkMCIGA1UEAwwbQ2xhc3MyIGUtU3ppZ25vIFNTTCBDQSAyMDE2", + "whitelist": false, + "attachment": { + "hash": "98b37ae1cc647006d33e1b0891b42ab2c5f41787f32cce3681803aba7fb66524", + "size": 2580, + "filename": "NZP4aC4bQmjlxXRAcygA6LF8yzeZlyKkTTGIC7B9glM=.pem", + "location": "security-state-staging/intermediates/410dd037-e90b-43ec-967e-bcf28d2920e0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NZP4aC4bQmjlxXRAcygA6LF8yzeZlyKkTTGIC7B9glM=", + "crlite_enrolled": false, + "id": "7c942e26-4b79-4354-935f-aa9e284f0568", + "last_modified": 1666727874900 + }, + { + "schema": 1666727352661, + "derHash": "TWcjeyz6z/893IPC/xySqOCpinG9BO05+K/0Kq+ZNx0=", + "subject": "CN=Shuidi Webtrust SSL Domain Validated,O=Shanghai Ping An Credit Reference Company Limited,C=CN", + "subjectDN": "MHgxCzAJBgNVBAYTAkNOMTowOAYDVQQKDDFTaGFuZ2hhaSBQaW5nIEFuIENyZWRpdCBSZWZlcmVuY2UgQ29tcGFueSBMaW1pdGVkMS0wKwYDVQQDDCRTaHVpZGkgV2VidHJ1c3QgU1NMIERvbWFpbiBWYWxpZGF0ZWQ=", + "whitelist": false, + "attachment": { + "hash": "63bdeba64d270d7fe2be7ee4b7e9a192c4f735067578e4fd400929b5b7d868b1", + "size": 1780, + "filename": "z8UvZVIyaOTkJosl2Vn8Hbcn7ChKCosFEmKBgvRLh6U=.pem", + "location": "security-state-staging/intermediates/8c50117d-cf04-4cb1-a7c7-4359f02fe4df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z8UvZVIyaOTkJosl2Vn8Hbcn7ChKCosFEmKBgvRLh6U=", + "crlite_enrolled": false, + "id": "d35f58a2-ee1e-4379-a992-e748ce904d72", + "last_modified": 1666727874871 + }, + { + "schema": 1666727378766, + "derHash": "6BsB+fVpLPOCPG/TWIZUK/ru/F6pT04kbkLEqfxf6Ks=", + "subject": "CN=GDCA TrustAUTH R4 OV SSL CA G2,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJzAlBgNVBAMMHkdEQ0EgVHJ1c3RBVVRIIFI0IE9WIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "62f632973d4566730fb0c215f361db0c6377f4fe7015f37d2f43bbbff8b9422b", + "size": 1727, + "filename": "X_kDRJGyo1gr5XgS4hGkA1JH19Rbn0-jAdxRkGwOfis=.pem", + "location": "security-state-staging/intermediates/b0587fd9-8f56-4f2e-bb41-45600e3dd3f4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "X/kDRJGyo1gr5XgS4hGkA1JH19Rbn0+jAdxRkGwOfis=", + "crlite_enrolled": false, + "id": "242f8001-c709-472f-8e95-69caf24cb984", + "last_modified": 1666727874857 + }, + { + "schema": 1666727449528, + "derHash": "4F7UqeTHczCKk+hJhhIlrjSakrvUus3UkArU5zsTEQA=", + "subject": "CN=SECOM Passport for Web SR 3.0 CA,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MF8xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSkwJwYDVQQDEyBTRUNPTSBQYXNzcG9ydCBmb3IgV2ViIFNSIDMuMCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "10b46479f6babe96f43e63ab11f87d973a00150e7dc2e9e4b6ce7b5e22ab3005", + "size": 1634, + "filename": "Rhcj0dcdOovISeMPXTWpuUDiNf1EzyxSHeMUK0lfoR8=.pem", + "location": "security-state-staging/intermediates/ae31b0a6-8423-4c86-a060-ba964c79794d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Rhcj0dcdOovISeMPXTWpuUDiNf1EzyxSHeMUK0lfoR8=", + "crlite_enrolled": false, + "id": "6ccb21c1-806e-410f-a6cb-594456ae82c0", + "last_modified": 1666727874843 + }, + { + "schema": 1666727436119, + "derHash": "1yERA4jKbyC7qf0ajbpO+4wWOSo9662XxVPurwrKyqw=", + "subject": "CN=TeliaSonera Server CA v2,O=TeliaSonera,C=FI", + "subjectDN": "MEYxCzAJBgNVBAYTAkZJMRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEhMB8GA1UEAwwYVGVsaWFTb25lcmEgU2VydmVyIENBIHYy", + "whitelist": false, + "attachment": { + "hash": "2f9dcba71811d0e494412cb5ac40c7215fd1e0e68794cf9d419462eb22bfdd4d", + "size": 2528, + "filename": "Fq3YMR2ibLgpoD509egJDn5cPXPfnXC5MUd2IWwV_qA=.pem", + "location": "security-state-staging/intermediates/e40e2f56-df5c-40a6-85f6-694d1d00f167.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Fq3YMR2ibLgpoD509egJDn5cPXPfnXC5MUd2IWwV/qA=", + "crlite_enrolled": false, + "id": "3082ff87-55fc-4380-b33c-8f226a13dcec", + "last_modified": 1666727874827 + }, + { + "schema": 1666727427156, + "derHash": "tnb/oxeeiBIJOhter+6HauemqvIxB42tG/shzSiTdko=", + "subject": "CN=GlobalSign RSA OV SSL CA 2018,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIFJTQSBPViBTU0wgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "4a0e56bd3b0d5e1f85efac014a7e99c83489b11c4826b602e42b144c4f6e91da", + "size": 1553, + "filename": "hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=.pem", + "location": "security-state-staging/intermediates/3f1383e4-4860-4b8b-bf8b-eac0b66ceb61.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=", + "crlite_enrolled": false, + "id": "5ec3f078-2414-46ca-aaf8-e9630ae2109a", + "last_modified": 1666727874812 + }, + { + "schema": 1666727388343, + "derHash": "ppxZlm67zf7H9P8CiMhv9gNW+nhgIIuTtDoJWwYAzB4=", + "subject": "CN=nazwaSSL,OU=http://nazwa.pl,O=nazwa.pl sp. z o.o.,C=PL", + "subjectDN": "MFgxCzAJBgNVBAYTAlBMMRwwGgYDVQQKDBNuYXp3YS5wbCBzcC4geiBvLm8uMRgwFgYDVQQLDA9odHRwOi8vbmF6d2EucGwxETAPBgNVBAMMCG5hendhU1NM", + "whitelist": false, + "attachment": { + "hash": "882b6da025cdfc61d89e03806a350163dfdfc251470ff6e5f8433ca8370adea9", + "size": 1605, + "filename": "AW6U8qPqk114rfl2sAhiEim2Pf0mq_Rb_BeWSlVAiP4=.pem", + "location": "security-state-staging/intermediates/4a692d29-8a7d-4aa6-b0fa-426ad6f59300.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AW6U8qPqk114rfl2sAhiEim2Pf0mq/Rb/BeWSlVAiP4=", + "crlite_enrolled": false, + "id": "bbeb9d9e-3dd2-4546-8c96-d7fc9be4f2e6", + "last_modified": 1666727874798 + }, + { + "schema": 1666727382505, + "derHash": "6sJBwEQKNoMBETgzNrwgysdAnCD26I1PhPSCe+kZ4zg=", + "subject": "CN=e-Szigno SSL CA 2014,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHgxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEdMBsGA1UEAwwUZS1Temlnbm8gU1NMIENBIDIwMTQxHzAdBgkqhkiG9w0BCQEWEGluZm9AZS1zemlnbm8uaHU=", + "whitelist": false, + "attachment": { + "hash": "a0cfba79ba47c056ad89715d67ca81adb743a5af504f88e8533bb086aa28c2a6", + "size": 2268, + "filename": "DuvXKXobTisU7ufJpGVYzPTgBJ3Vjeta2kpZtG9qXc8=.pem", + "location": "security-state-staging/intermediates/632fc789-8c70-460e-a724-365f1a2f468b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DuvXKXobTisU7ufJpGVYzPTgBJ3Vjeta2kpZtG9qXc8=", + "crlite_enrolled": false, + "id": "b199af11-7ea2-49db-8145-22cd545ac30f", + "last_modified": 1666727874782 + }, + { + "schema": 1666727339368, + "derHash": "VTJKmDJRL8bJnxW/Dp7T1r60OYzO4ZS3/4SdltkTDUQ=", + "subject": "CN=GDCA TrustAUTH R4 Extended Validation SSL CA,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN", + "subjectDN": "MHgxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjE1MDMGA1UEAwwsR0RDQSBUcnVzdEFVVEggUjQgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "b7f589fd332a96773c813b272dcd486bc1231918f0643ed0662f171680cc3963", + "size": 2093, + "filename": "LA_cQZEhZA_J3pK28of_Jf7Wn0WmfSQvp4eYJW5o8NQ=.pem", + "location": "security-state-staging/intermediates/f42f0088-3a96-4549-833e-5948b8098639.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LA/cQZEhZA/J3pK28of/Jf7Wn0WmfSQvp4eYJW5o8NQ=", + "crlite_enrolled": false, + "id": "eee3bb7d-8e10-46cc-af2c-14e1ec4763e3", + "last_modified": 1666727874768 + }, + { + "schema": 1666727453162, + "derHash": "7gnUnHz0QYQNpRWNDnDUxsrrT8z+Z1juTMRccaKH3zk=", + "subject": "CN=Shuidi Webtrust SSL Organization Validated,O=Shanghai Ping An Credit Reference Company Limited,C=CN", + "subjectDN": "MH4xCzAJBgNVBAYTAkNOMTowOAYDVQQKDDFTaGFuZ2hhaSBQaW5nIEFuIENyZWRpdCBSZWZlcmVuY2UgQ29tcGFueSBMaW1pdGVkMTMwMQYDVQQDDCpTaHVpZGkgV2VidHJ1c3QgU1NMIE9yZ2FuaXphdGlvbiBWYWxpZGF0ZWQ=", + "whitelist": false, + "attachment": { + "hash": "f975da322d7fcdc38e3cb51f4091077525b9e851b700830f29f444cceea7c7ca", + "size": 1788, + "filename": "bQEpVS_P2TaJBIhgpEXBC8jHqXLloS9b4EkKf3WILE8=.pem", + "location": "security-state-staging/intermediates/08ed8214-4b33-43b4-8ba3-b97456144534.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bQEpVS/P2TaJBIhgpEXBC8jHqXLloS9b4EkKf3WILE8=", + "crlite_enrolled": false, + "id": "5ac2630d-d43f-4a21-8de4-363eea88f44e", + "last_modified": 1666727874755 + }, + { + "schema": 1666727450612, + "derHash": "wzO2FjiwMV+oAcziHMTqlu9/ZaOZlFAYapnRm7IBKPc=", + "subject": "CN=Yandex CA,OU=Yandex Certification Authority,O=Yandex LLC,C=RU", + "subjectDN": "MF8xCzAJBgNVBAYTAlJVMRMwEQYDVQQKEwpZYW5kZXggTExDMScwJQYDVQQLEx5ZYW5kZXggQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxEjAQBgNVBAMTCVlhbmRleCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "af258c6dfdfa092e5143efddb130a9995d357d1ad61ae4f796479ef04168a7a4", + "size": 1674, + "filename": "LNFe-yc4_NZbJVynpxAeAd-brU3EPwGbtwF6VeUjI_Y=.pem", + "location": "security-state-staging/intermediates/479968c7-7027-4f7e-8b5c-3222fd1e1b98.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LNFe+yc4/NZbJVynpxAeAd+brU3EPwGbtwF6VeUjI/Y=", + "crlite_enrolled": false, + "id": "bf9d3f01-d8b6-4d86-8653-747e081c93a4", + "last_modified": 1666727874741 + }, + { + "schema": 1666727417176, + "derHash": "dcWz8B/R9RosRHq3x4XXLmn6nEcsCFcefq3zuOq65ww=", + "subject": "CN=Entrust Certification Authority - L1M,OU=See www.entrust.net/legal-terms+OU=(c) 2014 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTQgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFN", + "whitelist": false, + "attachment": { + "hash": "0f6a7a76e82068b37ee6c3f29a6d0c18baf727d5d7df2bb42b93f0257d84d61a", + "size": 1853, + "filename": "VYZwGiJkq3NNo1YRI2RGiSTI1mqTWG8zDcRf1_KAN6I=.pem", + "location": "security-state-staging/intermediates/4c299a33-dc8e-4885-82a2-d1b50cc80a72.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VYZwGiJkq3NNo1YRI2RGiSTI1mqTWG8zDcRf1/KAN6I=", + "crlite_enrolled": false, + "id": "d372a7cc-1a5d-4429-8b38-67272cb151ed", + "last_modified": 1666727874728 + }, + { + "schema": 1666727399101, + "derHash": "98fij7XnnzFKqsa7upMvFeGnIGn0NdTJ5wf5PKFILuM=", + "subject": "CN=Qualified e-Szigno TLS CA 2018,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHlF1YWxpZmllZCBlLVN6aWdubyBUTFMgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "f62124b291cbf9f0ce787625de446e21f2feb0426a05212dbc6111779c02cb02", + "size": 2231, + "filename": "B-kJdgVu-sQKTRurO5lOkBlDQ-2-UfVcgWNPVcJ2soQ=.pem", + "location": "security-state-staging/intermediates/b048750f-1cf2-47ec-9890-3fbaec58d1eb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B+kJdgVu+sQKTRurO5lOkBlDQ+2+UfVcgWNPVcJ2soQ=", + "crlite_enrolled": false, + "id": "eb56311d-499d-4871-aa3c-4d3359fc4ec0", + "last_modified": 1666727874713 + }, + { + "schema": 1666727402518, + "derHash": "4fLpUAD4FeEcgUkEMLXQLI2B0NJWyF32i1FtbCd2GSY=", + "subject": "CN=SECOM Passport for Web EV 2.0 CA,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MF8xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSkwJwYDVQQDEyBTRUNPTSBQYXNzcG9ydCBmb3IgV2ViIEVWIDIuMCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "11b2cc7c19c9ea658d73d93b55ba2118401c64553f83df52d6c23d8bb092eba4", + "size": 1618, + "filename": "Wa2FjlVfGKwvkiH0LYWh-y-ihHlaTmVQ-gqZEsR3RwY=.pem", + "location": "security-state-staging/intermediates/13abab2d-5558-442e-9c8c-9f54f283182e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wa2FjlVfGKwvkiH0LYWh+y+ihHlaTmVQ+gqZEsR3RwY=", + "crlite_enrolled": false, + "id": "7310f222-43d0-494d-aa65-443d9a816261", + "last_modified": 1666727874698 + }, + { + "schema": 1666727340433, + "derHash": "ykOJyJ3fwxvsJsdLRKhJjFiy2DhRb6AbFPE5NinlikA=", + "subject": "CN=AffirmTrust Certificate Authority - DV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGCMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0FmZmlybVRydXN0IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIERWMQ==", + "whitelist": false, + "attachment": { + "hash": "24062c77649490466b59b727862356b7b091396ef80b8d336e7a301146304ddf", + "size": 1662, + "filename": "68BRY3XqvlKubfNnBJ64F_17BHUniDemUwxZNNvfdaA=.pem", + "location": "security-state-staging/intermediates/4683bd6d-bd49-4e42-b154-69a18d94d8c8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "68BRY3XqvlKubfNnBJ64F/17BHUniDemUwxZNNvfdaA=", + "crlite_enrolled": false, + "id": "dcda7a02-af57-41d8-9206-72360ea3f7c1", + "last_modified": 1666727874684 + }, + { + "schema": 1666727381008, + "derHash": "D2ctkqCwbO6UjwOyclAmAsbjfSoq1pSjHV3jExlukoI=", + "subject": "CN=Certum Digital Identification CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGKMQswCQYDVQQGEwJQTDEiMCAGA1UECgwZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECwweQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDDCVDZXJ0dW0gRGlnaXRhbCBJZGVudGlmaWNhdGlvbiBDQSBTSEEy", + "whitelist": false, + "attachment": { + "hash": "dba1fd363ac38cad212628cf7ec587f5e6478eb1b348d0b671e2f4ecad1116ce", + "size": 1731, + "filename": "QdcqIv2OPNsD75w32d9tMDyai2-ClzSRt_49i0WYwbU=.pem", + "location": "security-state-staging/intermediates/a1b2a835-7618-44f6-a32a-dbdc1ae40941.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QdcqIv2OPNsD75w32d9tMDyai2+ClzSRt/49i0WYwbU=", + "crlite_enrolled": false, + "id": "8110be87-231b-42a7-80a7-db15071d3b5a", + "last_modified": 1666727874670 + }, + { + "schema": 1666727431536, + "derHash": "bEfTZcE7yMw9be9djwerjb6jyNSUXWUaqYVKnJo8xxw=", + "subject": "CN=Certum Extended Validation CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGHMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSswKQYDVQQDEyJDZXJ0dW0gRXh0ZW5kZWQgVmFsaWRhdGlvbiBDQSBTSEEy", + "whitelist": false, + "attachment": { + "hash": "0c8cfb3c29a92dbd2d2289a8b8babc8b12040ee59493a752f19f90d04a232032", + "size": 1731, + "filename": "HYLX8fqXEbN3Nn7-5kDCagbbzZnSoXsP-rAxbZJ4isM=.pem", + "location": "security-state-staging/intermediates/d9eaba09-d66e-487d-9974-8980bc0a838e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HYLX8fqXEbN3Nn7+5kDCagbbzZnSoXsP+rAxbZJ4isM=", + "crlite_enrolled": false, + "id": "53f85a16-b4c6-49b5-b7ae-79f9a96b81be", + "last_modified": 1666727874656 + }, + { + "schema": 1666727400963, + "derHash": "qV8jtSrxCJWIb7ZTI9KamHbqfTlvgF5MooDVYcJuPa0=", + "subject": "CN=Certyfikat SSL,O=home.pl S.A.,C=PL", + "subjectDN": "MD0xCzAJBgNVBAYTAlBMMRUwEwYDVQQKDAxob21lLnBsIFMuQS4xFzAVBgNVBAMMDkNlcnR5ZmlrYXQgU1NM", + "whitelist": false, + "attachment": { + "hash": "45a923fe0feea027c9100ec2d0aa873a99af0b2acd334a796bd4d4b8399213b0", + "size": 1565, + "filename": "5PJ4jB1sjJ5kx3a0Ds2noJPc07ZOb_y1vR1tN1t5FsQ=.pem", + "location": "security-state-staging/intermediates/907a7262-b883-4896-bb78-a84247ab3ab1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5PJ4jB1sjJ5kx3a0Ds2noJPc07ZOb/y1vR1tN1t5FsQ=", + "crlite_enrolled": false, + "id": "2c153960-0405-4d5f-8576-a05ed26e5b62", + "last_modified": 1666727874628 + }, + { + "schema": 1666727390390, + "derHash": "Oyffndk8ESqgiwYqauOXP355pRkdfpuV1wgXgODWrOo=", + "subject": "CN=certSIGN Enterprise CA Class 3 G2,OU=certSIGN Enterprise CA Class 3 G2,O=certSIGN,C=RO", + "subjectDN": "MHgxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEqMCgGA1UECxMhY2VydFNJR04gRW50ZXJwcmlzZSBDQSBDbGFzcyAzIEcyMSowKAYDVQQDEyFjZXJ0U0lHTiBFbnRlcnByaXNlIENBIENsYXNzIDMgRzI=", + "whitelist": false, + "attachment": { + "hash": "b4245e3bdf682d3b5991b85df5dc0d5234efbfa10c3e6191331eafc4de6d44b3", + "size": 1638, + "filename": "NgZU2Z5HKX3Ynzv2v13QXVNlTGQrS2rrCCv2W4gJn_o=.pem", + "location": "security-state-staging/intermediates/50534563-bc40-4117-a35e-c5f2825173e5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NgZU2Z5HKX3Ynzv2v13QXVNlTGQrS2rrCCv2W4gJn/o=", + "crlite_enrolled": false, + "id": "44fdaa59-55ab-4b5a-9584-878de01ee51f", + "last_modified": 1666727874614 + }, + { + "schema": 1666727411509, + "derHash": "jxn/4C/Hle1wdl0UNq3fdy/g8Hc9pDbtvbQqLjDi6Cg=", + "subject": "CN=GlobalSign ECC EV SSL CA 2018,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIEVDQyBFViBTU0wgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "f363653deddf2dfcf9778cf76ffb95c3df7a78ee504183ca843b0302e14a7651", + "size": 1102, + "filename": "OD_WDbD3VsfMwwNzzy9MWd9JXppKB77Vb3ST2wn9meg=.pem", + "location": "security-state-staging/intermediates/44d3815f-42a9-4cec-bf3e-876bdcd1f014.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OD/WDbD3VsfMwwNzzy9MWd9JXppKB77Vb3ST2wn9meg=", + "crlite_enrolled": false, + "id": "0fd62746-093f-43ab-bba0-8f8ee357a4e7", + "last_modified": 1666727874601 + }, + { + "schema": 1666727331976, + "derHash": "h8cVU0Res8M8PgcQcRuZ6cd3PwTZGsOKn0wILuJBAeo=", + "subject": "CN=GlobalSign ECC OV SSL CA 2018,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1HbG9iYWxTaWduIEVDQyBPViBTU0wgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "390a77060a6993d4717518c954c7ca27a2d0f5fbf0e41a6b5af1c798425dcc9c", + "size": 1102, + "filename": "KJpedoXG-Rd6IJnYeOJjxUjlaDEDI8K1vCBBgzeJkC4=.pem", + "location": "security-state-staging/intermediates/87b2c54d-9bb3-4349-af42-cefe085827c4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KJpedoXG+Rd6IJnYeOJjxUjlaDEDI8K1vCBBgzeJkC4=", + "crlite_enrolled": false, + "id": "9054318d-a1b1-4bd8-b4ea-20ce57f44d1f", + "last_modified": 1666727874587 + }, + { + "schema": 1666727337174, + "derHash": "frRF6jikamQR8KEqDhbNPQc+kbElZ5l3BB/7zoO+PBo=", + "subject": "CN=OKCERT R4 DV SSL CA G2,OU=Kingnettech,O=Kingnet Information Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MHcxCzAJBgNVBAYTAkNOMTEwLwYDVQQKDChLaW5nbmV0IEluZm9ybWF0aW9uIFRlY2hub2xvZ3kgQ28uLCBMdGQuMRQwEgYDVQQLDAtLaW5nbmV0dGVjaDEfMB0GA1UEAwwWT0tDRVJUIFI0IERWIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "857ccc0777952ba7b08a36e3f5c6ace78f7260f270402953c6a16a68d3690e2a", + "size": 1776, + "filename": "Y9JE2NiQ3ArheJd2k5RFD7JxnbQzgfJYS2Z0JfG64Uo=.pem", + "location": "security-state-staging/intermediates/4b034577-3c79-416d-9af6-374a4c0b5405.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y9JE2NiQ3ArheJd2k5RFD7JxnbQzgfJYS2Z0JfG64Uo=", + "crlite_enrolled": false, + "id": "9dfe1a67-5cce-4d31-a077-99d8791d279d", + "last_modified": 1666727874573 + }, + { + "schema": 1666727376029, + "derHash": "YJkw64B61CCv2iqKphtnSDA5FozXZuCZQqSL/n873BA=", + "subject": "OU=Public Certification Authority - G2,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MGAxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEsMCoGA1UECwwjUHVibGljIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "b2b74d8c5d2ce4f283391974773f823f1441c476105a4e78f1b99db3e02f9528", + "size": 2117, + "filename": "tnFqrstaexEa6Zz-CVGGozhI8IF1FafM6uzX3zzHrUI=.pem", + "location": "security-state-staging/intermediates/b062306c-bad7-433f-bcc1-81512b1194eb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tnFqrstaexEa6Zz+CVGGozhI8IF1FafM6uzX3zzHrUI=", + "crlite_enrolled": false, + "id": "4835ad01-5f2c-4639-abc1-05d2b5279fb5", + "last_modified": 1666727874544 + }, + { + "schema": 1666727343840, + "derHash": "LeYg8tEgCqkLFsPM9nD9ftFDeasG+osDHP742gUepaI=", + "subject": "CN=ACCVCA-120,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMjAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "edd78aa1bd6255d7f1003657a126700b8500239227d9aba192fc06dd0a8d1d6b", + "size": 2686, + "filename": "XH3Wh5FuH9ZeZcCdLZM0VbsyQg3aeFV1wujvNEYNRQ8=.pem", + "location": "security-state-staging/intermediates/65d00951-3633-4568-89cc-188eb82084b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XH3Wh5FuH9ZeZcCdLZM0VbsyQg3aeFV1wujvNEYNRQ8=", + "crlite_enrolled": false, + "id": "cf7c93de-8a6e-4b12-b0aa-7dc5ef535f84", + "last_modified": 1666727874530 + }, + { + "schema": 1666727417343, + "derHash": "zXMOU/5zLY8J1ows2/0e/ZioVfXhSQIcAcsu7h2Bhuc=", + "subject": "CN=COMODO RSA Extended Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "7d2f460009e99bedb3ed4c894ebf320d7eed0cf7460a7d81e4eb7dcfd8793cf7", + "size": 2158, + "filename": "Fbr_5aSOo4KRal8YE49t4lc76IOnK_oto9NWV1cSKWM=.pem", + "location": "security-state-staging/intermediates/0e5c6bab-00ba-4b47-8e40-654d140bf187.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Fbr/5aSOo4KRal8YE49t4lc76IOnK/oto9NWV1cSKWM=", + "crlite_enrolled": false, + "id": "5db685fc-9b69-480f-8fc6-f5e4386636ad", + "last_modified": 1666727874516 + }, + { + "schema": 1666727351122, + "derHash": "OwzCA4StfyTrQ48rgMY+vgA/fyFbiHfkGOuwSEAo21c=", + "subject": "CN=Entrust Certification Authority - L1K,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFL", + "whitelist": false, + "attachment": { + "hash": "87d3147517902aed8e819b14b1dacf02ee1294c4c3b31b4bb42a0369c1435b9f", + "size": 1711, + "filename": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=.pem", + "location": "security-state-staging/intermediates/5cc18fd8-15e6-494b-a189-5e1a98f43e77.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=", + "crlite_enrolled": false, + "id": "ca408a50-c8da-4bfe-967d-1c1fd0bb946c", + "last_modified": 1666727874488 + }, + { + "schema": 1666727347154, + "derHash": "9cLyPGUY+dGbbzm+rqT7rhADG6ncmFzhVjpSDaCtQRY=", + "subject": "CN=Entrust Certification Authority - L1K,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFL", + "whitelist": false, + "attachment": { + "hash": "fe5ad678578f0f457c9742e59bdd7a274fc2a2703d89bb47f109be35c9fb47c2", + "size": 1796, + "filename": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=.pem", + "location": "security-state-staging/intermediates/a8904bd0-aa03-46a0-bade-1596b9597a4c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=", + "crlite_enrolled": false, + "id": "9e54305c-cd5d-4716-a6ac-2c9ac161fb04", + "last_modified": 1666727874460 + }, + { + "schema": 1666727428816, + "derHash": "C0Bc/ppr6wmP+5aRIcX2cQ8/f6nqEBpkGPevIB09OTg=", + "subject": "CN=Apple Public Server RSA CA 12 - G1,O=Apple Inc.,ST=California,C=US", + "subjectDN": "MGQxKzApBgNVBAMTIkFwcGxlIFB1YmxpYyBTZXJ2ZXIgUlNBIENBIDEyIC0gRzExEzARBgNVBAoTCkFwcGxlIEluYy4xEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "2f8960fd8a1b0bc311f2c5d7bb7825254affcf347c165514fa071119dc28f794", + "size": 1642, + "filename": "1CC6SL5QjEUUEr5JiV4Zw8QxiSkGVmp2CRJ4mm1IhKU=.pem", + "location": "security-state-staging/intermediates/eb7ea53e-f578-4b8a-913a-dd87bcd7ab01.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1CC6SL5QjEUUEr5JiV4Zw8QxiSkGVmp2CRJ4mm1IhKU=", + "crlite_enrolled": false, + "id": "6acdc223-21b9-4ff8-847f-e22f72f1795a", + "last_modified": 1666727874418 + }, + { + "schema": 1666727430188, + "derHash": "lqWizTmADPtqKoMO5S3PR/uwD/GwMgTbNpFc6jHxM0I=", + "subject": "CN=GDCA TrustAUTH R4 EV SSL CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG4xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJDAiBgNVBAMMG0dEQ0EgVHJ1c3RBVVRIIFI0IEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d72e2007f8b0bdbb9bfdfca3bc7bdf7eebe0d9a1b5b3e0aa3c49210a42752f35", + "size": 2056, + "filename": "LA_cQZEhZA_J3pK28of_Jf7Wn0WmfSQvp4eYJW5o8NQ=.pem", + "location": "security-state-staging/intermediates/2b13103f-f111-4488-951a-d948d81a84f6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LA/cQZEhZA/J3pK28of/Jf7Wn0WmfSQvp4eYJW5o8NQ=", + "crlite_enrolled": false, + "id": "2a556f13-fd00-4e58-82e2-6de75321c936", + "last_modified": 1666727874404 + }, + { + "schema": 1666727395436, + "derHash": "aB68GCKwebl+BATkaH2bbAwIksgg9Vc4ooKq5iUpvdg=", + "subject": "CN=Entrust Certification Authority - QTSP1,O=Entrust Datacard Europe S.L.,C=ES", + "subjectDN": "MIGAMTAwLgYDVQQDDCdFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gUVRTUDExGDAWBgNVBGEMD1ZBVEVTLUI4MTE4ODA0NzElMCMGA1UECgwcRW50cnVzdCBEYXRhY2FyZCBFdXJvcGUgUy5MLjELMAkGA1UEBhMCRVM=", + "whitelist": false, + "attachment": { + "hash": "1b36e574afaad2d084671e777c7351c3fb5ab139919e47a5ad362e0629f3d0a9", + "size": 1784, + "filename": "aZtZPtGaXbm8qg3lvpcFEicPNLfVwGoRKVgV17tW99k=.pem", + "location": "security-state-staging/intermediates/ba3c2414-b8f6-4b6f-b58a-13bd454deef5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aZtZPtGaXbm8qg3lvpcFEicPNLfVwGoRKVgV17tW99k=", + "crlite_enrolled": false, + "id": "d64bc41f-40a9-4404-b8e7-977885a8b020", + "last_modified": 1666727874390 + }, + { + "schema": 1666727414953, + "derHash": "6k7i+qV65LU5tjl3/luyBbavsy96c7KzY+S+As2Kkek=", + "subject": "CN=AffirmTrust Certificate Authority - OV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGCMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0FmZmlybVRydXN0IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIE9WMQ==", + "whitelist": false, + "attachment": { + "hash": "fc3aae931bd9e488d29bffc6c6628b5ba9b6c606359b942aad8056a2af13c8c0", + "size": 1662, + "filename": "RM5J-l4IeDRKjL4ZuWGG2Jt3Wae0v4pWKttrQv-DsGM=.pem", + "location": "security-state-staging/intermediates/8c875b16-e424-4471-88e1-1af28bb1e98b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RM5J+l4IeDRKjL4ZuWGG2Jt3Wae0v4pWKttrQv+DsGM=", + "crlite_enrolled": false, + "id": "b68b971e-8343-4eb4-a930-a11700292076", + "last_modified": 1666727874361 + }, + { + "schema": 1666727441777, + "derHash": "RWO5NuNauXV29a7xk12bx+mXeEHwVzvS4WvKyVNKavk=", + "subject": "CN=AffirmTrust Certificate Authority - DV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGCMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0FmZmlybVRydXN0IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIERWMQ==", + "whitelist": false, + "attachment": { + "hash": "f469b6a26ab18f58e6fd0cfbda5e03e69cf3d3264a9c4790878f903d60151468", + "size": 1674, + "filename": "68BRY3XqvlKubfNnBJ64F_17BHUniDemUwxZNNvfdaA=.pem", + "location": "security-state-staging/intermediates/62e47fee-8839-40c8-9719-edf4a5d2615c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "68BRY3XqvlKubfNnBJ64F/17BHUniDemUwxZNNvfdaA=", + "crlite_enrolled": false, + "id": "dd1c7414-0b47-4e26-8b61-6e686bc90126", + "last_modified": 1666727874346 + }, + { + "schema": 1666727380667, + "derHash": "9nh/qMrX0sJ5oDdL+1A4B8xq3MfCNwknPdWkBH0a73g=", + "subject": "CN=SZCA DV SSL CA,O=Shenzhen Digital Certificate Authority Center Co.\\, Ltd,C=CN", + "subjectDN": "MGcxCzAJBgNVBAYTAkNOMT8wPQYDVQQKDDZTaGVuemhlbiBEaWdpdGFsIENlcnRpZmljYXRlIEF1dGhvcml0eSBDZW50ZXIgQ28uLCBMdGQxFzAVBgNVBAMMDlNaQ0EgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "a9b2ca0380643dfb1ea6c1f34c39fa58988e778b07ffa23714878880023314a7", + "size": 1756, + "filename": "PPn3ERZq7pmEq4QyyPxBu8LvJxjiPlPsQLVTHYbOwbo=.pem", + "location": "security-state-staging/intermediates/da53fafc-fb80-458f-bfea-530aa2404b02.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PPn3ERZq7pmEq4QyyPxBu8LvJxjiPlPsQLVTHYbOwbo=", + "crlite_enrolled": false, + "id": "9edde23a-7218-41f2-abd4-607c025433f0", + "last_modified": 1666727874332 + }, + { + "schema": 1666727424108, + "derHash": "z4iRXPmWkywrTL4wOQdtEZu3KLTzHkm2OlAi/mVImhI=", + "subject": "CN=AffirmTrust Extended Validation CA - EV1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGDMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKEFmZmlybVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBFVjE=", + "whitelist": false, + "attachment": { + "hash": "59f97b75ff4aa85380593bee67266d9df3536810543f8906024dce481dec03a9", + "size": 1662, + "filename": "xAD7ZgT8h1RQOQsz_6DxwKe17_oiwt-Jy6y1G1Akw00=.pem", + "location": "security-state-staging/intermediates/2d7384a5-c21e-4c9c-bab1-09bac6329f82.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xAD7ZgT8h1RQOQsz/6DxwKe17/oiwt+Jy6y1G1Akw00=", + "crlite_enrolled": false, + "id": "de2d8ef8-81da-4d27-98dc-2aac5604a760", + "last_modified": 1666727874316 + }, + { + "schema": 1666727444992, + "derHash": "1sP8STus0d+KG6MPSuJiVLKkUo5IdggerMahagkKo2o=", + "subject": "CN=Entrust Certification Authority - L1K,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFL", + "whitelist": false, + "attachment": { + "hash": "baf3c774613f8942857b2ad6d40766ac8a7a67fdbc624e9c4a17b50ca45bddd5", + "size": 1792, + "filename": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=.pem", + "location": "security-state-staging/intermediates/f8d538de-5cc7-4991-8270-0a2d1d7d3b8b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "980Ionqp3wkYtN9SZVgMzuWQzJta1nfxNPwTem1X0uc=", + "crlite_enrolled": false, + "id": "b1366715-e202-4ce8-9018-b39313524fe8", + "last_modified": 1666727874302 + }, + { + "schema": 1666727405122, + "derHash": "4MLr0fa61P6q4xoxB+aavukC2zi5374z8FcL2jSUwgo=", + "subject": "CN=GlobalSign CloudSSL CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENsb3VkU1NMIENBIC0gU0hBMjU2IC0gRzM=", + "whitelist": false, + "attachment": { + "hash": "5708992fbcf9481fd4654b18ba083f1c9225a94e396e861bbd592d38a6bee662", + "size": 1626, + "filename": "-VZJxHgrOOiVyUxgMRbfoo-GIWrMKd4aellBBHtBcKg=.pem", + "location": "security-state-staging/intermediates/48379e9d-8361-44d6-a4a7-eb48ae824248.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+VZJxHgrOOiVyUxgMRbfoo+GIWrMKd4aellBBHtBcKg=", + "crlite_enrolled": false, + "id": "d57a5e4c-dfdb-4c21-809b-ec5a60174f9c", + "last_modified": 1666727874288 + }, + { + "schema": 1666727351287, + "derHash": "cNud7ZRN011HTqFf8qpOJfOTqJPs2lQ1nTBbwxlkmBc=", + "subject": "CN=Apple Public Server ECC CA 12 - G1,O=Apple Inc.,ST=California,C=US", + "subjectDN": "MGQxKzApBgNVBAMTIkFwcGxlIFB1YmxpYyBTZXJ2ZXIgRUNDIENBIDEyIC0gRzExEzARBgNVBAoTCkFwcGxlIEluYy4xEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "7b664a38a0036f3f518be6f71fd3dccba44bd002424257ded7f358d26fc7fc10", + "size": 1366, + "filename": "MxdHGRZ7LjsiyER0ypQ2XivrmpBgAU9_a6gf1ko2sQk=.pem", + "location": "security-state-staging/intermediates/58a031a8-c0ba-426e-a1de-b8e9854cc946.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MxdHGRZ7LjsiyER0ypQ2XivrmpBgAU9/a6gf1ko2sQk=", + "crlite_enrolled": false, + "id": "20411773-904e-4e23-bf8b-23aa4d20b75d", + "last_modified": 1666727874274 + }, + { + "schema": 1666727449352, + "derHash": "XCnb6pt8yLAkGPKMHIc2398XBmXQmO9oHZA752mH0kk=", + "subject": "CN=Apple IST CA 8 - G1,OU=Certification Authority,O=Apple Inc.,C=US", + "subjectDN": "MGIxHDAaBgNVBAMME0FwcGxlIElTVCBDQSA4IC0gRzExIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "2d9d379dbd5d0002b3c68677e98b81199b8b1b9abba7cf87839774bbeb1339c0", + "size": 1386, + "filename": "4k-OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=.pem", + "location": "security-state-staging/intermediates/ce55fa72-c68e-4449-9da1-b8dd4268c16b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4k+OjCGF2i9eiNRXnoF8R79ur7yFBfD5YP1aDfRHOtM=", + "crlite_enrolled": false, + "id": "d5fc3ad9-0ada-4751-b516-44478b4bd331", + "last_modified": 1666727874260 + }, + { + "schema": 1666727417691, + "derHash": "a6qwxDPXef1qS21W1jBNXm6l3mif41pDA4pAKPNF32A=", + "subject": "CN=TrustID Server CA O1,OU=TrustID Server,O=IdenTrust,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxFzAVBgNVBAsTDlRydXN0SUQgU2VydmVyMR0wGwYDVQQDExRUcnVzdElEIFNlcnZlciBDQSBPMQ==", + "whitelist": false, + "attachment": { + "hash": "ff57c4d65b0b7547b7257b720248edc7df260a756f562751560760f5692ed85a", + "size": 2398, + "filename": "TpfnK_TFKAbtiqXG0BETu96LfmJwwZKETBR58_SIZnk=.pem", + "location": "security-state-staging/intermediates/cb7629a5-e914-4dfc-8b42-a9f8565378d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TpfnK/TFKAbtiqXG0BETu96LfmJwwZKETBR58/SIZnk=", + "crlite_enrolled": false, + "id": "74822f06-6362-433e-a670-6209b0f88ebc", + "last_modified": 1666727874216 + }, + { + "schema": 1666727400615, + "derHash": "pXZN/QRUrToGQouQfbiqQ22mMXIJ6v1h3ULlaud/mxM=", + "subject": "CN=vTrus DV SSL CA G1,O=iTrusChina Co.\\, Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRpVHJ1c0NoaW5hIENvLiwgTHRkLjEbMBkGA1UEAwwSdlRydXMgRFYgU1NMIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "2139889982ea46a63a464eeadd8924a0691d8a5aafe08c973053fa5c343bb807", + "size": 1715, + "filename": "Lh5Dl4rrZsfMQM9Q9U2BcmEKABgw3NzU5omS-Hb7j-E=.pem", + "location": "security-state-staging/intermediates/5381eea4-5663-4fb1-8a43-154a02daae8e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Lh5Dl4rrZsfMQM9Q9U2BcmEKABgw3NzU5omS+Hb7j+E=", + "crlite_enrolled": false, + "id": "74de0e26-7443-4e36-b943-532a38006251", + "last_modified": 1666727874202 + }, + { + "schema": 1666727421848, + "derHash": "7vkGZCTCNQjpxl+EZxsU4W2hvsNY51/GOC7KBwroYb4=", + "subject": "CN=Abitab SSL Organization Validated,OU=IDdigital,O=Abitab S.A.,C=UY", + "subjectDN": "MGMxCzAJBgNVBAYTAlVZMRQwEgYDVQQKDAtBYml0YWIgUy5BLjESMBAGA1UECwwJSURkaWdpdGFsMSowKAYDVQQDDCFBYml0YWIgU1NMIE9yZ2FuaXphdGlvbiBWYWxpZGF0ZWQ=", + "whitelist": false, + "attachment": { + "hash": "c30af6395f2acc919d52e2b39391c424dfd9016f96111216e9bc5fd3dc34421c", + "size": 1707, + "filename": "E4GLDtTEwO7UCeAtO8ibbOu6FLZxSIUC_fBzIbM5mV4=.pem", + "location": "security-state-staging/intermediates/2cbc872a-e92a-4c95-ad98-01379cbdc630.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "E4GLDtTEwO7UCeAtO8ibbOu6FLZxSIUC/fBzIbM5mV4=", + "crlite_enrolled": false, + "id": "d02db623-261b-4357-8746-76ac75242c86", + "last_modified": 1666727874188 + }, + { + "schema": 1666727425803, + "derHash": "i7L2iD/tKJpSG6J8R4SClQh04UPKzOxvwCWZDAxGgT4=", + "subject": "CN=HydrantID Server CA O1,OU=HydrantID Trusted Certificate Service,O=IdenTrust,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxLjAsBgNVBAsTJUh5ZHJhbnRJRCBUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2UxHzAdBgNVBAMTFkh5ZHJhbnRJRCBTZXJ2ZXIgQ0EgTzE=", + "whitelist": false, + "attachment": { + "hash": "5c37cd80ee6407081923d28c6774e88ec067942ade58ad3454319f095efcf4e3", + "size": 2431, + "filename": "EeppXTka8xyJcDQhuKdo9XL4LWROHEY7l1qZG55lMIo=.pem", + "location": "security-state-staging/intermediates/fc98d0bf-3fd0-4144-8840-b8ab888570c1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EeppXTka8xyJcDQhuKdo9XL4LWROHEY7l1qZG55lMIo=", + "crlite_enrolled": false, + "id": "5b05b0f5-6f11-4694-90c1-40b4a6d81b2c", + "last_modified": 1666727874175 + }, + { + "schema": 1666727330809, + "derHash": "vMOlTXCDEWMWzgO5pAyjbzNvSDmsWBhi1DEahPcBUU0=", + "subject": "CN=OKCERT R4 OV SSL CA G2,OU=Kingnettech,O=Kingnet Information Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MHcxCzAJBgNVBAYTAkNOMTEwLwYDVQQKDChLaW5nbmV0IEluZm9ybWF0aW9uIFRlY2hub2xvZ3kgQ28uLCBMdGQuMRQwEgYDVQQLDAtLaW5nbmV0dGVjaDEfMB0GA1UEAwwWT0tDRVJUIFI0IE9WIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "579d6f5ecf8bcd46d62a770353e586d532320b1e7caea44c51a03952e3fdadde", + "size": 1776, + "filename": "J_LNyLPuoW1JcAhG6eG-bHMYhGpN5b-M_HHHd56hnzU=.pem", + "location": "security-state-staging/intermediates/a65aafdc-334e-4b10-b8d7-597fbfe091bc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "J/LNyLPuoW1JcAhG6eG+bHMYhGpN5b+M/HHHd56hnzU=", + "crlite_enrolled": false, + "id": "101d25a5-7aab-4b79-9230-e4cb060e9e42", + "last_modified": 1666727874161 + }, + { + "schema": 1666727419218, + "derHash": "zeI6UjA8PKZ6S7vMlYL/XJIDqpjLDzhxOTCM4iiVBqI=", + "subject": "CN=AffirmTrust Extended Validation CA - EVEC1,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGFMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMzAxBgNVBAMTKkFmZmlybVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBFVkVDMQ==", + "whitelist": false, + "attachment": { + "hash": "865dd39def93692e37eef786ad2c8cc23e7d0c975e5371aadd94657e67cbbcb9", + "size": 1223, + "filename": "EJPx6trc98AgGgNEdBceeQPzoTVw1_xu9uLPoj5Wczw=.pem", + "location": "security-state-staging/intermediates/48c70d0d-3354-46cd-857a-bedd9c0140c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EJPx6trc98AgGgNEdBceeQPzoTVw1/xu9uLPoj5Wczw=", + "crlite_enrolled": false, + "id": "07decb3e-5474-49fb-8e4b-b0ce15e562f9", + "last_modified": 1666727874147 + }, + { + "schema": 1666727356259, + "derHash": "7MKVtt3NCEunF5+1O90dQiy2wKjZTxVNSlsXeAtyee0=", + "subject": "CN=Amazon,OU=Server CA 3B,O=Amazon,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xFTATBgNVBAsTDFNlcnZlciBDQSAzQjEPMA0GA1UEAxMGQW1hem9u", + "whitelist": false, + "attachment": { + "hash": "c7c4797af7be4621b3d92ccb6bd9cb9b0dd0a450c089b66be3ba98c8bc4186e3", + "size": 1008, + "filename": "s5SRPmhQ_9M4pBTJFJbNff5De6KuZpZr6r1_ZkgUbbE=.pem", + "location": "security-state-staging/intermediates/72c3d6bb-f1e7-4495-af68-0ba087eda278.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "s5SRPmhQ/9M4pBTJFJbNff5De6KuZpZr6r1/ZkgUbbE=", + "crlite_enrolled": false, + "id": "302e34c5-2646-4c4a-ac02-84ccf409fc91", + "last_modified": 1666727874133 + }, + { + "schema": 1666727348528, + "derHash": "nfd0iMS3SsMuPOxMZD0AHVw7q/pAAf/Rk9yhDIvlyzo=", + "subject": "CN=AffirmTrust Extended Validation CA - EV2,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGDMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKEFmZmlybVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBFVjI=", + "whitelist": false, + "attachment": { + "hash": "89053e842a9e8970354492a7c4548abec7c0be523c780e0f953ea552f30a541f", + "size": 2012, + "filename": "x6O8qvzc0zILZCc4XHPUu67PSku6NJHUaXQY4KvAUgg=.pem", + "location": "security-state-staging/intermediates/3085f02a-64cc-4c75-9005-38f7daa19fab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x6O8qvzc0zILZCc4XHPUu67PSku6NJHUaXQY4KvAUgg=", + "crlite_enrolled": false, + "id": "6b507c8d-2732-4c9b-9a07-d2512c0e2117", + "last_modified": 1666727874119 + }, + { + "schema": 1666727373293, + "derHash": "twC6Sa9NGecvsVotrDwhO6RMMZ+n2pJ3KzaC4St4EJM=", + "subject": "CN=AffirmTrust Extended Validation CA - EV3,OU=See www.affirmtrust.com/repository,O=AffirmTrust,C=CA", + "subjectDN": "MIGDMQswCQYDVQQGEwJDQTEUMBIGA1UEChMLQWZmaXJtVHJ1c3QxKzApBgNVBAsTIlNlZSB3d3cuYWZmaXJtdHJ1c3QuY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKEFmZmlybVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBFVjM=", + "whitelist": false, + "attachment": { + "hash": "2e254ce9b0bd7ecaa13ed912c586e0853458c939d61e8f564883c7f10f882aab", + "size": 1674, + "filename": "-TrM-YTWywZV8UwhobD51F1gyN0R9i8HSXRhwDUA_PQ=.pem", + "location": "security-state-staging/intermediates/1a96dd1e-3da0-45c3-af06-b3f0b4132469.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+TrM+YTWywZV8UwhobD51F1gyN0R9i8HSXRhwDUA/PQ=", + "crlite_enrolled": false, + "id": "5c30def2-02f2-4f59-9e75-fb7cec21fda7", + "last_modified": 1666727874105 + }, + { + "schema": 1666727335328, + "derHash": "5n0YY5NnxbKb96VoO1aw8MIxVcj+lFK8/lFoFDYCN0I=", + "subject": "CN=Abitab SSL Domain Validated,OU=IDdigital,O=Abitab S.A.,C=UY", + "subjectDN": "MF0xCzAJBgNVBAYTAlVZMRQwEgYDVQQKDAtBYml0YWIgUy5BLjESMBAGA1UECwwJSURkaWdpdGFsMSQwIgYDVQQDDBtBYml0YWIgU1NMIERvbWFpbiBWYWxpZGF0ZWQ=", + "whitelist": false, + "attachment": { + "hash": "8256a9036481e14355f63a485a8508dd9d4b897eba0e2777a7ceada7c537c808", + "size": 1699, + "filename": "9wg1kxbl_ew-5-7tpattPPu_j9bgDctkucNkpOG-U6s=.pem", + "location": "security-state-staging/intermediates/8b5bc65e-d9cc-4c82-9694-c4d4d3657828.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9wg1kxbl/ew+5+7tpattPPu/j9bgDctkucNkpOG+U6s=", + "crlite_enrolled": false, + "id": "1568c2be-d6ba-48ba-b4b8-ce6ec52e0dbc", + "last_modified": 1666727874091 + }, + { + "schema": 1666727348363, + "derHash": "2v0lBqfpF8Em5ZBEeUOdd5UIfwT6e75hR5Zye5oWHW8=", + "subject": "CN=vTrus OV SSL CA G1,O=iTrusChina Co.\\, Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRpVHJ1c0NoaW5hIENvLiwgTHRkLjEbMBkGA1UEAwwSdlRydXMgT1YgU1NMIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "6d43b5a68905a0f438a343d2b49648cb9b0920fd66cc6fcdd135b5e84025a94a", + "size": 1715, + "filename": "iJYYHEjYNccDf7l4rZqt8_6DdC728xy4x8RcQNOya3o=.pem", + "location": "security-state-staging/intermediates/f238a915-dd39-437d-96c4-daca3c66e661.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iJYYHEjYNccDf7l4rZqt8/6DdC728xy4x8RcQNOya3o=", + "crlite_enrolled": false, + "id": "5a1bc619-eb90-4733-b11b-42fc5acbb075", + "last_modified": 1666727874077 + }, + { + "schema": 1666727374635, + "derHash": "MJp4I7s9Hd9EpXPsC/n9UWZiX95zd2GExiIStMC06U4=", + "subject": "CN=Root Global CA - G2,O=Root Networks\\, LLC,C=US", + "subjectDN": "MEgxCzAJBgNVBAYTAlVTMRswGQYDVQQKDBJSb290IE5ldHdvcmtzLCBMTEMxHDAaBgNVBAMME1Jvb3QgR2xvYmFsIENBIC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "cfe4daa3fda4a067b507bc4e87a81144512c76604b39da965d2fd02591e76353", + "size": 1715, + "filename": "SWMEi8LaBMvhivSePD6iqXTAoaa_XCxGaHOnp0EwbRU=.pem", + "location": "security-state-staging/intermediates/21c96bd4-d994-48b4-8aaf-e1fc560e61ab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SWMEi8LaBMvhivSePD6iqXTAoaa/XCxGaHOnp0EwbRU=", + "crlite_enrolled": false, + "id": "1416d740-c734-4daa-8679-a129b8a0cdb3", + "last_modified": 1666727874063 + }, + { + "schema": 1666727336499, + "derHash": "ODkvF857aCwZjSnG5x0nQJZKIHTI0lWObP9kwngj8Sk=", + "subject": "CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "f9fb67b8408e95dec2ccd730836629e9b9d21217185bc11efea7ba000d32c9b2", + "size": 1963, + "filename": "grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=.pem", + "location": "security-state-staging/intermediates/866b660e-4e5e-4198-8a99-1bfad68e708d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=", + "crlite_enrolled": false, + "id": "11673ac8-c50e-4666-a891-ff70f87c1a73", + "last_modified": 1666727874049 + }, + { + "schema": 1666727395953, + "derHash": "ji0Hv8WRsU+jFGhuHMjZu19vjQkcBZUZSqLHWFuZGIc=", + "subject": "CN=XinChaCha Trust SSL Organization Validated,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MHoxCzAJBgNVBAYTAkNOMTYwNAYDVQQKDC1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xMzAxBgNVBAMMKlhpbkNoYUNoYSBUcnVzdCBTU0wgT3JnYW5pemF0aW9uIFZhbGlkYXRlZA==", + "whitelist": false, + "attachment": { + "hash": "ee098ec8d0e0db01810ca1c3b3201c226be5f7b8a356ca702dca8dd33aec3a43", + "size": 1780, + "filename": "RHqNaKq-7GhfAxN8i7T10nLSrr6EQCmPkPfRvnXA2S0=.pem", + "location": "security-state-staging/intermediates/5a268c9f-0c99-4ae8-8988-0610fa6a632a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RHqNaKq+7GhfAxN8i7T10nLSrr6EQCmPkPfRvnXA2S0=", + "crlite_enrolled": false, + "id": "6ee147c4-6e70-4972-8a69-4e4ff8257c86", + "last_modified": 1666727874035 + }, + { + "schema": 1666727429850, + "derHash": "JYWSjSxb/ZUuAlvRLifGd2Ikz3Uuw2LTAxzdSTUYRNQ=", + "subject": "CN=Apple Public EV Server ECC CA 1 - G1,O=Apple Inc.,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMS0wKwYDVQQDEyRBcHBsZSBQdWJsaWMgRVYgU2VydmVyIEVDQyBDQSAxIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "0c68df98e690b7945017fb11b9de0a5e05c426b30a289db35a64ca96eb1ac781", + "size": 1341, + "filename": "Lr2fY89asbE9ohd7WW8tLI0pvoPLX-Wt_N7mP-ri3c0=.pem", + "location": "security-state-staging/intermediates/934c88bb-99cf-4fc7-8742-47d2064a1e39.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Lr2fY89asbE9ohd7WW8tLI0pvoPLX+Wt/N7mP+ri3c0=", + "crlite_enrolled": false, + "id": "6cb0600d-434e-4a59-a8d8-60ea5bf117e9", + "last_modified": 1666727874021 + }, + { + "schema": 1666727402345, + "derHash": "1u8+Cevg2TcOUfXAmlMrOscNPOgiJT+fyEwo6b+lUNU=", + "subject": "CN=Apple Public EV Server RSA CA 2 - G1,O=Apple Inc.,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMS0wKwYDVQQDEyRBcHBsZSBQdWJsaWMgRVYgU2VydmVyIFJTQSBDQSAyIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "f1a0bae2b273ddf514b8e42304f3dbb8040fd3721185f80ada46dd69c0ddd5de", + "size": 1861, + "filename": "4ZMBBKlWyhpdsKqKnbXEkuo4CnSxw6b7svlcKIJWfEA=.pem", + "location": "security-state-staging/intermediates/b422a0ee-0a04-4468-a9fa-5635e30c83bb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4ZMBBKlWyhpdsKqKnbXEkuo4CnSxw6b7svlcKIJWfEA=", + "crlite_enrolled": false, + "id": "b3dc6b21-3ad1-4586-8cdb-5e2dd1c613bc", + "last_modified": 1666727874007 + }, + { + "schema": 1666727437660, + "derHash": "TUUDnuzKEU1kgioKqAlE1m4I+u5isdJT8UAph2nyJ7Q=", + "subject": "CN=XinChaCha Trust SSL Domain Validated,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MHQxCzAJBgNVBAYTAkNOMTYwNAYDVQQKDC1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xLTArBgNVBAMMJFhpbkNoYUNoYSBUcnVzdCBTU0wgRG9tYWluIFZhbGlkYXRlZA==", + "whitelist": false, + "attachment": { + "hash": "68b6dde00d9409bcff41cb8d3a250a7199377a35f14c4af0235cb34b2ced91d2", + "size": 1772, + "filename": "d2LYPy24Y7JLfKDPVkl-Qxb3wvxf9gwxrL4YMYl4enA=.pem", + "location": "security-state-staging/intermediates/1d26950e-1533-4bb9-9e82-339b1a07f6c1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "d2LYPy24Y7JLfKDPVkl+Qxb3wvxf9gwxrL4YMYl4enA=", + "crlite_enrolled": false, + "id": "d85db883-7604-4b25-88f2-ae094367a108", + "last_modified": 1666727873993 + }, + { + "schema": 1666727438533, + "derHash": "NAylukAtFAtlosl2566BKKFQXCnRkODgNPWcyuepK8I=", + "subject": "CN=Apple Public EV Server RSA CA 1 - G1,O=Apple Inc.,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMS0wKwYDVQQDEyRBcHBsZSBQdWJsaWMgRVYgU2VydmVyIFJTQSBDQSAxIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "ea385fed15dbd58a3c6f6fafe8e1873268b80afe2586ec31c75f1eebd9fb7c21", + "size": 1833, + "filename": "9C7mf4J789KvLX59lcMyYpsH6bpdmoAGTByZNhcusLA=.pem", + "location": "security-state-staging/intermediates/f25279c5-ac9c-4d91-99fc-93ec4b61b575.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9C7mf4J789KvLX59lcMyYpsH6bpdmoAGTByZNhcusLA=", + "crlite_enrolled": false, + "id": "e7170fbb-6ceb-4f32-b932-7da04ce6562c", + "last_modified": 1666727873979 + }, + { + "schema": 1666727371284, + "derHash": "ARbxf5fN70reLmPPLBsGT9mfQE0rkUEAvCQfB4GFMyM=", + "subject": "CN=emSign ECC EV SSL CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MG0xCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSIwIAYDVQQDExllbVNpZ24gRUNDIEVWIFNTTCBDQSAtIEcz", + "whitelist": false, + "attachment": { + "hash": "cf06e95703019f175db9c5a52f46e1ddea5ac2ad994d77248a0ec9479bc9be49", + "size": 1183, + "filename": "f01011qEaFf6Wqq3wyJQCu96P1p4Ia4fYG9V_7Q6Pu4=.pem", + "location": "security-state-staging/intermediates/98578ae6-b345-4b4a-9f77-0f821db4c50a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "f01011qEaFf6Wqq3wyJQCu96P1p4Ia4fYG9V/7Q6Pu4=", + "crlite_enrolled": false, + "id": "c3a3ece5-9d3e-4a95-9e75-88b6770383fc", + "last_modified": 1666727873966 + }, + { + "schema": 1666727452477, + "derHash": "QzTusswRT4K+5vin5a6gOkLrLh9wy9ZhAuQU1y8AM7k=", + "subject": "CN=emSign EV SSL CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGkxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR4wHAYDVQQDExVlbVNpZ24gRVYgU1NMIENBIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "d1a4e2a86d82be89bc5a8cdd12e0721917280f6b41222ff230bfd34bde486b4d", + "size": 1626, + "filename": "uyublT6SsX3mh_3EIO2_Tv5ftRcL40wCzJ0G9rOjYKE=.pem", + "location": "security-state-staging/intermediates/ba44d28c-de08-40a9-a1e1-21782eca68cf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uyublT6SsX3mh/3EIO2/Tv5ftRcL40wCzJ0G9rOjYKE=", + "crlite_enrolled": false, + "id": "c280c564-6ffb-47f0-ab87-127727d076a0", + "last_modified": 1666727873952 + }, + { + "schema": 1666727441942, + "derHash": "a1HR3PTreu5CQYXLG5WAV0s5y5Y4Y94+wa0x3bB2zp8=", + "subject": "CN=emSign ECC SSL CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGoxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDExZlbVNpZ24gRUNDIFNTTCBDQSAtIEcz", + "whitelist": false, + "attachment": { + "hash": "89e969a3a81606f2a36171383c4b70f380c4613a543363e819611910547c8eca", + "size": 1183, + "filename": "9O08XqhJ4GK5qjGNH9IY24XQxhGv8dsFbbZDBPNw1xA=.pem", + "location": "security-state-staging/intermediates/7b91ca89-e860-4ed9-a170-78aaec18ecf8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9O08XqhJ4GK5qjGNH9IY24XQxhGv8dsFbbZDBPNw1xA=", + "crlite_enrolled": false, + "id": "ed563635-4e2d-4c84-83bd-179c24abeb41", + "last_modified": 1666727873939 + }, + { + "schema": 1666727373803, + "derHash": "oGHURTmXFMOPwQGm6a+9s4HxEvpd59W8FJBFWNHtMnY=", + "subject": "CN=emSign ECC SSL CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEfMB0GA1UEAxMWZW1TaWduIEVDQyBTU0wgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "8c77ffa9b893c57610bd16e4953c97a408efab95236b88e9a574f1475ed5146e", + "size": 1134, + "filename": "TPymzs2gSTCnRzT83dxpTHxfGl2tdQ9snhCfXXcejy4=.pem", + "location": "security-state-staging/intermediates/86a79f6f-14ab-4ade-a0ec-33f05a4994dd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TPymzs2gSTCnRzT83dxpTHxfGl2tdQ9snhCfXXcejy4=", + "crlite_enrolled": false, + "id": "5dc47e36-cabd-43eb-ac61-417a710e88b8", + "last_modified": 1666727873925 + }, + { + "schema": 1666727430016, + "derHash": "wKV48hCeb0LT2TmUje6rcpsg97I7Qjer2ElN9VTPmFw=", + "subject": "CN=emSign ECC EV SSL CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEiMCAGA1UEAxMZZW1TaWduIEVDQyBFViBTU0wgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "853ba62a0ec592eb9c2ee33d281011227250af71cdcbf9b0e894d233249115ca", + "size": 1138, + "filename": "UEYlLVSYKuv5nRGqAxngK1P6jpyy3oyLZGnFpQIPQFk=.pem", + "location": "security-state-staging/intermediates/81418df8-ee8d-48b2-804c-11c4866e8e31.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UEYlLVSYKuv5nRGqAxngK1P6jpyy3oyLZGnFpQIPQFk=", + "crlite_enrolled": false, + "id": "b968e489-d60c-472f-8f60-2e7fcd90e869", + "last_modified": 1666727873911 + }, + { + "schema": 1666727401990, + "derHash": "zAslEu4gKAw28w1XCErLa4Y22UtIF0LkJDNcd2fnWMY=", + "subject": "CN=SZCA OV SSL CA,O=Shenzhen Digital Certificate Authority Center Co.\\, Ltd,C=CN", + "subjectDN": "MGcxCzAJBgNVBAYTAkNOMT8wPQYDVQQKDDZTaGVuemhlbiBEaWdpdGFsIENlcnRpZmljYXRlIEF1dGhvcml0eSBDZW50ZXIgQ28uLCBMdGQxFzAVBgNVBAMMDlNaQ0EgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "f14773351856d24a716ac777ab1f49b20bc3e773a90bd6e81448699608ed3745", + "size": 1756, + "filename": "bJ62__O9KMouzGPMfpN1AzgR1FpzP3mVaUCWzzs5FjI=.pem", + "location": "security-state-staging/intermediates/2377fa54-4f75-4365-adcf-533a61b44d4d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bJ62//O9KMouzGPMfpN1AzgR1FpzP3mVaUCWzzs5FjI=", + "crlite_enrolled": false, + "id": "31bbaba6-e6cc-4825-8c0e-724e8d0c9398", + "last_modified": 1666727873897 + }, + { + "schema": 1666727423268, + "derHash": "YD23B6WEADvtbx1D3NTq4TzRjXmOgn3i86MfMZP8Daw=", + "subject": "CN=NII Open Domain CA - G7 RSA,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFoxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSQwIgYDVQQDExtOSUkgT3BlbiBEb21haW4gQ0EgLSBHNyBSU0E=", + "whitelist": false, + "attachment": { + "hash": "c415b80eaca6cc1fe0d46f93c28c3f50a71fdc2d34c05596ad432c7d1071765c", + "size": 1772, + "filename": "gdcmbbO--ObnWtuH5s-3w7lzQiYlLeXyMGuHZ7bgaG0=.pem", + "location": "security-state-staging/intermediates/921112a4-f1b9-4d7d-ab3a-4e36fab6dc15.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gdcmbbO++ObnWtuH5s+3w7lzQiYlLeXyMGuHZ7bgaG0=", + "crlite_enrolled": false, + "id": "8e4c3aba-f9c2-4fae-891a-3f9906c7f95d", + "last_modified": 1666727873883 + }, + { + "schema": 1666727333641, + "derHash": "ansqo0FAOaZj1di7q4JWo5eahMMyv14e6PbQ4K2oRmg=", + "subject": "CN=DigiCert QV EV TLS ICA G1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKDA5EaWdpQ2VydCwgSW5jLjEiMCAGA1UEAwwZRGlnaUNlcnQgUVYgRVYgVExTIElDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "da7ebd3537612c5ba7de71120be602b9e04c9127ce688075aa408dbe6025f441", + "size": 1943, + "filename": "WlGHvWN6kWbS5I0pZA1mzBa36H7tg_dkp0nOMyIfpIg=.pem", + "location": "security-state-staging/intermediates/fdd44b41-87a8-41b1-b24d-bc629efb1193.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WlGHvWN6kWbS5I0pZA1mzBa36H7tg/dkp0nOMyIfpIg=", + "crlite_enrolled": false, + "id": "2b7934df-863e-4ca7-a586-c7e3f8289d51", + "last_modified": 1666727873869 + }, + { + "schema": 1666727330629, + "derHash": "DFoJ24rt99LR3eFNzMLbbqlZvPbwEDYNg2w0LGJNfg4=", + "subject": "CN=Entrust Certification Authority - L1F,OU=See www.entrust.net/legal-terms+OU=(c) 2016 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTYgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFG", + "whitelist": false, + "attachment": { + "hash": "0f073bd985840e123b19311aa4772e90dcd0ac807527490689309ef0b449e371", + "size": 1613, + "filename": "45FtWowsHe863G4x3FzC8qT3R9tgrLIVwZGjN5hwd80=.pem", + "location": "security-state-staging/intermediates/9b3cc3eb-ea08-4278-a4c3-b86ffe4570a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "45FtWowsHe863G4x3FzC8qT3R9tgrLIVwZGjN5hwd80=", + "crlite_enrolled": false, + "id": "a62aae33-e09c-4f50-95fc-8f7ec5599006", + "last_modified": 1666727873855 + }, + { + "schema": 1666727438698, + "derHash": "aGkkLNitKsd7wCiUe8fQxPbpy/CJnWVwmBDYn5S11w0=", + "subject": "CN=GDCA TrustAUTH R4 EV SSL CA G2,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJzAlBgNVBAMMHkdEQ0EgVHJ1c3RBVVRIIFI0IEVWIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "2e1f89d236b55a9e53ce42bf4e6c979ac2d4e9d9e836793810d01c10dc91ff0f", + "size": 1727, + "filename": "GlfrRgw9jW9VNC7Y1aXDsTsHh6dUPAEud8fhzOO-EdY=.pem", + "location": "security-state-staging/intermediates/76ca0f35-dc5e-4a6b-a715-cae8b21ba328.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GlfrRgw9jW9VNC7Y1aXDsTsHh6dUPAEud8fhzOO+EdY=", + "crlite_enrolled": false, + "id": "15d2f4f5-d5d0-4fc9-8825-8c5fcf5cbe4f", + "last_modified": 1666727873827 + }, + { + "schema": 1666727388136, + "derHash": "r1Wzcupk/zCA8x+EKZE4Ft6rrzkPxdKsJx6an2LY9XU=", + "subject": "CN=GLOBALTRUST 2020 SERVER OV 1,O=e-commerce monitoring GmbH,C=AT", + "subjectDN": "MFkxCzAJBgNVBAYTAkFUMSMwIQYDVQQKExplLWNvbW1lcmNlIG1vbml0b3JpbmcgR21iSDElMCMGA1UEAxMcR0xPQkFMVFJVU1QgMjAyMCBTRVJWRVIgT1YgMQ==", + "whitelist": false, + "attachment": { + "hash": "9bda2cec072809b7fd712c9af77ff70cd54d0e888d69cdbe60a612aac223231e", + "size": 2450, + "filename": "rBtkNiZaQg6185kYFySxZ_8yMhILmvFbSXHwtC5IHyM=.pem", + "location": "security-state-staging/intermediates/50f2da3e-8a21-4c8f-b044-a9be6e5043b0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rBtkNiZaQg6185kYFySxZ/8yMhILmvFbSXHwtC5IHyM=", + "crlite_enrolled": false, + "id": "fcf9f03f-2cbd-43fe-9c9d-9d9d05d57e03", + "last_modified": 1666727873813 + }, + { + "schema": 1666727362222, + "derHash": "aPxiN8S4ciob37lYP3KIQKGL/MPWxuHWsKMHYkV329A=", + "subject": "CN=United Trust,O=United SSL Deutschland GmbH,C=DE", + "subjectDN": "MEoxCzAJBgNVBAYTAkRFMSQwIgYDVQQKDBtVbml0ZWQgU1NMIERldXRzY2hsYW5kIEdtYkgxFTATBgNVBAMMDFVuaXRlZCBUcnVzdA==", + "whitelist": false, + "attachment": { + "hash": "8661e2fe1fbcab89e692525afade74cf624d91acbb1f708ae781032a33836ba9", + "size": 1715, + "filename": "XZu2IV7FaRVUnQgzYvxIRg7nAzuxDYMcfMIz8GOVLTc=.pem", + "location": "security-state-staging/intermediates/33a780ba-7504-4df4-a3f5-ab6f273a6059.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XZu2IV7FaRVUnQgzYvxIRg7nAzuxDYMcfMIz8GOVLTc=", + "crlite_enrolled": false, + "id": "83cb24fb-4a7e-40ae-bcbc-c2c96302fb71", + "last_modified": 1666727873799 + }, + { + "schema": 1666727434241, + "derHash": "/pWiTn+Ul45Y2ZNQq3q3iXdKmHzSUYfBKMGR0gdSO7E=", + "subject": "CN=GLOBALTRUST 2020 SERVER QUALIFIED EV 1,O=e-commerce monitoring GmbH,C=AT", + "subjectDN": "MGMxCzAJBgNVBAYTAkFUMSMwIQYDVQQKExplLWNvbW1lcmNlIG1vbml0b3JpbmcgR21iSDEvMC0GA1UEAxMmR0xPQkFMVFJVU1QgMjAyMCBTRVJWRVIgUVVBTElGSUVEIEVWIDE=", + "whitelist": false, + "attachment": { + "hash": "895ff083ad30ad3da5ac84d2a84e2fec9d509e8ccbf06669fe2fc95001179b70", + "size": 2479, + "filename": "zXQfBWS96DJ9x-JTaXe4CKy6j5kJ5VMFEZA20rNiOkg=.pem", + "location": "security-state-staging/intermediates/7e71183e-5775-45e4-ab9c-77e8c9a48231.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zXQfBWS96DJ9x+JTaXe4CKy6j5kJ5VMFEZA20rNiOkg=", + "crlite_enrolled": false, + "id": "ee150f50-3385-4e05-8b0c-addb39af8e93", + "last_modified": 1666727873785 + }, + { + "schema": 1666727370771, + "derHash": "pbJ9hEoOy2jx0GVyMgKi8HxmfelrhOaKmvI0ZpOX1sQ=", + "subject": "CN=GLOBALTRUST 2020 SERVER EV 1,O=e-commerce monitoring GmbH,C=AT", + "subjectDN": "MFkxCzAJBgNVBAYTAkFUMSMwIQYDVQQKExplLWNvbW1lcmNlIG1vbml0b3JpbmcgR21iSDElMCMGA1UEAxMcR0xPQkFMVFJVU1QgMjAyMCBTRVJWRVIgRVYgMQ==", + "whitelist": false, + "attachment": { + "hash": "396e177eebbe840e5c4dcc3bd1a8d4d7e9dfabd64b3327f2bdc46ab88a973eb3", + "size": 2463, + "filename": "cLEJD43_DCCaOOhEje7M7jHpKR3cFlGJOqFQtQmq0yU=.pem", + "location": "security-state-staging/intermediates/02b4e2ed-9cfb-4ea2-a949-4141bf1155e9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cLEJD43/DCCaOOhEje7M7jHpKR3cFlGJOqFQtQmq0yU=", + "crlite_enrolled": false, + "id": "85774d69-8d0d-4820-b696-519efe606b10", + "last_modified": 1666727873771 + }, + { + "schema": 1666727415990, + "derHash": "J3z0tl9TD8wHKF74nYMRiEiEAE5PLH2+fKAG6WU2zWI=", + "subject": "CN=Shoper® SSL,OU=Dreamcommerce S.A.,O=Dreamcommerce S.A.,C=PL", + "subjectDN": "MF4xCzAJBgNVBAYTAlBMMRswGQYDVQQKDBJEcmVhbWNvbW1lcmNlIFMuQS4xGzAZBgNVBAsMEkRyZWFtY29tbWVyY2UgUy5BLjEVMBMGA1UEAwwMU2hvcGVywq4gU1NM", + "whitelist": false, + "attachment": { + "hash": "e4b74658c87e531a9abfebbd57bf225a85f5e2a737dd73be3794a19bc905b06b", + "size": 1613, + "filename": "u2qzRrAdmNr-Meysupi9x7UNMaLYZzuT4OTThn_6xF4=.pem", + "location": "security-state-staging/intermediates/dee40a5f-efa0-4a09-8a63-181ab2062fb5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "u2qzRrAdmNr+Meysupi9x7UNMaLYZzuT4OTThn/6xF4=", + "crlite_enrolled": false, + "id": "5ffb5aee-a080-4a31-aec0-6301099b7425", + "last_modified": 1666727873758 + }, + { + "schema": 1666727393255, + "derHash": "m/q9jZEoRQC01BLFclyHyzoBP5QP8eYoEOaSnGk5Ptk=", + "subject": "CN=CUISEC GCC R3 TLS EV CA 2021,O=联通智慧安全科技有限公司,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTogZTpgJrmmbrmhaflronlhajnp5HmioDmnInpmZDlhazlj7gxJTAjBgNVBAMTHENVSVNFQyBHQ0MgUjMgVExTIEVWIENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "2d49d4197ed82f1b42a22adc7c313e685dbb3ad2d71e872eca62a72ad44e5f09", + "size": 1727, + "filename": "9VJFGEWro7X56n20u_Yid3uitID-FtwujrXPJd_UUi8=.pem", + "location": "security-state-staging/intermediates/0ed5c801-2c4d-48d9-94a2-8d9ee4fac113.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9VJFGEWro7X56n20u/Yid3uitID+FtwujrXPJd/UUi8=", + "crlite_enrolled": false, + "id": "657d3530-54d0-4905-8819-2c4ed441e0b6", + "last_modified": 1666727873744 + }, + { + "schema": 1666727371927, + "derHash": "aS/huLommVMd5M0vUq44+hGasB9LMat4/m2z/fA6cDE=", + "subject": "CN=CUISEC GCC R3 TLS DV CA 2021,O=联通智慧安全科技有限公司,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTogZTpgJrmmbrmhaflronlhajnp5HmioDmnInpmZDlhazlj7gxJTAjBgNVBAMTHENVSVNFQyBHQ0MgUjMgVExTIERWIENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "2362379bf98129dd0a1754e516e9383efeac16c9d49e8d5dbe74dd8e76f58a09", + "size": 1727, + "filename": "1G8aNb1oR_IGXiLx3-c3BvkRmQ2AjMYBtH40HeLEUds=.pem", + "location": "security-state-staging/intermediates/d36a1d9e-0533-432a-858f-28d514ac308a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1G8aNb1oR/IGXiLx3+c3BvkRmQ2AjMYBtH40HeLEUds=", + "crlite_enrolled": false, + "id": "a49ffd3d-57de-4238-898a-cc903eb6e447", + "last_modified": 1666727873730 + }, + { + "schema": 1666727364984, + "derHash": "x/FF1HT13CCyemThaWF5UiRpB4szP+j/iHuXzngF42I=", + "subject": "CN=CUISEC GCC R3 TLS OV CA 2021,O=联通智慧安全科技有限公司,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTogZTpgJrmmbrmhaflronlhajnp5HmioDmnInpmZDlhazlj7gxJTAjBgNVBAMTHENVSVNFQyBHQ0MgUjMgVExTIE9WIENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "799039d203bff502b78cba6679ff4243bd84a7daa095ccda9b9dad4f7bdbd772", + "size": 1727, + "filename": "7CtV2q_DG2mt1q7CNaMX5LYyXLV5l477mMcjjYZs2u4=.pem", + "location": "security-state-staging/intermediates/f5f73cb5-53fb-4dc9-8de0-799355a73b83.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7CtV2q/DG2mt1q7CNaMX5LYyXLV5l477mMcjjYZs2u4=", + "crlite_enrolled": false, + "id": "90d490c9-4f55-4511-92c3-af9d041f1998", + "last_modified": 1666727873716 + }, + { + "schema": 1666727342323, + "derHash": "HparstZQK13OUY7AC1oeVDNJ79Lj9ovpq8ESiyVv7dc=", + "subject": "CN=GDCA TrustAUTH R4 SSL CA,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN", + "subjectDN": "MGQxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjEhMB8GA1UEAwwYR0RDQSBUcnVzdEFVVEggUjQgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "0c153a0a3e193285fb42bc65ed86726b6ce20e989c5ce61bee6ac0249ccc9114", + "size": 2052, + "filename": "UNDNK48i2z9IOnLBTQP6yg9UerUnpfpb-nZvgw-0Who=.pem", + "location": "security-state-staging/intermediates/917af39e-808a-4852-8c1c-b2360829b69b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UNDNK48i2z9IOnLBTQP6yg9UerUnpfpb+nZvgw+0Who=", + "crlite_enrolled": false, + "id": "056dcecf-b54d-4dca-bd31-f6fa1134a775", + "last_modified": 1666727873702 + }, + { + "schema": 1666727386115, + "derHash": "VgCvtrriqDtmucu+nO7I9T4mQgppk5pI3MbVa5l5CmM=", + "subject": "CN=GDCA TrustAUTH R4 OV SSL CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG4xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJDAiBgNVBAMMG0dEQ0EgVHJ1c3RBVVRIIFI0IE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "9212a9d7e5c2bfcfbf43aaf53d8aa2e3501f467ef547dee098ab48f5d850d6ad", + "size": 2052, + "filename": "UNDNK48i2z9IOnLBTQP6yg9UerUnpfpb-nZvgw-0Who=.pem", + "location": "security-state-staging/intermediates/4aa91fab-6b52-44a6-a3c1-ef65ee1e7d8f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UNDNK48i2z9IOnLBTQP6yg9UerUnpfpb+nZvgw+0Who=", + "crlite_enrolled": false, + "id": "a463b3f5-089a-40a1-ac08-8bb23ab16b4c", + "last_modified": 1666727873688 + }, + { + "schema": 1666727429335, + "derHash": "MaCWocuRiHbuV8hbI0n8nMLCy0/Pm903eHzp2levR5A=", + "subject": "CN=Alibaba Cloud GCC R3 TLS OV CA 2021,O=Alibaba Cloud Computing Co.\\, Ltd.,C=CN", + "subjectDN": "MGcxCzAJBgNVBAYTAkNOMSowKAYDVQQKEyFBbGliYWJhIENsb3VkIENvbXB1dGluZyBDby4sIEx0ZC4xLDAqBgNVBAMTI0FsaWJhYmEgQ2xvdWQgR0NDIFIzIFRMUyBPViBDQSAyMDIx", + "whitelist": false, + "attachment": { + "hash": "c64bbdc4fcc4298ffb453c1558ab7102e685784efb5cc1dae31840e3a119e67a", + "size": 1735, + "filename": "HacabMO6K4pM-shfB1EkMCGhH2TSlSvVizEDPU1Ruso=.pem", + "location": "security-state-staging/intermediates/f8675c4a-871f-4996-b046-3c346fb294db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HacabMO6K4pM+shfB1EkMCGhH2TSlSvVizEDPU1Ruso=", + "crlite_enrolled": false, + "id": "136cca7b-196b-4678-a6b0-4b880a8df6c5", + "last_modified": 1666727873674 + }, + { + "schema": 1666727396635, + "derHash": "HdCVRJ/7PP8UsiJNWWuD/ULytHaDVTx5fREVDJGGkb0=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBIMiAyMDIx", + "whitelist": false, + "attachment": { + "hash": "e06bf97fe011b418ca619844016454cc4529a0fe9de1bf1ec3daa57af5a3a724", + "size": 1715, + "filename": "P_B6nUOG4cUlLNI8o81n2MESbUnYMzT49Flc6X5W1bk=.pem", + "location": "security-state-staging/intermediates/f804d5ba-d989-47f7-8217-4f41b5fb5978.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "P/B6nUOG4cUlLNI8o81n2MESbUnYMzT49Flc6X5W1bk=", + "crlite_enrolled": false, + "id": "308fb64b-a058-4676-aa68-e8e33b332c2f", + "last_modified": 1666727873661 + }, + { + "schema": 1666727359631, + "derHash": "R7LvvDZw59tLQfIsUfwC7oT7Lb8wgqSfLCaIEi6SEKE=", + "subject": "CN=emSign SSL CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGYxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRswGQYDVQQDExJlbVNpZ24gU1NMIENBIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "99a70855fffb043bc441083724ab6299ce68f28ce7c76547b9782e13a92d30ce", + "size": 1622, + "filename": "qvzNg6-PS7JbCFR-FL-eAfFZOiN6pXXsH1tc9auHlF8=.pem", + "location": "security-state-staging/intermediates/bc26b3e5-5243-4ed4-89a5-ee29b7471158.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qvzNg6+PS7JbCFR+FL+eAfFZOiN6pXXsH1tc9auHlF8=", + "crlite_enrolled": false, + "id": "136071c8-bc0a-49f6-9eed-0f027892d327", + "last_modified": 1666727873645 + }, + { + "schema": 1666727398616, + "derHash": "+RqsoOTlM3R6CIC/z28mcg3B0FSUw5ONpoAikNWgmzI=", + "subject": "CN=emSign SSL CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFUxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEbMBkGA1UEAxMSZW1TaWduIFNTTCBDQSAtIEMx", + "whitelist": false, + "attachment": { + "hash": "371dbaddc94c282e8343a28dc05dcb74778a143ee818d5884a3116b27bd5028b", + "size": 1577, + "filename": "Vu8rCm62GhPpPUIskAEOlG-x_WJEzc46NqUrWffwTVo=.pem", + "location": "security-state-staging/intermediates/5c3885bb-cc4d-4eb7-8b65-2d767339453e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vu8rCm62GhPpPUIskAEOlG+x/WJEzc46NqUrWffwTVo=", + "crlite_enrolled": false, + "id": "40ee5283-fd3d-4e3c-9d19-0ac5439365bb", + "last_modified": 1666727873631 + }, + { + "schema": 1666727333145, + "derHash": "9vFZKGoUAd5Tl+IaAJBTSoX157n5j9SlpHsd/9S/3tQ=", + "subject": "CN=emSign EV SSL CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEeMBwGA1UEAxMVZW1TaWduIEVWIFNTTCBDQSAtIEMx", + "whitelist": false, + "attachment": { + "hash": "6e8a3c81fcf028c7f31ff8a6ecf47b000e0c56f86618ee42c5ab92b7dd438a5f", + "size": 1581, + "filename": "bJpaGvT-ExoQi2_an1HG3Mo5yMrot02ORyF_NS5p5J8=.pem", + "location": "security-state-staging/intermediates/586120f4-7f7a-48aa-a621-1d452827f4f0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bJpaGvT+ExoQi2/an1HG3Mo5yMrot02ORyF/NS5p5J8=", + "crlite_enrolled": false, + "id": "219bee21-4e35-434a-b98f-7a9cdbbf3432", + "last_modified": 1666727873617 + }, + { + "schema": 1666727440600, + "derHash": "p+gwVumz2d2xgWuVUY9qXlod/foo9gUzschQhV6qQmM=", + "subject": "CN=Telia Domain Validation CA v3,O=Telia Finland Oyj,C=FI", + "subjectDN": "MFExCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEmMCQGA1UEAwwdVGVsaWEgRG9tYWluIFZhbGlkYXRpb24gQ0EgdjM=", + "whitelist": false, + "attachment": { + "hash": "0f8c227df79307dbeec994ba6d8bd832268f9d2b0c636499e29d589ed747a88c", + "size": 2353, + "filename": "RU2jIfMztj4AADghygjY9gbxUkW13LXRSS7JeONgrxc=.pem", + "location": "security-state-staging/intermediates/f38aa272-1880-4c2f-b745-6c01a69b0614.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RU2jIfMztj4AADghygjY9gbxUkW13LXRSS7JeONgrxc=", + "crlite_enrolled": false, + "id": "b07c6446-0c44-4a00-bb56-ca8b2b55a54e", + "last_modified": 1666727873604 + }, + { + "schema": 1666727442289, + "derHash": "EoGtj6vog/IJ6WNkSNGoDDc9qnaGyBOicPrUj19eWJo=", + "subject": "CN=Telia Server CA v3,O=Telia Finland Oyj,C=FI", + "subjectDN": "MEYxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEbMBkGA1UEAwwSVGVsaWEgU2VydmVyIENBIHYz", + "whitelist": false, + "attachment": { + "hash": "c49599398958207a50863ebc4e7e508a20b8551b440ae2d3d28ab90e317f5c12", + "size": 2341, + "filename": "TCgTLigAKZ2ouWM1T2dh92G9wUDP8Wlma6sqwmm3aVc=.pem", + "location": "security-state-staging/intermediates/bdcf129f-b3fa-4ce1-8a67-6da86c05d54f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TCgTLigAKZ2ouWM1T2dh92G9wUDP8Wlma6sqwmm3aVc=", + "crlite_enrolled": false, + "id": "681d294f-17d7-400e-a4ca-507ee1d90a50", + "last_modified": 1666727873590 + }, + { + "schema": 1666727415821, + "derHash": "lW/5zJFIdNnK+WVbzLaWwb5Jolv5KNXEHA9TlaE12Lg=", + "subject": "CN=Telekom Security DV RSA CA 21,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJjAkBgNVBAMMHVRlbGVrb20gU2VjdXJpdHkgRFYgUlNBIENBIDIx", + "whitelist": false, + "attachment": { + "hash": "acb3894c8cbada20f584e21eba9807c60116c18b1ffb053f0d45638c7fbade05", + "size": 1752, + "filename": "xKFly8BVCDp0QQH2za-7ZzsqYFz305rqc0YqMPtU7Uw=.pem", + "location": "security-state-staging/intermediates/5cc07929-0f31-444c-9904-7e3f405702e3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xKFly8BVCDp0QQH2za+7ZzsqYFz305rqc0YqMPtU7Uw=", + "crlite_enrolled": false, + "id": "1c1cd633-821a-4775-9051-53aaf439c3b3", + "last_modified": 1666727873576 + }, + { + "schema": 1666727372257, + "derHash": "SB5YKiBqfXBAzNoXzyXTSXhaKrlO11UqslTc04sDLsA=", + "subject": "CN=Microsoft RSA TLS Issuing AOC CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBSU0EgVExTIElzc3VpbmcgQU9DIENBIDAx", + "whitelist": false, + "attachment": { + "hash": "2304a6019fd3a16f19edfca5fefde59b3c14ab1accadb45eea875e9cd13c9b04", + "size": 2694, + "filename": "dITy8WGNNfbyOcBbrTC4t62TxwJNQADThxQUF-492_Q=.pem", + "location": "security-state-staging/intermediates/b8a6c3f7-8eee-49d2-bb3c-f8d0a6335b62.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dITy8WGNNfbyOcBbrTC4t62TxwJNQADThxQUF+492/Q=", + "crlite_enrolled": false, + "id": "eaaba7c6-7727-4861-b733-17dc938f8649", + "last_modified": 1666727873562 + }, + { + "schema": 1666727392907, + "derHash": "XGSxcxqBON6n0Rya6GIokflF66RoJeer/kdU8KYBGvg=", + "subject": "CN=Microsoft ECC TLS Issuing AOC CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBFQ0MgVExTIElzc3VpbmcgQU9DIENBIDAx", + "whitelist": false, + "attachment": { + "hash": "b8980a779b7c2fe67041f19cdecab2d5a43f61ef0793b5cede8a3b9e111eb0b6", + "size": 1544, + "filename": "qd93pb-3n6NM25ddlkow2f5_xOZwvTm_G4wmBehD21Q=.pem", + "location": "security-state-staging/intermediates/7fc14ec5-80cf-4527-8ceb-7135fd5ae374.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qd93pb+3n6NM25ddlkow2f5/xOZwvTm/G4wmBehD21Q=", + "crlite_enrolled": false, + "id": "c10233f0-5a2e-4b4b-bfec-4ee643137071", + "last_modified": 1666727873549 + }, + { + "schema": 1666727435278, + "derHash": "YoSgO6+uhhow46JaAwQoMG9+tSvQuld6HZYqgJqDDJ4=", + "subject": "CN=NetLock Minősített Eat. (Class Q Legal) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIG5MQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTFHMEUGA1UEAww+TmV0TG9jayBNaW7FkXPDrXRldHQgRWF0LiAoQ2xhc3MgUSBMZWdhbCkgVGFuw7pzw610dsOhbnlraWFkw7M=", + "whitelist": false, + "attachment": { + "hash": "be44ba2f0e98d91976e0b376d14bfcfbf5e04990413aeb73457eb447f773a585", + "size": 2203, + "filename": "wbqAvjVExNzYgkWT-psQa7MDcYIscGSOYZa0cm5x83M=.pem", + "location": "security-state-staging/intermediates/602c1224-b665-4b69-848a-f07cbc00e0be.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wbqAvjVExNzYgkWT+psQa7MDcYIscGSOYZa0cm5x83M=", + "crlite_enrolled": false, + "id": "73e86e8a-47ad-4f60-be1d-64ed2011a0bb", + "last_modified": 1666727873477 + }, + { + "schema": 1666727346963, + "derHash": "k7KBvYHYPPmGZZ3/0K9XmTuS5uRhQWJTn3UFJM4Ru8s=", + "subject": "CN=Abitab SSL Extended Validation,OU=IDdigital,O=Abitab S.A.,C=UY", + "subjectDN": "MGAxCzAJBgNVBAYTAlVZMRQwEgYDVQQKDAtBYml0YWIgUy5BLjESMBAGA1UECwwJSURkaWdpdGFsMScwJQYDVQQDDB5BYml0YWIgU1NMIEV4dGVuZGVkIFZhbGlkYXRpb24=", + "whitelist": false, + "attachment": { + "hash": "a994aaf2225099f989467d317d95162a61ab0f2a8b48927a953540b65a87fe3e", + "size": 1703, + "filename": "y2KaAsU1VfJfWRQYPeq3HVxO1GVrwSw4xGq7rSJtyII=.pem", + "location": "security-state-staging/intermediates/972694b4-ca85-44f1-91f0-7e0a868a3149.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "y2KaAsU1VfJfWRQYPeq3HVxO1GVrwSw4xGq7rSJtyII=", + "crlite_enrolled": false, + "id": "04ee2b55-620a-4d2e-a4c7-00b5c73ec962", + "last_modified": 1666727873463 + }, + { + "schema": 1666727407351, + "derHash": "ThpWTHGgQoiXDlPAN9HPwlwfm+/FJ2zzvVAe4mLwQgI=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSBIMiAyMDIx", + "whitelist": false, + "attachment": { + "hash": "d499679a13f287b719305ece5b064b2730137bf3006a9913e7ab121e378f3262", + "size": 1715, + "filename": "D2Dxjr_K2WrUouIXuBMldR6ucBSdBjfQI6boJU6R6i8=.pem", + "location": "security-state-staging/intermediates/fae57392-fd90-4733-a1bb-c543b9e8ff29.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D2Dxjr/K2WrUouIXuBMldR6ucBSdBjfQI6boJU6R6i8=", + "crlite_enrolled": false, + "id": "105b6627-b2cd-462b-8da2-204dc969ac8c", + "last_modified": 1666727873449 + }, + { + "schema": 1666727359279, + "derHash": "k6B4mNibLMoWa6bx+KFBOM5Dgo5JG4MZJryCR9ORzHI=", + "subject": "CN=Starfield Secure Certificate Authority - G2,OU=http://certs.starfieldtech.com/repository/,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIHGMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEzMDEGA1UECxMqaHR0cDovL2NlcnRzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvMTQwMgYDVQQDEytTdGFyZmllbGQgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy", + "whitelist": false, + "attachment": { + "hash": "4283da4ef9b6b21e475fb4a88aff9532a5c74e35282cb5459a20ee419646571c", + "size": 1792, + "filename": "8kGWrpQHhmc0jwLo43RYo6bmqtHgsNxhARjM5yFCe_w=.pem", + "location": "security-state-staging/intermediates/f0b4b8ab-658e-452d-a259-15b15f68060a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8kGWrpQHhmc0jwLo43RYo6bmqtHgsNxhARjM5yFCe/w=", + "crlite_enrolled": false, + "id": "eda1d6cc-95f3-4925-b185-1972acc69e5a", + "last_modified": 1666727873435 + }, + { + "schema": 1666727361878, + "derHash": "WaNFbnUOMl/LE1ncKegoGJtJgsEZxk+s/WcocRswUy8=", + "subject": "CN=www.lh.pl,OU=LH.pl,O=LH.pl Sp. z o.o.,C=PL", + "subjectDN": "MEwxCzAJBgNVBAYTAlBMMRkwFwYDVQQKDBBMSC5wbCBTcC4geiBvLm8uMQ4wDAYDVQQLDAVMSC5wbDESMBAGA1UEAwwJd3d3LmxoLnBs", + "whitelist": false, + "attachment": { + "hash": "402828757f41d8cffb0ca05dbda29cf243d74ac40d69868042f3c4704cefe6be", + "size": 1589, + "filename": "SPflJTPfbRg6NWYkxrOaqWFjkUz6d6fxe8POctPyEFo=.pem", + "location": "security-state-staging/intermediates/9852f8f4-698c-4682-be77-b302a242b445.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SPflJTPfbRg6NWYkxrOaqWFjkUz6d6fxe8POctPyEFo=", + "crlite_enrolled": false, + "id": "88fae53f-9c98-4491-98fc-5382066d04ba", + "last_modified": 1666727873421 + }, + { + "schema": 1666727448300, + "derHash": "yX8vbmqK227P5JePCMqPbwEjqUeEUithCt9qtRQ5/GI=", + "subject": "CN=Entrust Certification Authority - ES QWAC2,O=Entrust EU\\, S.L.,C=ES", + "subjectDN": "MHcxCzAJBgNVBAYTAkVTMRkwFwYDVQQKExBFbnRydXN0IEVVLCBTLkwuMRgwFgYDVQRhEw9WQVRFUy1CODExODgwNDcxMzAxBgNVBAMTKkVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBFUyBRV0FDMg==", + "whitelist": false, + "attachment": { + "hash": "c89879379b239f0ec22d71c3cdeb8f6c77f92e590721b0a38079ea2b45ae445a", + "size": 1768, + "filename": "NETZGOHSZHXfrGg1K5hk7YhF7a83qOCppH-2JqWEUuA=.pem", + "location": "security-state-staging/intermediates/c4f9e2c4-78ee-4792-87ad-84da6465225b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NETZGOHSZHXfrGg1K5hk7YhF7a83qOCppH+2JqWEUuA=", + "crlite_enrolled": false, + "id": "51be04bd-ac1e-4d6f-9c90-2c24092b0cc9", + "last_modified": 1666727873407 + }, + { + "schema": 1666727330997, + "derHash": "GlzNcUq9fHr1Kg+pRrycj4aWvL8ifYEzlDDl0zlOzJc=", + "subject": "CN=TU Ilmenau CA G2,O=Technische Universitaet Ilmenau,C=DE", + "subjectDN": "MFIxCzAJBgNVBAYTAkRFMSgwJgYDVQQKDB9UZWNobmlzY2hlIFVuaXZlcnNpdGFldCBJbG1lbmF1MRkwFwYDVQQDDBBUVSBJbG1lbmF1IENBIEcy", + "whitelist": false, + "attachment": { + "hash": "ac88cae9a73fc870c8732cac7b3ef3c93112a4abcf6b7e4ed2de251558a6cd88", + "size": 1951, + "filename": "6SXfWL___HfILksBCAAWXWAQClUVsEeSl6Lg8jmVlWo=.pem", + "location": "security-state-staging/intermediates/4aa70727-c6f8-46b4-9a57-73a0a9d47ebd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6SXfWL///HfILksBCAAWXWAQClUVsEeSl6Lg8jmVlWo=", + "crlite_enrolled": false, + "id": "4a92eec8-9b5a-4058-aff9-c75fd8949758", + "last_modified": 1666727873394 + }, + { + "schema": 1666727350274, + "derHash": "Abnz0I4xqejhYA0RjCq/2FaHXqYIJwIEaYZbokLuvhw=", + "subject": "CN=KIT-CA,O=Karlsruhe Institute of Technology,L=Karlsruhe,ST=Baden-Wuerttemberg,C=DE", + "subjectDN": "MHsxCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxEjAQBgNVBAcMCUthcmxzcnVoZTEqMCgGA1UECgwhS2FybHNydWhlIEluc3RpdHV0ZSBvZiBUZWNobm9sb2d5MQ8wDQYDVQQDDAZLSVQtQ0E=", + "whitelist": false, + "attachment": { + "hash": "57d86dbb18c2e8959844b08e15275c9f4e998e6bdecb15b2b7a77730e1c220a5", + "size": 2008, + "filename": "DvZiVD0jtzGADZxUQnbhnHsFjD8JVapxjCjP6aLBeoY=.pem", + "location": "security-state-staging/intermediates/5e91cd24-d2d6-4885-96ae-f41fd7f2fa91.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DvZiVD0jtzGADZxUQnbhnHsFjD8JVapxjCjP6aLBeoY=", + "crlite_enrolled": false, + "id": "3131956b-39c5-40b6-93ef-b46c278f30de", + "last_modified": 1666727873380 + }, + { + "schema": 1666727446023, + "derHash": "rL+uvcvOGshMmM8kFAtgYalzGOkmIVQJ3Az0x75QZiA=", + "subject": "CN=Fraunhofer Service CA - G02,OU=Fraunhofer Corporate PKI,O=Fraunhofer,L=Muenchen,ST=Bayern,C=DE", + "subjectDN": "MIGPMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjETMBEGA1UECgwKRnJhdW5ob2ZlcjEhMB8GA1UECwwYRnJhdW5ob2ZlciBDb3Jwb3JhdGUgUEtJMSQwIgYDVQQDDBtGcmF1bmhvZmVyIFNlcnZpY2UgQ0EgLSBHMDI=", + "whitelist": false, + "attachment": { + "hash": "f5d44378aebefee88f997f5baa67fce4e41c10b120f38a27f2ed2087d1347498", + "size": 2028, + "filename": "k3nI5IvSB1BwykyJ-lu8LZoHsTKb-Zq2JRhAXR7OjBU=.pem", + "location": "security-state-staging/intermediates/86390395-14c7-4e6b-8840-51150f08950a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k3nI5IvSB1BwykyJ+lu8LZoHsTKb+Zq2JRhAXR7OjBU=", + "crlite_enrolled": false, + "id": "97c725bf-dfc1-4940-bed8-defc980dd7a3", + "last_modified": 1666727873366 + }, + { + "schema": 1666727384090, + "derHash": "EleqwvTurGyklCwsg/C2e0GjtHEgxNU0KZKVE6ytRow=", + "subject": "CN=DFN-Verein Global Issuing CA,OU=DFN-PKI,O=Verein zur Foerderung eines Deutschen Forschungsnetzes e. V.,C=DE", + "subjectDN": "MIGNMQswCQYDVQQGEwJERTFFMEMGA1UECgw8VmVyZWluIHp1ciBGb2VyZGVydW5nIGVpbmVzIERldXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYuMRAwDgYDVQQLDAdERk4tUEtJMSUwIwYDVQQDDBxERk4tVmVyZWluIEdsb2JhbCBJc3N1aW5nIENB", + "whitelist": false, + "attachment": { + "hash": "69e4d49fecfd6be962aff5ae8fab5ea53f3d878ed48ee7a692e4baadc6b9b1cc", + "size": 2028, + "filename": "2plWEFSbja5Tz0pERjlr4FL2fr0H4L48Rt0ZF3sKBEQ=.pem", + "location": "security-state-staging/intermediates/8de4ccb4-4260-4e43-8cbf-5a833dadfdd3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2plWEFSbja5Tz0pERjlr4FL2fr0H4L48Rt0ZF3sKBEQ=", + "crlite_enrolled": false, + "id": "93b2b132-1d87-4798-b133-b6722f1ba378", + "last_modified": 1666727873352 + }, + { + "schema": 1666727386603, + "derHash": "4bKV4UZcJOCVHsC5D799owtnjp6c5EF9/+nzQELfQ4Y=", + "subject": "CN=TU Dresden CA,O=Technische Universitaet Dresden,L=Dresden,ST=Sachsen,C=DE", + "subjectDN": "MHMxCzAJBgNVBAYTAkRFMRAwDgYDVQQIDAdTYWNoc2VuMRAwDgYDVQQHDAdEcmVzZGVuMSgwJgYDVQQKDB9UZWNobmlzY2hlIFVuaXZlcnNpdGFldCBEcmVzZGVuMRYwFAYDVQQDDA1UVSBEcmVzZGVuIENB", + "whitelist": false, + "attachment": { + "hash": "4bb4a85a2aef8c2bbc7477ee066a25d177f13127df617d2caa763c5c9f3f5c92", + "size": 1995, + "filename": "dc-5wn2Qr-3zgoir8SQHWj7HfNM321MTym4ESb5GtH0=.pem", + "location": "security-state-staging/intermediates/d5d6b138-b20a-438c-bed9-6af0e662b862.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dc+5wn2Qr+3zgoir8SQHWj7HfNM321MTym4ESb5GtH0=", + "crlite_enrolled": false, + "id": "5aecaad6-1f2a-4e95-82f0-497d2e825f43", + "last_modified": 1666727873338 + }, + { + "schema": 1666727443658, + "derHash": "/CJFvlncZGHUEZw6Bu2+5NKIVWvYjEeeMO1fPoFhZGk=", + "subject": "CN=MPG CA - G02,O=Max-Planck-Gesellschaft,L=Muenchen,ST=Bayern,C=DE", + "subjectDN": "MGoxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMSAwHgYDVQQKDBdNYXgtUGxhbmNrLUdlc2VsbHNjaGFmdDEVMBMGA1UEAwwMTVBHIENBIC0gRzAy", + "whitelist": false, + "attachment": { + "hash": "43c1869e7e9b39a3e88534fec1a8ad7b6aa63eab2ad5f1a0dc457dc65ab08804", + "size": 1983, + "filename": "KEB9gT0zG0ZMgr0_mhORKVvNbNKMDEvo89gurjLLzYk=.pem", + "location": "security-state-staging/intermediates/608fe6af-14bc-4de0-83bc-a7b26be45449.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KEB9gT0zG0ZMgr0/mhORKVvNbNKMDEvo89gurjLLzYk=", + "crlite_enrolled": false, + "id": "271d3f60-f00e-48ab-a3ed-13509369476e", + "last_modified": 1666727873324 + }, + { + "schema": 1666727410682, + "derHash": "TJ4FOPmFaQ3p1c4cOPFsJLTDmhcQwIgc2wbir9t1e00=", + "subject": "CN=Deutscher Bundestag CA - G02,O=Deutscher Bundestag,L=Berlin,ST=Berlin,C=DE", + "subjectDN": "MHQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEcMBoGA1UECgwTRGV1dHNjaGVyIEJ1bmRlc3RhZzElMCMGA1UEAwwcRGV1dHNjaGVyIEJ1bmRlc3RhZyBDQSAtIEcwMg==", + "whitelist": false, + "attachment": { + "hash": "691728107bc477123f2b50cf26ee2991f3ba95b969683fd4fe04926cd388a729", + "size": 1999, + "filename": "p-DGsZUedDpJ0iAR6valV2XZykAPIyB_t6FdqCpsNck=.pem", + "location": "security-state-staging/intermediates/801fd9f6-afcf-4260-913d-7cedfb8d1f69.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "p+DGsZUedDpJ0iAR6valV2XZykAPIyB/t6FdqCpsNck=", + "crlite_enrolled": false, + "id": "4b7cd555-ac58-4744-b9ae-1f90ccfdbd42", + "last_modified": 1666727873310 + }, + { + "schema": 1666727376360, + "derHash": "HE7qOker0SJWjqtUfga1IRH384hmLCRsjsviZgufJvE=", + "subject": "CN=Certum Extended Validation RSA CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL", + "subjectDN": "MIGFMQswCQYDVQQGEwJQTDEhMB8GA1UECgwYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLDB5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxKjAoBgNVBAMMIUNlcnR1bSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJTQSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "895232045d915e2d74fa2e3ae83d4d8bdd52cbca18b2f199bf7e101a30790e5e", + "size": 2467, + "filename": "YAoKh_WpTocjrNw-_mxOvRJz65CHU5ydQ4-Rk-lZrRU=.pem", + "location": "security-state-staging/intermediates/08f26c7b-1519-439b-8f7e-8fa5232bf630.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YAoKh/WpTocjrNw+/mxOvRJz65CHU5ydQ4+Rk+lZrRU=", + "crlite_enrolled": false, + "id": "6abd7bd3-4396-4e19-a9d9-cbb1751f738c", + "last_modified": 1666727873295 + }, + { + "schema": 1666727413377, + "derHash": "xwY6jcZoIF5mFTEI++O9v27bb4zl9hajab72Mk3LY1Q=", + "subject": "CN=Certum Extended Validation ECC CA,OU=Certum Certification Authority,O=Asseco Data Systems S.A.,C=PL", + "subjectDN": "MIGFMQswCQYDVQQGEwJQTDEhMB8GA1UECgwYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLDB5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxKjAoBgNVBAMMIUNlcnR1bSBFeHRlbmRlZCBWYWxpZGF0aW9uIEVDQyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ab80d710e77c37afc3b1329d4e503f8098e4f3384c63346e0f589d9f3d863004", + "size": 1317, + "filename": "_yId-7ixJTA0RijRdjr4SGgKejXGhLaHSxMjvZxoMeA=.pem", + "location": "security-state-staging/intermediates/6224bea6-0a6c-4100-a887-c799ccf546bf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/yId+7ixJTA0RijRdjr4SGgKejXGhLaHSxMjvZxoMeA=", + "crlite_enrolled": false, + "id": "fe4c9d57-29aa-400b-a933-93193441cf39", + "last_modified": 1666727873281 + }, + { + "schema": 1666727384413, + "derHash": "BjYnNVyUGhyT/FFcuu8vFz1KZG3esTnLjHXBAiIimU8=", + "subject": "CN=TunTrust Services CA,O=Agence Nationale de Certification Electronique,C=TN", + "subjectDN": "MGUxCzAJBgNVBAYTAlROMTcwNQYDVQQKDC5BZ2VuY2UgTmF0aW9uYWxlIGRlIENlcnRpZmljYXRpb24gRWxlY3Ryb25pcXVlMR0wGwYDVQQDDBRUdW5UcnVzdCBTZXJ2aWNlcyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "6d48f6225c948caddb62fb80efcf61b14a6a5634db59f3e7761aad71133055a5", + "size": 2475, + "filename": "FVFmpBEgpC-vZukErx3K3k8dxByWTA6BMWT_HfCOlGA=.pem", + "location": "security-state-staging/intermediates/e7286378-ad7a-4f09-a6e1-2e9dd4726345.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FVFmpBEgpC+vZukErx3K3k8dxByWTA6BMWT/HfCOlGA=", + "crlite_enrolled": false, + "id": "49cc61d4-9f5b-4e11-a121-35d16785794f", + "last_modified": 1666727873253 + }, + { + "schema": 1666727347656, + "derHash": "JCtpdC/LHlsqv5iJi5RXIYdUTltNmRF4ZXNiH2p0uCw=", + "subject": "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI", + "subjectDN": "MEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2Mg==", + "whitelist": false, + "attachment": { + "hash": "6f399d9adcd59cadec87760241621f46a9daf25db3c442d61999a94042f01988", + "size": 1951, + "filename": "wrPDGkophQqo889HKhFp_3G0FlefakSC7HdEuD35iKw=.pem", + "location": "security-state-staging/intermediates/4c25a27e-2c0e-442f-893f-37ea349bb645.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wrPDGkophQqo889HKhFp/3G0FlefakSC7HdEuD35iKw=", + "crlite_enrolled": false, + "id": "f1dcb796-9a85-4f2d-a0fb-9b3f7b4c9089", + "last_modified": 1666727873239 + }, + { + "schema": 1666727391404, + "derHash": "728p9jb2K91HUxIvQfNBnufCh3WHvkqYB631iUZFjn8=", + "subject": "CN=Telia Root CA v2,O=Telia Finland Oyj,C=FI", + "subjectDN": "MEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2Mg==", + "whitelist": false, + "attachment": { + "hash": "ef7efd4cbf4031585f1a5951e9b5d6bac8a753a47e1861138b45eaaf0f441821", + "size": 2284, + "filename": "wrPDGkophQqo889HKhFp_3G0FlefakSC7HdEuD35iKw=.pem", + "location": "security-state-staging/intermediates/9808e62b-cdbd-44b3-941f-6f29f249116a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wrPDGkophQqo889HKhFp/3G0FlefakSC7HdEuD35iKw=", + "crlite_enrolled": false, + "id": "8adb25e4-ae4d-4bdd-aa24-a1b07637fb72", + "last_modified": 1666727873224 + }, + { + "schema": 1666727348199, + "derHash": "MlNBL9rUUjEIwJi7DuDv7df6/dAPsw5Hxrup/j4c24g=", + "subject": "CN=GDCA TrustAUTH R4 Primer CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG4xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJDAiBgNVBAMMG0dEQ0EgVHJ1c3RBVVRIIFI0IFByaW1lciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "63e5b17371590f3ec401bd9eb3b8ed4d9589063e1d6eef4f8889c34e61e19155", + "size": 2056, + "filename": "0QPovc90TjBNTpee1IAatbvOdHFYmqVUPYNToEldWCY=.pem", + "location": "security-state-staging/intermediates/5786a959-598e-4837-8220-7a23733b51ce.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0QPovc90TjBNTpee1IAatbvOdHFYmqVUPYNToEldWCY=", + "crlite_enrolled": false, + "id": "ac8f566c-4341-4d69-9204-a2ed8524ca5e", + "last_modified": 1666727873196 + }, + { + "schema": 1666727452137, + "derHash": "NcC4pXfRHJS6Zl4kLeRdZodSKlMeORu02ZXSYfOCmrc=", + "subject": "CN=certSIGN CA Class 2 G2,OU=certSIGN CA Class 2 G2,O=certSIGN,C=RO", + "subjectDN": "MGIxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEfMB0GA1UECxMWY2VydFNJR04gQ0EgQ2xhc3MgMiBHMjEfMB0GA1UEAxMWY2VydFNJR04gQ0EgQ2xhc3MgMiBHMg==", + "whitelist": false, + "attachment": { + "hash": "df0ec1a6797109126df83a75e016edf4ec5eae8f6a58946debcf2ddee8cf2524", + "size": 1609, + "filename": "noYGcVGJvDlxvfPLFE31WgvXSwkoZGrfCEpsm_L3qt4=.pem", + "location": "security-state-staging/intermediates/e03925b7-c8a0-4286-aa06-57ae5c110937.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "noYGcVGJvDlxvfPLFE31WgvXSwkoZGrfCEpsm/L3qt4=", + "crlite_enrolled": false, + "id": "a0571407-53b3-407c-bbd4-0be77d0d2d3e", + "last_modified": 1666727873182 + }, + { + "schema": 1666727331515, + "derHash": "I5/6htcQM7olWRR4IFfYfoQhrt1ZELeGkotqEkjD40E=", + "subject": "CN=CA Disig R2I3 Certification Service,O=Disig a.s.,L=Bratislava,C=SK", + "subjectDN": "MGUxCzAJBgNVBAYTAlNLMRMwEQYDVQQHDApCcmF0aXNsYXZhMRMwEQYDVQQKDApEaXNpZyBhLnMuMSwwKgYDVQQDDCNDQSBEaXNpZyBSMkkzIENlcnRpZmljYXRpb24gU2VydmljZQ==", + "whitelist": false, + "attachment": { + "hash": "3420f786d68e2c9b43dcb43b746ca258b7a646ade27a146a00fe8af877df4f59", + "size": 2142, + "filename": "QUf9pBcUZxnKMgP_Op4uLhRku-x-b_JvkEQPmCrNbZY=.pem", + "location": "security-state-staging/intermediates/ccf776b5-dad9-4394-b424-8c2cacda75f9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QUf9pBcUZxnKMgP/Op4uLhRku+x+b/JvkEQPmCrNbZY=", + "crlite_enrolled": false, + "id": "85d742bd-cad8-4921-b914-8cb3b1931d2c", + "last_modified": 1666727873154 + }, + { + "schema": 1666727440932, + "derHash": "MAO/iFNCfHuRAj91OYU9mHxY3E4Ru+BH0qkwXAGmFSw=", + "subject": "CN=certSIGN Non-Repudiation CA Class 4 G2,OU=certSIGN Non-Repudiation CA Class 4 G2,O=certSIGN,C=RO", + "subjectDN": "MIGCMQswCQYDVQQGEwJSTzERMA8GA1UEChMIY2VydFNJR04xLzAtBgNVBAsTJmNlcnRTSUdOIE5vbi1SZXB1ZGlhdGlvbiBDQSBDbGFzcyA0IEcyMS8wLQYDVQQDEyZjZXJ0U0lHTiBOb24tUmVwdWRpYXRpb24gQ0EgQ2xhc3MgNCBHMg==", + "whitelist": false, + "attachment": { + "hash": "bb516b77cbf95fd334340bb2777b602490882a3aa757c5d82da5214f34585cda", + "size": 1654, + "filename": "5z5YTIwg2yLkx8NYmJAGw7XsuJfhisYA72CTV71K7Tc=.pem", + "location": "security-state-staging/intermediates/052ceaa4-fdf0-40d7-8242-4aa8fcb41d3e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5z5YTIwg2yLkx8NYmJAGw7XsuJfhisYA72CTV71K7Tc=", + "crlite_enrolled": false, + "id": "0d31579b-64f1-4e30-bf5f-c484b7588ede", + "last_modified": 1666727873140 + }, + { + "schema": 1666727396796, + "derHash": "yJwktBWnLJQJZn6I+15ukqONVgnDyZ5V2ZtLHUWJkPk=", + "subject": "CN=certSIGN Qualified CA Class 3 G2,OU=certSIGN Qualified CA Class 3 G2,O=certSIGN,C=RO", + "subjectDN": "MHYxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEpMCcGA1UECxMgY2VydFNJR04gUXVhbGlmaWVkIENBIENsYXNzIDMgRzIxKTAnBgNVBAMTIGNlcnRTSUdOIFF1YWxpZmllZCBDQSBDbGFzcyAzIEcy", + "whitelist": false, + "attachment": { + "hash": "8790a1c05eb34050a0518d6b450074d0a070eab4fdfc196e7812a7cc01b12fc8", + "size": 1634, + "filename": "UPMbIpu91S39VXXONt79PssKsPf6YLoNySvPP-qGdwY=.pem", + "location": "security-state-staging/intermediates/dcc54254-18e6-46af-8584-ac6c726a3de5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UPMbIpu91S39VXXONt79PssKsPf6YLoNySvPP+qGdwY=", + "crlite_enrolled": false, + "id": "17f3db66-1a26-4fc2-b72b-4734ccc0cd35", + "last_modified": 1666727873126 + }, + { + "schema": 1666727446208, + "derHash": "sJNdwEtOYMDELe9+xXobHY+VjReYjnHMgKjPXmNbpbQ=", + "subject": "CN=D-TRUST SSL Class 3 CA 1 EV 2009,O=D-Trust GmbH,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKTAnBgNVBAMMIEQtVFJVU1QgU1NMIENsYXNzIDMgQ0EgMSBFViAyMDA5", + "whitelist": false, + "attachment": { + "hash": "a0be570032309c0aac96a0960dbae7ab544e4e0e1377b894c66b261ef8bb0ed5", + "size": 1886, + "filename": "lv5BNZ5aWd27ooolULDolFTwIaaWjHvG4yyH3rss4X8=.pem", + "location": "security-state-staging/intermediates/b08f7a1d-1d7f-4bb2-943d-d367a79b03d1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lv5BNZ5aWd27ooolULDolFTwIaaWjHvG4yyH3rss4X8=", + "crlite_enrolled": false, + "id": "6a2eddcc-3e30-4ca4-9940-4edce439b163", + "last_modified": 1666727873084 + }, + { + "schema": 1666727377048, + "derHash": "/x4NIzGWt+wgvt/GWYjgrhmF7GrDMqea3/5xuGvAIIo=", + "subject": "CN=Trustwave Organization Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIG1MQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE9MDsGA1UEAxM0VHJ1c3R3YXZlIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNIQTI1NiBDQSwgTGV2ZWwgMTEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbQ==", + "whitelist": false, + "attachment": { + "hash": "c4e85dd715d693fa9930a447454ded3d627e3d3b3b286d7920644bb09c1f9485", + "size": 1788, + "filename": "OSqnIRIhJQGKEtc7abZMpS4Y3djh39opzqI2WqlYVqM=.pem", + "location": "security-state-staging/intermediates/47da45ba-e89b-4a33-9469-9f4ccb00099f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OSqnIRIhJQGKEtc7abZMpS4Y3djh39opzqI2WqlYVqM=", + "crlite_enrolled": false, + "id": "146dea34-1807-4e59-b4c6-47e194cb050a", + "last_modified": 1666727873022 + }, + { + "schema": 1666727414415, + "derHash": "IxbQWi4tNH+hQRNbmO0J9W6B8c9WeXk9OzndbY5GGkg=", + "subject": "CN=D-TRUST CA 2-2 EV 2016,O=D-Trust GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxHzAdBgNVBAMTFkQtVFJVU1QgQ0EgMi0yIEVWIDIwMTYxFzAVBgNVBGETDk5UUkRFLUhSQjc0MzQ2", + "whitelist": false, + "attachment": { + "hash": "0825406b026a6a7eccc3247066b51626c668bc7f4c309505d5c1e32a1af60822", + "size": 2182, + "filename": "i6ujKRFlQ2Ig06L83VzPhB9sBlGOZCDwkUjNLjHtSJo=.pem", + "location": "security-state-staging/intermediates/8b7d4055-606f-43ea-83f8-a86a2a904797.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i6ujKRFlQ2Ig06L83VzPhB9sBlGOZCDwkUjNLjHtSJo=", + "crlite_enrolled": false, + "id": "6fc15004-2622-45ba-a821-4bcba503d6f5", + "last_modified": 1666727873007 + }, + { + "schema": 1666727329370, + "derHash": "WOoGiLeQmqqOqqL/8287vo9KKtA71wJDs5A0adtIkPc=", + "subject": "CN=Trustwave Organization Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIG1MQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE9MDsGA1UEAxM0VHJ1c3R3YXZlIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNIQTI1NiBDQSwgTGV2ZWwgMTEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbQ==", + "whitelist": false, + "attachment": { + "hash": "1844432d6d4b7a217e3b736de1419af2fca76a2f6ccc1ce30a69111d8d201479", + "size": 1817, + "filename": "OSqnIRIhJQGKEtc7abZMpS4Y3djh39opzqI2WqlYVqM=.pem", + "location": "security-state-staging/intermediates/cbb140b8-1fb0-4a2a-bd22-98b808d0732b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OSqnIRIhJQGKEtc7abZMpS4Y3djh39opzqI2WqlYVqM=", + "crlite_enrolled": false, + "id": "d7fe49e4-283f-4e24-9961-b2cdc4d883ed", + "last_modified": 1666727872994 + }, + { + "schema": 1666727348717, + "derHash": "d9bCr1p7hvY9mRjIdTN3nyrwjTXPoU2kk4yAP1PeGKE=", + "subject": "CN=SwissSign Personal Gold CA 2014 - G22,O=SwissSign AG,C=CH", + "subjectDN": "MFQxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxLjAsBgNVBAMTJVN3aXNzU2lnbiBQZXJzb25hbCBHb2xkIENBIDIwMTQgLSBHMjI=", + "whitelist": false, + "attachment": { + "hash": "37ef04fb6f6156e237c6f04ae3b383028744a07d5a6a4197c49d3411c82108f0", + "size": 2389, + "filename": "l5kY-a2-qCNGMn7LXb_lGEENF-dZOLccQOZBKr40wVE=.pem", + "location": "security-state-staging/intermediates/6a316d67-95bd-4ab5-aef3-82bd300391d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "l5kY+a2+qCNGMn7LXb/lGEENF+dZOLccQOZBKr40wVE=", + "crlite_enrolled": false, + "id": "26a70827-d9ed-46cc-8952-77eb4121605e", + "last_modified": 1666727872980 + }, + { + "schema": 1666727400445, + "derHash": "asFZtMK8jnKfO4RkLvEoa8yA13X+J4x0CtpGjVlDkCU=", + "subject": "CN=D-TRUST SSL Class 3 CA 1 2009,O=D-Trust GmbH,C=DE", + "subjectDN": "MEwxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJjAkBgNVBAMMHUQtVFJVU1QgU1NMIENsYXNzIDMgQ0EgMSAyMDA5", + "whitelist": false, + "attachment": { + "hash": "554db3f3f6728e13a10d546724cfe973f631696acc81d3d80206909beb978612", + "size": 1861, + "filename": "9w0QP9HzLXkfs-4zENaUFq2XKcQON1oyksoJ-Gg2AZE=.pem", + "location": "security-state-staging/intermediates/bb452f94-c0b6-479f-b577-d019a89c31d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9w0QP9HzLXkfs+4zENaUFq2XKcQON1oyksoJ+Gg2AZE=", + "crlite_enrolled": false, + "id": "37dde6cf-08e9-4e8e-bad3-9c993d870f8d", + "last_modified": 1666727872966 + }, + { + "schema": 1666727387966, + "derHash": "lyoYG2ApTroHMzucGYJEDUM5WrqR1FDsDvtIWu1J1ac=", + "subject": "CN=D-TRUST SSL CA 2 2020,O=D-Trust GmbH,C=DE", + "subjectDN": "MEQxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxHjAcBgNVBAMTFUQtVFJVU1QgU1NMIENBIDIgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "dff0ac820c6ed7f8e2a14edfc11acfc4f548f8a7afc8f0f31cf6e7cea3bd1ec8", + "size": 1918, + "filename": "Qw7yBnlh3ygQw2Ss4PCmfsq94Je-YAYnMuM9CpdMS0w=.pem", + "location": "security-state-staging/intermediates/2e9665a5-d214-4616-9a99-e5f11afc8ab6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qw7yBnlh3ygQw2Ss4PCmfsq94Je+YAYnMuM9CpdMS0w=", + "crlite_enrolled": false, + "id": "c3a9fa2d-eff6-4044-8fbd-014bd0c42505", + "last_modified": 1666727872950 + }, + { + "schema": 1666727383033, + "derHash": "xCpNjAkEuyEZBvQ7RBu7ybWgA7o2EYef00rldvfC92Q=", + "subject": "CN=DigiCert QuoVadis TLS ICA QV Root CA 1 G3,O=DigiCert\\, Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1EaWdpQ2VydCwgSW5jMTIwMAYDVQQDDClEaWdpQ2VydCBRdW9WYWRpcyBUTFMgSUNBIFFWIFJvb3QgQ0EgMSBHMw==", + "whitelist": false, + "attachment": { + "hash": "f131ca0a9a73adfbd8b9dbf2f0ffc7a130b98e7064f31563619f38656747629e", + "size": 1967, + "filename": "mDRXN3fhAPbsEZzZ6KgUrT9K8v0Uy1_lYQrkjelgldw=.pem", + "location": "security-state-staging/intermediates/59c08984-5d90-4b92-b199-9c225bbad4af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mDRXN3fhAPbsEZzZ6KgUrT9K8v0Uy1/lYQrkjelgldw=", + "crlite_enrolled": false, + "id": "d6e5312b-851e-4527-a9d9-ab6b24c9674e", + "last_modified": 1666727872909 + }, + { + "schema": 1666727397966, + "derHash": "zW65N+4Xqfz/YKeQ+L3gypq8oHs+9GB03Rl48Lyk1Ek=", + "subject": "CN=EAEko Herri Administrazioen CA - CA AAPP Vascas (2),OU=AZZ Ziurtagiri publikoa - Certificado publico SCA,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGdMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xOjA4BgNVBAsMMUFaWiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBTQ0ExPDA6BgNVBAMMM0VBRWtvIEhlcnJpIEFkbWluaXN0cmF6aW9lbiBDQSAtIENBIEFBUFAgVmFzY2FzICgyKQ==", + "whitelist": false, + "attachment": { + "hash": "8a1839dcde98a8a35e268cfeba8b062ef97d7715e9cc268450315ee23cfb2c2c", + "size": 2560, + "filename": "1du3EfOp93FwG10en54tLejZ-LiGC4VDvyvFeaa50D8=.pem", + "location": "security-state-staging/intermediates/9e4524c0-0fc8-4de5-bb45-2b4a4446d9a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1du3EfOp93FwG10en54tLejZ+LiGC4VDvyvFeaa50D8=", + "crlite_enrolled": false, + "id": "ae24e0ff-b8b9-4d71-8508-3ceac79ef326", + "last_modified": 1666727872896 + }, + { + "schema": 1666727392248, + "derHash": "ftGTYa1zTXA/ut8Cn1LsO2ZI2N1WuroIhO1PhZtbk3U=", + "subject": "CN=Herritar eta Erakundeen CA - CA de Ciudadanos y Entidades (4),OU=NZZ Ziurtagiri publikoa - Certificado publico SCI,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGnMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xOjA4BgNVBAsMMU5aWiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBTQ0kxRjBEBgNVBAMMPUhlcnJpdGFyIGV0YSBFcmFrdW5kZWVuIENBIC0gQ0EgZGUgQ2l1ZGFkYW5vcyB5IEVudGlkYWRlcyAoNCk=", + "whitelist": false, + "attachment": { + "hash": "4e2f85a9143fff0c499621d1b49e3f60d3c41d4a5456e8430b978d6171ec3334", + "size": 2576, + "filename": "psxZHe33uBE3X2CLur2frMJV_OKLVcjyPVVShpA--N4=.pem", + "location": "security-state-staging/intermediates/5cd6b31f-ffd5-4d52-a9e8-fa36ce7bbed8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "psxZHe33uBE3X2CLur2frMJV/OKLVcjyPVVShpA++N4=", + "crlite_enrolled": false, + "id": "9827f84e-7c8f-4336-952d-450a06e83326", + "last_modified": 1666727872882 + }, + { + "schema": 1666727429161, + "derHash": "JTA8/QvxuqHvJIwp8HP//C58gVgu4jtFx/HDsy40Gtg=", + "subject": "CN=EAEko HAetako langileen CA - CA personal de AAPP vascas (2),OU=AZZ Ziurtagiri publikoa - Certificado publico SCA,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGlMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xOjA4BgNVBAsMMUFaWiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBTQ0ExRDBCBgNVBAMMO0VBRWtvIEhBZXRha28gbGFuZ2lsZWVuIENBIC0gQ0EgcGVyc29uYWwgZGUgQUFQUCB2YXNjYXMgKDIp", + "whitelist": false, + "attachment": { + "hash": "33e5893bceb45f8047a84fd02b68d534b9290eb9deebc53433c7defcf5607d2c", + "size": 2572, + "filename": "fIoFCop2r5Hw838PL7crV4ts16Lq1s1xjk7E-I98dE8=.pem", + "location": "security-state-staging/intermediates/9635a458-c14c-408e-9445-8e8074645a18.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fIoFCop2r5Hw838PL7crV4ts16Lq1s1xjk7E+I98dE8=", + "crlite_enrolled": false, + "id": "ca492503-140b-4ef0-895d-d6ffd21fe324", + "last_modified": 1666727872868 + }, + { + "schema": 1666727333312, + "derHash": "DluAQLOrYKUNLV+xHhmusuRbVkszW3nXc9QrgdghnHU=", + "subject": "CN=DKB CA 1O1,O=Deutsche Kreditbank AG,C=DE", + "subjectDN": "MEMxCzAJBgNVBAYTAkRFMR8wHQYDVQQKExZEZXV0c2NoZSBLcmVkaXRiYW5rIEFHMRMwEQYDVQQDEwpES0IgQ0EgMU8x", + "whitelist": false, + "attachment": { + "hash": "0d6a04b61a3f6fe0d25a45ebce669b61b803818acf9fec52dce995e7820a5646", + "size": 2369, + "filename": "6mn1Oob4B3Gc-G5_S1wP0LQc9KpzGrXRD5LWKSS3GlE=.pem", + "location": "security-state-staging/intermediates/0abef2b9-f3eb-4326-b533-c2518a91342f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6mn1Oob4B3Gc+G5/S1wP0LQc9KpzGrXRD5LWKSS3GlE=", + "crlite_enrolled": false, + "id": "5f3d2450-bc1b-4592-b9c5-d733f00a7357", + "last_modified": 1666727872853 + }, + { + "schema": 1666727450781, + "derHash": "M3xiU3dZLx38xuZWMcYF64uW4AwUa0N/wfBnJoFU6Vk=", + "subject": "CN=CA Teknikoa - CA Tecnica,OU=AZZ Ziurtagiri publikoa - Certificado publico SCA,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGCMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xOjA4BgNVBAsMMUFaWiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBTQ0ExITAfBgNVBAMMGENBIFRla25pa29hIC0gQ0EgVGVjbmljYQ==", + "whitelist": false, + "attachment": { + "hash": "0c9abfeebfbd944291d27dfed50b0748b9adc5e8a3e675787d264631975bf3fa", + "size": 2523, + "filename": "DQi2term9F9bDPt5Nfb1rvSpznThwvfs2UDIgalF_gU=.pem", + "location": "security-state-staging/intermediates/4f1d16ba-fc95-4a1f-a3f9-da34f76cd637.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DQi2term9F9bDPt5Nfb1rvSpznThwvfs2UDIgalF/gU=", + "crlite_enrolled": false, + "id": "a8fcbf40-670e-4659-b931-283e4034014e", + "last_modified": 1666727872839 + }, + { + "schema": 1666727430691, + "derHash": "teRJHPHgoGwZRB+sKVtngiZClgP8xBTGJuIQsu/JXwA=", + "subject": "CN=Certigna Identity CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGCMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MSIwIAYDVQRhDBlOVFJGUi0wMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQQDDBRDZXJ0aWduYSBJZGVudGl0eSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "8fffbc881ae6f4ef224bc772397cce665787accfbbd9be25a200ea93e8a42613", + "size": 2515, + "filename": "lwJkDQYtogkGJEFJMX5DjskCXh2W7dNBRH_eJZlWjWo=.pem", + "location": "security-state-staging/intermediates/56d8fb27-0de0-4b61-ab41-299e1d0c14c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lwJkDQYtogkGJEFJMX5DjskCXh2W7dNBRH/eJZlWjWo=", + "crlite_enrolled": false, + "id": "52365c35-c17b-4e16-abb3-36a0687ea4c5", + "last_modified": 1666727872825 + }, + { + "schema": 1666727447424, + "derHash": "uNXWXCP/nYyQL/5r7B3S8gaTryDpiuR3UfHsspgSe24=", + "subject": "CN=Certigna Wild CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MHkxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MRkwFwYDVQQDDBBDZXJ0aWduYSBXaWxkIENB", + "whitelist": false, + "attachment": { + "hash": "c9d1b633464898b551171e5655c6d033a8158730020ca58cbc318d75b497caf4", + "size": 2503, + "filename": "6PsBH2V6vxU_w6DnSyY4EpqQYeF8QrYgohY-fWMfhmE=.pem", + "location": "security-state-staging/intermediates/26d15fd4-439e-44a4-9647-68a200a9c445.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6PsBH2V6vxU/w6DnSyY4EpqQYeF8QrYgohY+fWMfhmE=", + "crlite_enrolled": false, + "id": "eea82515-872e-42a6-a344-f0b1c78abfca", + "last_modified": 1666727872811 + }, + { + "schema": 1666727338355, + "derHash": "B/LOVcoapsuZJxmx5CPB0Cwep1mm4uq04VDIgoLiJVA=", + "subject": "CN=Certigna Services CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MH0xCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQQDDBRDZXJ0aWduYSBTZXJ2aWNlcyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "daabfa439246bec01c439eeb12080417731f3b5fc130616edf23facbe1995d4b", + "size": 2511, + "filename": "Vvyvg4-bOEbI6aO7K28ioVsUfckLqCSKqlIqZTEB_uE=.pem", + "location": "security-state-staging/intermediates/eafb024c-95d9-45ad-af85-982024cc73e9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vvyvg4+bOEbI6aO7K28ioVsUfckLqCSKqlIqZTEB/uE=", + "crlite_enrolled": false, + "id": "42115fab-f048-4a66-92be-15d909da568b", + "last_modified": 1666727872798 + }, + { + "schema": 1666727452311, + "derHash": "I7zV16lqUTqYHq0nk25ZqAKKgHvXKGBBj2i1VaKRFnA=", + "subject": "CN=Certigna Identity CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGCMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MSIwIAYDVQRhDBlOVFJGUi0wMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQQDDBRDZXJ0aWduYSBJZGVudGl0eSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "211c9fabe3b82a58caaded8a0e4fe51f77d8506e143103dc3f8db31704cb7e6e", + "size": 2178, + "filename": "lwJkDQYtogkGJEFJMX5DjskCXh2W7dNBRH_eJZlWjWo=.pem", + "location": "security-state-staging/intermediates/00c1f21f-a695-478a-9e33-19d48f9525d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lwJkDQYtogkGJEFJMX5DjskCXh2W7dNBRH/eJZlWjWo=", + "crlite_enrolled": false, + "id": "f695b618-333e-4759-a681-19c5f52aeb16", + "last_modified": 1666727872784 + }, + { + "schema": 1666727413032, + "derHash": "IR8wg7nnegHQgoVliXoc6UXuquBJQszDaQh9gIDJ5KY=", + "subject": "CN=Certigna Wild CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MHkxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MRkwFwYDVQQDDBBDZXJ0aWduYSBXaWxkIENB", + "whitelist": false, + "attachment": { + "hash": "5496347e4439fb545ba2bee7caa37b999d7fbae9c05425339345faf2c1e52d1f", + "size": 2166, + "filename": "6PsBH2V6vxU_w6DnSyY4EpqQYeF8QrYgohY-fWMfhmE=.pem", + "location": "security-state-staging/intermediates/fc3270ce-2a3b-48cd-9a3c-dba02a58d9a5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6PsBH2V6vxU/w6DnSyY4EpqQYeF8QrYgohY+fWMfhmE=", + "crlite_enrolled": false, + "id": "fc74dd7b-d279-49bc-8b15-430100d07015", + "last_modified": 1666727872770 + }, + { + "schema": 1666727367232, + "derHash": "ceZTv79eclFbQJm71eyIcoErR8bsH6mt0yfhySyeoW0=", + "subject": "CN=Certigna Services CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MH0xCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQQDDBRDZXJ0aWduYSBTZXJ2aWNlcyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1e69d697d43d9ba8bb5bef41ca322d6effdfa8f6fb680b6b8c84759cb9c7151f", + "size": 2170, + "filename": "Vvyvg4-bOEbI6aO7K28ioVsUfckLqCSKqlIqZTEB_uE=.pem", + "location": "security-state-staging/intermediates/fd277823-b07c-41b0-bff8-0cdb90cd2145.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vvyvg4+bOEbI6aO7K28ioVsUfckLqCSKqlIqZTEB/uE=", + "crlite_enrolled": false, + "id": "e83a378a-19a8-4110-bf0b-0f49b8965b01", + "last_modified": 1666727872757 + }, + { + "schema": 1666727418712, + "derHash": "679NxgDBfaBDgd79z8EZw/NO+0oE0IYJELgTx3ktdYU=", + "subject": "CN=Certigna Entity CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MHsxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MRswGQYDVQQDDBJDZXJ0aWduYSBFbnRpdHkgQ0E=", + "whitelist": false, + "attachment": { + "hash": "ada86b610b9f472f00e1921d532cf509f6692c87cb45580cbdd6cdb13bc4ffbd", + "size": 2170, + "filename": "0Eiyrr-wFIp1JqShtXzNCtJt2yUU0-j_mDnLwk1-AHk=.pem", + "location": "security-state-staging/intermediates/f1c34a68-978a-4f12-85f8-06564ca3f194.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0Eiyrr+wFIp1JqShtXzNCtJt2yUU0+j/mDnLwk1+AHk=", + "crlite_enrolled": false, + "id": "b641a082-df69-4132-a8a8-a1a801f83c2c", + "last_modified": 1666727872743 + }, + { + "schema": 1666727400274, + "derHash": "HMNYpt+gp2u1RwZg1487JfI8zWOVZn5JzPyCAdo9GS0=", + "subject": "CN=Certigna Entity CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MHsxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxHTAbBgNVBGEMFE5UUkZSLTQ4MTQ2MzA4MTAwMDM2MRswGQYDVQQDDBJDZXJ0aWduYSBFbnRpdHkgQ0E=", + "whitelist": false, + "attachment": { + "hash": "61a81d4d9681c5c86696195f2dcb40c8e795f9b490e5f72a7fa8ab4d6fd0ada9", + "size": 2507, + "filename": "0Eiyrr-wFIp1JqShtXzNCtJt2yUU0-j_mDnLwk1-AHk=.pem", + "location": "security-state-staging/intermediates/bfaf2dab-dfcf-4771-bf64-700aa69c0281.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0Eiyrr+wFIp1JqShtXzNCtJt2yUU0+j/mDnLwk1+AHk=", + "crlite_enrolled": false, + "id": "7c421e3b-f390-4235-af5f-e8a08ad5b7b3", + "last_modified": 1666727872729 + }, + { + "schema": 1666727441457, + "derHash": "20djOcy/zJ5L0dbLYGyifwBnnh74pYHnI2MJudY//jc=", + "subject": "CN=CA de Certificados SSL EV,OU=BZ Ziurtagiri publikoa - Certificado publico EV,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGBMQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xODA2BgNVBAsML0JaIFppdXJ0YWdpcmkgcHVibGlrb2EgLSBDZXJ0aWZpY2FkbyBwdWJsaWNvIEVWMSIwIAYDVQQDDBlDQSBkZSBDZXJ0aWZpY2Fkb3MgU1NMIEVW", + "whitelist": false, + "attachment": { + "hash": "6fa0a579644b4f4e2520de6c187e3d213f16c5035b15ab14690651899dbc5ca9", + "size": 2523, + "filename": "BjaOlbGE-OH8-vlWV2hZE4pvFyfXCm2QvaM1twHCrYk=.pem", + "location": "security-state-staging/intermediates/2ea9cda5-c628-44e6-aa94-ad77c8ed451b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BjaOlbGE+OH8+vlWV2hZE4pvFyfXCm2QvaM1twHCrYk=", + "crlite_enrolled": false, + "id": "e6c4396d-16a0-4773-a029-036b775926fd", + "last_modified": 1666727872716 + }, + { + "schema": 1666727401487, + "derHash": "cBMXJQIQq5EoBSfDtDaTEBW43O5ppok62k/60lpv5E8=", + "subject": "CN=DigiCert Baltimore TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEJhbHRpbW9yZSBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "99d10648a3600bd057bdf4d20caa46cac2a2675f2558b64ed5ae507f022c63a6", + "size": 1703, + "filename": "cxJYF2dxgQBCK6TeOX11kvdv3gHxeJgxQeT9-UzsuNw=.pem", + "location": "security-state-staging/intermediates/9707c48f-2459-4eb3-af34-9a6117bf3acd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cxJYF2dxgQBCK6TeOX11kvdv3gHxeJgxQeT9+UzsuNw=", + "crlite_enrolled": false, + "id": "dbbc2d12-9a44-48cc-96e6-e877bf97bbda", + "last_modified": 1666727872702 + }, + { + "schema": 1666727398776, + "derHash": "WkmxWuYP9ifaJyqHQ9ZxYrrKEJYWggMhOs+CJ69MSUI=", + "subject": "CN=Herritar eta Erakundeen CA - CA de Ciudadanos y Entidades (3),OU=NZZ Ziurtagiri publikoa - Certificado publico SCI,O=IZENPE S.A.,C=ES", + "subjectDN": "MIGnMQswCQYDVQQGEwJFUzEUMBIGA1UEChMLSVpFTlBFIFMuQS4xOjA4BgNVBAsTMU5aWiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBTQ0kxRjBEBgNVBAMTPUhlcnJpdGFyIGV0YSBFcmFrdW5kZWVuIENBIC0gQ0EgZGUgQ2l1ZGFkYW5vcyB5IEVudGlkYWRlcyAoMyk=", + "whitelist": false, + "attachment": { + "hash": "d85bcef74f09e1dbd2ce16da48da2ee5fc7a20a7fff9caa5dc49af6709f5f48c", + "size": 2576, + "filename": "bMhIINBfI65xm4qrUlaPfhKn2U2zsWEE7sOwUjKeoT8=.pem", + "location": "security-state-staging/intermediates/d89eb328-693b-4b24-90f8-34bf2e499dca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bMhIINBfI65xm4qrUlaPfhKn2U2zsWEE7sOwUjKeoT8=", + "crlite_enrolled": false, + "id": "fb272741-7da4-469b-ab59-52ece6f412f9", + "last_modified": 1666727872689 + }, + { + "schema": 1666727362603, + "derHash": "MGKRjZ3WF5JScbx/gIC4pqXSGFu9iA94Yv1MBDsZQZE=", + "subject": "CN=Buypass Class 2 CA 5,O=Buypass AS-983163327,C=NO", + "subjectDN": "MEsxCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEdMBsGA1UEAwwUQnV5cGFzcyBDbGFzcyAyIENBIDU=", + "whitelist": false, + "attachment": { + "hash": "fd5a69fd438ea769861dbb346922f606e66c972bf1b76d0b4b699d88aef6ef6a", + "size": 2170, + "filename": "QlGZmcMUM6a8-CxL2TmTAfoYCm-fXAouAzzKYCxGoss=.pem", + "location": "security-state-staging/intermediates/d6a8096b-9b05-41b8-984a-b244edc193b6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QlGZmcMUM6a8+CxL2TmTAfoYCm+fXAouAzzKYCxGoss=", + "crlite_enrolled": false, + "id": "355287c5-30e3-4992-b72c-8013c887d757", + "last_modified": 1666727872675 + }, + { + "schema": 1666727355401, + "derHash": "VKiey5iaHcz0ATY584l0oLwHQsKZPftr0Mvx9GKWRQE=", + "subject": "CN=Buypass Class 2 CA 2,O=Buypass AS-983163327,C=NO", + "subjectDN": "MEsxCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEdMBsGA1UEAwwUQnV5cGFzcyBDbGFzcyAyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "0614048608f149da266521b3b029d5d9fbab892a53adc1151476ffbb67f64ea8", + "size": 1849, + "filename": "61MXD2Y0eBaJtXDtSP1wW5LIdaIPjtVmYYrVCM8GDDg=.pem", + "location": "security-state-staging/intermediates/807b4084-3ed6-4c3d-9105-8e356f3bdc73.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "61MXD2Y0eBaJtXDtSP1wW5LIdaIPjtVmYYrVCM8GDDg=", + "crlite_enrolled": false, + "id": "ef80dba5-2722-4073-94a2-06a8e7cf1eee", + "last_modified": 1666727872662 + }, + { + "schema": 1666727387274, + "derHash": "2qBDVAf6RMKKuTnmgjgTYFd5CThzqWZJrW4DsF0oYmw=", + "subject": "CN=Buypass Class 3 CA 2,O=Buypass AS-983163327,C=NO", + "subjectDN": "MEsxCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEdMBsGA1UEAwwUQnV5cGFzcyBDbGFzcyAzIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "c3adfe4cbf2b1d7e9a605067bbec92b2d0430c2b1227c76517242204d270ea39", + "size": 1849, + "filename": "4ZQYrjRV6ScgdFiC0vn_Vy4QMIji7Tv-wkkEKEEQ6_I=.pem", + "location": "security-state-staging/intermediates/f4c24d5d-274d-468d-ad24-5434b0abf493.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4ZQYrjRV6ScgdFiC0vn/Vy4QMIji7Tv+wkkEKEEQ6/I=", + "crlite_enrolled": false, + "id": "a3808703-8211-46eb-9e79-a0dff56992fd", + "last_modified": 1666727872648 + }, + { + "schema": 1666727342983, + "derHash": "2keSYE5VSjiU78l0icxLZIMbLPflUSszle+ZY9bv3X8=", + "subject": "CN=TrustAsia ECC OV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIE9WIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "bbdeb7bbda57357d249e4f0ea5619cf90f3726579c496a08689eb39f6246cd01", + "size": 1353, + "filename": "iEDE3x3qYk1A22s-oFPMI_sJ50fi0xXzNpI5zdVmxDg=.pem", + "location": "security-state-staging/intermediates/9cdedd46-4987-4160-9d5f-7ba0b36973fc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iEDE3x3qYk1A22s+oFPMI/sJ50fi0xXzNpI5zdVmxDg=", + "crlite_enrolled": false, + "id": "e8fae9fd-982a-49f7-8275-54c9628b0d0a", + "last_modified": 1666727872634 + }, + { + "schema": 1666727385603, + "derHash": "T02M9edf963fZ3oCag79skPCAt5Nf0RgQOXL6izgPVQ=", + "subject": "CN=ZoTrus ECC EV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MFAxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMR0wGwYDVQQDExRab1RydXMgRUNDIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "26cb41eb5647449046f5342bc2149078f21cbde818f5b94c77441351cfea837a", + "size": 1232, + "filename": "3XI_2i9yaUzveumnB2CjghCDgOCbwLS6Zu1T9hcZOQM=.pem", + "location": "security-state-staging/intermediates/3031c2b4-bd88-4967-947d-8a1bb6169260.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3XI/2i9yaUzveumnB2CjghCDgOCbwLS6Zu1T9hcZOQM=", + "crlite_enrolled": false, + "id": "95b20461-07c6-4705-aad6-152f0c409957", + "last_modified": 1666727872621 + }, + { + "schema": 1666727389363, + "derHash": "DDfUmcRXkpQcYvHhnwbpdVYlzhFjOi6NuGnHqy14wxk=", + "subject": "CN=TrustAsia RSA OV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIE9WIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "7a6196f9d0bd849dfe10d63cb26b788e9dad0e50b1fa52db3be03dacd3afe61f", + "size": 1804, + "filename": "I6wax7AL21Sejsnn10Aj3krBMY8KLuzllaqlStYexGI=.pem", + "location": "security-state-staging/intermediates/65eb8f80-ee22-48ad-a079-def8bfca9e3f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "I6wax7AL21Sejsnn10Aj3krBMY8KLuzllaqlStYexGI=", + "crlite_enrolled": false, + "id": "aa4b92b3-a07f-4032-b16a-342e7054ef8b", + "last_modified": 1666727872607 + }, + { + "schema": 1666727404771, + "derHash": "6mljaWdQVkoiiz64jg/UMExdonp6+S0dQZk1elAHgxU=", + "subject": "CN=WoTrus EV SSL CA,O=WoTrus CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1RydXMgQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29UcnVzIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "fc57e3fad962bd4b00feeaae47893e01b5b42af32dadb1d3e0e55cad0d68a007", + "size": 1691, + "filename": "bfm30K2ufQC3gQINxl7OwREoFSQEJrK4ecMD3jy_Qlc=.pem", + "location": "security-state-staging/intermediates/744615e6-3b14-49f9-9fef-3d7691a5c6d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bfm30K2ufQC3gQINxl7OwREoFSQEJrK4ecMD3jy/Qlc=", + "crlite_enrolled": false, + "id": "9cb658c6-3e52-4a94-b178-1d2ab7a03ad7", + "last_modified": 1666727872593 + }, + { + "schema": 1666727402170, + "derHash": "Zd4yKh73r/7etzhxOMJgYIJbCMwn4Zkt2erIM3KXlXs=", + "subject": "CN=SpaceSSL CA,OU=SpaceSSL Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MHIxCzAJBgNVBAYTAlBMMSIwIAYDVQQKDBlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMSkwJwYDVQQLDCBTcGFjZVNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEUMBIGA1UEAwwLU3BhY2VTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "52e6e18f68c49814eb88ea228460269803ebcae2f04a71366b605d06fd85ae21", + "size": 1638, + "filename": "AOXy6QeFRXr3Le2jy9VUHVc0TY6NofDQseFQcaDRlow=.pem", + "location": "security-state-staging/intermediates/189976ec-c00a-4ec0-bc47-3f11ee1374ff.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AOXy6QeFRXr3Le2jy9VUHVc0TY6NofDQseFQcaDRlow=", + "crlite_enrolled": false, + "id": "a846caeb-4b0d-4bb1-af76-d203f6bd83ea", + "last_modified": 1666727872580 + }, + { + "schema": 1666727380169, + "derHash": "kK8bYaCCZipjnKpoxqP7Qeu6N2DDUaSh6J+22srUhvw=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSBIMiAyMDIx", + "whitelist": false, + "attachment": { + "hash": "cd5cf65b5f603e9fadcbf6fb536bc9043b3881094161a4a6e0b15cb311a3ed46", + "size": 1272, + "filename": "WZLG43M_xjDLnY1jaF9kQcG-7bpQC8fM0xt9RK4n3dY=.pem", + "location": "security-state-staging/intermediates/626980a9-463c-4a9d-891e-45a1de9a7525.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WZLG43M/xjDLnY1jaF9kQcG+7bpQC8fM0xt9RK4n3dY=", + "crlite_enrolled": false, + "id": "bfebb62d-c09f-4bec-9fe4-bfa95ea3ef7b", + "last_modified": 1666727872565 + }, + { + "schema": 1666727424275, + "derHash": "CbwbE3wDEjnveIZz6U6xf18+yrB9Otv7SF51q/qvO5o=", + "subject": "CN=GlobalSign Domain Validation CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MGAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTYwNAYDVQQDEy1HbG9iYWxTaWduIERvbWFpbiBWYWxpZGF0aW9uIENBIC0gU0hBMjU2IC0gRzM=", + "whitelist": false, + "attachment": { + "hash": "4827709bafd08a7e0584c4d4411937345e9fa8505ca1a15d2c4483e729c804a6", + "size": 1626, + "filename": "1kflsxIeB1bcxW1C61yFNdNQjO_xuF0ORdL9EtMe-Rs=.pem", + "location": "security-state-staging/intermediates/fdb62a70-c092-4d32-81b3-d72418696f57.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1kflsxIeB1bcxW1C61yFNdNQjO/xuF0ORdL9EtMe+Rs=", + "crlite_enrolled": false, + "id": "1c345873-cf2f-48aa-a6a3-93a5661939df", + "last_modified": 1666727872549 + }, + { + "schema": 1666727406839, + "derHash": "Xr2YdOa4jaOPSnuVZs5dTfBeDCJNlJhq2MxeugBKDAQ=", + "subject": "CN=CrowdStrike Global EV CA G2,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEkMCIGA1UEAxMbQ3Jvd2RTdHJpa2UgR2xvYmFsIEVWIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "a8fc7f0924e8d457aed66b875eaec7fb55fa49bfb53ef60ff6a412eba3edc497", + "size": 1727, + "filename": "CyxodpBYldiqnjPTH1njRYIU3fXbBlov3Ly_xnmsIrE=.pem", + "location": "security-state-staging/intermediates/bef337d4-8432-4ed7-8461-f7619440d1d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CyxodpBYldiqnjPTH1njRYIU3fXbBlov3Ly/xnmsIrE=", + "crlite_enrolled": false, + "id": "ea3f9292-c7b7-48b2-acef-4af36a5aca25", + "last_modified": 1666727872522 + }, + { + "schema": 1666727378946, + "derHash": "AzAobfNhLA6Wjc1RinoxbV4HkNHKMkuQaw7wF8C+Pqc=", + "subject": "CN=WoSign DV SSL CA,O=WoSign CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1NpZ24gQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29TaWduIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a75ee51db3cb9c7a4b54e1099fdadd77ab4124acdb08f7ba375a7b614929984f", + "size": 1691, + "filename": "G_V-4AYI9XAkNjdk4SZqLrWkylfZbgPG1S4cNnAU1F4=.pem", + "location": "security-state-staging/intermediates/d43405c6-d4c4-41e3-baac-5b53fab6891e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G/V+4AYI9XAkNjdk4SZqLrWkylfZbgPG1S4cNnAU1F4=", + "crlite_enrolled": false, + "id": "24d073eb-1380-4a6f-b7e1-f8821cd871c7", + "last_modified": 1666727872508 + }, + { + "schema": 1666727435940, + "derHash": "OBiZWyigkNg6YWZxF9UMd8Hx3HoTdoDy5VznVTFPLQo=", + "subject": "CN=Yekta Domain Validated SSL CA 1,O=Pardazeshgaran Shahr Hooshmand Yekta Co.,C=IR", + "subjectDN": "MGoxCzAJBgNVBAYTAklSMTEwLwYDVQQKDChQYXJkYXplc2hnYXJhbiBTaGFociBIb29zaG1hbmQgWWVrdGEgQ28uMSgwJgYDVQQDDB9ZZWt0YSBEb21haW4gVmFsaWRhdGVkIFNTTCBDQSAx", + "whitelist": false, + "attachment": { + "hash": "bb7867525742b369a2d4aa0d32435efc5335cb5345f63c531ba57670798dd390", + "size": 1760, + "filename": "H9EWOWmPlQDDBwAbp_31T2xDnk5xl_tppGco9Jp0eOk=.pem", + "location": "security-state-staging/intermediates/4300d654-d85c-40f8-97a8-d15d616ba1c3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "H9EWOWmPlQDDBwAbp/31T2xDnk5xl/tppGco9Jp0eOk=", + "crlite_enrolled": false, + "id": "1ebb8ae1-2451-4e9b-9bbf-440aa561b615", + "last_modified": 1666727872492 + }, + { + "schema": 1666727356082, + "derHash": "F/CKT13DjEm7F9zWSyznQs/quZvltn28FMLolnccYOE=", + "subject": "CN=Yekta Organization Validated SSL CA 1,O=Pardazeshgaran Shahr Hooshmand Yekta Co.,C=IR", + "subjectDN": "MHAxCzAJBgNVBAYTAklSMTEwLwYDVQQKDChQYXJkYXplc2hnYXJhbiBTaGFociBIb29zaG1hbmQgWWVrdGEgQ28uMS4wLAYDVQQDDCVZZWt0YSBPcmdhbml6YXRpb24gVmFsaWRhdGVkIFNTTCBDQSAx", + "whitelist": false, + "attachment": { + "hash": "486e0a3b6a007b50af4caea8b8eb13afd585138e57d8fe341eec1ec7a20adc71", + "size": 1768, + "filename": "Ufih1VeDxhjbteVJNXygXP2MY_Q8c5bmwG0Ll8AQXTY=.pem", + "location": "security-state-staging/intermediates/1e2884de-0e02-466d-87d3-08333e45105e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ufih1VeDxhjbteVJNXygXP2MY/Q8c5bmwG0Ll8AQXTY=", + "crlite_enrolled": false, + "id": "382c3d21-f0c0-430a-9512-eb2ca7e514b4", + "last_modified": 1666727872479 + }, + { + "schema": 1666727420650, + "derHash": "tfYuw4ExzRSx/JW4d/TSEL5L+sx+amqhQi2J40t6xME=", + "subject": "CN=GoGetSSL Domain Validation CA SHA2,OU=GoGetSSL Certification Authority,O=EnVers Group SIA,C=LV", + "subjectDN": "MIGAMQswCQYDVQQGEwJMVjEZMBcGA1UECgwQRW5WZXJzIEdyb3VwIFNJQTEpMCcGA1UECwwgR29HZXRTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxKzApBgNVBAMMIkdvR2V0U1NMIERvbWFpbiBWYWxpZGF0aW9uIENBIFNIQTI=", + "whitelist": false, + "attachment": { + "hash": "1a7005883cb2d9190f4111d29fd64d2d6eb960cc76830263a72234e826bbf517", + "size": 1748, + "filename": "p185bYxiFO-ZwKAV-mIneHw-vuH2rqpYkDOyjKN7DyE=.pem", + "location": "security-state-staging/intermediates/eaf84a71-fc34-486e-afdf-26e1ecaca22a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "p185bYxiFO+ZwKAV+mIneHw+vuH2rqpYkDOyjKN7DyE=", + "crlite_enrolled": false, + "id": "35456425-adac-45ee-84d4-3a5c1b317e14", + "last_modified": 1666727872465 + }, + { + "schema": 1666727408223, + "derHash": "wl8elgALw24qpc1UvyT0i3aJChYuGtjhBJkmUFEGJsI=", + "subject": "CN=TrustAsia DV SSL CA - C3,O=TrustAsia Technologies Inc.,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMSQwIgYDVQQKDBtUcnVzdEFzaWEgVGVjaG5vbG9naWVzIEluYy4xITAfBgNVBAMMGFRydXN0QXNpYSBEViBTU0wgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "8818c8f5c68801123839b3cc239cbd89d6750c3eebd094b7e193b8d4c008359f", + "size": 1691, + "filename": "kZD1rAhydUcYx_iHiwQ-xsh92idc5Cryvs4-2MeQnpk=.pem", + "location": "security-state-staging/intermediates/30df4c61-9f07-4a43-9956-25742af9cddb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kZD1rAhydUcYx/iHiwQ+xsh92idc5Cryvs4+2MeQnpk=", + "crlite_enrolled": false, + "id": "2e43a311-dc6c-4644-b4fd-dabaca2c1929", + "last_modified": 1666727872450 + }, + { + "schema": 1666727335162, + "derHash": "k+M699owMFMNCQycVXYsrefq6UP4NDSdEFepDrZ/MGs=", + "subject": "CN=DigiCert QV TLS ICA G1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMRcwFQYDVQQKDA5EaWdpQ2VydCwgSW5jLjEfMB0GA1UEAwwWRGlnaUNlcnQgUVYgVExTIElDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "e53ef7db1684caaae756089b951bc6e4365a4f5c50235345441db0a95fe9771c", + "size": 1943, + "filename": "z2OteA0mfCO56LKn5Y19lFMNUGHucFGGWmVD8x0rpLU=.pem", + "location": "security-state-staging/intermediates/04030cb6-4ee3-4cec-92bb-45022fc5c066.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z2OteA0mfCO56LKn5Y19lFMNUGHucFGGWmVD8x0rpLU=", + "crlite_enrolled": false, + "id": "e117a5f6-6353-4757-b12d-27c470add07c", + "last_modified": 1666727872435 + }, + { + "schema": 1666727334308, + "derHash": "/nNANw1ZWjn/4Q3OIZdEfOZ8mL4o8DMMj8hd0DISSG0=", + "subject": "CN=XinChaCha Trust SSL Extended Validated,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MHYxCzAJBgNVBAYTAkNOMTYwNAYDVQQKDC1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xLzAtBgNVBAMMJlhpbkNoYUNoYSBUcnVzdCBTU0wgRXh0ZW5kZWQgVmFsaWRhdGVk", + "whitelist": false, + "attachment": { + "hash": "11c238131ce3dc238f6eaf26ec690ecf4d548a913c3c5bc452afce5fbc790b99", + "size": 1776, + "filename": "eMfyN-Ikg69beln4AW1OIVajdbo0bMtS7jlQTjdmeVM=.pem", + "location": "security-state-staging/intermediates/e6f8b0f0-9bb3-4ffd-b7bc-95b78916692e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "eMfyN+Ikg69beln4AW1OIVajdbo0bMtS7jlQTjdmeVM=", + "crlite_enrolled": false, + "id": "93b12a0d-0df4-4127-af9a-a1ef7dbef757", + "last_modified": 1666727872421 + }, + { + "schema": 1666727381525, + "derHash": "bEDQf0cFpbTwTGrs3Fob9f040rbPLbfyEsolEHW+El0=", + "subject": "CN=Shuidi Webtrust SSL Extended Validated,O=Shanghai Ping An Credit Reference Company Limited,C=CN", + "subjectDN": "MHoxCzAJBgNVBAYTAkNOMTowOAYDVQQKDDFTaGFuZ2hhaSBQaW5nIEFuIENyZWRpdCBSZWZlcmVuY2UgQ29tcGFueSBMaW1pdGVkMS8wLQYDVQQDDCZTaHVpZGkgV2VidHJ1c3QgU1NMIEV4dGVuZGVkIFZhbGlkYXRlZA==", + "whitelist": false, + "attachment": { + "hash": "15549ed43fba7dd4ac582772db414c06ac7f55ba18ecf419619290411515f7e8", + "size": 1780, + "filename": "YARY8M4pNVQjSsk5Xgw-N3b7ZaQssU3-Ml2q5c55P1Q=.pem", + "location": "security-state-staging/intermediates/63152062-fb6c-4aea-80be-e8ee9f7ee7e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YARY8M4pNVQjSsk5Xgw+N3b7ZaQssU3+Ml2q5c55P1Q=", + "crlite_enrolled": false, + "id": "1944c7f1-bf0e-4729-a9ec-68ff7ce41381", + "last_modified": 1666727872407 + }, + { + "schema": 1666727374976, + "derHash": "f79dtJF2OZMAdqr/ePyR3aDv7qhsrTihjZiUfXzTaUg=", + "subject": "CN=SZCA EV SSL CA,O=Shenzhen Digital Certificate Authority Center Co.\\, Ltd,C=CN", + "subjectDN": "MGcxCzAJBgNVBAYTAkNOMT8wPQYDVQQKDDZTaGVuemhlbiBEaWdpdGFsIENlcnRpZmljYXRlIEF1dGhvcml0eSBDZW50ZXIgQ28uLCBMdGQxFzAVBgNVBAMMDlNaQ0EgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "e03f3052b8789db5fcf3cdcc2c99f637a46dfc7e75b7e90821532f0139858a50", + "size": 1756, + "filename": "m9Fzkl4nfip9REp7mGOZ4yhSFF4FL4A--9o7eUTYzMQ=.pem", + "location": "security-state-staging/intermediates/7c9c9455-5d39-4925-822c-e353ba07464d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "m9Fzkl4nfip9REp7mGOZ4yhSFF4FL4A++9o7eUTYzMQ=", + "crlite_enrolled": false, + "id": "26dd89a7-d444-4647-be98-393c4cd93ed5", + "last_modified": 1666727872380 + }, + { + "schema": 1666727445175, + "derHash": "xUU/fBRwFqmqD19LsdIX3r4cLTaMU890019VxSM8SYo=", + "subject": "CN=OKCERT R4 EV SSL CA G2,OU=Kingnettech,O=Kingnet Information Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MHcxCzAJBgNVBAYTAkNOMTEwLwYDVQQKDChLaW5nbmV0IEluZm9ybWF0aW9uIFRlY2hub2xvZ3kgQ28uLCBMdGQuMRQwEgYDVQQLDAtLaW5nbmV0dGVjaDEfMB0GA1UEAwwWT0tDRVJUIFI0IEVWIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "7d588ca61738784840bbd7ff005ff17935bc0d1a36366ef3279871fa2e31ba71", + "size": 1776, + "filename": "fFdFUEieyjNs9DctpEE6fSI3FB56V1hzaJMiFIo0Rnk=.pem", + "location": "security-state-staging/intermediates/72d9b43e-856b-4662-a065-6f3500f76267.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fFdFUEieyjNs9DctpEE6fSI3FB56V1hzaJMiFIo0Rnk=", + "crlite_enrolled": false, + "id": "ee0c36d3-570c-4d2a-a081-be514ff0a94b", + "last_modified": 1666727872366 + }, + { + "schema": 1666727446556, + "derHash": "Maw0azEHPcDRNOKfwhLMShXtNTDuoe3PyNrLNkktXeQ=", + "subject": "CN=4fastssl.com,OU=3S2N Sp. z o.o.,O=3S2N Sp. z o.o.,C=PL", + "subjectDN": "MFgxCzAJBgNVBAYTAlBMMRgwFgYDVQQKDA8zUzJOIFNwLiB6IG8uby4xGDAWBgNVBAsMDzNTMk4gU3AuIHogby5vLjEVMBMGA1UEAwwMNGZhc3Rzc2wuY29t", + "whitelist": false, + "attachment": { + "hash": "cae137f6053594f20d796d4a5a5d5e9f305e814739d0e7870c9bd2f123fd1605", + "size": 1630, + "filename": "sN7Yv0qTyjW-BJTfkzdVvYxd1ExUFwl3cvwbLztk5Fc=.pem", + "location": "security-state-staging/intermediates/bea7e911-09b3-4259-995d-27e5b79f0dda.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sN7Yv0qTyjW+BJTfkzdVvYxd1ExUFwl3cvwbLztk5Fc=", + "crlite_enrolled": false, + "id": "49094aa8-a7a0-4644-bc88-83ff76b96f56", + "last_modified": 1666727872352 + }, + { + "schema": 1666727350604, + "derHash": "43IiEmajMN0T6xOI36rx+rEd8lS2M4XLY3v++PtftnU=", + "subject": "CN=QIDUOCA 2018 DV SSL,OU=Domain Validated SSL,O=Suzhou Qiduo Information Technology Co.\\, Ltd.,C=CN", + "subjectDN": "MIGCMQswCQYDVQQGEwJDTjE2MDQGA1UECgwtU3V6aG91IFFpZHVvIEluZm9ybWF0aW9uIFRlY2hub2xvZ3kgQ28uLCBMdGQuMR0wGwYDVQQLDBREb21haW4gVmFsaWRhdGVkIFNTTDEcMBoGA1UEAwwTUUlEVU9DQSAyMDE4IERWIFNTTA==", + "whitelist": false, + "attachment": { + "hash": "0ba2fb2707f62d2ab80d7dea9b69d32058ae47514caecd5cf221db84eb014577", + "size": 1752, + "filename": "HjT11uxb0dXXxZnh6FUmEWVHULtStPZ1LLzRYdkdoqY=.pem", + "location": "security-state-staging/intermediates/b6478416-94fe-4096-b983-23b33e8a6d14.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HjT11uxb0dXXxZnh6FUmEWVHULtStPZ1LLzRYdkdoqY=", + "crlite_enrolled": false, + "id": "774024b8-2afe-482e-ad94-a88d0107d630", + "last_modified": 1666727872338 + }, + { + "schema": 1666727344753, + "derHash": "pBaiukkMRU4juFvwh9t7E39PR9l0fmD4aS/0yN8OBis=", + "subject": "CN=TrustOcean Certification Authority,O=QiaoKr Corporation Limited,C=CN", + "subjectDN": "MF8xCzAJBgNVBAYTAkNOMSMwIQYDVQQKDBpRaWFvS3IgQ29ycG9yYXRpb24gTGltaXRlZDErMCkGA1UEAwwiVHJ1c3RPY2VhbiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "40aa115c2456db45d24f826f71babce7e8711e6dadb79d51f81544b5cc127980", + "size": 1703, + "filename": "AqnxlVGHcuEywBdQxncOoMBd2w-idNlfoDEnTgEx5S4=.pem", + "location": "security-state-staging/intermediates/a719d773-6e0a-47c4-84e1-8f61cf95748f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AqnxlVGHcuEywBdQxncOoMBd2w+idNlfoDEnTgEx5S4=", + "crlite_enrolled": false, + "id": "13b2016a-1e62-488b-bfaa-2c73a57c8dc9", + "last_modified": 1666727872324 + }, + { + "schema": 1666727389695, + "derHash": "itR/bXCkT6gK8PkxEl/+OnaHb/rSGaTUChPAONyF5p4=", + "subject": "CN=TWCA Global Root CA,OU=Root CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0E=", + "whitelist": false, + "attachment": { + "hash": "6e89794002729d6ef21257984707c517e72b2e5f2de8c602a8b0b3cdc1a90d19", + "size": 1914, + "filename": "xES1tmzl1x4bXkDyc4XJXL_SSgW1b3DKwJkvD1DDN5w=.pem", + "location": "security-state-staging/intermediates/9bf8c741-9386-401d-8c59-f5410390ae2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xES1tmzl1x4bXkDyc4XJXL/SSgW1b3DKwJkvD1DDN5w=", + "crlite_enrolled": false, + "id": "954d0ac8-d979-4964-a8f1-6ae3ae761ebc", + "last_modified": 1666727872311 + }, + { + "schema": 1666727394431, + "derHash": "0VgzPgKxDID2lmt3Bat5VwsX2jix45IUWUDtfmfuT9g=", + "subject": "CN=TrustAsia RSA EV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIEVWIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "a5261900f9c803468ebe0d8d9a0abeaeced2a9d2a845da37daa5650f563dd624", + "size": 1792, + "filename": "F5bT3sFndGlyea2tS1nY_6Ln9zvFckAmivpIsKkT5XQ=.pem", + "location": "security-state-staging/intermediates/7cf5568f-520a-4c42-a67a-874421ee65db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "F5bT3sFndGlyea2tS1nY/6Ln9zvFckAmivpIsKkT5XQ=", + "crlite_enrolled": false, + "id": "42a054e1-89f9-48cc-b92a-4da9e9c308e3", + "last_modified": 1666727872297 + }, + { + "schema": 1666727329881, + "derHash": "z20DM9C+LGmkLUU5YN7p4QnZ6IQ+owYaFnHW6vhet9g=", + "subject": "CN=emSign Class 1 CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGoxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDExZlbVNpZ24gQ2xhc3MgMSBDQSAtIEcx", + "whitelist": false, + "attachment": { + "hash": "266278533c3786da3d8865fcd54e0e8fbedf26868dc64378e368d49cb3b7817b", + "size": 1589, + "filename": "8eYpGcWTiEZyMyyg19Q9T-bCQt78vGjuJDkLU10HGx8=.pem", + "location": "security-state-staging/intermediates/ca8d7c44-0de7-4fdb-a4bb-a07b24618b68.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8eYpGcWTiEZyMyyg19Q9T+bCQt78vGjuJDkLU10HGx8=", + "crlite_enrolled": false, + "id": "1cb8aaf5-1bf5-4af2-992d-adeedaabcaa7", + "last_modified": 1666727872267 + }, + { + "schema": 1666727386927, + "derHash": "nGSppD6ZDpj7zoMXstTBwH/+bgMtqLttYKaW4v8Djx8=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDI=", + "whitelist": false, + "attachment": { + "hash": "10551307f634eba8fbd03c34df0f74b6bf4e7d3b3857885e7e915dc171786627", + "size": 1329, + "filename": "OhdUJ-wrpPRtpX53tkyqVLKQoNpdCCWve8MQQaQDQ2A=.pem", + "location": "security-state-staging/intermediates/f2b86d33-5739-44cf-b054-65badcce6a6f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OhdUJ+wrpPRtpX53tkyqVLKQoNpdCCWve8MQQaQDQ2A=", + "crlite_enrolled": false, + "id": "d2a922d8-4206-443a-b5a3-7ca1cfd237ba", + "last_modified": 1666727872249 + }, + { + "schema": 1666727367887, + "derHash": "yKYQupQXdw0sAt4ivKjFakKK916ONU76NsVoIh3bfPw=", + "subject": "CN=WISeKey CertifyID SSL GB CA 2,O=WISeKey,C=CH", + "subjectDN": "MEcxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSYwJAYDVQQDEx1XSVNlS2V5IENlcnRpZnlJRCBTU0wgR0IgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "25f18c47254a897f57257e91e33dccd040b88a2d2805589a22993da2900db84f", + "size": 1699, + "filename": "ktJRzJxhVE71wdby3vum5K-dgm6QylD4z-l9CBGOUQU=.pem", + "location": "security-state-staging/intermediates/3394e1c3-463f-4ac0-b8fd-f016d1818723.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ktJRzJxhVE71wdby3vum5K+dgm6QylD4z+l9CBGOUQU=", + "crlite_enrolled": false, + "id": "a5ae68c0-c7e5-4258-b324-f431b91dd676", + "last_modified": 1666727872234 + }, + { + "schema": 1666628237732, + "derHash": "FGwc+bt9IxBg0Pq9mG6YUPAFAfWjt7asQsUbOMjiKiM=", + "subject": "CN=Inst. of Accelerating Systems and Applications TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGQMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFIMEYGA1UEAww/SW5zdC4gb2YgQWNjZWxlcmF0aW5nIFN5c3RlbXMgYW5kIEFwcGxpY2F0aW9ucyBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "b3a716e931da57106ba84bf10a2a8da0fff0d2636ab6d360fef476ca4f2059dd", + "size": 2946, + "filename": "D7Xfk-7YUggnXZisbV8HX7kcYYgzDkMy57Y3cC1Fqqs=.pem", + "location": "security-state-staging/intermediates/6bb7cf22-95ce-47fd-a9cb-82d4ddb64e6b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D7Xfk+7YUggnXZisbV8HX7kcYYgzDkMy57Y3cC1Fqqs=", + "crlite_enrolled": false, + "id": "3193b50c-9a46-4a98-8893-61edfe5a37a0", + "last_modified": 1666727872220 + }, + { + "schema": 1666727420466, + "derHash": "ukJRIvMdm658vh5/ov9Det8oPgFDM0hfdpHLhWrsd2o=", + "subject": "CN=Harokopio University TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHYxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMS4wLAYDVQQDDCVIYXJva29waW8gVW5pdmVyc2l0eSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "139fe5385c60b18fad23f54f3e417cba7efd06eb3de735d28d1857c5a1501d66", + "size": 3031, + "filename": "mTSefyaEu5Ue6uOHJdZVbu7WMV69SsHOEk-8P2x2eZw=.pem", + "location": "security-state-staging/intermediates/47d0b70a-4b5b-4d4e-a67b-c0920e173ea1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mTSefyaEu5Ue6uOHJdZVbu7WMV69SsHOEk+8P2x2eZw=", + "crlite_enrolled": false, + "id": "b0dce077-d224-4f84-8dcf-8f24cfbd18b4", + "last_modified": 1666727872206 + }, + { + "schema": 1666727428992, + "derHash": "lJ1rS3YcoTStPnqFcRhvWA7oh/LGtWi1FA9BV/mNaN0=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDE=", + "whitelist": false, + "attachment": { + "hash": "1a3bc0dfeb8f9e3f79809953a727d69dc11a315684b4cf57c35d226206502e31", + "size": 1329, + "filename": "OZOx9JJC3JsSDSjFX2iEA3_ED6gCha3L1ZrOeRNoyx8=.pem", + "location": "security-state-staging/intermediates/e45aef57-3b9a-453f-bcc3-d1f5b6cc8d9f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OZOx9JJC3JsSDSjFX2iEA3/ED6gCha3L1ZrOeRNoyx8=", + "crlite_enrolled": false, + "id": "cbe28fbe-3eec-45be-a5ef-d53c15988acd", + "last_modified": 1666727872179 + }, + { + "schema": 1666727364817, + "derHash": "kmUc1IoCiKowdTVN3BXcJNCvjuZwFbaKFFlwoJNHXhk=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "82b4c8e10f053e786fb7faab5851c6767f411878b722fa4fe6665cc129ba1fd9", + "size": 1715, + "filename": "RTNL_r8kG4tFP4WLDbH3t3jORvLYveRyVe5Lped3y2k=.pem", + "location": "security-state-staging/intermediates/656d64cb-73fc-4bac-8a0a-77d5959346d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RTNL/r8kG4tFP4WLDbH3t3jORvLYveRyVe5Lped3y2k=", + "crlite_enrolled": false, + "id": "d9a8325b-38fd-4205-a3e8-1e692af95693", + "last_modified": 1666727872165 + }, + { + "schema": 1666727375150, + "derHash": "1oMbpDYH9awZd41idTFWKvVRRfGRyrXvr6DgAFRCswI=", + "subject": "CN=Microsoft Azure TLS Issuing CA 05,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNQ==", + "whitelist": false, + "attachment": { + "hash": "eec823ad61a76103455ac29ab1ea726af33851691074cffd1fb703c49fe85217", + "size": 2121, + "filename": "4i4h0jN9NROr1xKJI-TQ1Q_ZIfUjPMXtmWUsDR3Pjiw=.pem", + "location": "security-state-staging/intermediates/2f77f92c-4a14-472b-8226-b3089173a1cc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4i4h0jN9NROr1xKJI+TQ1Q/ZIfUjPMXtmWUsDR3Pjiw=", + "crlite_enrolled": false, + "id": "f1115643-4628-4823-bd7e-4206b13e8548", + "last_modified": 1666727872151 + }, + { + "schema": 1666727411172, + "derHash": "IgBA3aN3yURCnozpE7fYGzbeNOvy3CGNIGY5PZSGNDk=", + "subject": "CN=Democritus University of Thrace TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGBMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTE5MDcGA1UEAwwwRGVtb2NyaXR1cyBVbml2ZXJzaXR5IG9mIFRocmFjZSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "2b498d0f65d028b05a74fa4640bd66948239d368515933db839e4b93ede1e4f5", + "size": 3677, + "filename": "hwhNNDnScyM0IPzP1QnWxg68NK1ZdubxLavxfWWRRq8=.pem", + "location": "security-state-staging/intermediates/c3624ffa-9df7-428e-820e-ab17d7e9b01e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hwhNNDnScyM0IPzP1QnWxg68NK1ZdubxLavxfWWRRq8=", + "crlite_enrolled": false, + "id": "edb91198-19bb-4d3c-a791-893c32c45d4f", + "last_modified": 1666727872138 + }, + { + "schema": 1666727381175, + "derHash": "iM5J46T6N+rijo41+P+M91aMqEVjnO87jf305Gk6wUo=", + "subject": "CN=GRNET TLS RSA SubCA R1,O=National Infrastructures for Research and Technology,L=Athens,C=GR", + "subjectDN": "MH4xCzAJBgNVBAYTAkdSMQ8wDQYDVQQHDAZBdGhlbnMxPTA7BgNVBAoMNE5hdGlvbmFsIEluZnJhc3RydWN0dXJlcyBmb3IgUmVzZWFyY2ggYW5kIFRlY2hub2xvZ3kxHzAdBgNVBAMMFkdSTkVUIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "f328c203f2fc28e9cf49f1316f46e3a851918d9f7ebc8d5e65e54a3ffd065086", + "size": 2930, + "filename": "mRBQN6GrKf_OuCxLxwvdLuH0S0HerSNcK0j4LUEd3RM=.pem", + "location": "security-state-staging/intermediates/7afbedd2-ae48-4bc8-8d48-dc64ace9dc35.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mRBQN6GrKf/OuCxLxwvdLuH0S0HerSNcK0j4LUEd3RM=", + "crlite_enrolled": false, + "id": "9401998f-a51c-428b-974d-6cfaa8d0b342", + "last_modified": 1666727872124 + }, + { + "schema": 1666727452643, + "derHash": "HilpD5V/ZLqcNlijEFV1GqzUtECpWJ36HXgP4HBp21I=", + "subject": "CN=DigiCert Trusted G4 TLS RSA SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxNDAyBgNVBAMTK0RpZ2lDZXJ0IFRydXN0ZWQgRzQgVExTIFJTQSBTSEEzODQgMjAyMCBDQTE=", + "whitelist": false, + "attachment": { + "hash": "2e8510d320f88c97a1376323998a78ee6b2ee8797c8465ca06754b2f331b95e6", + "size": 2418, + "filename": "rjm2hHKNhSnI_7YDoWFLmDSpnIwnr9n9bbNU0dhDiJg=.pem", + "location": "security-state-staging/intermediates/d3cdd04e-0ece-4631-bf2e-a72a9a235e6c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rjm2hHKNhSnI/7YDoWFLmDSpnIwnr9n9bbNU0dhDiJg=", + "crlite_enrolled": false, + "id": "30b2c510-287a-40d5-bb01-a78967c987ff", + "last_modified": 1666727872110 + }, + { + "schema": 1666727360155, + "derHash": "3CcoMxMS51xNe0QvO92nkMt3qdashU1bzhisQ8pZkJo=", + "subject": "CN=ATT Atlas R3 OV TLS CA 2020,O=ATT Services Inc,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBBVFQgU2VydmljZXMgSW5jMSQwIgYDVQQDExtBVFQgQXRsYXMgUjMgT1YgVExTIENBIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "e93ec3c93798a4c69c537ee7a488670b0c74cf8ea19467445243e440fc143d44", + "size": 1699, + "filename": "7gswJkaWI1c2mfCUfT3FpRjIef2nGdyV07Uoa-HSRf4=.pem", + "location": "security-state-staging/intermediates/fd37c482-2844-4b47-900c-0db97ccfd1b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7gswJkaWI1c2mfCUfT3FpRjIef2nGdyV07Uoa+HSRf4=", + "crlite_enrolled": false, + "id": "1bf1b1cc-b27b-4072-9ece-3af42ed599ff", + "last_modified": 1666727872096 + }, + { + "schema": 1666727365525, + "derHash": "BO7qjlC0d1s8JHlyYpF+5QAC7Ex1tWzfPuHBjPylulI=", + "subject": "CN=Microsoft RSA TLS CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xIDAeBgNVBAMTF01pY3Jvc29mdCBSU0EgVExTIENBIDAx", + "whitelist": false, + "attachment": { + "hash": "d21d673254fc1596b7b7c3a9a38e33ab09f786822535934e507dec3291deab5a", + "size": 1914, + "filename": "aJfLgiqk2RiMTiftDuK1Y2Y68uYH6daftf7O6r4ccks=.pem", + "location": "security-state-staging/intermediates/b0087125-69f0-40e4-bcb8-f571b5a20b41.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aJfLgiqk2RiMTiftDuK1Y2Y68uYH6daftf7O6r4ccks=", + "crlite_enrolled": false, + "id": "879312d0-6226-4fa9-9f66-54ca56679a6f", + "last_modified": 1666727872082 + }, + { + "schema": 1666727447252, + "derHash": "8AQ+n++zeup5TmkEN9wjnWvSxcoPtYoSJEVVoaiioBo=", + "subject": "CN=Panteion Univ. of Social and Political Sciences TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGRMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFJMEcGA1UEAwxAUGFudGVpb24gVW5pdi4gb2YgU29jaWFsIGFuZCBQb2xpdGljYWwgU2NpZW5jZXMgVExTIFJTQSBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "5b566ae520575a3ba6254926329f23c0f42094b610cf62409f19d510d3b03b3f", + "size": 2970, + "filename": "gPEc0jcCVlywHAUQHhYL3X5OqG89O9YInrCwyDjgJ1A=.pem", + "location": "security-state-staging/intermediates/401b3c9f-2550-49a6-99e6-8ef5a2d8c9a7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gPEc0jcCVlywHAUQHhYL3X5OqG89O9YInrCwyDjgJ1A=", + "crlite_enrolled": false, + "id": "9ac31ce5-768f-4264-8378-bfdb65182d3a", + "last_modified": 1666727872069 + }, + { + "schema": 1666727361519, + "derHash": "EdXvRg2rNYK3QhIxJxJ9VAQPscIG4m8CXLWEWPIlERo=", + "subject": "CN=HARICA SSL ECC SubCA R2,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR", + "subjectDN": "MIGGMQswCQYDVQQGEwJHUjEPMA0GA1UEBwwGQXRoZW5zMUQwQgYDVQQKDDtIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTEgMB4GA1UEAwwXSEFSSUNBIFNTTCBFQ0MgU3ViQ0EgUjI=", + "whitelist": false, + "attachment": { + "hash": "432760b1e895b5d2fe217fd84490cbcec25add1cef0892e3c134e7153e8ad2c9", + "size": 1370, + "filename": "nYO5UGTgmkq1Vf1wcAVPqg8McgUqLIhjZ8R8zTDqvQI=.pem", + "location": "security-state-staging/intermediates/1de65a81-c7e8-4b9b-9bc2-8e1aed5a6082.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nYO5UGTgmkq1Vf1wcAVPqg8McgUqLIhjZ8R8zTDqvQI=", + "crlite_enrolled": false, + "id": "ef5a6571-972b-4736-9c03-1dc6e89a9a48", + "last_modified": 1666727872055 + }, + { + "schema": 1666727343512, + "derHash": "SV5mJKFTQMTHSrhgN3SK1i0e9PFOfIGrfNtim+FAR1c=", + "subject": "CN=DigiCert High Assurance TLS RSA SHA256 2020 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE4MDYGA1UEAxMvRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTE=", + "whitelist": false, + "attachment": { + "hash": "c9012e07687a18f6da4ce67dbedfda0fe7d55b7b2ad957e9fb032f08807d1484", + "size": 1768, + "filename": "KKzhMaY72_nD-ZVShAg153XyomvfRUrrKjaWxh3ZCRo=.pem", + "location": "security-state-staging/intermediates/ebad371c-c44c-4c6a-adaf-80e0919cb160.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KKzhMaY72/nD+ZVShAg153XyomvfRUrrKjaWxh3ZCRo=", + "crlite_enrolled": false, + "id": "1f394e59-5d02-4846-a6c1-db9ef28d0213", + "last_modified": 1666727872042 + }, + { + "schema": 1666727369932, + "derHash": "JMcpmGTgoqaWT1UcDo3yRhUy+oxI5Nu7YIBxZpHxkOU=", + "subject": "CN=Microsoft Azure TLS Issuing CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMQ==", + "whitelist": false, + "attachment": { + "hash": "1963ee36a9c20eb3169ad73cd42da09bfc6829eb50f71d6216925c6b9c2dddcb", + "size": 2121, + "filename": "NHwusbC7w844JzTmvIRIo8NL7D6StITK9phzFgtJi0w=.pem", + "location": "security-state-staging/intermediates/8b305ed8-e20d-4a9d-b915-24f4a186cd46.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NHwusbC7w844JzTmvIRIo8NL7D6StITK9phzFgtJi0w=", + "crlite_enrolled": false, + "id": "eb2cd267-3056-4c85-aa35-b0985e1c423a", + "last_modified": 1666727872028 + }, + { + "schema": 1666727333802, + "derHash": "XEUjNNnJyaLqQsx3pxZeF5XunYTrcMt4S0fqnZKlgtk=", + "subject": "CN=Soluti CA - DV,O=SOLUTI - SOLUCOES EM NEGOCIOS INTELIGENTES S/A,C=BR", + "subjectDN": "MF8xCzAJBgNVBAYTAkJSMTcwNQYDVQQKEy5TT0xVVEkgLSBTT0xVQ09FUyBFTSBORUdPQ0lPUyBJTlRFTElHRU5URVMgUy9BMRcwFQYDVQQDEw5Tb2x1dGkgQ0EgLSBEVg==", + "whitelist": false, + "attachment": { + "hash": "8265936e7b87a7845ad3792c1bfbde5b8469f44276f048e773d7d306888e920a", + "size": 1756, + "filename": "kAzMeuwjUBWfPL5YEYfQG9qK_ABDxl0BMvPtzwEPN7E=.pem", + "location": "security-state-staging/intermediates/8d6429a5-772d-4666-be02-ba8282f7be70.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kAzMeuwjUBWfPL5YEYfQG9qK/ABDxl0BMvPtzwEPN7E=", + "crlite_enrolled": false, + "id": "513590d9-2a18-481c-b691-bf785c25086b", + "last_modified": 1666727872015 + }, + { + "schema": 1666727379438, + "derHash": "vLwYxGO2HzoDOxDHSXTtiiwyivzWejONmHFQajUVQZ8=", + "subject": "CN=e-Szigno Class3 SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIENsYXNzMyBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "141f708e3e75831bbe1ea4307dbbb1564e45c53708e07ce12ace8a2c364453d4", + "size": 1670, + "filename": "69Duo3nmlQnUEvqzlU27qTDaDY9K1yN0wfdopIs9Y7s=.pem", + "location": "security-state-staging/intermediates/1ae3f00a-6c6f-4f50-b7d4-c275a086e226.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "69Duo3nmlQnUEvqzlU27qTDaDY9K1yN0wfdopIs9Y7s=", + "crlite_enrolled": false, + "id": "0ae7a24d-a084-4832-94f5-f7b373e4faf0", + "last_modified": 1666727872001 + }, + { + "schema": 1666727431023, + "derHash": "cB6yP5VWTNVWnNIOXwXCiIkAuum6A6v1q+V7/gS1SmA=", + "subject": "CN=HARICA EV TLS ECC SubCA R1,O=Hellenic Academic and Research Institutions CA,L=Athens,C=GR", + "subjectDN": "MHwxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHDAZBdGhlbnMxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExIzAhBgNVBAMMGkhBUklDQSBFViBUTFMgRUNDIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "244713695ba67258093ce03ecbc69ba3cae541d762cb47302f341d9624076e4a", + "size": 1337, + "filename": "guE2rUJ3slSQ9iYP0Ub25zrLqasAFng1eTC5H9BqTUg=.pem", + "location": "security-state-staging/intermediates/997a3650-4546-43e5-b667-1f739e65da1c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "guE2rUJ3slSQ9iYP0Ub25zrLqasAFng1eTC5H9BqTUg=", + "crlite_enrolled": false, + "id": "f6fcaaf9-35fe-465b-a0a2-0f8a86cf791e", + "last_modified": 1666727871960 + }, + { + "schema": 1666727453492, + "derHash": "nHgvawVYrcscye+EUyDRRZLJIg/PADDAmA2ddTsGYk8=", + "subject": "CN=International Hellenic University TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGDMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTE7MDkGA1UEAwwySW50ZXJuYXRpb25hbCBIZWxsZW5pYyBVbml2ZXJzaXR5IFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "8a07cae21b98e0bc01564fe635446705b4fba20406f16757917f18c4200ecacd", + "size": 2954, + "filename": "i3AYNFBcJ3BXrQRfRHmas7YdtYYMFl9BDRWLHPQMDXo=.pem", + "location": "security-state-staging/intermediates/5484d264-a532-433a-b267-1fc4ee5dd909.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i3AYNFBcJ3BXrQRfRHmas7YdtYYMFl9BDRWLHPQMDXo=", + "crlite_enrolled": false, + "id": "b95a9ab3-38b6-48d6-be96-1e330f22cee3", + "last_modified": 1666727871932 + }, + { + "schema": 1666727380502, + "derHash": "SP+LSUZox1IwS0i/6Bh1iYfe9lguXwm5IfS2C7PWqN0=", + "subject": "CN=Microsoft Azure TLS Issuing CA 06,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNg==", + "whitelist": false, + "attachment": { + "hash": "b800befca1341c29912755ead3105bdab854e4517a96d3492de73bd48b6aca1c", + "size": 2121, + "filename": "Wl8MFY-9zijGG8QgEHCAK5fhA-ydPZxaLQOFdiEPz3U=.pem", + "location": "security-state-staging/intermediates/d631f0df-1b1d-4f64-9d14-9fe8304d44fa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wl8MFY+9zijGG8QgEHCAK5fhA+ydPZxaLQOFdiEPz3U=", + "crlite_enrolled": false, + "id": "163fe716-2a45-49db-8d69-b86ff58b3114", + "last_modified": 1666727871889 + }, + { + "schema": 1666727369075, + "derHash": "Zz6P7rEWgnfQFTER0g7Tjp2lH0QL5/1QshhUCTd0Gkg=", + "subject": "CN=DigiCert QuoVadis TLS ICA QuoVadis Root CA 2,O=DigiCert\\, Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1EaWdpQ2VydCwgSW5jMTUwMwYDVQQDDCxEaWdpQ2VydCBRdW9WYWRpcyBUTFMgSUNBIFF1b1ZhZGlzIFJvb3QgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "1972543fba2841f4a4d34b8431f1bd5c4f6ebc8c99b64d08cdf482a4c3962399", + "size": 1975, + "filename": "tZqVkxHOD3FG-_Ed3JNxf_Hs9TQHX1EhCqi5gjq2-HE=.pem", + "location": "security-state-staging/intermediates/22594db7-961b-430b-8c49-c9376e058f31.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tZqVkxHOD3FG+/Ed3JNxf/Hs9TQHX1EhCqi5gjq2+HE=", + "crlite_enrolled": false, + "id": "75e7a977-22ac-46fa-90d3-ac395c179b34", + "last_modified": 1666727871875 + }, + { + "schema": 1666727366901, + "derHash": "2ImiZB9d+UE1OBfIelzQ2c2s7TiGJAv72TcynpTTpFs=", + "subject": "CN=DKB CA 1O1,O=Deutsche Kreditbank AG,C=DE", + "subjectDN": "MEMxCzAJBgNVBAYTAkRFMR8wHQYDVQQKExZEZXV0c2NoZSBLcmVkaXRiYW5rIEFHMRMwEQYDVQQDEwpES0IgQ0EgMU8x", + "whitelist": false, + "attachment": { + "hash": "6287889eb83caf068cc4db7657eb9e8483df60643fd9ff7134830908e11552a1", + "size": 2385, + "filename": "6mn1Oob4B3Gc-G5_S1wP0LQc9KpzGrXRD5LWKSS3GlE=.pem", + "location": "security-state-staging/intermediates/1490878b-1375-4f9a-95d1-3f43d9fc736a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6mn1Oob4B3Gc+G5/S1wP0LQc9KpzGrXRD5LWKSS3GlE=", + "crlite_enrolled": false, + "id": "b12364e4-45b8-4ea6-8cd4-c43755ac5620", + "last_modified": 1666727871848 + }, + { + "schema": 1666727356596, + "derHash": "F0S0dBAf5qkZNvXmAwfGPaWLiEBbBFMj3sjIwAF2r3c=", + "subject": "CN=National and Kapodistrian University of Athens TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGQMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFIMEYGA1UEAww/TmF0aW9uYWwgYW5kIEthcG9kaXN0cmlhbiBVbml2ZXJzaXR5IG9mIEF0aGVucyBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "df16fbb01588423cf146051d4f51094e350cb86c612580c2969a7e0949610e78", + "size": 2922, + "filename": "PmFP9aUtjAk7nFzQ7X_3QtLDOEdeNBJ9s0RIbfOFB9Y=.pem", + "location": "security-state-staging/intermediates/e274e9a8-4d36-46c9-9f02-63b67c1252a2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PmFP9aUtjAk7nFzQ7X/3QtLDOEdeNBJ9s0RIbfOFB9Y=", + "crlite_enrolled": false, + "id": "9f250868-30ab-4a11-8a09-ba0e03b8130e", + "last_modified": 1666727871835 + }, + { + "schema": 1666727442113, + "derHash": "ffTT70V5j4xDhPxwK6UqRM571imLFBYo1Kurx2ePZGc=", + "subject": "CN=Microsoft Azure TLS Issuing CA 06,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNg==", + "whitelist": false, + "attachment": { + "hash": "8d704ee294bd149733993801e0cbad6deaa30f162b02e59509443d2f13b07b74", + "size": 2653, + "filename": "Wl8MFY-9zijGG8QgEHCAK5fhA-ydPZxaLQOFdiEPz3U=.pem", + "location": "security-state-staging/intermediates/02efee7c-c22f-45e0-a6cc-daa72a4e6d89.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wl8MFY+9zijGG8QgEHCAK5fhA+ydPZxaLQOFdiEPz3U=", + "crlite_enrolled": false, + "id": "c2d5a60b-44df-4ee6-b081-765b3e6cbedd", + "last_modified": 1666727871821 + }, + { + "schema": 1666727337676, + "derHash": "BDerLsLCtIkClsE1A0sh2xRkNLgxfucDqoqpQ8XqUa4=", + "subject": "CN=Microsoft Azure TLS Issuing CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMQ==", + "whitelist": false, + "attachment": { + "hash": "2777739491d74eeced727118790444484a83437e4e704c6697aa928b36a78bbb", + "size": 2653, + "filename": "NHwusbC7w844JzTmvIRIo8NL7D6StITK9phzFgtJi0w=.pem", + "location": "security-state-staging/intermediates/4c8d2520-1f13-47a3-9b9b-85990fa78aa4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NHwusbC7w844JzTmvIRIo8NL7D6StITK9phzFgtJi0w=", + "crlite_enrolled": false, + "id": "a114ce1e-4757-4407-8ea8-d7421902f389", + "last_modified": 1666727871807 + }, + { + "schema": 1666727360825, + "derHash": "MqTlVONjEW/kjiLwHawXNnUqcccg+Z9GLVZQItO60H0=", + "subject": "CN=DPDHL Global TLS CA - I5,O=Deutsche Post AG,C=DE", + "subjectDN": "MEsxCzAJBgNVBAYTAkRFMRkwFwYDVQQKExBEZXV0c2NoZSBQb3N0IEFHMSEwHwYDVQQDExhEUERITCBHbG9iYWwgVExTIENBIC0gSTU=", + "whitelist": false, + "attachment": { + "hash": "136f459d98a2f42f67f606dcf55c11198fea1f244cf8003c576d60d969cef1ac", + "size": 1695, + "filename": "90SkSHjn6UuYfQCvunxuVf7tHQJ3Y7-_aukXzu-S0_M=.pem", + "location": "security-state-staging/intermediates/f131cbbc-5c8f-418b-a1e0-f488964c17a5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "90SkSHjn6UuYfQCvunxuVf7tHQJ3Y7+/aukXzu+S0/M=", + "crlite_enrolled": false, + "id": "7b49a84f-6d1c-420c-a9b0-3d55f31c4b04", + "last_modified": 1666727871794 + }, + { + "schema": 1666727381691, + "derHash": "Je7RMdcZNZfkjU42U2zFz/v56yBCxiVzrolo2KNpXxw=", + "subject": "CN=University of Piraeus TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHcxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMS8wLQYDVQQDDCZVbml2ZXJzaXR5IG9mIFBpcmFldXMgVExTIFJTQSBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "48211f2889f371ca61b5cb4884d4a6db4be9c06976e808e2d4019d763e5f19eb", + "size": 2800, + "filename": "yoaRLzm23zeQXpsVd163dslqNpFi19V_XBBOcxeUJWo=.pem", + "location": "security-state-staging/intermediates/5a2ec80d-2b8e-4dcd-8682-f7f9d642c490.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yoaRLzm23zeQXpsVd163dslqNpFi19V/XBBOcxeUJWo=", + "crlite_enrolled": false, + "id": "5342d8df-fd36-4c82-85ac-dd4baab11fd0", + "last_modified": 1666727871780 + }, + { + "schema": 1666727334470, + "derHash": "TsQ5ZypENAGmbieUfMO1iX8TK2Z/cSzBo3AYo8yFsWo=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDI=", + "whitelist": false, + "attachment": { + "hash": "d6b88839e53fd1739f156f78254837d2b4b300f968dc9ab78c1f926d2648da2c", + "size": 1508, + "filename": "OhdUJ-wrpPRtpX53tkyqVLKQoNpdCCWve8MQQaQDQ2A=.pem", + "location": "security-state-staging/intermediates/c7978a8f-9ea4-4bdb-a90f-d52d5ad11e64.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OhdUJ+wrpPRtpX53tkyqVLKQoNpdCCWve8MQQaQDQ2A=", + "crlite_enrolled": false, + "id": "f76e0282-0a3d-4444-b1a1-7e599a44f7c8", + "last_modified": 1666727871767 + }, + { + "schema": 1666727354370, + "derHash": "s2ef3dxkSFi5fbtn3neN1WxuXVOpa3DoWrUJ0JhoGG0=", + "subject": "CN=SwissSign RSA TLS OV ICA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIE9WIElDQSAyMDIxIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "5099e02991e2988acc1fb3e634306f0281eec0bcfb43b34279f9f9c5f89b3ac7", + "size": 2605, + "filename": "I0jJpdj7EtBTwYf-1gPJDZDyw1ViWFSUbUg4x-wTlgE=.pem", + "location": "security-state-staging/intermediates/d7be12d2-4ddc-468c-b942-95b02f7668ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "I0jJpdj7EtBTwYf+1gPJDZDyw1ViWFSUbUg4x+wTlgE=", + "crlite_enrolled": false, + "id": "b9db9702-fd9d-4e21-8319-8998e1be5e47", + "last_modified": 1666727871753 + }, + { + "schema": 1666727381856, + "derHash": "Yk1VdqZSshMHaL/oS5Ze7//ZFgPSXNX3FVp9wnidrDg=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 05,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDU=", + "whitelist": false, + "attachment": { + "hash": "2468274c7b25fd2b19d422592a89957967b1d0b2667a280258fa181e4990089d", + "size": 1512, + "filename": "k09MzmwiRPkPmkpgmUtprMk_uALSVedNLtfOBkCMvXE=.pem", + "location": "security-state-staging/intermediates/80b6ec62-bff9-48b4-a3cf-f8e34874b58f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k09MzmwiRPkPmkpgmUtprMk/uALSVedNLtfOBkCMvXE=", + "crlite_enrolled": false, + "id": "806d2ed6-4b90-44a3-8208-2c60f2a2e5f3", + "last_modified": 1666727871704 + }, + { + "schema": 1666727410506, + "derHash": "KIs1Rm+44ii5iDIBnhp5Vqw+nxVCgMyXSG7Mjiycq8E=", + "subject": "CN=IdenTrust Public Sector Server CA 1,OU=TrustID Server,O=IdenTrust,C=US", + "subjectDN": "MGgxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxFzAVBgNVBAsTDlRydXN0SUQgU2VydmVyMSwwKgYDVQQDEyNJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBTZXJ2ZXIgQ0EgMQ==", + "whitelist": false, + "attachment": { + "hash": "76f53e60805d58cfdf8c7851caca2cc1b2e1d58ccecc5f8c3c4894a02b95a3fa", + "size": 2393, + "filename": "8tH7Dh4xfmk7Qci7zhMFmxVcpAzVMwFBeAsRhtLDaLg=.pem", + "location": "security-state-staging/intermediates/be5afe2d-0613-41bb-a455-fa59165d360a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8tH7Dh4xfmk7Qci7zhMFmxVcpAzVMwFBeAsRhtLDaLg=", + "crlite_enrolled": false, + "id": "5d584211-bcd0-45bf-bf15-9dcd99bf12c5", + "last_modified": 1666727871690 + }, + { + "schema": 1666727345590, + "derHash": "1KWUHHFB7RlJoMbOndRaCrlNwzeQLrChIJhSc47r6FQ=", + "subject": "CN=GlobalSign GCC R3 EV QWAC CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSowKAYDVQQDEyFHbG9iYWxTaWduIEdDQyBSMyBFViBRV0FDIENBIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "9a306430e243544a1c823baa60388aa1b7c197ccafd79a5fd9847056d91f6d8b", + "size": 2032, + "filename": "6Vipkd7oFTwhj_Z54b71vjmwq0aEJc6KkharcmzGaV4=.pem", + "location": "security-state-staging/intermediates/aa20f5f4-8e47-481a-8643-a8f0b3a9b75d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6Vipkd7oFTwhj/Z54b71vjmwq0aEJc6KkharcmzGaV4=", + "crlite_enrolled": false, + "id": "a28a9fda-0567-4644-bebe-d6b8d624039c", + "last_modified": 1666727871676 + }, + { + "schema": 1666727395098, + "derHash": "BYfWvSgZWHq5D7WWSApXk72fdQaj6s5z9eqzZgF/4lk=", + "subject": "CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMyBUTFMgRUNDIFNIQTM4NCAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "e39f57552856841abac298f8f881d2723ead960888690aad6f46d69c5cc4fe74", + "size": 1264, + "filename": "qBRjZmOmkSNJL0p70zek7odSIzqs_muR4Jk9xYyCP-E=.pem", + "location": "security-state-staging/intermediates/574d0f4a-0f9f-4ac1-bbbd-b912aa74f87c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qBRjZmOmkSNJL0p70zek7odSIzqs/muR4Jk9xYyCP+E=", + "crlite_enrolled": false, + "id": "4f7ced2c-6137-44bf-9977-e3b22023b2ff", + "last_modified": 1666727871662 + }, + { + "schema": 1666727375686, + "derHash": "irOgrPKJ5u91S+RJI2hD1n9FwZG93WZIS4Xm5gVWqa8=", + "subject": "CN=SHECA OV Server CA G5,O=UniTrust,C=CN", + "subjectDN": "MEAxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEeMBwGA1UEAwwVU0hFQ0EgT1YgU2VydmVyIENBIEc1", + "whitelist": false, + "attachment": { + "hash": "f80efdafc27d6bb8367919d6877b83658178c312d7ff4c7951be66667c2a033e", + "size": 2008, + "filename": "Ml9jtIo6CaZwLt7q6tlW9x4oQNlrHC-AQOHP17SXtCk=.pem", + "location": "security-state-staging/intermediates/17850783-b36e-4b59-bacf-1099cfa63707.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ml9jtIo6CaZwLt7q6tlW9x4oQNlrHC+AQOHP17SXtCk=", + "crlite_enrolled": false, + "id": "9bfb32bb-d95f-4440-a86b-01e4010249cf", + "last_modified": 1666727871648 + }, + { + "schema": 1666727376687, + "derHash": "FamHYevgEVVNo6RtIGsIEssutproeqoRpt1MuE7VFCo=", + "subject": "CN=Microsoft Azure TLS Issuing CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMg==", + "whitelist": false, + "attachment": { + "hash": "b94f365e9c87f17f0cce2845177df14966cfe660eb761de3a71b860db6a98fd9", + "size": 2121, + "filename": "zAwfx2iFcQ5vMOCc9vt-MXLdLl08EquNsOWgDF0hOw8=.pem", + "location": "security-state-staging/intermediates/c871cd6e-8739-4945-ad56-640037061116.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zAwfx2iFcQ5vMOCc9vt+MXLdLl08EquNsOWgDF0hOw8=", + "crlite_enrolled": false, + "id": "9c580ae1-ee63-4434-966d-7c79332c7aef", + "last_modified": 1666727871635 + }, + { + "schema": 1666727391920, + "derHash": "Efukp+5XnHDh1X+fm2qMIJ4SGcTx04Rvg//OdOnl4s4=", + "subject": "CN=Soluti CA - EV,O=SOLUTI - SOLUCOES EM NEGOCIOS INTELIGENTES S/A,C=BR", + "subjectDN": "MF8xCzAJBgNVBAYTAkJSMTcwNQYDVQQKEy5TT0xVVEkgLSBTT0xVQ09FUyBFTSBORUdPQ0lPUyBJTlRFTElHRU5URVMgUy9BMRcwFQYDVQQDEw5Tb2x1dGkgQ0EgLSBFVg==", + "whitelist": false, + "attachment": { + "hash": "b45919819e02ad79724a514eaf858e77f666b1352e04efbd7b970ef878509a6a", + "size": 1776, + "filename": "Z6aApIGWlcMWZHFtBmP1FgZhyLKarTalSo0g5-EeG0U=.pem", + "location": "security-state-staging/intermediates/b2a82526-2d56-4d6f-82ed-ec715bf0fbe2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z6aApIGWlcMWZHFtBmP1FgZhyLKarTalSo0g5+EeG0U=", + "crlite_enrolled": false, + "id": "2c989aad-c7f6-46c5-b0e4-6b645ccbe43f", + "last_modified": 1666727871621 + }, + { + "schema": 1666727333970, + "derHash": "8RRGn7gHeBM6H3Dk2DOO2rl91CzrjswByvtw1rh98R4=", + "subject": "CN=certSIGN Web CA,O=CERTSIGN SA,C=RO", + "subjectDN": "MFYxCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEYMBYGA1UEAxMPY2VydFNJR04gV2ViIENBMRcwFQYDVQRhEw5WQVRSTy0xODI4ODI1MA==", + "whitelist": false, + "attachment": { + "hash": "d637585d0fa1d3a2b6cea2a2ab6f2b7d30229bc1eb350aeb2b8cc2f529b5998a", + "size": 2308, + "filename": "G6NjfcFmFAa5DouUTqNGb3I5tG34kmES3evs_0ubJ68=.pem", + "location": "security-state-staging/intermediates/c62060d4-ea13-40e6-a9df-e05ed725084c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G6NjfcFmFAa5DouUTqNGb3I5tG34kmES3evs/0ubJ68=", + "crlite_enrolled": false, + "id": "cfc63177-c5f5-4799-aa1d-6254a02add98", + "last_modified": 1666727871594 + }, + { + "schema": 1666727369257, + "derHash": "Ek6q8m9XDE+02J9dYQePFbiFNF/K8MV/NHfYxjtasm8=", + "subject": "CN=Actalis Extended Validation Server CA G3,O=Actalis S.p.A.,L=Ponte San Pietro,ST=Bergamo,C=IT", + "subjectDN": "MIGGMQswCQYDVQQGEwJJVDEQMA4GA1UECAwHQmVyZ2FtbzEZMBcGA1UEBwwQUG9udGUgU2FuIFBpZXRybzEXMBUGA1UECgwOQWN0YWxpcyBTLnAuQS4xMTAvBgNVBAMMKEFjdGFsaXMgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZXJ2ZXIgQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "332f65bf2214e30be6f0940dc3bde167397bf14fc227615cd42ca92857dd83bf", + "size": 2641, + "filename": "Qit8ertDRalW2_YZANpfGKsGuNxcSeZ5UXpPg8_gmpE=.pem", + "location": "security-state-staging/intermediates/c1867d54-9646-4584-9e6b-81591c358a34.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qit8ertDRalW2/YZANpfGKsGuNxcSeZ5UXpPg8/gmpE=", + "crlite_enrolled": false, + "id": "ac2457c9-1258-4a6f-87ab-12f556c131e1", + "last_modified": 1666727871580 + }, + { + "schema": 1666727432201, + "derHash": "oE+mnoTM1E9eUmtVhjHjRrIUF9TRtgagjK3jko2/EVg=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2022 Q1,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjIgUTE=", + "whitelist": false, + "attachment": { + "hash": "3f4317e778b3da30eec0cd9dbe908ae50b5736d6612f97db5ec06bc2d774dc25", + "size": 1715, + "filename": "bB0N8F_eQUgJWwMgm9RHXw1K3t_cLoC1Y1kTX0-qGSw=.pem", + "location": "security-state-staging/intermediates/ccd669e0-4496-4491-af67-b2b12c0361a6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bB0N8F/eQUgJWwMgm9RHXw1K3t/cLoC1Y1kTX0+qGSw=", + "crlite_enrolled": false, + "id": "c9d98cb3-1e77-460b-87d4-c645e50c2df1", + "last_modified": 1666727871566 + }, + { + "schema": 1666727451288, + "derHash": "ZTkjWloflBgMUjPq+KXWcQLeqhbAdRrvNbPmfOVgfyk=", + "subject": "CN=University of Macedonia TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHkxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTEwLwYDVQQDDChVbml2ZXJzaXR5IG9mIE1hY2Vkb25pYSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "ae06df6f60a344d617561d51acd425a5d9262f72b70e495cadc899fc6c771424", + "size": 2824, + "filename": "Yr1zUmzXNRDeB9FlIE3oqt2RAilAhCi90mNw8jOw5j0=.pem", + "location": "security-state-staging/intermediates/bfbdf0be-59be-49fc-8075-6081dda2595d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Yr1zUmzXNRDeB9FlIE3oqt2RAilAhCi90mNw8jOw5j0=", + "crlite_enrolled": false, + "id": "c05bc812-3468-404b-95a6-2df3643ef7c5", + "last_modified": 1666727871552 + }, + { + "schema": 1666727340954, + "derHash": "9igmTHnb+9/P3sZDFxvcvU3nSD+cgOpZBoiHrkSaLkg=", + "subject": "CN=HARICA EV TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,L=Athens,C=GR", + "subjectDN": "MHwxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHDAZBdGhlbnMxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExIzAhBgNVBAMMGkhBUklDQSBFViBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "846d7358205bc7e15ac8e01f9d19e231ffbaa461c8bdace8a0b4ec9f552e012c", + "size": 2475, + "filename": "qQHYBA8HuHBDaaxDEsg5_SBSGWrd6XH3D7_lIoKEbz8=.pem", + "location": "security-state-staging/intermediates/fd9949c8-bc15-4c99-842a-5380bcde9492.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qQHYBA8HuHBDaaxDEsg5/SBSGWrd6XH3D7/lIoKEbz8=", + "crlite_enrolled": false, + "id": "a1a1316b-b603-434d-9d43-a671f7ff9a75", + "last_modified": 1666727871539 + }, + { + "schema": 1666727366562, + "derHash": "eu3lN07Kn/NMVyfBDbmCIi11MVd5dEm4g2T3Du+gwkw=", + "subject": "CN=Alibaba Cloud GCC R3 AlphaSSL CA 2021,O=Alibaba Cloud Computing Co.\\, Ltd.,C=CN", + "subjectDN": "MGkxCzAJBgNVBAYTAkNOMSowKAYDVQQKEyFBbGliYWJhIENsb3VkIENvbXB1dGluZyBDby4sIEx0ZC4xLjAsBgNVBAMTJUFsaWJhYmEgQ2xvdWQgR0NDIFIzIEFscGhhU1NMIENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "433ece3434c90b6bc0e4051245e9b8830f6689098ff3fae65e6394c641b077ca", + "size": 1735, + "filename": "AL3NHbHkwjd0miX1rwn-16RIrjnUTmWCdIdhb8wn4MU=.pem", + "location": "security-state-staging/intermediates/3e72d62e-adc7-4224-bd94-39d2b96357d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AL3NHbHkwjd0miX1rwn+16RIrjnUTmWCdIdhb8wn4MU=", + "crlite_enrolled": false, + "id": "7fa4194d-d6bd-4083-bca2-e937396a97c7", + "last_modified": 1666727871525 + }, + { + "schema": 1666727357938, + "derHash": "XavaBGJy7siey5WmKX26kQ0BRPfutYFNBzrlSkqM9kY=", + "subject": "CN=University of Crete TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHUxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMS0wKwYDVQQDDCRVbml2ZXJzaXR5IG9mIENyZXRlIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "e4d2eb126199d8d61712014de0e3dad3246a3892e005035771f8ee122aca08d5", + "size": 3011, + "filename": "OiyYqIVTvazzKZtgPhUjzE_UUtrL4LTPfcYtNSGRrdI=.pem", + "location": "security-state-staging/intermediates/7af79d1f-d621-4f9d-96ca-a78801ed430e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OiyYqIVTvazzKZtgPhUjzE/UUtrL4LTPfcYtNSGRrdI=", + "crlite_enrolled": false, + "id": "647085ae-93f0-4b67-936b-ee397672bfb3", + "last_modified": 1666727871512 + }, + { + "schema": 1666727367069, + "derHash": "eMtRLeSM7c1xNN9XyXPB4aB2krj1QqRKzruLt3bVXVk=", + "subject": "CN=CrowdStrike GCC R3 TLS OV CA 2020,O=CrowdStrike Inc.,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDcm93ZFN0cmlrZSBJbmMuMSowKAYDVQQDEyFDcm93ZFN0cmlrZSBHQ0MgUjMgVExTIE9WIENBIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "4db0721974365af16dd868628e17ddb2016a9a91ef607ffb13a995ff49b25874", + "size": 1707, + "filename": "nyCr34aeGoF5NDljMipBAETTIQQ7RVPxD3A4z08s6S0=.pem", + "location": "security-state-staging/intermediates/8cf1ea32-84c6-417e-ad52-0ab57becaf78.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nyCr34aeGoF5NDljMipBAETTIQQ7RVPxD3A4z08s6S0=", + "crlite_enrolled": false, + "id": "7cd75c73-53a3-4d79-954c-c72716711cea", + "last_modified": 1666727871499 + }, + { + "schema": 1666727437324, + "derHash": "LK77tV5w31qJhf6bwQ3VakDD3tqz2hUwopaCAVxbfGY=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDE=", + "whitelist": false, + "attachment": { + "hash": "a45e27eb4ce4e2624d46479d0ecf5a612e520e17ac29d9b405427a94f6798f65", + "size": 1512, + "filename": "OZOx9JJC3JsSDSjFX2iEA3_ED6gCha3L1ZrOeRNoyx8=.pem", + "location": "security-state-staging/intermediates/0aaac946-3682-4510-bb2f-61af7a692f2c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OZOx9JJC3JsSDSjFX2iEA3/ED6gCha3L1ZrOeRNoyx8=", + "crlite_enrolled": false, + "id": "23219f23-4f65-40c8-9baf-cf8d86adfbc1", + "last_modified": 1666727871485 + }, + { + "schema": 1666727337842, + "derHash": "UidMV85N7jtJ23p/9wjAQPdxiYs76IclqG+0QwGC/hQ=", + "subject": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "8232e99c25dab6bffa9135e65268a146b33ee8fa94e4ca565e723561bfa0e72a", + "size": 1703, + "filename": "RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=.pem", + "location": "security-state-staging/intermediates/741b70e1-9a0c-4119-8c26-f064675f513a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=", + "crlite_enrolled": false, + "id": "63b55855-0566-405f-8f8c-2ca3b30341e2", + "last_modified": 1666727871472 + }, + { + "schema": 1666727449176, + "derHash": "i2zMO+kvASHmSUrHBL8Lm5zrui42PI6BItkJhNPr84Y=", + "subject": "CN=Aetna Inc. Secure EV CA,O=Aetna Inc,C=US", + "subjectDN": "MEMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxIDAeBgNVBAMTF0FldG5hIEluYy4gU2VjdXJlIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "153fc69ce23adaf4f6e523b0d02cdfe58c1b6f81b54754c9650869d207c3df57", + "size": 1727, + "filename": "0rv4XQwSpZni_0C8FcOJhSJNEzvghB5GUVNKhM-UmQE=.pem", + "location": "security-state-staging/intermediates/462c6883-febc-43cb-a54c-b1a9342fa047.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0rv4XQwSpZni/0C8FcOJhSJNEzvghB5GUVNKhM+UmQE=", + "crlite_enrolled": false, + "id": "5d664e66-02e9-432b-b133-0fb68b6e65ef", + "last_modified": 1666727871457 + }, + { + "schema": 1666727408742, + "derHash": "NFC204KQw8pde7OLcUlbv3LG0MRNuikiRfm8qYQ6n/8=", + "subject": "CN=Actalis Domain Validation Server CA G3,O=Actalis S.p.A.,L=Ponte San Pietro,ST=Bergamo,C=IT", + "subjectDN": "MIGEMQswCQYDVQQGEwJJVDEQMA4GA1UECAwHQmVyZ2FtbzEZMBcGA1UEBwwQUG9udGUgU2FuIFBpZXRybzEXMBUGA1UECgwOQWN0YWxpcyBTLnAuQS4xLzAtBgNVBAMMJkFjdGFsaXMgRG9tYWluIFZhbGlkYXRpb24gU2VydmVyIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "428ff62276c3ecb6dc5848fb6212834d3718a3c55c9f538eca07bc4998f52fe6", + "size": 2637, + "filename": "hiyjWqnkYADYC0yxNwbD5MAi01UNWZQ5v2vsDd0s9Uo=.pem", + "location": "security-state-staging/intermediates/2d0ed481-2385-4322-8ab3-a9fc2a69fd06.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hiyjWqnkYADYC0yxNwbD5MAi01UNWZQ5v2vsDd0s9Uo=", + "crlite_enrolled": false, + "id": "fa698a10-fa9a-496d-a30b-92ed03b67538", + "last_modified": 1666727871435 + }, + { + "schema": 1666727415648, + "derHash": "iPwYvQcb4fvFP/vIAfA/WyxNqHu6DAmOK0gI8Z6rBf4=", + "subject": "CN=RNP ICPEdu OV SSL CA 2019,O=Rede Nacional de Ensino e Pesquisa - RNP,C=BR", + "subjectDN": "MGQxCzAJBgNVBAYTAkJSMTEwLwYDVQQKEyhSZWRlIE5hY2lvbmFsIGRlIEVuc2lubyBlIFBlc3F1aXNhIC0gUk5QMSIwIAYDVQQDExlSTlAgSUNQRWR1IE9WIFNTTCBDQSAyMDE5", + "whitelist": false, + "attachment": { + "hash": "292ae2e45b6a02ae65ded412e50c0abf3b623685bd49a8f6369bb4d5010c40d8", + "size": 1784, + "filename": "34rLDmx5N4tFlu_FTyuZjezXxhR4lhOmxmSZ9Pjypro=.pem", + "location": "security-state-staging/intermediates/81c78315-9a78-4834-b63e-93cae6a19168.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "34rLDmx5N4tFlu/FTyuZjezXxhR4lhOmxmSZ9Pjypro=", + "crlite_enrolled": false, + "id": "fe9c421e-21c9-46fa-ba09-fdc2dc7cf8e2", + "last_modified": 1666727871422 + }, + { + "schema": 1666727378079, + "derHash": "77RIRPN+5t4/tMyNLAyDL8j+66lQYjfg0fx61FdKTMo=", + "subject": "CN=University of Western Macedonia TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGBMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTE5MDcGA1UEAwwwVW5pdmVyc2l0eSBvZiBXZXN0ZXJuIE1hY2Vkb25pYSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "b2d7372a7ef5ecf677ad4b5db93e58617392828832e9de0c17c21d2131c00584", + "size": 2848, + "filename": "kTSX_4RoREKHkIC-2xr1SaXHZsAeaVcmjRu6cQGraoE=.pem", + "location": "security-state-staging/intermediates/07669fc2-7f2c-4c1b-bd9a-4c6d1a61990a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kTSX/4RoREKHkIC+2xr1SaXHZsAeaVcmjRu6cQGraoE=", + "crlite_enrolled": false, + "id": "e480a971-87fa-4f6a-9928-fc5ad661bb08", + "last_modified": 1666727871408 + }, + { + "schema": 1666727422199, + "derHash": "Gvi01Jxae22lNt6pL9+vRXNiBa/KLa//rstxLanD69E=", + "subject": "CN=Technical University of Crete TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MH8xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTcwNQYDVQQDDC5UZWNobmljYWwgVW5pdmVyc2l0eSBvZiBDcmV0ZSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "55201b962dc6510422f5c81d5c66b5b28c4d7f2a96442f66bec08c6f21d80312", + "size": 2800, + "filename": "t--9MaZjKuDEfu4IV5cTbN0TXadfwJu6Tn4gQDEWuoo=.pem", + "location": "security-state-staging/intermediates/91ae18bc-b487-4db4-8b9f-62a9e6fb0fae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t++9MaZjKuDEfu4IV5cTbN0TXadfwJu6Tn4gQDEWuoo=", + "crlite_enrolled": false, + "id": "9a265025-ac5a-4515-b727-c872caf26539", + "last_modified": 1666727871394 + }, + { + "schema": 1666727357603, + "derHash": "jAmUsq7QCF/lQoE+4DxLLXMuzcwleuZFMRTGyHWTO9Q=", + "subject": "CN=HEAL-LINK Hellenic Academic Libraries Link TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGMMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFEMEIGA1UEAww7SEVBTC1MSU5LIEhlbGxlbmljIEFjYWRlbWljIExpYnJhcmllcyBMaW5rIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "6af86798cbceb0361fdfda830f83801cb28d97ffe17af684cbf743440c780327", + "size": 2946, + "filename": "YAtUtdUCD9mINjDHhpnZ-aWI7q9rbr-ylz-Qj5YyuqM=.pem", + "location": "security-state-staging/intermediates/e5897f96-356d-4de4-904d-88a4eb5a6491.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YAtUtdUCD9mINjDHhpnZ+aWI7q9rbr+ylz+Qj5YyuqM=", + "crlite_enrolled": false, + "id": "b69c35d2-1375-4d12-b1cf-1fb46d1288fb", + "last_modified": 1666727871381 + }, + { + "schema": 1666727377736, + "derHash": "xesadjm52NcLT4Kt2AeUF17ktqPbGGGzhxfJb8GRSSc=", + "subject": "CN=NAVER Secure Certification Authority 1,O=NAVER BUSINESS PLATFORM Corp.,C=KR", + "subjectDN": "MGYxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBDb3JwLjEvMC0GA1UEAwwmTkFWRVIgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IDE=", + "whitelist": false, + "attachment": { + "hash": "99d81a11ba4ee9372795e89c14a2c5c6011f433176a402bb5dd5889a378b6c0b", + "size": 1991, + "filename": "cJ2mHuIKySslEdkckvymNSKxLhYRvvY9Z8ivbKik0Tg=.pem", + "location": "security-state-staging/intermediates/5c987bd0-2b88-4f95-b3f8-39291eb74a2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cJ2mHuIKySslEdkckvymNSKxLhYRvvY9Z8ivbKik0Tg=", + "crlite_enrolled": false, + "id": "de2932da-102f-4bcc-a4a1-1fd5a41201f1", + "last_modified": 1666727871368 + }, + { + "schema": 1666727343344, + "derHash": "BeQAXbDDgvO9ZrR3KekBFXdgG/b3sofppSztcQ0lg0Y=", + "subject": "CN=Microsoft RSA TLS CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xIDAeBgNVBAMTF01pY3Jvc29mdCBSU0EgVExTIENBIDAy", + "whitelist": false, + "attachment": { + "hash": "b31f15756dd2424f4c72909e8e5489c3443c030299ab3270a47adebded78e565", + "size": 1914, + "filename": "1wMGTin0PoCN5O41h0-XIHXuzGRwDEa8ehHf7wSdSQE=.pem", + "location": "security-state-staging/intermediates/e5e7d7b9-3a61-4e9d-9834-9fba4f249f40.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1wMGTin0PoCN5O41h0+XIHXuzGRwDEa8ehHf7wSdSQE=", + "crlite_enrolled": false, + "id": "b181e65f-51fe-45f7-847d-c73c3a039a51", + "last_modified": 1666727871354 + }, + { + "schema": 1666727379278, + "derHash": "ntHt6pWqW3K1E6PkGhug0u+xTOyIiR400ASVyVVWYFw=", + "subject": "CN=CrowdStrike Global EV CA G2,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEkMCIGA1UEAxMbQ3Jvd2RTdHJpa2UgR2xvYmFsIEVWIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "97a63445016de40593136e1104d0b5b7fa88512347b74c1337c1ab82c6d566fb", + "size": 1743, + "filename": "CyxodpBYldiqnjPTH1njRYIU3fXbBlov3Ly_xnmsIrE=.pem", + "location": "security-state-staging/intermediates/f6986554-454b-40ee-866c-6fb52413316c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CyxodpBYldiqnjPTH1njRYIU3fXbBlov3Ly/xnmsIrE=", + "crlite_enrolled": false, + "id": "75aa3a64-ae1e-4d39-b34e-97bb23150bd8", + "last_modified": 1666727871340 + }, + { + "schema": 1666727430520, + "derHash": "HJ0+jgzx5POcc00Z5Hmulx/DBmwAA1Oitq8yeVqX6hs=", + "subject": "CN=University of the Peloponnese TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MH8xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTcwNQYDVQQDDC5Vbml2ZXJzaXR5IG9mIHRoZSBQZWxvcG9ubmVzZSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "62423c0e3ec6576788de101d79059e8f036149513d58557e3f00b8e2f89e9b9a", + "size": 3819, + "filename": "D5AmHvgsz8nnufCXywDHL8VpnKB80L_QvVJn75tQULs=.pem", + "location": "security-state-staging/intermediates/6d2392d9-2ccd-4f86-9ddc-749b84bd77de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D5AmHvgsz8nnufCXywDHL8VpnKB80L/QvVJn75tQULs=", + "crlite_enrolled": false, + "id": "00a71082-d7c7-4968-a033-819e01b49dcf", + "last_modified": 1666727871327 + }, + { + "schema": 1666727393939, + "derHash": "co5L0rOZhA6dQ3r5dbAZiRC0Ept6twjLXAtEXTsE2Zk=", + "subject": "CN=DigiCert QuoVadis TLS ICA QV Root CA 3 G3,O=DigiCert\\, Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1EaWdpQ2VydCwgSW5jMTIwMAYDVQQDDClEaWdpQ2VydCBRdW9WYWRpcyBUTFMgSUNBIFFWIFJvb3QgQ0EgMyBHMw==", + "whitelist": false, + "attachment": { + "hash": "33b0457754bdf6a9483a692883283da10d986392b040ab57a618f0f1c7920dfd", + "size": 1967, + "filename": "7ApQ2uogI9DcsOpR1VtehjK8iLSy9QdPNJ4xQbOXGvw=.pem", + "location": "security-state-staging/intermediates/36357784-4f17-43f9-9fb3-fa12572e824d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7ApQ2uogI9DcsOpR1VtehjK8iLSy9QdPNJ4xQbOXGvw=", + "crlite_enrolled": false, + "id": "6ed20e0a-4d1d-49d2-acdb-b260cd5923f4", + "last_modified": 1666727871300 + }, + { + "schema": 1666727408914, + "derHash": "KmKGw6wJrmzTdIskoz8w+5PamR+IP3ACgksUqRSVWFU=", + "subject": "CN=Nyatwork,O=Nyatwork Communication Ltd,C=CA", + "subjectDN": "MEUxCzAJBgNVBAYTAkNBMSMwIQYDVQQKDBpOeWF0d29yayBDb21tdW5pY2F0aW9uIEx0ZDERMA8GA1UEAwwITnlhdHdvcms=", + "whitelist": false, + "attachment": { + "hash": "1b3c2d7116e9d075b69357d2ae84fbd7ff85f479e7e595094a8e3194b9069459", + "size": 1711, + "filename": "o0jjxzbIt8jj8zaT6IFOw5B3fUAt1GpNhfNVqM7gAVY=.pem", + "location": "security-state-staging/intermediates/7dfff9a0-2288-42d4-a9d9-ab2bbc41f4b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o0jjxzbIt8jj8zaT6IFOw5B3fUAt1GpNhfNVqM7gAVY=", + "crlite_enrolled": false, + "id": "63714663-63e6-4640-a1d0-23f7931f0ee4", + "last_modified": 1666727871285 + }, + { + "schema": 1666727365351, + "derHash": "QhZScWOtLKqCXTv0j2GnZh0KvIm1irdrI6HhCZnwdp8=", + "subject": "CN=SHECA EV Server CA G2,O=UniTrust,C=CN", + "subjectDN": "MEAxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEeMBwGA1UEAwwVU0hFQ0EgRVYgU2VydmVyIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "fd9b635f3000431dd27c22b7ea2f2d637beacb75ccb76da39b98eda1c4871977", + "size": 2003, + "filename": "bUpcnmc4JpksMlGlIr1WSxLb8xQLir7IBpzPm_ONH8I=.pem", + "location": "security-state-staging/intermediates/9dc9607c-c2c0-4b03-975f-5821d5fdfb79.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bUpcnmc4JpksMlGlIr1WSxLb8xQLir7IBpzPm/ONH8I=", + "crlite_enrolled": false, + "id": "04059b9a-9277-47a6-b91c-fd937c69c6cf", + "last_modified": 1666727871267 + }, + { + "schema": 1666727418879, + "derHash": "2eSoA/tJju6Tefz5CsMGeuZdCYzHeJDDfldeJJzX18k=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIyIFEx", + "whitelist": false, + "attachment": { + "hash": "92ab09e1e79d49e44c86537b1abbde38e45f433060cb6d33b9a6ecaa6a058085", + "size": 1715, + "filename": "y55D7j3luCHY1W8t3APrJcmdLg5GymgZPl7Xw-vI1u8=.pem", + "location": "security-state-staging/intermediates/ce03c78f-04a2-472a-974c-e431282ab41c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "y55D7j3luCHY1W8t3APrJcmdLg5GymgZPl7Xw+vI1u8=", + "crlite_enrolled": false, + "id": "95476db8-9ecd-41a5-a165-c19b7b039ad0", + "last_modified": 1666727871248 + }, + { + "schema": 1666727370444, + "derHash": "qzIDs+ogF9UJcmodgik+/8uMQs61LJrxwO7pa1wCvLo=", + "subject": "CN=Microsoft Azure TLS Issuing CA 05,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwNQ==", + "whitelist": false, + "attachment": { + "hash": "dab72f8a61c3d1392090bfa1d53928ff5e675385a1b03742a601a3457a12342f", + "size": 2653, + "filename": "4i4h0jN9NROr1xKJI-TQ1Q_ZIfUjPMXtmWUsDR3Pjiw=.pem", + "location": "security-state-staging/intermediates/72a185d7-4eb2-47d8-b2a4-e045b0a06de6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4i4h0jN9NROr1xKJI+TQ1Q/ZIfUjPMXtmWUsDR3Pjiw=", + "crlite_enrolled": false, + "id": "5f4eb28e-9486-4fc3-a0b6-bf4fa86b1b59", + "last_modified": 1666727871232 + }, + { + "schema": 1666727435110, + "derHash": "ffgAB19SA8AXNk6BGVqayf8AxQfWSnD3N9jT6Ms/CEU=", + "subject": "CN=e-Szigno Qualified TLS CA 2018,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHmUtU3ppZ25vIFF1YWxpZmllZCBUTFMgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "df20fd29ecb80ea583b981311eed6c99114bffa8ab98b5265dc4c8efcc1897f0", + "size": 1674, + "filename": "qd9EIyfp7CEtbkxafeyYAuC_8wQBWqGZflkLznwnuyc=.pem", + "location": "security-state-staging/intermediates/65c259ab-b9fe-47fa-8021-1f7094795218.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qd9EIyfp7CEtbkxafeyYAuC/8wQBWqGZflkLznwnuyc=", + "crlite_enrolled": false, + "id": "7353f12b-afbe-4adf-983c-a3d3f4e94143", + "last_modified": 1666727871207 + }, + { + "schema": 1666727334633, + "derHash": "gBxSLTrROOTwXUZ+o2nBzCdgeL8oQBjQUl67vFNCyDY=", + "subject": "CN=JPRS Organization Validation Authority - G4,O=Japan Registry Services Co.\\, Ltd.,C=JP", + "subjectDN": "MG8xCzAJBgNVBAYTAkpQMSowKAYDVQQKEyFKYXBhbiBSZWdpc3RyeSBTZXJ2aWNlcyBDby4sIEx0ZC4xNDAyBgNVBAMTK0pQUlMgT3JnYW5pemF0aW9uIFZhbGlkYXRpb24gQXV0aG9yaXR5IC0gRzQ=", + "whitelist": false, + "attachment": { + "hash": "5073da9684a445c50c1705a593be556ce4f1fa98e69b8c50acc426e2088d88b7", + "size": 1788, + "filename": "xOSyACYAjsQ-aXD2iEFnKCVOcUsX8AVVqCGczMoh8lw=.pem", + "location": "security-state-staging/intermediates/c4c67ff5-aef5-4b0a-8eff-7dd84e9fd7c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xOSyACYAjsQ+aXD2iEFnKCVOcUsX8AVVqCGczMoh8lw=", + "crlite_enrolled": false, + "id": "67adf801-f8c9-4aa3-8959-8c52c5b8e487", + "last_modified": 1666727871191 + }, + { + "schema": 1666727424612, + "derHash": "AD9x3EggIWV1/Fqs/jsa63b3Kupbjo/O/IC59RekphI=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 05,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDU=", + "whitelist": false, + "attachment": { + "hash": "312e807dea2263ef605990bfa0b57b238b64d9fea0fc945e37f48f3e6246d576", + "size": 1329, + "filename": "k09MzmwiRPkPmkpgmUtprMk_uALSVedNLtfOBkCMvXE=.pem", + "location": "security-state-staging/intermediates/6aaee85d-0f49-4200-aef8-443dcc265c89.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k09MzmwiRPkPmkpgmUtprMk/uALSVedNLtfOBkCMvXE=", + "crlite_enrolled": false, + "id": "aa89fd7f-c3e7-48eb-8a53-205dfa9060a4", + "last_modified": 1666727871176 + }, + { + "schema": 1666727444500, + "derHash": "wWH1qt5A+8lyPwiS3pY9TRBAVWGmvcaacnmPkYvtGc0=", + "subject": "CN=GRnet SSL RSA SubCA R2,O=Greek Research and Technology Network,L=Athens,C=GR", + "subjectDN": "MG8xCzAJBgNVBAYTAkdSMQ8wDQYDVQQHDAZBdGhlbnMxLjAsBgNVBAoMJUdyZWVrIFJlc2VhcmNoIGFuZCBUZWNobm9sb2d5IE5ldHdvcmsxHzAdBgNVBAMMFkdSbmV0IFNTTCBSU0EgU3ViQ0EgUjI=", + "whitelist": false, + "attachment": { + "hash": "b79afab6661ea8fe2b65bca5f3420f2b51f2d3b92e8ca101c79dd565ce180ace", + "size": 2861, + "filename": "AC8xqpj5E7pReTXZrh3N31yqqxl7UQm0Z14O0g7xxVE=.pem", + "location": "security-state-staging/intermediates/c191a430-c180-4c9c-95c1-bff17d764fec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AC8xqpj5E7pReTXZrh3N31yqqxl7UQm0Z14O0g7xxVE=", + "crlite_enrolled": false, + "id": "a64d4793-18fc-4884-997b-e093edd08beb", + "last_modified": 1666727871158 + }, + { + "schema": 1666727405802, + "derHash": "2gxJgfYzbgbBKffoFDsqVKqFoUpjykT84s3cae7niMk=", + "subject": "CN=Ionian University TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHMxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSswKQYDVQQDDCJJb25pYW4gVW5pdmVyc2l0eSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "6d6a49e9b8eeccc422f75f419749ae74cff5bfdd57a2b2e6d792c3e683e082f5", + "size": 2779, + "filename": "tQEYBAU52znFOxCYz6CBZu4mfcBhcZVQIztFuFsaj5w=.pem", + "location": "security-state-staging/intermediates/e914eb3c-fb32-4c24-988f-0a44fad6557a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tQEYBAU52znFOxCYz6CBZu4mfcBhcZVQIztFuFsaj5w=", + "crlite_enrolled": false, + "id": "bd02f375-a882-4a65-b9b8-3d879928ab3d", + "last_modified": 1666727871138 + }, + { + "schema": 1666727435776, + "derHash": "8Z1VzQij6kK9kVCAc4IxdN2SNwITwXf4JTF1badQilE=", + "subject": "CN=JPRS Domain Validation Authority - G4,O=Japan Registry Services Co.\\, Ltd.,C=JP", + "subjectDN": "MGkxCzAJBgNVBAYTAkpQMSowKAYDVQQKEyFKYXBhbiBSZWdpc3RyeSBTZXJ2aWNlcyBDby4sIEx0ZC4xLjAsBgNVBAMTJUpQUlMgRG9tYWluIFZhbGlkYXRpb24gQXV0aG9yaXR5IC0gRzQ=", + "whitelist": false, + "attachment": { + "hash": "d1fbc1604b7b78d8673b6b7247a33b5d142c6ebaa41b8e0412cedc570a24b3d7", + "size": 1780, + "filename": "KbEUDafDNIUlOxLiaD-8KAlxp-gea65O57qq4l5SFl8=.pem", + "location": "security-state-staging/intermediates/0efba185-f867-4720-aca4-8e7d3667ab7c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KbEUDafDNIUlOxLiaD+8KAlxp+gea65O57qq4l5SFl8=", + "crlite_enrolled": false, + "id": "94dee99a-f803-4f21-bbe1-cec73c87cf5e", + "last_modified": 1666727871121 + }, + { + "schema": 1666727392408, + "derHash": "FRo+WWnGYW62N6hyKxdM/ZU4eqznjVfDvSPwyzAIGGo=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 06,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDY=", + "whitelist": false, + "attachment": { + "hash": "3f94a2f59b7465d9ec3993abf5497a7454a624492a51b0f1af1453447008be7a", + "size": 1508, + "filename": "yBjnrcmcUp2nylDRWnQvSPRspmhm1f_fOuKrsNiaSdA=.pem", + "location": "security-state-staging/intermediates/02495c18-91a4-42fb-956a-4fe8ed43bfee.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yBjnrcmcUp2nylDRWnQvSPRspmhm1f/fOuKrsNiaSdA=", + "crlite_enrolled": false, + "id": "97c50b5c-b8db-4d7e-9a48-a00cfe50809d", + "last_modified": 1666727871096 + }, + { + "schema": 1666727436978, + "derHash": "05zjn/b0SdTzOR7iAE1wXsIvmc/8pAqI+F2yZFSt29E=", + "subject": "CN=Microsoft Azure TLS Issuing CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKjAoBgNVBAMTIU1pY3Jvc29mdCBBenVyZSBUTFMgSXNzdWluZyBDQSAwMg==", + "whitelist": false, + "attachment": { + "hash": "ce0b3cc7344615f4e2c08e375e5170baea84e9c13a2b7eab45e0eb275cfec111", + "size": 2653, + "filename": "zAwfx2iFcQ5vMOCc9vt-MXLdLl08EquNsOWgDF0hOw8=.pem", + "location": "security-state-staging/intermediates/ba9ba353-fc26-4c61-a866-b85b659ec9a5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zAwfx2iFcQ5vMOCc9vt+MXLdLl08EquNsOWgDF0hOw8=", + "crlite_enrolled": false, + "id": "99c86985-1228-476e-a19f-d0460513f062", + "last_modified": 1666727871078 + }, + { + "schema": 1666727373459, + "derHash": "cLahDAynbeznrb6XC3ajfYoChXsTTHUFsYTr1fyk8+o=", + "subject": "CN=HARICA SSL RSA SubCA R3,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR", + "subjectDN": "MIGGMQswCQYDVQQGEwJHUjEPMA0GA1UEBwwGQXRoZW5zMUQwQgYDVQQKDDtIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTEgMB4GA1UEAwwXSEFSSUNBIFNTTCBSU0EgU3ViQ0EgUjM=", + "whitelist": false, + "attachment": { + "hash": "43ca449af1e8a26a749b01b098609a279952a10f46461d77553e631a7c26b72f", + "size": 2503, + "filename": "jBZXnMw3910qHNl4GpA8ufttU_ABxauO7Xs8rkNWmHE=.pem", + "location": "security-state-staging/intermediates/bcc8709c-0a8e-402e-9add-fd2ed5432aed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jBZXnMw3910qHNl4GpA8ufttU/ABxauO7Xs8rkNWmHE=", + "crlite_enrolled": false, + "id": "2cc7f045-9a71-48f4-803f-bab798c14e12", + "last_modified": 1666727871059 + }, + { + "schema": 1666727347473, + "derHash": "OcsZn0HGqCqtg8KBASdZbQLMTsdm0N/jGwHVDRd0dJ8=", + "subject": "CN=SwissSign RSA TLS EV ICA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIEVWIElDQSAyMDIxIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "762606517f59b459f2621c2fc75b3dbaa0b1b65d43e690165ec66b602a2a333c", + "size": 2605, + "filename": "vhTi8lZlOLaSt18G-qUx2M9Th5OcyP5bSIpb1W75B-4=.pem", + "location": "security-state-staging/intermediates/9a5ff13c-6bef-4c4b-9d80-0208b7f03565.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vhTi8lZlOLaSt18G+qUx2M9Th5OcyP5bSIpb1W75B+4=", + "crlite_enrolled": false, + "id": "1e82dbfd-7318-4a95-a187-db977a5119d2", + "last_modified": 1666727871040 + }, + { + "schema": 1666727421175, + "derHash": "96mhsv2WSj8mcL1mjVYft8VdOqmrg5Hn4WlwLbij288=", + "subject": "CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBIeWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "256160038ace5cd8340f2f662d64ec98899b7badd35e08010600d30dfbdc15f6", + "size": 1479, + "filename": "e0IRz5Tio3GA1Xs4fUVWmH1xHDiH2dMbVtCBSkOIdqM=.pem", + "location": "security-state-staging/intermediates/397c16a4-6646-447e-b182-2d66d18a585d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e0IRz5Tio3GA1Xs4fUVWmH1xHDiH2dMbVtCBSkOIdqM=", + "crlite_enrolled": false, + "id": "63d718c2-bafa-4fbc-849e-d91646caaedc", + "last_modified": 1666727871018 + }, + { + "schema": 1666727412530, + "derHash": "QouHuAYaeaunWHf9A4OtuDGhRZuGvECM59jReylbdbs=", + "subject": "CN=Aetna Inc. Secure CA2,O=Aetna Inc,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxHjAcBgNVBAMTFUFldG5hIEluYy4gU2VjdXJlIENBMg==", + "whitelist": false, + "attachment": { + "hash": "a58ea3a319c970a4e047ee2377bfe5c443ccc58cfa59814ccf05e2d54f5fbd07", + "size": 1723, + "filename": "NIgvyyhcGFo7vlL_K_w6cn8iFWO9ftJXWrIU77O5hMI=.pem", + "location": "security-state-staging/intermediates/8cf1b54e-cf3b-494c-b0a2-0ed470316bd4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NIgvyyhcGFo7vlL/K/w6cn8iFWO9ftJXWrIU77O5hMI=", + "crlite_enrolled": false, + "id": "683cede4-7626-4358-b2eb-145dd74960f4", + "last_modified": 1666727870998 + }, + { + "schema": 1666727439208, + "derHash": "EpdlWLaOjh6qeaYpqOTRft75P1rDDebfsM3uOJ1W0VY=", + "subject": "CN=TuringSign RSA Secure CA,O=Turing Crypto GmbH,C=DE", + "subjectDN": "ME0xCzAJBgNVBAYTAkRFMRswGQYDVQQKExJUdXJpbmcgQ3J5cHRvIEdtYkgxITAfBgNVBAMTGFR1cmluZ1NpZ24gUlNBIFNlY3VyZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "2f15e1f6218dde794713d0b2b8cfc8bd6eaad84e6e30aeda4ccb44a17decc213", + "size": 2028, + "filename": "LjNGvuYlXy4cly5bwppVDU4T8q5pCHAHEEAG--Nx1WA=.pem", + "location": "security-state-staging/intermediates/34baea23-ff0b-4e43-a267-0e4a95aa4599.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LjNGvuYlXy4cly5bwppVDU4T8q5pCHAHEEAG++Nx1WA=", + "crlite_enrolled": false, + "id": "c8b18f59-94dd-41d5-a00b-f89d1a5d798a", + "last_modified": 1666727870976 + }, + { + "schema": 1666727383209, + "derHash": "RnJ7kMWl9zN1CC2bG8nxe0RfUCCpXjff3x7yCyh8URU=", + "subject": "CN=University of Ioannina TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHgxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTAwLgYDVQQDDCdVbml2ZXJzaXR5IG9mIElvYW5uaW5hIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "5b0c0e0b04ad684e029d7708f105750f61ff7614fe4dc6446734648df0e03bc7", + "size": 2836, + "filename": "PUZlCSUltdtmQPkFYuFiu4nfB3U5nmSRkJODEG7M5_U=.pem", + "location": "security-state-staging/intermediates/62455ace-2340-4c05-a5e7-e2fbef604fe4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PUZlCSUltdtmQPkFYuFiu4nfB3U5nmSRkJODEG7M5/U=", + "crlite_enrolled": false, + "id": "1076de33-ae6c-405d-9f28-c3765fec72b8", + "last_modified": 1666727870959 + }, + { + "schema": 1666727344920, + "derHash": "xnDHm/J3r357NKaqT6MERBgzxr0Bpwp+m3otlMHB+SY=", + "subject": "CN=certSIGN Qualified CA,O=CERTSIGN SA,C=RO", + "subjectDN": "MFwxCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEeMBwGA1UEAxMVY2VydFNJR04gUXVhbGlmaWVkIENBMRcwFQYDVQRhEw5WQVRSTy0xODI4ODI1MA==", + "whitelist": false, + "attachment": { + "hash": "22d738f384d471ab73b8f3d1ec90fd80d0f61bf77294a7139dc772f58bb58e1a", + "size": 2316, + "filename": "hztK7Nl786SeQOWatriSlHoTSUt-1THcFFIGKRr08bk=.pem", + "location": "security-state-staging/intermediates/5162d95c-0f56-42cb-a39f-2014ebc94b1b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hztK7Nl786SeQOWatriSlHoTSUt+1THcFFIGKRr08bk=", + "crlite_enrolled": false, + "id": "cef6f84f-b3d4-4255-bc17-7b667dfb0575", + "last_modified": 1666727870945 + }, + { + "schema": 1666727341125, + "derHash": "aZ7WW909XnkHdtFl6noyW3rq9BlkeSGpL/KLsGgNcro=", + "subject": "CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMyBUTFMgRUNDIFNIQTM4NCAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "190186fe0e64bede8e018e200240f43f01cc7897439f9a7496667ddf0f0fa079", + "size": 1264, + "filename": "qBRjZmOmkSNJL0p70zek7odSIzqs_muR4Jk9xYyCP-E=.pem", + "location": "security-state-staging/intermediates/7a904036-5485-4587-bd6f-3afe7994392a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qBRjZmOmkSNJL0p70zek7odSIzqs/muR4Jk9xYyCP+E=", + "crlite_enrolled": false, + "id": "a5b3dfaa-3b95-4b1c-9e4d-0c827e7214ea", + "last_modified": 1666727870932 + }, + { + "schema": 1666727366399, + "derHash": "+dMhStb11tKzyBYflgKLdoErBYYUpmeGuSyP69rAIO4=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIyIFEx", + "whitelist": false, + "attachment": { + "hash": "09600768299e6006e17871433cfa0b5cdf6197aec92f5acc16c7336a85485e09", + "size": 1268, + "filename": "YnrjW9deXDaWO8A6Q2-8RN8iRv5dtgowKQv9ki2Qx2o=.pem", + "location": "security-state-staging/intermediates/e6449200-3f47-468c-bd11-7bc8c0389089.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YnrjW9deXDaWO8A6Q2+8RN8iRv5dtgowKQv9ki2Qx2o=", + "crlite_enrolled": false, + "id": "d8bb5569-b8b9-4d0c-b76c-61a051cbcdc4", + "last_modified": 1666727870918 + }, + { + "schema": 1666727435608, + "derHash": "Dugs637KJBzMKdTliAYsQ+RH7ebGlvE1rMQRlmEmuoM=", + "subject": "CN=Aristotle University of Thessaloniki SSL RSA SubCA R2,O=Aristotle University of Thessaloniki,L=Thessaloniki,C=GR", + "subjectDN": "MIGTMQswCQYDVQQGEwJHUjEVMBMGA1UEBwwMVGhlc3NhbG9uaWtpMS0wKwYDVQQKDCRBcmlzdG90bGUgVW5pdmVyc2l0eSBvZiBUaGVzc2Fsb25pa2kxPjA8BgNVBAMMNUFyaXN0b3RsZSBVbml2ZXJzaXR5IG9mIFRoZXNzYWxvbmlraSBTU0wgUlNBIFN1YkNBIFIy", + "whitelist": false, + "attachment": { + "hash": "5adc7f8de911464e9365cd89af357728f2d20e293ebc0e1a79965827081898aa", + "size": 2962, + "filename": "MOR9elaY2GOSXRQ0lkPZuOKdFNjk0evRDxZV72l0G6Y=.pem", + "location": "security-state-staging/intermediates/8787a675-ce72-466c-a57b-3f64d55f7a1c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MOR9elaY2GOSXRQ0lkPZuOKdFNjk0evRDxZV72l0G6Y=", + "crlite_enrolled": false, + "id": "ea2014b3-5f09-421e-8ffa-df84d388e8e5", + "last_modified": 1666727870904 + }, + { + "schema": 1666727401833, + "derHash": "KFBuM/VaYHIRXz4Eyb2/WvUxLKj8EyUIuXrJn+4FFug=", + "subject": "CN=DigiCert High Assurance CA-3b,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIENBLTNi", + "whitelist": false, + "attachment": { + "hash": "7efe69203d1d472d2aa26d67c48aa5083a3a8ef95534b9e7cc3bed159868e965", + "size": 1739, + "filename": "1jqJVFODXrZURu0Yd9r3uRS24OAQ3A4Crn2vR8KpNT8=.pem", + "location": "security-state-staging/intermediates/5f8fc657-c7a9-44f7-9089-ddb5e9eee62f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1jqJVFODXrZURu0Yd9r3uRS24OAQ3A4Crn2vR8KpNT8=", + "crlite_enrolled": false, + "id": "f0d900b5-9ed9-4503-af48-b4b909171337", + "last_modified": 1666727870891 + }, + { + "schema": 1666727374124, + "derHash": "OZWwGheACuz1SA6TnZ2kTtzMfPKTVlfGF51aHvol100=", + "subject": "CN=Athens University of Economics and Business TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGNMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFFMEMGA1UEAww8QXRoZW5zIFVuaXZlcnNpdHkgb2YgRWNvbm9taWNzIGFuZCBCdXNpbmVzcyBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "f666565b973bbe6ac92d7ca5bbf71fce7c755479036228d848c4ab07b1299cf6", + "size": 2877, + "filename": "oHI6zIj2JBHJtqjt17sQSYylYI0zRaSACkBuS9578tg=.pem", + "location": "security-state-staging/intermediates/1fe4770f-c334-4037-8b11-b358a50e9832.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oHI6zIj2JBHJtqjt17sQSYylYI0zRaSACkBuS9578tg=", + "crlite_enrolled": false, + "id": "2944f1f2-3da9-407b-adcc-deb9a4981005", + "last_modified": 1666727870877 + }, + { + "schema": 1666727349403, + "derHash": "3JmT3DOnH9lYCHh9GaS1VwTmAdV+vOrMiALHi1w29cc=", + "subject": "CN=Greek Universities Network TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHwxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTQwMgYDVQQDDCtHcmVlayBVbml2ZXJzaXRpZXMgTmV0d29yayBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "1cd78e7decfefeda51df3217ba844a88ad64445b6510e5a6e1ac6c3eaba92119", + "size": 2800, + "filename": "-Wo-2quCv6ywG-coso5aG2NSMQ-PKhucBmfbix2syuA=.pem", + "location": "security-state-staging/intermediates/06544994-5d29-4ad0-a79f-9039de153492.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+Wo+2quCv6ywG+coso5aG2NSMQ+PKhucBmfbix2syuA=", + "crlite_enrolled": false, + "id": "d2c86868-c6cc-4104-a8c1-c6d746c47f1c", + "last_modified": 1666727870864 + }, + { + "schema": 1666727370109, + "derHash": "diU4Q5UJxBHEN9PFZ1Y+E3hnEoH8ShRkrdAxhwhDZ24=", + "subject": "CN=GlobalSign GCC R3 DV TLS CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIEdDQyBSMyBEViBUTFMgQ0EgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "a57c3388263edee7694a304c5fb1c3c28046d3d7c1493d3ddf93c385aaf9b3ad", + "size": 1687, + "filename": "Sc7wL4FfOw8ued5w3JOSzu5MzB471PfqdyN4hnMYbX4=.pem", + "location": "security-state-staging/intermediates/4c97000c-ae11-45dd-8665-3182a2f39537.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Sc7wL4FfOw8ued5w3JOSzu5MzB471PfqdyN4hnMYbX4=", + "crlite_enrolled": false, + "id": "bd9f3b2f-2763-49ee-a006-67c03c2545ff", + "last_modified": 1666727870837 + }, + { + "schema": 1666727348026, + "derHash": "z9/vA3z9vWuw4xHpuygcpgJxxdriaVMSAzgG+ksOlLc=", + "subject": "CN=Greek School Network TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHYxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMS4wLAYDVQQDDCVHcmVlayBTY2hvb2wgTmV0d29yayBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "4b8f6af09b01a4535f7dfa862de0ef7a65a3071f22eaf5e0ad2addcb68526259", + "size": 3275, + "filename": "Qrg8oKp5CK8PmUjVC4rnbh8VZo2IQZkWRT9cewFmdNw=.pem", + "location": "security-state-staging/intermediates/8b822dd3-618e-4f61-9cfd-061d4c50723d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qrg8oKp5CK8PmUjVC4rnbh8VZo2IQZkWRT9cewFmdNw=", + "crlite_enrolled": false, + "id": "8d148037-de0e-43a0-8be8-0e509e590e4e", + "last_modified": 1666727870823 + }, + { + "schema": 1666727412190, + "derHash": "pmUAegXv4YidZqQN7svGwaJx6RkAaBH9uNvX4GdSEtE=", + "subject": "CN=Siemens Issuing CA Internet Server 2020,O=Siemens,C=DE", + "subjectDN": "MFExMDAuBgNVBAMMJ1NpZW1lbnMgSXNzdWluZyBDQSBJbnRlcm5ldCBTZXJ2ZXIgMjAyMDEQMA4GA1UECgwHU2llbWVuczELMAkGA1UEBhMCREU=", + "whitelist": false, + "attachment": { + "hash": "5ab8cb8a5146787aeb9025fd757a28e47c16893e2603c336459385a83349e955", + "size": 1719, + "filename": "uOU9UZhtVd_opiG5DbxESujdJ9UY8BLJblR-MMq9FbY=.pem", + "location": "security-state-staging/intermediates/db9c0ed6-0b9d-4944-98d1-863d8b5ce519.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uOU9UZhtVd/opiG5DbxESujdJ9UY8BLJblR+MMq9FbY=", + "crlite_enrolled": false, + "id": "46298d4d-e394-4fcb-adc0-3082f97262e0", + "last_modified": 1666727870809 + }, + { + "schema": 1666727402874, + "derHash": "Zz401+HmslwYfzkcxJE4tHIqkH4z9bdIe2E7tt/niQk=", + "subject": "CN=University of Patras TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHYxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMS4wLAYDVQQDDCVVbml2ZXJzaXR5IG9mIFBhdHJhcyBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "f88a069d6389c290483843e15c9aaeeeacfc20e6b7cc7ade5d37b096404890fa", + "size": 2836, + "filename": "rjG2r01snR1vtNko4nWlkriwJxHfOXpXBBH61kCBBWk=.pem", + "location": "security-state-staging/intermediates/5e452e7d-0d62-49a6-ac96-0286762a043b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rjG2r01snR1vtNko4nWlkriwJxHfOXpXBBH61kCBBWk=", + "crlite_enrolled": false, + "id": "757bad34-1d42-4e5d-b9b3-c0a53e84ea95", + "last_modified": 1666727870796 + }, + { + "schema": 1666727386764, + "derHash": "GTe5v2YvtXhAe3erh9jWYrFjJ8+SM0DQ9y2VGVKxnIA=", + "subject": "CN=TuringSign ECC Secure CA,O=Turing Crypto GmbH,C=DE", + "subjectDN": "ME0xCzAJBgNVBAYTAkRFMRswGQYDVQQKExJUdXJpbmcgQ3J5cHRvIEdtYkgxITAfBgNVBAMTGFR1cmluZ1NpZ24gRUNDIFNlY3VyZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0c5cf83ed5b3664d80db1c28c2aa11c8a0e114bee2267c442ec1ed894a803551", + "size": 1406, + "filename": "Qg8WXvoy1Rxl-vs4yqRTbwJDijUK-I6ahTBJYsotkfo=.pem", + "location": "security-state-staging/intermediates/698e3a37-1d12-44d8-b154-08b235a7e128.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qg8WXvoy1Rxl+vs4yqRTbwJDijUK+I6ahTBJYsotkfo=", + "crlite_enrolled": false, + "id": "caa4bf78-66e4-42e9-862f-1ed6e684e693", + "last_modified": 1666727870783 + }, + { + "schema": 1666727404949, + "derHash": "2MyJxqprdFKIGQeSfIHmbaAadZjPdnTk4uEz8WVG1fA=", + "subject": "CN=DigiCert QuoVadis TLS ICA QV Root CA 3,O=DigiCert\\, Inc,C=US", + "subjectDN": "MFYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1EaWdpQ2VydCwgSW5jMS8wLQYDVQQDDCZEaWdpQ2VydCBRdW9WYWRpcyBUTFMgSUNBIFFWIFJvb3QgQ0EgMw==", + "whitelist": false, + "attachment": { + "hash": "28b7d0137b37f064e0e72dc8cba088a4bfad9fa88c195b84dc966fbc31cf4083", + "size": 1955, + "filename": "pT-wsiZiRbT5xEjXobbcxQVI0ciaSQwCykbcFUnahBA=.pem", + "location": "security-state-staging/intermediates/32052bb6-9019-4826-b1ec-d7f1b6c94e9d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pT+wsiZiRbT5xEjXobbcxQVI0ciaSQwCykbcFUnahBA=", + "crlite_enrolled": false, + "id": "50f4ff42-4da7-4477-b2c6-43861e12f6b6", + "last_modified": 1666727870769 + }, + { + "schema": 1666727433713, + "derHash": "RM6X9clX3QVtRMPhvfzrLPmntCjtCJg5JkM2MDvKx+8=", + "subject": "CN=CEDEFOP TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGkxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSEwHwYDVQQDDBhDRURFRk9QIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "c59254fc95564665618fe12e4e4fd0c517ecac0821b801ddbef6729ce7880f17", + "size": 2800, + "filename": "56n8EMBsMqyH4pNbD4KmUsbeas26-ouP-jDHJMDFKcA=.pem", + "location": "security-state-staging/intermediates/cba21052-349c-4d03-809c-7c6c8babaf41.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "56n8EMBsMqyH4pNbD4KmUsbeas26+ouP+jDHJMDFKcA=", + "crlite_enrolled": false, + "id": "82012d4f-835b-4cee-878e-afe4de303a8a", + "last_modified": 1666727870756 + }, + { + "schema": 1666727399939, + "derHash": "d4xRba7HAO5Ys1geQR5cDdR4ZjpRY6KYlTQVB9bpZN0=", + "subject": "CN=SHECA DV Server CA G5,O=UniTrust,C=CN", + "subjectDN": "MEAxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEeMBwGA1UEAwwVU0hFQ0EgRFYgU2VydmVyIENBIEc1", + "whitelist": false, + "attachment": { + "hash": "4153674bf85308618da92043981bdd2d2f7ca5a4ed109d39c822cdd2408d1775", + "size": 2008, + "filename": "LSv8B00n0rDwNaioIz0qIgnC9J7YkSK9NS35qVjVZK4=.pem", + "location": "security-state-staging/intermediates/0b27eb21-d455-438c-8fc1-4c0ecd0017eb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LSv8B00n0rDwNaioIz0qIgnC9J7YkSK9NS35qVjVZK4=", + "crlite_enrolled": false, + "id": "bd67f59e-d27b-4cfe-a22f-7c86e42343de", + "last_modified": 1666727870742 + }, + { + "schema": 1666727358621, + "derHash": "2rP6DW6CEAb+cJrhIXarovOXcGzJxx9cHA0JgETa69w=", + "subject": "CN=HARICA QWAC RSA SubCA R1,OU=Hellenic Academic and Research Institutions CA,O=Greek Universities Network (GUnet),L=Athens,C=GR", + "subjectDN": "MIHBMQswCQYDVQQGEwJHUjEPMA0GA1UEBwwGQXRoZW5zMSswKQYDVQQKDCJHcmVlayBVbml2ZXJzaXRpZXMgTmV0d29yayAoR1VuZXQpMRgwFgYDVQRhDA9WQVRHUi0wOTkwMjgyMjAxNzA1BgNVBAsMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExITAfBgNVBAMMGEhBUklDQSBRV0FDIFJTQSBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "0383e24a6bb65fc956706df83ec74d0428271d9eca74c56cff9893834d254d7b", + "size": 2568, + "filename": "4_BwvJtY4Tb6_B1gdFJmSV4cG4FmBSDa7puZdnWS2Pk=.pem", + "location": "security-state-staging/intermediates/d90f7551-8721-4067-8ccc-bc20473b7ed2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4/BwvJtY4Tb6/B1gdFJmSV4cG4FmBSDa7puZdnWS2Pk=", + "crlite_enrolled": false, + "id": "4a6655ab-9a9a-43db-bb34-1602419c8ed9", + "last_modified": 1666727870728 + }, + { + "schema": 1666727400111, + "derHash": "DlXQmFSCu7fEkOuhR8WgIaLCogidOor1fQHt1UDKWkU=", + "subject": "CN=SwissSign RSA TLS DV ICA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIERWIElDQSAyMDIxIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "3ae7fc2f0d23bef14972e3154b5eea231f26a4fdaf80e3344c6d4b6e18e8d9db", + "size": 2605, + "filename": "Ss2dQDgixRL9ORRngPe7uCywHw5_E9Qk2Wziz9rLT1s=.pem", + "location": "security-state-staging/intermediates/a1b17860-82f5-4bf3-87cb-42cd84d8b918.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ss2dQDgixRL9ORRngPe7uCywHw5/E9Qk2Wziz9rLT1s=", + "crlite_enrolled": false, + "id": "f0051e35-0e04-44e0-b38c-9ae0dfcb304f", + "last_modified": 1666727870715 + }, + { + "schema": 1666727337012, + "derHash": "KXW6tR0A2GLQ4W7t74MGp1nGXNS58A2vUOzfy07DluQ=", + "subject": "CN=Microsoft Azure ECC TLS Issuing CA 06,O=Microsoft Corporation,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLjAsBgNVBAMTJU1pY3Jvc29mdCBBenVyZSBFQ0MgVExTIElzc3VpbmcgQ0EgMDY=", + "whitelist": false, + "attachment": { + "hash": "913a6555d1accfb6fad27f0850000c3dead37ef7c0c8683ab4771d4154dafae2", + "size": 1329, + "filename": "yBjnrcmcUp2nylDRWnQvSPRspmhm1f_fOuKrsNiaSdA=.pem", + "location": "security-state-staging/intermediates/d4441bdb-1976-4959-915a-7e64feadf03a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yBjnrcmcUp2nylDRWnQvSPRspmhm1f/fOuKrsNiaSdA=", + "crlite_enrolled": false, + "id": "23eb6ffd-3fb0-4819-9292-f84d510529b1", + "last_modified": 1666727870702 + }, + { + "schema": 1666727387103, + "derHash": "jz5daFi5J9KE/bhaGxWHXNeOY7ggwg3cU5zNhLDBpqo=", + "subject": "CN=Alibaba Cloud GCC R3 DV TLS CA 2021,O=Alibaba Cloud Computing Co.\\, Ltd.,C=CN", + "subjectDN": "MGcxCzAJBgNVBAYTAkNOMSowKAYDVQQKEyFBbGliYWJhIENsb3VkIENvbXB1dGluZyBDby4sIEx0ZC4xLDAqBgNVBAMTI0FsaWJhYmEgQ2xvdWQgR0NDIFIzIERWIFRMUyBDQSAyMDIx", + "whitelist": false, + "attachment": { + "hash": "f98193821503fbb97e18ccaf2e25c0be62c2a4b730272b187bee61068dd23f96", + "size": 1735, + "filename": "Pg1Dubcu-8AyH8ZdyHa-bP6sDppBv9fvXsyA66WgFbo=.pem", + "location": "security-state-staging/intermediates/05386631-1c18-4e3b-93c0-4350043f3a9b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Pg1Dubcu+8AyH8ZdyHa+bP6sDppBv9fvXsyA66WgFbo=", + "crlite_enrolled": false, + "id": "e207f6b2-a65d-496f-93d0-ba609cae2a11", + "last_modified": 1666727870674 + }, + { + "schema": 1666727408573, + "derHash": "U2o1o/pjA3nPO01LThOVr5z+0iQxOOY05DaaMeKHKEM=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIyIFEx", + "whitelist": false, + "attachment": { + "hash": "27ae75ce3a1364b7c6d354f4b20a7958b356098ef77f069b6822b1d1e0f1d519", + "size": 1715, + "filename": "FOIhxqHWraoWquKZ7Vdl8ZZq_LUDlRo5cGfh_Ue22N4=.pem", + "location": "security-state-staging/intermediates/d725c849-ba09-4744-9b67-2287530c0737.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FOIhxqHWraoWquKZ7Vdl8ZZq/LUDlRo5cGfh/Ue22N4=", + "crlite_enrolled": false, + "id": "7cfb8b2b-97d9-4b79-9fd1-b7cabd248b02", + "last_modified": 1666727870661 + }, + { + "schema": 1666727440773, + "derHash": "q3SxQRrSPiIn+4iiqTBKGkWlxIQLNjXxA2pG6DdCeaw=", + "subject": "CN=Soluti CA - OV,O=SOLUTI - SOLUCOES EM NEGOCIOS INTELIGENTES S/A,C=BR", + "subjectDN": "MF8xCzAJBgNVBAYTAkJSMTcwNQYDVQQKEy5TT0xVVEkgLSBTT0xVQ09FUyBFTSBORUdPQ0lPUyBJTlRFTElHRU5URVMgUy9BMRcwFQYDVQQDEw5Tb2x1dGkgQ0EgLSBPVg==", + "whitelist": false, + "attachment": { + "hash": "1289ff9c033377f8edc9f2ded9318205d7263d8c7040b70ff2db68630eaac699", + "size": 1776, + "filename": "rCbpz2JPbnICZtM69Lt2eclqdTss0Mj0Z4ejXMF1nD4=.pem", + "location": "security-state-staging/intermediates/1bbff038-8596-4d1c-aea4-d0a5730155a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rCbpz2JPbnICZtM69Lt2eclqdTss0Mj0Z4ejXMF1nD4=", + "crlite_enrolled": false, + "id": "bd39f78d-03b0-4fc6-9992-3728009c0be9", + "last_modified": 1666727870647 + }, + { + "schema": 1666727420998, + "derHash": "yAJfn8Zf38lbPKjMeGe5pYe1J3lzlXkXRj/IE9C2Jak=", + "subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "99c4ba193f55a59ed1208cbce1e7b5818313eba23dc8d0f4d770627ac60303b5", + "size": 1719, + "filename": "Wec45nQiFwKvHtuHxSAMGkt19k-uPSw9JlEkxhvYPHk=.pem", + "location": "security-state-staging/intermediates/81d5dbda-8b8c-4210-bd29-d0afb44912b8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wec45nQiFwKvHtuHxSAMGkt19k+uPSw9JlEkxhvYPHk=", + "crlite_enrolled": false, + "id": "6393a1a0-fd0e-418f-bc54-c3861d8508de", + "last_modified": 1666727870634 + }, + { + "schema": 1666727404421, + "derHash": "fP40N45zBp86aBO+yJoOR2k8Ia4mo3YNUM300F5A0fM=", + "subject": "CN=HARICA EV TLS ECC 2,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGQxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRwwGgYDVQQDDBNIQVJJQ0EgRVYgVExTIEVDQyAy", + "whitelist": false, + "attachment": { + "hash": "0cbc1a3c8520ea1553e56ab949687a5a8b3a644218bf74bd91ef2867e5a53440", + "size": 1297, + "filename": "nCKlApA0IK_LGFiqF8sW8G6WcSozlupFdMt2pzPnvPk=.pem", + "location": "security-state-staging/intermediates/0b33274d-9ec9-4f5c-b4b5-84bec2f46067.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nCKlApA0IK/LGFiqF8sW8G6WcSozlupFdMt2pzPnvPk=", + "crlite_enrolled": false, + "id": "b518e048-6e12-426b-b992-c10e5ef0272c", + "last_modified": 1666727870573 + }, + { + "schema": 1666727416499, + "derHash": "deMoZDv+dkLdhA4xaflCxmvwSRnYp/sK+WYCYSADw+M=", + "subject": "CN=HARICA EV TLS RSA 2,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGQxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRwwGgYDVQQDDBNIQVJJQ0EgRVYgVExTIFJTQSAy", + "whitelist": false, + "attachment": { + "hash": "e94efaa5388b5a79fe24617a6f0c04d959518ed580fd79a5bae5d273e6913ff5", + "size": 2442, + "filename": "L71qnaLy9ik60mK7sra3MIvUXyqNjaoejV3p0YDj4us=.pem", + "location": "security-state-staging/intermediates/6225a28c-e26b-4eb4-99ac-68eb3496d932.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "L71qnaLy9ik60mK7sra3MIvUXyqNjaoejV3p0YDj4us=", + "crlite_enrolled": false, + "id": "ba2e80c2-7b7e-4916-90fd-f428ad47e7a0", + "last_modified": 1666727870560 + }, + { + "schema": 1666727441294, + "derHash": "DU06L22sIiF1uh33i0gXtGkJW/AvR5GVEjUxFZAiZfs=", + "subject": "CN=TrustAsia ECC EV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIEVWIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "9794c993aca46e803c8aa078b5da879d883b9af78a42596631949036a1011842", + "size": 1341, + "filename": "9qS5OT_rkZzagnNnLjdawEe0ixdyP-C-JYxsk23BGI0=.pem", + "location": "security-state-staging/intermediates/c4ef2e24-67be-4d3f-9009-505483ee22cc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9qS5OT/rkZzagnNnLjdawEe0ixdyP+C+JYxsk23BGI0=", + "crlite_enrolled": false, + "id": "3e78916f-055f-4525-91bb-ad08e95c58eb", + "last_modified": 1666727870544 + }, + { + "schema": 1666727339721, + "derHash": "6IHTuDw7xpTX2Z+S3oOyv/XG7i2YcaRG3qEH1jl1Zfw=", + "subject": "CN=Apple Public EV Server RSA CA 3 - G1,O=Apple Inc.,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMS0wKwYDVQQDEyRBcHBsZSBQdWJsaWMgRVYgU2VydmVyIFJTQSBDQSAzIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "05636855833311a310b0828ba857207d0eccf6a019c54c9ebb31a2249c1664f8", + "size": 1800, + "filename": "aEQV_UnJvguPLzp--x3ZbyOodJnw5VpETKxl_Aol0Ac=.pem", + "location": "security-state-staging/intermediates/a98e7dae-1e30-457f-90d6-9f0c1d817640.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aEQV/UnJvguPLzp++x3ZbyOodJnw5VpETKxl/Aol0Ac=", + "crlite_enrolled": false, + "id": "7915f1a3-25a6-4fe0-8f20-a17e44488dcf", + "last_modified": 1666727870531 + }, + { + "schema": 1666727390226, + "derHash": "qEdUTMPNVOhVyjl4DCFiQx9cjjJ+xm1aQ88maHe+xQ8=", + "subject": "CN=HydrantID SSL CA D1,O=Avalanche Cloud Corporation,C=US", + "subjectDN": "MFExCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtBdmFsYW5jaGUgQ2xvdWQgQ29ycG9yYXRpb24xHDAaBgNVBAMTE0h5ZHJhbnRJRCBTU0wgQ0EgRDE=", + "whitelist": false, + "attachment": { + "hash": "06ef680fca20d39a376ae63a23282c3c882a1d9a10e1b090d7a649ca52cb495b", + "size": 1691, + "filename": "pFJuHwTjYt84g_qC76hABYpNYJN5mgRCOZ3SeolVQWw=.pem", + "location": "security-state-staging/intermediates/78620e34-e02c-45a6-829f-9e58d846ec6a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pFJuHwTjYt84g/qC76hABYpNYJN5mgRCOZ3SeolVQWw=", + "crlite_enrolled": false, + "id": "4881e3af-3f96-44cf-b1e0-4b814adb983c", + "last_modified": 1666727870517 + }, + { + "schema": 1666727383578, + "derHash": "fvP4lFbOY2VXsgxd+zf5jCU6C2YNLp5eeEXK+cA4x8E=", + "subject": "CN=SHECA EV Server CA G3,O=UniTrust,C=CN", + "subjectDN": "MEAxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEeMBwGA1UEAwwVU0hFQ0EgRVYgU2VydmVyIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "b11897cb46ef64d1a0bce0acb7e1da9d5d9319cb8cb0a1441d46af6510bd2188", + "size": 2003, + "filename": "a_eZydK7TPcC6VQUFv_ek8Goclpip8HIQF3iyBC1v9Y=.pem", + "location": "security-state-staging/intermediates/9a95e108-34b7-4a4a-9fd4-65e6fe531f0f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "a/eZydK7TPcC6VQUFv/ek8Goclpip8HIQF3iyBC1v9Y=", + "crlite_enrolled": false, + "id": "0cbd2855-9acf-477f-a083-016847b4753a", + "last_modified": 1666727870504 + }, + { + "schema": 1666727427981, + "derHash": "/s6a2nqknU/qnv8SNUIJWogMAE/Wkz+TZLArLjV06jg=", + "subject": "CN=Cybertrust Japan SureServer EV CA G3,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MGExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEtMCsGA1UEAxMkQ3liZXJ0cnVzdCBKYXBhbiBTdXJlU2VydmVyIEVWIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "442ad2ef3f029c8ae0616647479fbe53d99e0e9ed017c893d2b5952cbf146f8f", + "size": 1772, + "filename": "zJoepoy-7XeJoRmjbR7_i8oZ1WEujOKGTD9NNM0uWHM=.pem", + "location": "security-state-staging/intermediates/c3ae4321-0501-4974-bae5-2038e257036c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zJoepoy+7XeJoRmjbR7/i8oZ1WEujOKGTD9NNM0uWHM=", + "crlite_enrolled": false, + "id": "477f5a61-5c31-42a4-89b0-4e49b54028ef", + "last_modified": 1666727870490 + }, + { + "schema": 1666727444663, + "derHash": "AgcFbRcsgL37bcRb6eWAiEYHjR5u7xtu1wJZqzMqZME=", + "subject": "CN=Cybertrust Japan SureServer CA G4,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MF4xCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEqMCgGA1UEAxMhQ3liZXJ0cnVzdCBKYXBhbiBTdXJlU2VydmVyIENBIEc0", + "whitelist": false, + "attachment": { + "hash": "d14576fded8dc8bc2c5e4595b2e25160155f8f967c80541b129ff564174309bd", + "size": 1768, + "filename": "rS4Ex7fMz9dQhgdB6qjxP-jJJQwjIeb-7RhvvdO6xy8=.pem", + "location": "security-state-staging/intermediates/b6d9843d-8dea-45b8-acba-752b6481c4e7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rS4Ex7fMz9dQhgdB6qjxP+jJJQwjIeb+7RhvvdO6xy8=", + "crlite_enrolled": false, + "id": "354eb83b-1260-4775-bce0-910a0a18ed75", + "last_modified": 1666727870476 + }, + { + "schema": 1666727383752, + "derHash": "sU1QiQecHY92SduaXTzvsarAb2avxJIlxb4qoZ/UGjU=", + "subject": "CN=Entrust Certification Authority - L1N,OU=See www.entrust.net/legal-terms+OU=(c) 2014 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTQgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFO", + "whitelist": false, + "attachment": { + "hash": "2a107eb8056c22e82435858002ab7da59ecfe298d1cafd19ef86a74343e7a1bb", + "size": 2207, + "filename": "vJyqpf--OgJrh0WkGBWAggmVgZ5-o9t2KvyRdPxvIhs=.pem", + "location": "security-state-staging/intermediates/119f868d-4224-48fa-aca4-4e950d595c12.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vJyqpf++OgJrh0WkGBWAggmVgZ5+o9t2KvyRdPxvIhs=", + "crlite_enrolled": false, + "id": "b03bf780-0b68-4f48-9034-834ec2d038db", + "last_modified": 1666727870460 + }, + { + "schema": 1666727349051, + "derHash": "HIhGxAasYpT68NMqWhTnnIQThaV/EVm2HpAUYF435ek=", + "subject": "CN=DigiCert Assured ID G3 TLS ECC384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE6MDgGA1UEAxMxRGlnaUNlcnQgQXNzdXJlZCBJRCBHMyBUTFMgRUNDMzg0IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "682a6d671295b8e0ca15d671be54d68c7d5786b8b33b65eb4e943f238772b094", + "size": 1288, + "filename": "otVuF_gByhb3Aev07shrO8GeUcthd4Qw5N_K1L0fizo=.pem", + "location": "security-state-staging/intermediates/d6b7ddd7-f249-4342-9621-2f56bffe26af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "otVuF/gByhb3Aev07shrO8GeUcthd4Qw5N/K1L0fizo=", + "crlite_enrolled": false, + "id": "e0227843-5910-4bd0-adaa-01d35e7a7005", + "last_modified": 1666727870437 + }, + { + "schema": 1666727373633, + "derHash": "4i5rJZCOEQemB68GDgsk5QxtlWL/BPRVvg+N9BpQMsA=", + "subject": "CN=SZAFIR Trusted CA2,O=Krajowa Izba Rozliczeniowa S.A.,C=PL", + "subjectDN": "MFQxCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRswGQYDVQQDDBJTWkFGSVIgVHJ1c3RlZCBDQTI=", + "whitelist": false, + "attachment": { + "hash": "b998d1830338d9ca1bada5e5e1a33ef3a5f89c9c13ccfa1b8ea7a2f3aea980a1", + "size": 1825, + "filename": "Ea2smccTC5mFDdTrs5gKD6HaYthL4lhPx97QmLgbFQc=.pem", + "location": "security-state-staging/intermediates/d47f17a5-7858-4e12-ac5e-0454f2a87e62.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ea2smccTC5mFDdTrs5gKD6HaYthL4lhPx97QmLgbFQc=", + "crlite_enrolled": false, + "id": "43f6861b-21c6-4ebd-a31e-6d9e61e879af", + "last_modified": 1666727870423 + }, + { + "schema": 1666727393606, + "derHash": "mH/y47JaOhTLhD2Qezx/AHwnSSGvwQAX+F1PrnsMuLY=", + "subject": "CN=DigiCert Assured ID TLS RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE4MDYGA1UEAxMvRGlnaUNlcnQgQXNzdXJlZCBJRCBUTFMgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "ea98651742d65855a2b89e7d033a40bf1adf3bb22e730eda3ea9896019690d52", + "size": 2089, + "filename": "t6F6ltc_eNYw0WY4vUTTo7SvuavxVPXIIpqLyWMQG-g=.pem", + "location": "security-state-staging/intermediates/27c32677-48fe-4613-8b2f-ca869cef0d85.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t6F6ltc/eNYw0WY4vUTTo7SvuavxVPXIIpqLyWMQG+g=", + "crlite_enrolled": false, + "id": "3ef3ce19-bcfb-484b-9e2e-253adcc956b5", + "last_modified": 1666727870409 + }, + { + "schema": 1666727338014, + "derHash": "SWlaXw9+9u32mBk9me1Iuq3iDqRXQDwRzq1JLEWGZdo=", + "subject": "CN=TWCA Global EVSSL Certification Authority,OU=Global EVSSL Sub-CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MHMxCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExHDAaBgNVBAsTE0dsb2JhbCBFVlNTTCBTdWItQ0ExMjAwBgNVBAMTKVRXQ0EgR2xvYmFsIEVWU1NMIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "4f777697d0c427439f9282b53a652b4a868074c5e1ae55a6dfe51dc32d19b817", + "size": 1955, + "filename": "3sXbPfqhHfn3hza4ob3X6iuMjfN9qdgOhBAC5GOp-TY=.pem", + "location": "security-state-staging/intermediates/1b6005ce-3d05-4336-b84f-46f273145539.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3sXbPfqhHfn3hza4ob3X6iuMjfN9qdgOhBAC5GOp+TY=", + "crlite_enrolled": false, + "id": "c44d3056-f6b6-415c-8f13-43d0b71fb7b5", + "last_modified": 1666727870394 + }, + { + "schema": 1666727378252, + "derHash": "mxby9oDXxL1qZ/YJNA2mQWq/nkPxMmsBuYgZInHQtfI=", + "subject": "CN=TWCA Secure SSL Certification Authority,OU=Secure SSL Sub-CA,O=TAIWAN-CA,C=TW", + "subjectDN": "MG8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExGjAYBgNVBAsTEVNlY3VyZSBTU0wgU3ViLUNBMTAwLgYDVQQDEydUV0NBIFNlY3VyZSBTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "acd717a163c1e31ec674bb2953f2f16be424838b5d36e703984df5d77c80d023", + "size": 2032, + "filename": "8hqbhsMMFPaPA8t81pxqFer9-neHBcQvO7-TAKjWkb0=.pem", + "location": "security-state-staging/intermediates/b5f4faa4-0521-4c2b-939b-4bef5f921421.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8hqbhsMMFPaPA8t81pxqFer9+neHBcQvO7+TAKjWkb0=", + "crlite_enrolled": false, + "id": "163c4d13-0624-40c4-83d7-7b9bbea0f129", + "last_modified": 1666727870379 + }, + { + "schema": 1666727414774, + "derHash": "PhR5gEdlpiu3vU8N3rtVqUaiBjzSiC8FRhsXVPG2Z7E=", + "subject": "CN=HARICA Administration SSL ECC SubCA R2,O=Hellenic Academic and Research Institutions Cert. Authority,L=Athens,C=GR", + "subjectDN": "MIGVMQswCQYDVQQGEwJHUjEPMA0GA1UEBwwGQXRoZW5zMUQwQgYDVQQKDDtIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTEvMC0GA1UEAwwmSEFSSUNBIEFkbWluaXN0cmF0aW9uIFNTTCBFQ0MgU3ViQ0EgUjI=", + "whitelist": false, + "attachment": { + "hash": "d70d3f0dfbc3af1e7f43be02aa899e7f11297273559465b856d65421bb15a24c", + "size": 1890, + "filename": "XLCUgwMafFqrcdmzcM5gMLCX0OBG7_vbnVKij5FQYhg=.pem", + "location": "security-state-staging/intermediates/cd67a4e0-5a13-4946-972f-125a5e277867.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XLCUgwMafFqrcdmzcM5gMLCX0OBG7/vbnVKij5FQYhg=", + "crlite_enrolled": false, + "id": "e274886d-aee1-4955-a7c1-fc32f4c81589", + "last_modified": 1666727870352 + }, + { + "schema": 1666727454545, + "derHash": "0WuprLdP7kqoCH7kguhuf29fVfrFAlY5cwdT/h5wXjw=", + "subject": "CN=TrustAsia RSA OV TLS CA - S1,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFsxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQDDBxUcnVzdEFzaWEgUlNBIE9WIFRMUyBDQSAtIFMx", + "whitelist": false, + "attachment": { + "hash": "3478555efe90114245336918b07ce2fd5642c151b04ae871aa5b719513eff08c", + "size": 2044, + "filename": "3sdsoSIuRvtjF8OsOc4xIdevOWOclELQuaEQ85MqCYY=.pem", + "location": "security-state-staging/intermediates/a04c15b5-f9b9-47f9-91bc-106fd7754d29.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3sdsoSIuRvtjF8OsOc4xIdevOWOclELQuaEQ85MqCYY=", + "crlite_enrolled": false, + "id": "4a8de048-65c5-4477-b64e-79f9f290218e", + "last_modified": 1666727870336 + }, + { + "schema": 1666727415484, + "derHash": "wY1Tv5hk3Qm8vKz9Zy4lZtTIH2iJ42313UJcBCEdB2M=", + "subject": "CN=Hongkong Post e-Cert EV SSL CA 3 - 17,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK", + "subjectDN": "MH0xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEuMCwGA1UEAxMlSG9uZ2tvbmcgUG9zdCBlLUNlcnQgRVYgU1NMIENBIDMgLSAxNw==", + "whitelist": false, + "attachment": { + "hash": "e4408c6b03e1deb0777ad20ea8b3466779565bcb2d92a17de6ac350bdaa5de18", + "size": 2097, + "filename": "shxxZc44F1hF3pjKzr0Oaoq-CwRFBKIr0_noGHUYkjA=.pem", + "location": "security-state-staging/intermediates/bdeda58e-1628-4e55-87fb-0b68db6affa9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "shxxZc44F1hF3pjKzr0Oaoq+CwRFBKIr0/noGHUYkjA=", + "crlite_enrolled": false, + "id": "88070242-b7b9-4c79-870e-7b137524b1dc", + "last_modified": 1666727870323 + }, + { + "schema": 1666727441620, + "derHash": "aezbwxR/WB39y1Itne+yYLJnhK1JVcdOalJSLMxMRAg=", + "subject": "CN=Hongkong Post e-Cert SSL CA 3 - 17,O=Hongkong Post,L=Hong Kong,ST=Hong Kong,C=HK", + "subjectDN": "MHoxCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDErMCkGA1UEAxMiSG9uZ2tvbmcgUG9zdCBlLUNlcnQgU1NMIENBIDMgLSAxNw==", + "whitelist": false, + "attachment": { + "hash": "d819d0da20d45c8f93a0efb0d37fd86e274107134845bf5480d9b3697bb0711d", + "size": 2093, + "filename": "SUe0dPoWC5QKoJyXReRB3haevgxOd9ZVd5sn7N65_DY=.pem", + "location": "security-state-staging/intermediates/bfa15d55-724f-433e-8e8c-57f115fe9319.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SUe0dPoWC5QKoJyXReRB3haevgxOd9ZVd5sn7N65/DY=", + "crlite_enrolled": false, + "id": "e04bc9de-e9d2-45e5-a6d5-3e29309132df", + "last_modified": 1666727870309 + }, + { + "schema": 1666727379118, + "derHash": "AYGythcxeVYudWMjRAN8pSMBhhin9xaCaaPrhTZ/91w=", + "subject": "CN=DigiCert Assured ID Grid TLS RSA2048 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGUxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE9MDsGA1UEAxM0RGlnaUNlcnQgQXNzdXJlZCBJRCBHcmlkIFRMUyBSU0EyMDQ4IFNIQTI1NiAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "7f8f0eacf04c02686bfe08a2ccff1727606d8cbdee3d30408f7ee475b68600a3", + "size": 1804, + "filename": "_SfSGxeP1MQb2mpICOc8KzfYSH77KMzIMbCgylDKpPc=.pem", + "location": "security-state-staging/intermediates/4e91bcaa-5473-4b40-8c61-3bb878424076.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/SfSGxeP1MQb2mpICOc8KzfYSH77KMzIMbCgylDKpPc=", + "crlite_enrolled": false, + "id": "a5acb099-b5f7-485c-a851-34cf19a7efc6", + "last_modified": 1666727870282 + }, + { + "schema": 1666727355566, + "derHash": "Q6cONiC8RSM52HiqaPLlNYrEYHrHIsTXfTxNLCBPf3E=", + "subject": "CN=DigiCert Assured ID G2 TLS RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgQXNzdXJlZCBJRCBHMiBUTFMgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "fce800188289208c71c42766d767c63249b981ca723bba46e543fa2f5d87596c", + "size": 2093, + "filename": "Ld64SpoeXjpLjc-_7Wahk6p5-KVyzVSUptciuWsyxeY=.pem", + "location": "security-state-staging/intermediates/df08562c-f117-43d1-a40a-3997497594ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ld64SpoeXjpLjc+/7Wahk6p5+KVyzVSUptciuWsyxeY=", + "crlite_enrolled": false, + "id": "55420fa1-1fcf-457c-ae91-dd6b8197e62b", + "last_modified": 1666727870268 + }, + { + "schema": 1666727366231, + "derHash": "yikDieDYxipAg/Yoo59S/j84tzGZz/r3wDcjeKRA+2o=", + "subject": "CN=Entrust Certification Authority - L1M,OU=See www.entrust.net/legal-terms+OU=(c) 2014 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTQgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFN", + "whitelist": false, + "attachment": { + "hash": "02e23f9b06e438417e4e71c8b9fb42a8e2da31ad266ad1ca50792fd36d7251b9", + "size": 1829, + "filename": "VYZwGiJkq3NNo1YRI2RGiSTI1mqTWG8zDcRf1_KAN6I=.pem", + "location": "security-state-staging/intermediates/75652090-686b-4e77-99f1-1f4effac785a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VYZwGiJkq3NNo1YRI2RGiSTI1mqTWG8zDcRf1/KAN6I=", + "crlite_enrolled": false, + "id": "e7ad797a-d41f-4eb9-b47a-1af34311a9d1", + "last_modified": 1666727870255 + }, + { + "schema": 1666727352489, + "derHash": "Iy9jZ89WHgDIPhgKn8qFRrN3H7RQ68tKBSb4NJyMoTk=", + "subject": "CN=Entrust Certification Authority - L1E,OU=www.entrust.net/rpa is incorporated by reference+OU=(c) 2009 Entrust\\, Inc.,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIGxMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L3JwYSBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwOSBFbnRydXN0LCBJbmMuMS4wLAYDVQQDEyVFbnRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gTDFF", + "whitelist": false, + "attachment": { + "hash": "fb13e64f68c20f1be87aefb4a955348c9604a4103426723d49f8029f9fbcc797", + "size": 1833, + "filename": "HeMEDmta9acr_zmjHkdP_dKlW5RaqBlmADFOqTG56eA=.pem", + "location": "security-state-staging/intermediates/524fa4a4-38af-437a-a31e-038e5aaf46ef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HeMEDmta9acr/zmjHkdP/dKlW5RaqBlmADFOqTG56eA=", + "crlite_enrolled": false, + "id": "7a058667-a078-4b7b-a38e-adb1e3e95b39", + "last_modified": 1666727870242 + }, + { + "schema": 1666727335999, + "derHash": "r9s8OW2F0Z++WCBt8GWermFMSrGPXM8gt4Oq8LvpagM=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIyIFEy", + "whitelist": false, + "attachment": { + "hash": "810129eb69110a8c071f593ade366adbaa81a2420d3531e8f862ea7a5b423c11", + "size": 1642, + "filename": "VkNfITzp8T2xdmHOxT0ZZUfHySNBzxU2GWFfqmC3oLE=.pem", + "location": "security-state-staging/intermediates/8ae25e5b-e31b-4d80-be59-24ed2ecd5386.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VkNfITzp8T2xdmHOxT0ZZUfHySNBzxU2GWFfqmC3oLE=", + "crlite_enrolled": false, + "id": "6982bfcf-221f-44f5-b27e-d4af6dd1ab4f", + "last_modified": 1666727870227 + }, + { + "schema": 1666727419047, + "derHash": "RfGFRTDsA3qqy0st6bTQ/b/GGIir1O4EFBg9meYEN6w=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2022 Q2,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjIgUTI=", + "whitelist": false, + "attachment": { + "hash": "0f455b2160c81e5b49b03b477baaa020d52928d16a4fe13504fca71f176fcf2e", + "size": 1642, + "filename": "6bop5gsmVRhy033XqsI4UKTek3IMHHpiLVeicdR_Luw=.pem", + "location": "security-state-staging/intermediates/12de76d7-2251-470d-8c62-aebb1b54b3d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6bop5gsmVRhy033XqsI4UKTek3IMHHpiLVeicdR/Luw=", + "crlite_enrolled": false, + "id": "18283798-9f59-4d62-9a34-f643b9b7a76f", + "last_modified": 1666727870214 + }, + { + "schema": 1666727394100, + "derHash": "c1zDhuwaguZXpbRRyUlZYR08fQy7H9nZ7bQscZL4Rjo=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIyIFEy", + "whitelist": false, + "attachment": { + "hash": "5dc3ef9866bfcfb8743ef46140622103cd877f3ecd2310029ef8d8ff09b17dd4", + "size": 1642, + "filename": "7Zgd-tc1DR60EUxxu86y2PNtnPLUCDONUQJhN35HWIU=.pem", + "location": "security-state-staging/intermediates/63da4365-743a-4755-bfb8-beb8ff31e794.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7Zgd+tc1DR60EUxxu86y2PNtnPLUCDONUQJhN35HWIU=", + "crlite_enrolled": false, + "id": "12cc99be-332a-4aef-af43-25fe8420b210", + "last_modified": 1666727870201 + }, + { + "schema": 1666727433205, + "derHash": "qBav/3RuoPCgoGTAQy+dK30M557C0emUD0oYaiUYIoQ=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIyIFEy", + "whitelist": false, + "attachment": { + "hash": "049fc8dce345f116d37deb30e65527acd5a647e1de6e9d53685b97fe4fe7c5fe", + "size": 1195, + "filename": "TdoclaY2HEKPADeH2W-4kosHzm7UvYAafcidCBnsRDA=.pem", + "location": "security-state-staging/intermediates/6c42ca37-a352-4db9-b44d-e1a577fb1168.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TdoclaY2HEKPADeH2W+4kosHzm7UvYAafcidCBnsRDA=", + "crlite_enrolled": false, + "id": "6de1a4fe-3539-4329-9838-cb4883353866", + "last_modified": 1666727870187 + }, + { + "schema": 1666727450446, + "derHash": "Lf5H4USrs5+f5FHOzTUtmpvSiYI1HotlJP6xAdurH+w=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "63657216ab2e0265cbcbce85e9b2783ccd35376d4e7c0f127fcac181251aa5ed", + "size": 1642, + "filename": "_1VRS6Ha0tRFo_8a_6PqqF4wtj16qRxOO4rXTjqDNFk=.pem", + "location": "security-state-staging/intermediates/c70a5a54-9017-4d7c-814c-c431416e70eb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/1VRS6Ha0tRFo/8a/6PqqF4wtj16qRxOO4rXTjqDNFk=", + "crlite_enrolled": false, + "id": "681f5a20-f291-4509-bec3-6adbf5c05429", + "last_modified": 1666727870174 + }, + { + "schema": 1666727422931, + "derHash": "k45SZCUB3RbiPYrr+5frPDslYvUMMkFEw5CUayloSn4=", + "subject": "CN=Telekom Security DV RSA CA 22,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJjAkBgNVBAMMHVRlbGVrb20gU2VjdXJpdHkgRFYgUlNBIENBIDIy", + "whitelist": false, + "attachment": { + "hash": "2630819dec418a1e189974ade9765910f477438175848468a64894d5bbda8f67", + "size": 2113, + "filename": "PQgCR2Tyj2gl6d2EvsaoRq_hO9GQc1sBcmN7QkGUKf4=.pem", + "location": "security-state-staging/intermediates/0d78df23-c84c-4c49-8425-168287f3071c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PQgCR2Tyj2gl6d2EvsaoRq/hO9GQc1sBcmN7QkGUKf4=", + "crlite_enrolled": false, + "id": "159ab7e9-8e9c-4450-a603-927a7ba6336a", + "last_modified": 1666727870161 + }, + { + "schema": 1666727421678, + "derHash": "Bp3y+JGdkgnQxSSla6EYHfbRu/xKphyfAdCS4tXDof0=", + "subject": "CN=MSFT BALT RS256 CA,O=Microsoft Corporation,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xGzAZBgNVBAMTEk1TRlQgQkFMVCBSUzI1NiBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b68175bda43c438ac536ee134774227b69c8479a249c97fc24ebcdc2efe126fa", + "size": 2028, + "filename": "pDM-S2FkRzB_xtTd9uAioSOPn-QW1bRg8ecQtY4-I1A=.pem", + "location": "security-state-staging/intermediates/ca4850cd-a81e-479d-ba75-2a7072c7f60d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pDM+S2FkRzB/xtTd9uAioSOPn+QW1bRg8ecQtY4+I1A=", + "crlite_enrolled": false, + "id": "f18528dd-6f56-46ab-942c-d0660c571a8d", + "last_modified": 1666727870147 + }, + { + "schema": 1666727447078, + "derHash": "2w2hYDLxZDoklv3nQuK76B2spYzXYSBhQg4VTOG84r0=", + "subject": "OU=AC Componentes Informáticos,O=FNMT-RCM,C=ES", + "subjectDN": "MEcxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTElMCMGA1UECwwcQUMgQ29tcG9uZW50ZXMgSW5mb3Jtw6F0aWNvcw==", + "whitelist": false, + "attachment": { + "hash": "9667524969985c4cb1aa76f3e3c434be6441809f9c3d2bad6265165a0b509b08", + "size": 2430, + "filename": "MEJWDQI0WXBrEdYtj1u1WdwD26XsIXQQ-57NkgXsoGc=.pem", + "location": "security-state-staging/intermediates/70f3a46a-ba8f-4f11-89a5-866e51a76799.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MEJWDQI0WXBrEdYtj1u1WdwD26XsIXQQ+57NkgXsoGc=", + "crlite_enrolled": false, + "id": "c24c4af6-ee60-43b5-b538-bd2e1ea4cbf9", + "last_modified": 1666727870104 + }, + { + "schema": 1666727397123, + "derHash": "J47PIR4lGOndmROvxYPusxJ8gYS8vu2q171k6J6MQxA=", + "subject": "CN=QuoVadis Grid ICA G2,O=QuoVadis Limited,C=BM", + "subjectDN": "MEcxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR0wGwYDVQQDExRRdW9WYWRpcyBHcmlkIElDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "8e15c43f71096a592c3d5819db25f4547bb4ef4f8232293578abdf314d149392", + "size": 2353, + "filename": "5rlUfVlXaS82HCd6DZI7w8rIamphHCdjnXNpXREZynQ=.pem", + "location": "security-state-staging/intermediates/e6bcd10d-1e18-4c5f-a5f1-91850eec82ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5rlUfVlXaS82HCd6DZI7w8rIamphHCdjnXNpXREZynQ=", + "crlite_enrolled": false, + "id": "9b40ce27-a8d1-444f-a177-ff64ce1cb254", + "last_modified": 1666727870086 + }, + { + "schema": 1666727338881, + "derHash": "f+uTdOqwjTknF8ZHQ22uBhdqJMAQYH/aHM5eXwEGtHI=", + "subject": "CN=QuoVadis Qualified Web ICA G2,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MFcxCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdRdW9WYWRpcyBUcnVzdGxpbmsgQi5WLjEmMCQGA1UEAwwdUXVvVmFkaXMgUXVhbGlmaWVkIFdlYiBJQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "c551ff3d69db56e747169ab80b2fdea513f1de4806de98e0e34ab04da70a7566", + "size": 2385, + "filename": "HW4ktpkbc6tiWRVtXWUrZxqP505LD1tPqFHb3HiZbX8=.pem", + "location": "security-state-staging/intermediates/bd4b3ad6-5fca-4573-9e99-2959c279c1ac.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HW4ktpkbc6tiWRVtXWUrZxqP505LD1tPqFHb3HiZbX8=", + "crlite_enrolled": false, + "id": "294e341c-f6e5-4868-b5aa-b980b5afa7d5", + "last_modified": 1666727870071 + }, + { + "schema": 1666727437843, + "derHash": "qlf0gsvFFrTRDKQJfdkNmfWqcn0gcJDQfanDaZirua0=", + "subject": "CN=HydrantID SSL CA G3,O=HydrantID (Avalanche Cloud Corporation),C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMTAwLgYDVQQKDCdIeWRyYW50SUQgKEF2YWxhbmNoZSBDbG91ZCBDb3Jwb3JhdGlvbikxHDAaBgNVBAMME0h5ZHJhbnRJRCBTU0wgQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "7e0808e92ebc33ef96558a3d7c46b9b86290ea9b9c814fe1b375dd5f6e94179f", + "size": 2414, + "filename": "CgDzm8Z-Gk0j7D6yq4U4IstjpiYpBiVqyPDYr-62oO8=.pem", + "location": "security-state-staging/intermediates/84127ac4-c901-4bf0-b029-60d190c53fc6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CgDzm8Z+Gk0j7D6yq4U4IstjpiYpBiVqyPDYr+62oO8=", + "crlite_enrolled": false, + "id": "c4016ca8-8eb6-4dcf-b5d7-f9cf4687f6db", + "last_modified": 1666727870056 + }, + { + "schema": 1666727385101, + "derHash": "8DhCHwfyDWOiDTaR5aF4q4RZ6+VwwWR7dpBVTvI4dqs=", + "subject": "OU=AC Componentes Informáticos,O=FNMT-RCM,C=ES", + "subjectDN": "MEcxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTElMCMGA1UECwwcQUMgQ29tcG9uZW50ZXMgSW5mb3Jtw6F0aWNvcw==", + "whitelist": false, + "attachment": { + "hash": "711fb746c38ddfee6090169d62da5358968caf228ade2ad75cb8be4c4dd3832d", + "size": 2430, + "filename": "MEJWDQI0WXBrEdYtj1u1WdwD26XsIXQQ-57NkgXsoGc=.pem", + "location": "security-state-staging/intermediates/292b3931-1dd3-4cd0-9be6-5fd40e9e982a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MEJWDQI0WXBrEdYtj1u1WdwD26XsIXQQ+57NkgXsoGc=", + "crlite_enrolled": false, + "id": "f77b23d6-3d87-4035-8243-18504c783f83", + "last_modified": 1666727870028 + }, + { + "schema": 1666727330223, + "derHash": "0zqEf2QDd74K4aQpFToH6HyIJ/pIQLUVi83LheEKRTo=", + "subject": "CN=VR IDENT EV SSL CA 2020,O=Fiducia & GAD IT AG,C=DE", + "subjectDN": "ME0xCzAJBgNVBAYTAkRFMRwwGgYDVQQKDBNGaWR1Y2lhICYgR0FEIElUIEFHMSAwHgYDVQQDDBdWUiBJREVOVCBFViBTU0wgQ0EgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "a95e7ccf2ef4b020f65fbd061c44e49d966821be27623376e3f16a8a3ebd371f", + "size": 2361, + "filename": "te09bIALahNazDua57TEibeM7CatIkOAT8t-qu_Kdro=.pem", + "location": "security-state-staging/intermediates/e8b0b0a2-f7f0-4893-b9ef-32f493e8a146.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "te09bIALahNazDua57TEibeM7CatIkOAT8t+qu/Kdro=", + "crlite_enrolled": false, + "id": "92b7ea6b-c8ed-47fc-bd89-4834ef4a3ad5", + "last_modified": 1666727870010 + }, + { + "schema": 1666727401140, + "derHash": "y2Zmsyv/Lv7cxBh98Umm00pdELcWW5z/KmfA4xGu7tc=", + "subject": "CN=QuoVadis Europe EV SSL CA G1,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MFYxCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdRdW9WYWRpcyBUcnVzdGxpbmsgQi5WLjElMCMGA1UEAwwcUXVvVmFkaXMgRXVyb3BlIEVWIFNTTCBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "990290f2ea4ff420df521c3b3851971e7be41a8508394faa2cb562a69bce9e55", + "size": 2385, + "filename": "WkWVWx1-X3CuwtFGJvDjdr5hOX8PeMryJG2Va7Y6cj8=.pem", + "location": "security-state-staging/intermediates/a0cf3874-21b6-41ef-b36d-7e3b20b5db08.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WkWVWx1+X3CuwtFGJvDjdr5hOX8PeMryJG2Va7Y6cj8=", + "crlite_enrolled": false, + "id": "28a8c02e-5350-422e-a3eb-6f8150960132", + "last_modified": 1666727869996 + }, + { + "schema": 1666727390046, + "derHash": "YDiLdz0z6RQWSGE/NrkUnwFaEUN4us8+NpSGT/2kJuw=", + "subject": "CN=HydrantID EV SSL CA G2,O=HydrantID (Avalanche Cloud Corporation),C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMTAwLgYDVQQKDCdIeWRyYW50SUQgKEF2YWxhbmNoZSBDbG91ZCBDb3Jwb3JhdGlvbikxHzAdBgNVBAMMFkh5ZHJhbnRJRCBFViBTU0wgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "ba2cc602bd31081e17f7745ac462854773e79726958ff1b8a553cc7ecaba9db2", + "size": 2389, + "filename": "FpAps1LWs_UTm3xApHYkNccnnZX-7Wc_v8V0t70p6rg=.pem", + "location": "security-state-staging/intermediates/a6215fbd-d6a1-4e36-b3fa-4f60752c3ad6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FpAps1LWs/UTm3xApHYkNccnnZX+7Wc/v8V0t70p6rg=", + "crlite_enrolled": false, + "id": "c9938308-f338-4652-aac1-c4eafdc2080c", + "last_modified": 1666727869982 + }, + { + "schema": 1666727414241, + "derHash": "Fh7lOGMpsoon/0BXNlUqYh1ahEpDgR42I+Uu76C6hAo=", + "subject": "CN=QuoVadis Global SSL ICA G3,O=QuoVadis Limited,C=BM", + "subjectDN": "ME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "b8fd47c4234e14aae99e993f325e88c880408479e0c6ecf0a155bcb2481f0c25", + "size": 2353, + "filename": "KM3iZPSceB-hgYuNI-cSg4LRgTiUxCeGjrfXRQAY6Rs=.pem", + "location": "security-state-staging/intermediates/e04bb898-e61a-4362-98f8-e5c0e0559cd6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KM3iZPSceB+hgYuNI+cSg4LRgTiUxCeGjrfXRQAY6Rs=", + "crlite_enrolled": false, + "id": "3157d9c2-162a-4e53-9f7b-3bde40d1cd54", + "last_modified": 1666727869969 + }, + { + "schema": 1666727372418, + "derHash": "NH0Y3Mwu/FGpIOen+7B7+9o1YTaB+C3KXExyuwyDwDU=", + "subject": "CN=VR IDENT SSL CA 2020,O=Fiducia & GAD IT AG,C=DE", + "subjectDN": "MEoxCzAJBgNVBAYTAkRFMRwwGgYDVQQKDBNGaWR1Y2lhICYgR0FEIElUIEFHMR0wGwYDVQQDDBRWUiBJREVOVCBTU0wgQ0EgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "4158e246be129509cb8d8eb14b647517fea0196e81a9d593f4b8f64a11a6414f", + "size": 2357, + "filename": "fQUctsUYoew1aFnAFT0prf-kJmkVNqkPo2jJ5Jm9RaA=.pem", + "location": "security-state-staging/intermediates/ada7bb67-cb15-43b5-9c74-259e711941b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fQUctsUYoew1aFnAFT0prf+kJmkVNqkPo2jJ5Jm9RaA=", + "crlite_enrolled": false, + "id": "2b1f7c04-dd89-4f96-ad7d-e4e167a9099d", + "last_modified": 1666727869953 + }, + { + "schema": 1666727415303, + "derHash": "n/I8uTh7ngCDvVqhlU7t33kokKqOZ81NON0or0pDmtg=", + "subject": "CN=AC SERVIDORES SEGUROS TIPO2,OU=Ceres,O=FNMT-RCM,C=ES", + "subjectDN": "MHAxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEOMAwGA1UECwwFQ2VyZXMxGDAWBgNVBGEMD1ZBVEVTLVEyODI2MDA0SjEkMCIGA1UEAwwbQUMgU0VSVklET1JFUyBTRUdVUk9TIFRJUE8y", + "whitelist": false, + "attachment": { + "hash": "ce9833a29f1fcb59210dd0a59d92c50ef56f427317cc530e9f61e8b7c2584453", + "size": 1280, + "filename": "dCoEY_Du2xeUAuG2_98ZWoRzf_eJoM-rCMtK2M1wF94=.pem", + "location": "security-state-staging/intermediates/8489058b-a9f7-4278-ad80-159e9e5576de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dCoEY/Du2xeUAuG2/98ZWoRzf/eJoM+rCMtK2M1wF94=", + "crlite_enrolled": false, + "id": "78225120-3a97-4f14-84f9-0aa83efcdb19", + "last_modified": 1666727869939 + }, + { + "schema": 1666727387793, + "derHash": "Httr2RJ0iC23lb/FFPiqvhCtlVy8z9P9Wltf67LOW2g=", + "subject": "CN=AC SERVIDORES SEGUROS TIPO1,OU=Ceres,O=FNMT-RCM,C=ES", + "subjectDN": "MHAxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEOMAwGA1UECwwFQ2VyZXMxGDAWBgNVBGEMD1ZBVEVTLVEyODI2MDA0SjEkMCIGA1UEAwwbQUMgU0VSVklET1JFUyBTRUdVUk9TIFRJUE8x", + "whitelist": false, + "attachment": { + "hash": "1b46f0408f2325ad33c8b3c5dbcf16fa1e7e49dc37065a9692b2b6f8b93fc666", + "size": 1280, + "filename": "Rlnql4UHcyG02SDPnuECa_zDzdDX09uEM9PKaegr4Qs=.pem", + "location": "security-state-staging/intermediates/79cbc976-bf09-46d2-b7ca-8d9055a52b29.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Rlnql4UHcyG02SDPnuECa/zDzdDX09uEM9PKaegr4Qs=", + "crlite_enrolled": false, + "id": "aab7988d-97f1-489f-bb5a-2fcf4db1d18f", + "last_modified": 1666727869912 + }, + { + "schema": 1666727369431, + "derHash": "sarhvNVV6KPR492o7ITnV8VSZVNEzj/uu6+Y6JXb7aw=", + "subject": "CN=QuoVadis Europe SSL CA G2,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MFMxCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdRdW9WYWRpcyBUcnVzdGxpbmsgQi5WLjEiMCAGA1UEAwwZUXVvVmFkaXMgRXVyb3BlIFNTTCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "dfb31ebff112bccbef0ba90c29987f9962823acb59f69bd984351d9e2c7e22df", + "size": 2381, + "filename": "XOogr9_VItHwTOA_JogutdX4V06tVw9btHQLa2tR0_A=.pem", + "location": "security-state-staging/intermediates/88eff6f9-143b-4a55-bfef-19933a4e3007.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XOogr9/VItHwTOA/JogutdX4V06tVw9btHQLa2tR0/A=", + "crlite_enrolled": false, + "id": "12bb227c-277f-4680-a556-f5998b87f658", + "last_modified": 1666727869884 + }, + { + "schema": 1666727365874, + "derHash": "nOYws1+K4sZBnnNK2dL6MEdt2ec5Sx6Tsn+D93agJOo=", + "subject": "CN=AC Unidades de Sellado de Tiempo,OU=Ceres,O=FNMT-RCM,C=ES", + "subjectDN": "MHUxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEOMAwGA1UECwwFQ2VyZXMxGDAWBgNVBGEMD1ZBVEVTLVEyODI2MDA0SjEpMCcGA1UEAwwgQUMgVW5pZGFkZXMgZGUgU2VsbGFkbyBkZSBUaWVtcG8=", + "whitelist": false, + "attachment": { + "hash": "671a59fe01d2cd2074dbea84483abb7c9c75db400f3b54d2aebd79a620add787", + "size": 2836, + "filename": "RFLsjPTrs1EoFCjTCI7RLImxM2pGcGMiymQyT87-GT4=.pem", + "location": "security-state-staging/intermediates/86474fab-014b-4b8c-bd0d-647aa22080e5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RFLsjPTrs1EoFCjTCI7RLImxM2pGcGMiymQyT87+GT4=", + "crlite_enrolled": false, + "id": "b6e021cf-5602-4f60-93f5-659bb02bdfb1", + "last_modified": 1666727869870 + }, + { + "schema": 1666727368055, + "derHash": "9dTK1Ts/LvOYENptPNX/dmP0q7h3OIJ8zsr/b8w+r8Q=", + "subject": "CN=MSFT RS256 CA-1,O=Microsoft Corporation,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xGDAWBgNVBAMTD01TRlQgUlMyNTYgQ0EtMQ==", + "whitelist": false, + "attachment": { + "hash": "a023fc76ccc16c88e584716119317138d7bdceaf8896157e4bb9aa525e227e9b", + "size": 1695, + "filename": "4dcjodQDXb57s8lG7TLWD3Xe9ytnPSATT61TJbQtAwI=.pem", + "location": "security-state-staging/intermediates/dbe4ae7c-598c-423b-a4f2-aa6674a8ec18.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4dcjodQDXb57s8lG7TLWD3Xe9ytnPSATT61TJbQtAwI=", + "crlite_enrolled": false, + "id": "50064930-2d6a-442e-8edb-9151ed42396b", + "last_modified": 1666727869857 + }, + { + "schema": 1666727382191, + "derHash": "DWMgSDbsyonvv0d2uOvDKACBMbldcKknr+bw4emu1Ig=", + "subject": "CN=ZoTrus ECC OV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MFAxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMR0wGwYDVQQDExRab1RydXMgRUNDIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a2284106a606bc666bf93756cf85c68302222a8b5b3cc118f7ab3a57bcba9ea8", + "size": 1240, + "filename": "kMFHAfR4b2U_8eFzUzC98IWCVy6jfhYT7KhWmUMfFZc=.pem", + "location": "security-state-staging/intermediates/f12b578e-b704-425f-a0d8-d08080a40762.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kMFHAfR4b2U/8eFzUzC98IWCVy6jfhYT7KhWmUMfFZc=", + "crlite_enrolled": false, + "id": "da380fd2-3da8-422f-9f3e-70dae67eb537", + "last_modified": 1666727869843 + }, + { + "schema": 1666727438866, + "derHash": "UmbXFlEZYo+VWaFWUGDiz78hNU4ctyrGbHVbnunxkC0=", + "subject": "CN=DNSPod RSA OV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBSU0EgT1Y=", + "whitelist": false, + "attachment": { + "hash": "94a4db0a847ed1705af8b348555d54f2571d4dc67b870acf3622e4db1a41a1e5", + "size": 2231, + "filename": "xTgRLpThRByRVHJot49FqWpbMVYiv8384ejqkJOJbFc=.pem", + "location": "security-state-staging/intermediates/fefa5d10-d98f-4287-9506-4f85b5b19311.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xTgRLpThRByRVHJot49FqWpbMVYiv8384ejqkJOJbFc=", + "crlite_enrolled": false, + "id": "5e15deb4-1340-4b98-b021-7c82087df0ad", + "last_modified": 1666727869830 + }, + { + "schema": 1666727418384, + "derHash": "EbbbW2jc3zRHmufnt7uqcWWQVXetg9R08ruXF84kTOo=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA H2 2021,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIEgyIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "9cf02d5a547d487093c6600cddc45835d2265d1a9e325589d4fc84f0f6d7d961", + "size": 1715, + "filename": "xecaLv-HFBkn7bPt0j9a3x6oQIAxlTk0PaC8CEgAAK8=.pem", + "location": "security-state-staging/intermediates/60d13cbe-72e2-4434-bd54-82eab5032874.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xecaLv+HFBkn7bPt0j9a3x6oQIAxlTk0PaC8CEgAAK8=", + "crlite_enrolled": false, + "id": "ff529e2f-a01e-4a10-b6be-ec1b7f25db92", + "last_modified": 1666727869816 + }, + { + "schema": 1666727423438, + "derHash": "EAU3DtJ2sM7zkkTp5pnOSAe/mt4Fv6WfJjgJ+0YGtyw=", + "subject": "CN=Prodrive Technologies GCC R3 OV TLS CA 2022,O=Prodrive Technologies B.V.,C=NL", + "subjectDN": "MGgxCzAJBgNVBAYTAk5MMSMwIQYDVQQKExpQcm9kcml2ZSBUZWNobm9sb2dpZXMgQi5WLjE0MDIGA1UEAxMrUHJvZHJpdmUgVGVjaG5vbG9naWVzIEdDQyBSMyBPViBUTFMgQ0EgMjAyMg==", + "whitelist": false, + "attachment": { + "hash": "6340c990e40558efb9b4997feecb901894ab85f7fd56125b77a9af382a4b527f", + "size": 1662, + "filename": "tahHgXS9JtW4UDcnvNe8n5iWicFpEkCKocabIv7CHMA=.pem", + "location": "security-state-staging/intermediates/3d30febd-5714-457a-af91-9ac17d3d6918.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tahHgXS9JtW4UDcnvNe8n5iWicFpEkCKocabIv7CHMA=", + "crlite_enrolled": false, + "id": "e9fd6e58-e869-4d61-bb86-61fd93c8abbd", + "last_modified": 1666727869802 + }, + { + "schema": 1666727410854, + "derHash": "p5cQigWtkyhYX8DLhC+9gk+sZRC41+tP9HmR7uWKqSQ=", + "subject": "CN=GeoTrust Global TLS RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE0MDIGA1UEAxMrR2VvVHJ1c3QgR2xvYmFsIFRMUyBSU0E0MDk2IFNIQTI1NiAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "7debe4651a89d772bd8a304272699c20eda3b8d40eee9e84a3ccd55f8c63f444", + "size": 2068, + "filename": "aZeR2aS2g2bguhPzWzTQqUq9OCf4tXk7VWrF6R_zJkM=.pem", + "location": "security-state-staging/intermediates/d15529f9-49a2-467b-aaff-3eba01eb9996.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aZeR2aS2g2bguhPzWzTQqUq9OCf4tXk7VWrF6R/zJkM=", + "crlite_enrolled": false, + "id": "93e60034-59ff-49c4-a646-358a9907c217", + "last_modified": 1666727869788 + }, + { + "schema": 1666727345257, + "derHash": "kqX1Fa0106J8SQ7bE13nBEseOZ1gisGr6IP8gvtLFr4=", + "subject": "CN=RapidSSL Global TLS RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE0MDIGA1UEAxMrUmFwaWRTU0wgR2xvYmFsIFRMUyBSU0E0MDk2IFNIQTI1NiAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "92ad0528387f762c0d47a1b2aef3a70165915785e79a7440136f35f50719e121", + "size": 2068, + "filename": "lPraBjD-VHks5_sVEDOczhg00z9TGoGMAjndDyYGUNU=.pem", + "location": "security-state-staging/intermediates/9feab4a5-669f-46c9-9f31-2d561a1fc8de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lPraBjD+VHks5/sVEDOczhg00z9TGoGMAjndDyYGUNU=", + "crlite_enrolled": false, + "id": "5fb2df13-ba06-48c8-b568-679d6edd2144", + "last_modified": 1666727869774 + }, + { + "schema": 1666727332641, + "derHash": "i37VNYSEeB9MCDdrGDsBjJV5CMqsMqpyrNAuUXB6WKc=", + "subject": "CN=AgID CA SSL SERVER,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGaMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxGzAZBgNVBAMMEkFnSUQgQ0EgU1NMIFNFUlZFUg==", + "whitelist": false, + "attachment": { + "hash": "aeb288789225a7be93f2a26e7689afe09bc2d723ab018c424a83625b8a2b6097", + "size": 2942, + "filename": "xA7IvqP1-jwREgZJFKP3YrIzEm1R0lv-gp863KEGym8=.pem", + "location": "security-state-staging/intermediates/9deab71e-8ac6-41a9-8260-c8b869eac32a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xA7IvqP1+jwREgZJFKP3YrIzEm1R0lv+gp863KEGym8=", + "crlite_enrolled": false, + "id": "4668c79c-c956-430e-aa77-3238123864ff", + "last_modified": 1666727869760 + }, + { + "schema": 1666727438361, + "derHash": "5c0N1E4RIMeUFchPU2nXmuBPBH6A3AhWNRqrwjIFbKU=", + "subject": "CN=QuoVadis Global SSL ICA G2,O=QuoVadis Limited,C=BM", + "subjectDN": "ME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "31277c8cdd0e37bf32bacfe9cf3ce3c6b326d4510c64690765cda14d19566338", + "size": 1995, + "filename": "tYkfFN27P1GUjH5ME128BCg302dL2iwOYhz5wwFJb50=.pem", + "location": "security-state-staging/intermediates/590b331b-c3f1-4028-8b4b-57763d4e8493.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tYkfFN27P1GUjH5ME128BCg302dL2iwOYhz5wwFJb50=", + "crlite_enrolled": false, + "id": "d150b5c4-cf8a-4467-913a-da337ef53b40", + "last_modified": 1666727869747 + }, + { + "schema": 1666727329540, + "derHash": "XuWq1wSotpiIP0ApcgyPoct5yfo0Y8wkNAYp6+bFpis=", + "subject": "CN=Verokey Secure Web G2,O=Verokey,C=AU", + "subjectDN": "MD8xCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MR4wHAYDVQQDExVWZXJva2V5IFNlY3VyZSBXZWIgRzI=", + "whitelist": false, + "attachment": { + "hash": "8d08fa773eab40914d633ec0fc0c90f83553e8e8900c895ff3b9fac73917357c", + "size": 1683, + "filename": "YOICDd74CBBTQQEfP4A_1QP-tDZ8_JTzjErj5b1IsYo=.pem", + "location": "security-state-staging/intermediates/b1916190-6e0c-4a41-88c9-4b5aadcf3649.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YOICDd74CBBTQQEfP4A/1QP+tDZ8/JTzjErj5b1IsYo=", + "crlite_enrolled": false, + "id": "c449be6f-2cc0-481e-af04-2182142ce9f1", + "last_modified": 1666727869733 + }, + { + "schema": 1666727368893, + "derHash": "KBAe480v9vIl+/Dt6Uq1DWdir9urlk98nTzPfwLumDg=", + "subject": "CN=HARICA DV TLS RSA,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgRFYgVExTIFJTQQ==", + "whitelist": false, + "attachment": { + "hash": "c44af3dd21b1162c83733f015029c651f28517ff66e4ca9317a1c48f5a9b71b4", + "size": 2402, + "filename": "rk2I2etvSahZka3FPKyvABNS0NJaDCduKDeQ5Vvg_Xs=.pem", + "location": "security-state-staging/intermediates/079724df-81e4-4adb-bb4a-2ed5a779cd27.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rk2I2etvSahZka3FPKyvABNS0NJaDCduKDeQ5Vvg/Xs=", + "crlite_enrolled": false, + "id": "1b7a6bff-56c5-48ba-9925-40365223d34e", + "last_modified": 1666727869719 + }, + { + "schema": 1666727451127, + "derHash": "OSN3pxnj5lpA2GUbkjYduVMgs5yjYQcqOjz0LGbgDbw=", + "subject": "CN=HARICA DV TLS ECC,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgRFYgVExTIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "7e27af844502735b21ea2bd4cff1140421efb626bc02fbb901dd4a16acc835c4", + "size": 1256, + "filename": "4bU63rTG6VcPUxQDcQxr5b2fCm9EqiwrERC74jrdR8w=.pem", + "location": "security-state-staging/intermediates/7d79ac16-e5de-4231-a19b-6dff6536caf7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4bU63rTG6VcPUxQDcQxr5b2fCm9EqiwrERC74jrdR8w=", + "crlite_enrolled": false, + "id": "69aa65eb-ad45-40cb-9b9e-d6fe0ca89ada", + "last_modified": 1666727869705 + }, + { + "schema": 1666727344566, + "derHash": "n72IaUX7bLY+6/EQd9rJgORTaNJFi6XvCo1ycEb80pI=", + "subject": "CN=HARICA OV TLS RSA,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgT1YgVExTIFJTQQ==", + "whitelist": false, + "attachment": { + "hash": "4a00453535849da9506d3c880adf5594fd03e37f3474ed14201e9a54fe8e0c8d", + "size": 2402, + "filename": "-YHY-Ybt-5EAO47w-lRzEDyis4QTCGxh6IbF6_Pt1aA=.pem", + "location": "security-state-staging/intermediates/9ff2d282-9920-4c34-bb54-e699d43f70fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+YHY+Ybt+5EAO47w+lRzEDyis4QTCGxh6IbF6/Pt1aA=", + "crlite_enrolled": false, + "id": "07b1344c-3714-4729-bd44-f0bf0a6ae83f", + "last_modified": 1666727869692 + }, + { + "schema": 1666727337504, + "derHash": "xbSrvTuuOBFKOoI6isTNMcRUZbfBzr2E821yBUr76Ls=", + "subject": "CN=HARICA Institutional TLS RSA,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MG0xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSUwIwYDVQQDDBxIQVJJQ0EgSW5zdGl0dXRpb25hbCBUTFMgUlNB", + "whitelist": false, + "attachment": { + "hash": "d83071c3635664b97f98081ca6b4046bfd3701343ba051f343bd67fd33b619e7", + "size": 2467, + "filename": "zuce6_Gkwh58Gy-vEonepsazUULwkv4WD6baLfaKxJQ=.pem", + "location": "security-state-staging/intermediates/e37b4d53-3826-40dc-9611-c8111a6869fe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zuce6/Gkwh58Gy+vEonepsazUULwkv4WD6baLfaKxJQ=", + "crlite_enrolled": false, + "id": "87444841-f26d-4e30-bfcc-f7348f035320", + "last_modified": 1666727869679 + }, + { + "schema": 1666727369598, + "derHash": "1sxXuSYOmwEt+sQKgb/AIrktkfpJt8RcWd+GcCqsKQY=", + "subject": "CN=Verokey Verified Business,O=Verokey,C=AU", + "subjectDN": "MEMxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSIwIAYDVQQDExlWZXJva2V5IFZlcmlmaWVkIEJ1c2luZXNz", + "whitelist": false, + "attachment": { + "hash": "ad91c21bd6c4e12bab1179f64d3061f1a8c3f99e4e8edc9902360c09c0d6f009", + "size": 1687, + "filename": "jVuisrysh5HDay17vNJwtigzbmgB6GyT5_TBkYrj7aQ=.pem", + "location": "security-state-staging/intermediates/88d20507-b598-4ce5-9ba2-761d21a4e419.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jVuisrysh5HDay17vNJwtigzbmgB6GyT5/TBkYrj7aQ=", + "crlite_enrolled": false, + "id": "4e6df119-41d9-4a2f-833f-eded8105015b", + "last_modified": 1666727869652 + }, + { + "schema": 1666727430858, + "derHash": "OH1Ja5IgLUxEPNlP9C2hffLx5o4kTC+7p+KU290RNXs=", + "subject": "CN=WISeKey CertifyID Advanced GC CA 1,O=WISeKey,C=CH", + "subjectDN": "MEwxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSswKQYDVQQDEyJXSVNlS2V5IENlcnRpZnlJRCBBZHZhbmNlZCBHQyBDQSAx", + "whitelist": false, + "attachment": { + "hash": "3847cc74ceb46d2e07d37b69b668ff50587428a43ef992473d4a3e433bcb1972", + "size": 1293, + "filename": "Y3IJxxxojxFKeXVhVUyyPE4P-7QrAbKt0viCWXpb1G8=.pem", + "location": "security-state-staging/intermediates/233bd648-66bc-440b-8b31-293196be1b49.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y3IJxxxojxFKeXVhVUyyPE4P+7QrAbKt0viCWXpb1G8=", + "crlite_enrolled": false, + "id": "5905f153-1bda-44df-8205-013b8756834a", + "last_modified": 1666727869638 + }, + { + "schema": 1666727396472, + "derHash": "ubZIktCtRxksE/FdrM9bogQbs58wQagPqq1vxsUqilA=", + "subject": "CN=ZwTrus EV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxGTAXBgNVBAMTEFp3VHJ1cyBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "5927a6a46a8722d647f97bd1964a77034c437687eea5dd18501919340e3884d2", + "size": 2263, + "filename": "cdkux3rPkZm7pdS4qkV2xWrMQUeTTUDYeuHEFMkgU0o=.pem", + "location": "security-state-staging/intermediates/0abdba7c-086e-4aef-bf18-86e3082d8261.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cdkux3rPkZm7pdS4qkV2xWrMQUeTTUDYeuHEFMkgU0o=", + "crlite_enrolled": false, + "id": "3e007f89-e1f8-43a5-8443-ff0bd3234f7c", + "last_modified": 1666727869625 + }, + { + "schema": 1666727440428, + "derHash": "5Iksmktuk5Pr3+3LwvoHt8a0gKKRXGb0cJnm8v/pocU=", + "subject": "CN=TrustAsia RSA OV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIE9WIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "cb6dac2259c0487b1043568b04bbf032ce9be308e3815c4480a31d3c435b5f70", + "size": 2268, + "filename": "AjHoEUjfOt4P9CmQ3Tl9YEcwEf694_AE-BWBxOtPfxE=.pem", + "location": "security-state-staging/intermediates/29f2fb58-8f83-41f5-ad6e-736a5a05440d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AjHoEUjfOt4P9CmQ3Tl9YEcwEf694/AE+BWBxOtPfxE=", + "crlite_enrolled": false, + "id": "d48c9d5c-1dd4-4df5-8301-2edd5e04d3ed", + "last_modified": 1666727869612 + }, + { + "schema": 1666727385434, + "derHash": "Z7emcOC+BzRVIiRjZV+5DAwvEpsKNQWijQFHWqmkeaQ=", + "subject": "CN=ZwTrus OV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxGTAXBgNVBAMTEFp3VHJ1cyBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "ccf80a9bf2f9a47a0817b7dc39ff0dc2dd2b31e94d3f731c99e93913a73b0152", + "size": 2276, + "filename": "od08pFrvcsrEIVHG2etKzrvApEI5rI8Qv9IpYTeTclw=.pem", + "location": "security-state-staging/intermediates/f2d71897-6ff6-41e1-b3e6-65dc5b946850.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "od08pFrvcsrEIVHG2etKzrvApEI5rI8Qv9IpYTeTclw=", + "crlite_enrolled": false, + "id": "3992fd86-634f-4ad7-af81-c4d3e597b8ec", + "last_modified": 1666727869599 + }, + { + "schema": 1666727368721, + "derHash": "mI1rLYe69DghxNMEvisOjzJdwXt6Ig6TT1xbe63/jhw=", + "subject": "CN=TK Elevator Atlas R6 DV CA 2021,O=TK Elevator GmbH,C=DE", + "subjectDN": "MFIxCzAJBgNVBAYTAkRFMRkwFwYDVQQKExBUSyBFbGV2YXRvciBHbWJIMSgwJgYDVQQDEx9USyBFbGV2YXRvciBBdGxhcyBSNiBEViBDQSAyMDIx", + "whitelist": false, + "attachment": { + "hash": "312c7287eef71d4f7d30e54c499b166794efcc40b1af781c5b8665f5014a6849", + "size": 2398, + "filename": "mTRp9SMkTGsZMOg5Zj2QnO66SKHcsi9q-xqfABlYAYY=.pem", + "location": "security-state-staging/intermediates/1dd04da2-87fd-4a93-b982-c48b5e43dd25.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mTRp9SMkTGsZMOg5Zj2QnO66SKHcsi9q+xqfABlYAYY=", + "crlite_enrolled": false, + "id": "4b849760-6c7e-4a45-a66a-3543765ad16e", + "last_modified": 1666727869586 + }, + { + "schema": 1666727334140, + "derHash": "NoevAsSXMco0/k/bm15eDsrNiiBpVvGa+zggOsXwpWc=", + "subject": "CN=E-Tugra TLS ECC SubCA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHkxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEhMB8GA1UEAwwYRS1UdWdyYSBUTFMgRUNDIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "518d8b200dabf1a31bc8b1de701f5e5f16194c5369ed3992ff459a881cc920f6", + "size": 1264, + "filename": "LEpoPBInvdPKvBnEvgOGVD6niF1p0SyPl8CVl1YmY3A=.pem", + "location": "security-state-staging/intermediates/35f63447-f69f-42c6-9545-6789f0b2fcef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LEpoPBInvdPKvBnEvgOGVD6niF1p0SyPl8CVl1YmY3A=", + "crlite_enrolled": false, + "id": "ca4bf929-8439-479e-b4c1-488d61f0e0c2", + "last_modified": 1666727869573 + }, + { + "schema": 1666727448677, + "derHash": "Z2JSLktXrtCWBXSrBh957qBoxa49gV9QqXHOxJWKkrg=", + "subject": "CN=E-Tugra EV TLS RSA SubCA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHwxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEkMCIGA1UEAwwbRS1UdWdyYSBFViBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "f30055e196e29b777979a4db3af07548d6bdd39ba188bf047bf5f48c370b56b7", + "size": 2475, + "filename": "Q7voHmrw-oKRSwo4-19T_zMtPtYD7iixEcpIdSoj660=.pem", + "location": "security-state-staging/intermediates/32b8a883-88dd-41a6-8a17-97687478fad0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Q7voHmrw+oKRSwo4+19T/zMtPtYD7iixEcpIdSoj660=", + "crlite_enrolled": false, + "id": "4fe2b7c3-221c-468b-9ce6-53592504746a", + "last_modified": 1666727869559 + }, + { + "schema": 1666727436626, + "derHash": "ks5+p0JLPnosFMJuDVcq29VW4F4rKovTdhxpUlQlWFY=", + "subject": "CN=AnsonNet TLS Issuing RSA CA R1,O=Anson Network Limited,L=London,ST=London,C=GB", + "subjectDN": "MHgxCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEeMBwGA1UECgwVQW5zb24gTmV0d29yayBMaW1pdGVkMScwJQYDVQQDDB5BbnNvbk5ldCBUTFMgSXNzdWluZyBSU0EgQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "154d80862f6e2c986d448e376f8567e70a45ea0d34aecf0ebb7f68b218d96c21", + "size": 2556, + "filename": "mPShRNkBUp1VgXuaiIbIdh-5lcY6nG4Evw-dwtjk06A=.pem", + "location": "security-state-staging/intermediates/3d680c67-8d68-4ed6-9b45-81cb9cf46c24.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mPShRNkBUp1VgXuaiIbIdh+5lcY6nG4Evw+dwtjk06A=", + "crlite_enrolled": false, + "id": "8e624ddd-1848-4ea7-9cf2-8687c69d1a74", + "last_modified": 1666727869530 + }, + { + "schema": 1666727439374, + "derHash": "38zb8HfRQteSP2MW3UEeRix6i8BHgfK4cGWlxeJYp+A=", + "subject": "CN=SafeToOpen TLS ICA RSA R1,O=SafeToOpen Ltd,C=NZ", + "subjectDN": "MEoxCzAJBgNVBAYTAk5aMRcwFQYDVQQKDA5TYWZlVG9PcGVuIEx0ZDEiMCAGA1UEAwwZU2FmZVRvT3BlbiBUTFMgSUNBIFJTQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "eb5caad4c5ad7ec3c80e75b7f8d3ccafea219c526160279e1f258c34572dec48", + "size": 2434, + "filename": "uQ4czJaJhrriBdIxl7SyPwX8Wy4z12aj9ZqCb3mdYpo=.pem", + "location": "security-state-staging/intermediates/d1b811ac-67f8-47ab-9f95-4ed057e9dc7d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uQ4czJaJhrriBdIxl7SyPwX8Wy4z12aj9ZqCb3mdYpo=", + "crlite_enrolled": false, + "id": "500cca65-204d-49f0-b476-b5ecff874103", + "last_modified": 1666727869516 + }, + { + "schema": 1666727423769, + "derHash": "f9KDd8h8iY6QlMk+oAvxB6vhHbgLPYXitKZiaXaBI1s=", + "subject": "CN=E-Tugra TLS RSA SubCA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHkxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEhMB8GA1UEAwwYRS1UdWdyYSBUTFMgUlNBIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "67a6ef0e20a4f10f4f7afe9d0a49f3e53a2427b7c1bde77ecfe07d6d8fc03405", + "size": 2414, + "filename": "RT5Ru4yVpA814R-GLXv9mePZqXwAfupz-ZXLpFuW_-M=.pem", + "location": "security-state-staging/intermediates/f24ddc58-0468-4359-a255-44564546ef05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RT5Ru4yVpA814R+GLXv9mePZqXwAfupz+ZXLpFuW/+M=", + "crlite_enrolled": false, + "id": "119c2c49-ca20-4712-8014-35fa24a6fed6", + "last_modified": 1666727869503 + }, + { + "schema": 1666727338184, + "derHash": "/mtvnkS2cHl9UuXxbvG7ELSes61mJSL8Yys335pQRM0=", + "subject": "CN=Staclar TLS Issuing CA R1,O=Staclar\\, Inc.,L=Claymont,ST=Delaware,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhEZWxhd2FyZTERMA8GA1UEBwwIQ2xheW1vbnQxFjAUBgNVBAoMDVN0YWNsYXIsIEluYy4xIjAgBgNVBAMMGVN0YWNsYXIgVExTIElzc3VpbmcgQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "67b611ca940971fd26efead7be964277d923d41089ee481fac41e3b65427755f", + "size": 2495, + "filename": "2hrK22IRCeQc0huhCCGTSKY2R0LmyeFUaH4ArRR6YUc=.pem", + "location": "security-state-staging/intermediates/966b7afb-4aa7-4beb-b7bd-387d4c438b3d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2hrK22IRCeQc0huhCCGTSKY2R0LmyeFUaH4ArRR6YUc=", + "crlite_enrolled": false, + "id": "a6abb330-2c38-4187-a493-60ee7f54254a", + "last_modified": 1666727869488 + }, + { + "schema": 1666727447593, + "derHash": "hdsL7tBmjQTXB0AA+Y6EsI0P1UKf9KJi3ny8FSihodo=", + "subject": "CN=SSL.com EV SSL Intermediate CA RSA R3,O=SSL Corp,L=Houston,ST=Texas,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLjAsBgNVBAMMJVNTTC5jb20gRVYgU1NMIEludGVybWVkaWF0ZSBDQSBSU0EgUjM=", + "whitelist": false, + "attachment": { + "hash": "11331da9a8598630733f3a9da43b37663c38168a3cad1664b09163180bd5abbe", + "size": 2442, + "filename": "4V6KJv09j4wCTdB1kG8-rfM7XCExYEpiXCGzJNL58IQ=.pem", + "location": "security-state-staging/intermediates/f4cc28ea-1b80-4eba-b2f5-79b4e7dfae1f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4V6KJv09j4wCTdB1kG8+rfM7XCExYEpiXCGzJNL58IQ=", + "crlite_enrolled": false, + "id": "1dd50065-f86a-41c2-8b91-ff4405e7a8d9", + "last_modified": 1666727869472 + }, + { + "schema": 1666727436281, + "derHash": "OPWMr2UMzU9gJ9HJImgokaFl/vSR/B0zP+ipyFFYbUg=", + "subject": "CN=MuaSSL.com EV TLS Issuing RSA CA R1,O=Hao Quang Viet Software Company Limited,C=VN", + "subjectDN": "MG0xCzAJBgNVBAYTAlZOMTAwLgYDVQQKDCdIYW8gUXVhbmcgVmlldCBTb2Z0d2FyZSBDb21wYW55IExpbWl0ZWQxLDAqBgNVBAMMI011YVNTTC5jb20gRVYgVExTIElzc3VpbmcgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "bb7d521f4f724773fe95638749099d662db875548266c7bd91c12437f2405340", + "size": 2540, + "filename": "13AbTAPgSo4ZEXl_1d53tBhdk_1OZp42oZ8Uo8f-0gs=.pem", + "location": "security-state-staging/intermediates/d4d1aaec-0ac8-41ae-9b43-92254ef66246.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "13AbTAPgSo4ZEXl/1d53tBhdk/1OZp42oZ8Uo8f+0gs=", + "crlite_enrolled": false, + "id": "cbb7e5e0-5b42-40f1-95c2-1b5d9683d334", + "last_modified": 1666727869458 + }, + { + "schema": 1666727449705, + "derHash": "Xq8Fz8mgF3MNAeZojW45cQ4B/eN050sbJdOkgHauXbg=", + "subject": "CN=TrustSafe TLS ECC SubCA R1,O=Isimtescil Bilisim A.S.,C=TR", + "subjectDN": "MFQxCzAJBgNVBAYTAlRSMSAwHgYDVQQKDBdJc2ltdGVzY2lsIEJpbGlzaW0gQS5TLjEjMCEGA1UEAwwaVHJ1c3RTYWZlIFRMUyBFQ0MgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "31011fede2cbdd9d152e67d13d00417da333d3923d5ad536b36408c7538cb404", + "size": 1215, + "filename": "SKjptlNHzgEvyArVrdTup8ORkGWGikm3iFmLJI4ZRGM=.pem", + "location": "security-state-staging/intermediates/e3544df1-84f9-41ec-808a-18ad9bbd59ed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SKjptlNHzgEvyArVrdTup8ORkGWGikm3iFmLJI4ZRGM=", + "crlite_enrolled": false, + "id": "d340c468-a715-4aa3-a149-c150973125ed", + "last_modified": 1666727869444 + }, + { + "schema": 1666727411011, + "derHash": "co2vQG/ans1NVV3BLyfWfW3kRSRpVGShIGaSAPILKEs=", + "subject": "CN=AgID CA1,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGQMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxETAPBgNVBAMMCEFnSUQgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "ff3a99bc1b4b533a0133498e18b56144ddeaaae3dfb43cf3c18b178f16a864a6", + "size": 6127, + "filename": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=.pem", + "location": "security-state-staging/intermediates/f92cd274-a6c5-4692-89d8-bb79a6ff2229.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=", + "crlite_enrolled": false, + "id": "fbd44422-0d3b-47de-9e7b-2a8f647897eb", + "last_modified": 1666727869413 + }, + { + "schema": 1666727367726, + "derHash": "N/0pxwHWl3mY8gUVPqikwumWNU3wctSYTcXYsfdaK2E=", + "subject": "CN=SwissNS TLS Issuing RSA CA R1,O=swissns GmbH,L=Luzern,ST=Luzern,C=CH", + "subjectDN": "MG4xCzAJBgNVBAYTAkNIMQ8wDQYDVQQIDAZMdXplcm4xDzANBgNVBAcMBkx1emVybjEVMBMGA1UECgwMc3dpc3NucyBHbWJIMSYwJAYDVQQDDB1Td2lzc05TIFRMUyBJc3N1aW5nIFJTQSBDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "a53fc12268eec66194a5d0cfc553ffa2fd274a9484ffe30df291c165501d06f7", + "size": 2544, + "filename": "KAda-LJFeGtGthCqCejer2EUArFZNe-fmlkLdhkdcJc=.pem", + "location": "security-state-staging/intermediates/c94ad927-be0f-411c-a32c-6954659c0d48.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KAda+LJFeGtGthCqCejer2EUArFZNe+fmlkLdhkdcJc=", + "crlite_enrolled": false, + "id": "47f9f4d2-4baa-40bb-9b5b-aa8cefeea36f", + "last_modified": 1666727869399 + }, + { + "schema": 1666727395775, + "derHash": "0HC/AZz7pGyNNBTW/o19IQd/NUXC/oOePSUPMk43xas=", + "subject": "CN=AgID CA1,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGQMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxETAPBgNVBAMMCEFnSUQgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "acd4d6d8a473c1fc0d659c79901ef8ffbd4acdcd519a73233aa6ae62e6eafbda", + "size": 6208, + "filename": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=.pem", + "location": "security-state-staging/intermediates/995cf1f7-a18a-4753-a7dd-edadaef3dfe1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=", + "crlite_enrolled": false, + "id": "d5a2518e-b5a8-4ddb-bf31-9b97ea3416f9", + "last_modified": 1666727869386 + }, + { + "schema": 1666727382024, + "derHash": "lItxEa9C9UbVec/1ziveyCE03ZkUhCvdsMUocutgTjk=", + "subject": "CN=SSL.com SSL Intermediate CA ECC R2,O=SSL Corp,L=Houston,ST=Texas,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxKzApBgNVBAMMIlNTTC5jb20gU1NMIEludGVybWVkaWF0ZSBDQSBFQ0MgUjI=", + "whitelist": false, + "attachment": { + "hash": "3cba15ac901722eb627d84c26753e7fcdec2441185ba325d73cfce518fe053f4", + "size": 1264, + "filename": "zGgA4OU4DjJdvpRYUqbi5Vh2g9W5Oc_PgKihy9mkLsE=.pem", + "location": "security-state-staging/intermediates/aa57d145-8dd6-481f-a46e-41cbfe6621a5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zGgA4OU4DjJdvpRYUqbi5Vh2g9W5Oc/PgKihy9mkLsE=", + "crlite_enrolled": false, + "id": "6430f852-d358-4b1a-b2c9-577cf8cf4f97", + "last_modified": 1666727869373 + }, + { + "schema": 1666727357771, + "derHash": "knHKfojO0l7R0fjgivoDsR0f4S7RElWFraUBJD4srAk=", + "subject": "CN=AgID CA1,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGQMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxETAPBgNVBAMMCEFnSUQgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "134d08513f86579b09ea4693ac4d3685f5fc88e012db239d7b017feecbf3f87e", + "size": 6306, + "filename": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=.pem", + "location": "security-state-staging/intermediates/f3792909-e805-46b5-a4d0-bb3979399b7b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=", + "crlite_enrolled": false, + "id": "07f2dc33-3379-48ad-b352-dc3a300c8197", + "last_modified": 1666727869330 + }, + { + "schema": 1666727444337, + "derHash": "g5Tj0H780el+psuiFMOgVcF92afr+N3gAg/N23byhlM=", + "subject": "CN=SSL.com EV SSL Intermediate CA ECC R2,O=SSL Corp,L=Houston,ST=Texas,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLjAsBgNVBAMMJVNTTC5jb20gRVYgU1NMIEludGVybWVkaWF0ZSBDQSBFQ0MgUjI=", + "whitelist": false, + "attachment": { + "hash": "6136b10eec7590ed6f1f09c0923ca2fe1dfbfe0434bde3c88e28dff9f3940c86", + "size": 1280, + "filename": "d1tKuR2aFFnhw8eRBoNei7sB4YWR1iQmOEmGB337A0k=.pem", + "location": "security-state-staging/intermediates/4f7b8895-448f-453d-87a8-6de9c43888ae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "d1tKuR2aFFnhw8eRBoNei7sB4YWR1iQmOEmGB337A0k=", + "crlite_enrolled": false, + "id": "969fdfcb-97e7-4bc9-97a3-ca0ccaa6f86b", + "last_modified": 1666727869311 + }, + { + "schema": 1666727368394, + "derHash": "UnpgsCq/OkpVGcT2L7vVYOMDQHTu7IuHmaqTaGk/420=", + "subject": "CN=SSL.com RSA SSL subCA,O=SSL Corporation,L=Houston,ST=Texas,C=US", + "subjectDN": "MGkxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMR4wHAYDVQQDDBVTU0wuY29tIFJTQSBTU0wgc3ViQ0E=", + "whitelist": false, + "attachment": { + "hash": "a657eb066cd5c9adb694b2a70e729aab23c1595e9e270a8ad36f97bbc42782b1", + "size": 2292, + "filename": "7LcB5Z8ATVz4rcQtIY5xJir8_-F3e_HPi8IDdCnjCaE=.pem", + "location": "security-state-staging/intermediates/f14ed6a3-74e1-4689-a71e-23559d707304.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7LcB5Z8ATVz4rcQtIY5xJir8/+F3e/HPi8IDdCnjCaE=", + "crlite_enrolled": false, + "id": "b8970565-16ff-4a34-80ed-e7cbee7d048c", + "last_modified": 1666727869297 + }, + { + "schema": 1666727410331, + "derHash": "6uWboswzKBpc3exQz7/Z3QylVl8872mMk2ahDOwIf5c=", + "subject": "CN=Domain The Net Technologies Ltd CA for SSL R2,O=Domain The Net Technologies Ltd,C=IL", + "subjectDN": "MG8xCzAJBgNVBAYTAklMMSgwJgYDVQQKDB9Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkMTYwNAYDVQQDDC1Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkIENBIGZvciBTU0wgUjI=", + "whitelist": false, + "attachment": { + "hash": "74b27e430703697e4e0eb4a4518f35a8e27789b980b29dd2593702248ea4491d", + "size": 2483, + "filename": "1FBqLyRsP8ibxXXsW64LYWGTeYMGSsUTMFEetUQakD8=.pem", + "location": "security-state-staging/intermediates/beb201df-0f86-438c-911c-f798428aa9c4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1FBqLyRsP8ibxXXsW64LYWGTeYMGSsUTMFEetUQakD8=", + "crlite_enrolled": false, + "id": "ac504f95-1d16-427c-8e82-82da28c34bc6", + "last_modified": 1666727869284 + }, + { + "schema": 1666727422380, + "derHash": "UpmYc6PRKkVx25oWBXap2VGtTkp9oxxcEGE4BekiQyU=", + "subject": "CN=MuaSSL.com TLS Issuing RSA CA R1,O=Hao Quang Viet Software Company Limited,C=VN", + "subjectDN": "MGoxCzAJBgNVBAYTAlZOMTAwLgYDVQQKDCdIYW8gUXVhbmcgVmlldCBTb2Z0d2FyZSBDb21wYW55IExpbWl0ZWQxKTAnBgNVBAMMIE11YVNTTC5jb20gVExTIElzc3VpbmcgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "67a18d11409c233938fe1e13c9170fc4b63e8fd4084f92187f80bf60efc45b9e", + "size": 2475, + "filename": "665w_NZsPO7TEXwGSRnHz5bGQ3teDolKrTBrXhjcHcw=.pem", + "location": "security-state-staging/intermediates/020542f7-0952-455f-a0dc-6563cdf34539.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "665w/NZsPO7TEXwGSRnHz5bGQ3teDolKrTBrXhjcHcw=", + "crlite_enrolled": false, + "id": "f18dd59f-ebc7-4e0d-9963-60e432ae1d44", + "last_modified": 1666727869270 + }, + { + "schema": 1666727413717, + "derHash": "YBjw3/qk1I9rNj29iVtD1yBpH5ZY49lAcn+xSZ1SUAU=", + "subject": "CN=AgID CA1,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGQMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxETAPBgNVBAMMCEFnSUQgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "2ce5754aafd954d566863872c84b5b09cc520df3b7c3fcdf3f3109c176f7236f", + "size": 6204, + "filename": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=.pem", + "location": "security-state-staging/intermediates/71d7de8a-96d4-4d28-95c1-ddd904ad7b95.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=", + "crlite_enrolled": false, + "id": "f311d986-6d46-4d1e-87fe-52110484e044", + "last_modified": 1666727869243 + }, + { + "schema": 1666727453330, + "derHash": "TpO8rdXU6VMxrjYt+cYGbMp/lCqP3k0+4BHeNAdPWEA=", + "subject": "CN=3CX CA RSA R1,O=3CX,C=CY", + "subjectDN": "MDMxCzAJBgNVBAYTAkNZMQwwCgYDVQQKDAMzQ1gxFjAUBgNVBAMMDTNDWCBDQSBSU0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "04cae41535093f312f4dd11eec465655eba00dd1bd4634d9415ed76ed456b71e", + "size": 2402, + "filename": "bekp6gfql9A5khD9QJvDEc0869PoPQ1WjjhIU0GCZQI=.pem", + "location": "security-state-staging/intermediates/16c579d8-6440-475f-8026-f61d70b2e561.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bekp6gfql9A5khD9QJvDEc0869PoPQ1WjjhIU0GCZQI=", + "crlite_enrolled": false, + "id": "31daf90c-ae6a-4df7-9505-d52c9abded80", + "last_modified": 1666727869229 + }, + { + "schema": 1666727368224, + "derHash": "RkiQCwQnJiirey2C3fdM1beNd/hlLVu/KCS7ZN0Xhlk=", + "subject": "CN=AgID CA1,OU=Area Soluzioni per la Pubblica Amministrazione,O=Agenzia per l'Italia Digitale,L=Roma,C=IT", + "subjectDN": "MIGQMQswCQYDVQQGEwJJVDENMAsGA1UEBwwEUm9tYTEmMCQGA1UECgwdQWdlbnppYSBwZXIgbCdJdGFsaWEgRGlnaXRhbGUxNzA1BgNVBAsMLkFyZWEgU29sdXppb25pIHBlciBsYSBQdWJibGljYSBBbW1pbmlzdHJhemlvbmUxETAPBgNVBAMMCEFnSUQgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "db3cb445b73014519c03c7c1b234573a3cf1ba480f778be153e10d2069492dc1", + "size": 6383, + "filename": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=.pem", + "location": "security-state-staging/intermediates/6f6d6bc0-5ac7-497e-b77d-50b85aeaad14.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OePGxvE8liB3UXJpTSD4HtrVWoYTk0zap8N5Om0muGs=", + "crlite_enrolled": false, + "id": "ec340448-450e-44bb-9a1b-f58c6fa4661e", + "last_modified": 1666727869203 + }, + { + "schema": 1666727406308, + "derHash": "mKDDuhiZJYWV0E8V0TTFcy6GS3VcZIpI0cF/CiYO9ac=", + "subject": "CN=TrustSafe TLS RSA SubCA R1,O=Isimtescil Bilisim A.S.,C=TR", + "subjectDN": "MFQxCzAJBgNVBAYTAlRSMSAwHgYDVQQKDBdJc2ltdGVzY2lsIEJpbGlzaW0gQS5TLjEjMCEGA1UEAwwaVHJ1c3RTYWZlIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "e4ef6c34db503bb6ed621c04c798fb268d875749410a366df77dd3adf566fbdd", + "size": 2365, + "filename": "_SrUGYsWpDW44q_gIPL4e-vKroLPu0_n0OySZvb5g6U=.pem", + "location": "security-state-staging/intermediates/ee1ed267-ca75-44d6-96ec-65b62118aca2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/SrUGYsWpDW44q/gIPL4e+vKroLPu0/n0OySZvb5g6U=", + "crlite_enrolled": false, + "id": "ee8a3125-cbbe-46c1-9629-b23356d4abd2", + "last_modified": 1666727869189 + }, + { + "schema": 1666727394928, + "derHash": "tAAlDvKwmzDpqqPiwgAXuJEb0DnfivVJScYK7Vv2l9Q=", + "subject": "CN=SwissSign RSA TLS DV ICA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIERWIElDQSAyMDIyIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "90d577c9ad4e1f9832ed816d167516318b6e51b1bfeaae1a6807bd99d34c4b86", + "size": 2341, + "filename": "A7AXWj1rjKywVBFqQcQvoHEEWHeViDOFXrwzRs984Xc=.pem", + "location": "security-state-staging/intermediates/ab68565f-3826-44b7-a2c2-b26eacf94668.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "A7AXWj1rjKywVBFqQcQvoHEEWHeViDOFXrwzRs984Xc=", + "crlite_enrolled": false, + "id": "3bea0471-a70c-4dd4-a7b0-3cbde4c03a93", + "last_modified": 1666727869163 + }, + { + "schema": 1666727389190, + "derHash": "auYZQ79LT8yPCO1QRNHJeqCtQOG8/hvxtTC9OxUbNk0=", + "subject": "CN=SwissSign RSA TLS EV ICA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIEVWIElDQSAyMDIyIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "5ffafe2fdaee36d227c47a834e1c3679299ad102411750ca4ecb5add0bb9f9d0", + "size": 2341, + "filename": "Bn9jzIIfxMU9TZeTnNy2O98e9NZidLgEpgEoR6vfT8c=.pem", + "location": "security-state-staging/intermediates/b033a2bc-5579-4a47-b452-411a3fa6efe5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Bn9jzIIfxMU9TZeTnNy2O98e9NZidLgEpgEoR6vfT8c=", + "crlite_enrolled": false, + "id": "0d0b5a84-efc3-4ba8-bf33-278292a5e341", + "last_modified": 1666727869150 + }, + { + "schema": 1666727366735, + "derHash": "My+erjZQx3RUrxT+GmIaJJj9EodzZiiQoNEoNbNDbiM=", + "subject": "CN=SwissSign RSA TLS OV ICA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFAxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKjAoBgNVBAMTIVN3aXNzU2lnbiBSU0EgVExTIE9WIElDQSAyMDIyIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "1f7bac26e75fc5b719353a4ba63ede152143960de03f536d96ad8737d378798c", + "size": 2341, + "filename": "9iu6Ey595ED5KLMW1A3NSMf_Sfn9rdgavrL9DSHwZ8Y=.pem", + "location": "security-state-staging/intermediates/27edd448-6269-4a4d-9aa0-cb1db5639fc8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9iu6Ey595ED5KLMW1A3NSMf/Sfn9rdgavrL9DSHwZ8Y=", + "crlite_enrolled": false, + "id": "287aa90f-092e-4bff-a78a-707a0ea84213", + "last_modified": 1666727869136 + }, + { + "schema": 1666727393082, + "derHash": "5owzdvAQIZzKFOM38zGD752GHlc6nXYofzqq1NBPo48=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIyIFEz", + "whitelist": false, + "attachment": { + "hash": "b3e37a14bbfdd2d3f69e8f01b9a816d95a7479b8c14cf839df6fdfb1453ec883", + "size": 1195, + "filename": "ohmQ9W-FUxsSXCYCKjia3ARHzcJlH8EH06XNXd2hlWs=.pem", + "location": "security-state-staging/intermediates/a8f2ee36-6254-44e6-aa22-e580e522c69e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ohmQ9W+FUxsSXCYCKjia3ARHzcJlH8EH06XNXd2hlWs=", + "crlite_enrolled": false, + "id": "07905fa8-fe73-4334-805d-6a28fa6350b0", + "last_modified": 1666727869123 + }, + { + "schema": 1666727431699, + "derHash": "Etz/m2AmnjtUbD/7fnS6WOt+68v1lWdx94T3pYLC6no=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIyIFEz", + "whitelist": false, + "attachment": { + "hash": "bcb43fbd11578b570d27776e300f87eb5525df0ecfb473a6e4050c9252faaaf8", + "size": 1642, + "filename": "0OyeXCoPbY19oU2881iW7i1bAu8Ni-HMKGN08r_G5XI=.pem", + "location": "security-state-staging/intermediates/b5d214b9-02bf-46d5-8c86-4000177b8e47.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0OyeXCoPbY19oU2881iW7i1bAu8Ni+HMKGN08r/G5XI=", + "crlite_enrolled": false, + "id": "0102afe2-e620-4086-8721-bfcf4279d0af", + "last_modified": 1666727869110 + }, + { + "schema": 1666727347314, + "derHash": "615sGuMKD5uMV2kQW6nWiC5KzLpeLy9yrDiKL6yg/vM=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "48d7fb2872b9764b7517d7ec845608c055a064c655972e36e082a18069737f85", + "size": 1642, + "filename": "b5e0CjH-QPTCZha67VgJSQ3Uj1lumilZfgmxcw6hyi8=.pem", + "location": "security-state-staging/intermediates/a69121a6-44ac-4122-bd99-50cdfae0ac05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b5e0CjH+QPTCZha67VgJSQ3Uj1lumilZfgmxcw6hyi8=", + "crlite_enrolled": false, + "id": "e982faae-a5c3-4f6b-aa5e-e7c4b4e46e93", + "last_modified": 1666727869097 + }, + { + "schema": 1666727349745, + "derHash": "KekwU3adTRurhBuPXCYDvtXguC8zlnCGlWaJIUbokUE=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIyIFEz", + "whitelist": false, + "attachment": { + "hash": "b08a91fa2723f41383dda1dddc9ee5f8cad013137a51866aed652c3d5c4dfbd9", + "size": 1642, + "filename": "wn6PW6g02WtaumkExzEzNw1bo4Mzu0_IX3rY3SmY9t0=.pem", + "location": "security-state-staging/intermediates/ce376919-b9f5-4dd5-96e0-282cf246eb19.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wn6PW6g02WtaumkExzEzNw1bo4Mzu0/IX3rY3SmY9t0=", + "crlite_enrolled": false, + "id": "6bd389d2-bf1e-4c0b-b78e-970beaaeb339", + "last_modified": 1666727869083 + }, + { + "schema": 1666727377905, + "derHash": "iVeNcml6wlq8b204I4dEOWVBKsdEjxA8mx0Rq2OlVc0=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2022 Q3,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjIgUTM=", + "whitelist": false, + "attachment": { + "hash": "a80efbadc5730df2fa0157410ddb8d9b249da87dcb056e54b148b01e32fca2dc", + "size": 1642, + "filename": "u2PPfToTfxlnHMc8HYHgBrctBMaW9JogX77Hhq-zklU=.pem", + "location": "security-state-staging/intermediates/1a77e1c3-b927-44ab-96bf-2e72de1a7f04.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "u2PPfToTfxlnHMc8HYHgBrctBMaW9JogX77Hhq+zklU=", + "crlite_enrolled": false, + "id": "342d2e93-b4c7-4592-958a-ad2b8e43a772", + "last_modified": 1666727869069 + }, + { + "schema": 1666727424439, + "derHash": "3p0HVlZ2OucIY3PrkBJuwkDk4ICJthw8mhsCXLb0EcM=", + "subject": "CN=TrustAsia RSA EV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIEVWIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "03503c28535cc81c9a010e5b081c743ffedf5cce0b9284e04ed6f51593305461", + "size": 2259, + "filename": "M-84feL1t2OKDBjPZhzIU3INxh8ASlHmFLa9vdUs-us=.pem", + "location": "security-state-staging/intermediates/02401f12-2384-4285-955f-52310dc955c7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M+84feL1t2OKDBjPZhzIU3INxh8ASlHmFLa9vdUs+us=", + "crlite_enrolled": false, + "id": "0ef219bc-fbe7-42d4-9137-8110f437ca67", + "last_modified": 1666727869056 + }, + { + "schema": 1666727352819, + "derHash": "uB1wENfklRebtbtQQiPvLu8wVlV+45JFM3GBafGmcMg=", + "subject": "CN=Xcc Trust DV SSL CA,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTYwNAYDVQQKDC1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xHDAaBgNVBAMME1hjYyBUcnVzdCBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "601d041fddd1ce70edf5787db32f0b04152e2a8d90116673df0251341f023bec", + "size": 1727, + "filename": "DZa17leKR2iowb0LDivdU8QkXCyuGYv7w6Nw9QeQILQ=.pem", + "location": "security-state-staging/intermediates/8b78358d-0351-4d45-98f9-46796c32d569.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DZa17leKR2iowb0LDivdU8QkXCyuGYv7w6Nw9QeQILQ=", + "crlite_enrolled": false, + "id": "b9c2faa8-4eef-4262-8bef-884d8d91221e", + "last_modified": 1666727869043 + }, + { + "schema": 1666727344377, + "derHash": "o/KhCjZq/3dMu05uxMio73B8A+kytMRuUHh2eqzx7WA=", + "subject": "CN=TeleSec Business TLS-CA 2022,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF0xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJTAjBgNVBAMMHFRlbGVTZWMgQnVzaW5lc3MgVExTLUNBIDIwMjI=", + "whitelist": false, + "attachment": { + "hash": "1bd9e685ff8c38b24e7d0dc1ed0a05d0c29570645493d20e030473cd6f3d3047", + "size": 2109, + "filename": "EmQ45ScgekmAT8AO52aS5PjZ5yD-6qtqn6tXlb4fA6s=.pem", + "location": "security-state-staging/intermediates/5b6d4f6e-c1d9-4bdf-a494-a35e46a6182b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EmQ45ScgekmAT8AO52aS5PjZ5yD+6qtqn6tXlb4fA6s=", + "crlite_enrolled": false, + "id": "f9a17bcb-c6b3-4eb9-98a8-39a1883b2489", + "last_modified": 1666727869028 + }, + { + "schema": 1666727432033, + "derHash": "JijWXpTWLle2NEwxsikg7aMW2b5o7vXqzHNiIGt96vo=", + "subject": "CN=Xcc Trust OV SSL CA,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGMxCzAJBgNVBAYTAkNOMTYwNAYDVQQKDC1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xHDAaBgNVBAMME1hjYyBUcnVzdCBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "0a88c18b64caf47e86541e0b2f3e67a93891b3ad01e4480673bd3693aea234f0", + "size": 1727, + "filename": "oiGjXsvzUgiIjz9R3_nJmlOb0NbGNkMg5pWl3wyi9lU=.pem", + "location": "security-state-staging/intermediates/f5cac813-541b-4bf3-88f2-4e44e6d15962.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oiGjXsvzUgiIjz9R3/nJmlOb0NbGNkMg5pWl3wyi9lU=", + "crlite_enrolled": false, + "id": "ae096629-2b84-4ed3-a82e-af147a4121fd", + "last_modified": 1666727869015 + }, + { + "schema": 1666727451976, + "derHash": "RVgDgOS8DZcoyfMJite2CrJyEtD/FCAgxSMIkauEcb4=", + "subject": "CN=DNSPod RSA EV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBSU0EgRVY=", + "whitelist": false, + "attachment": { + "hash": "2fbf8fa0a58d0aee7cbc48f7c55c2f85f115b12aadcfb15a24a9f64c1c6805f5", + "size": 2219, + "filename": "7tlcAe1zc6UEu0ycBSzrKSHgelMORRz0zuoxf5grv3A=.pem", + "location": "security-state-staging/intermediates/61082769-e7e7-4c7a-a611-2dfbde36eeae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7tlcAe1zc6UEu0ycBSzrKSHgelMORRz0zuoxf5grv3A=", + "crlite_enrolled": false, + "id": "308cdddb-bce6-4c09-93bd-f689f76af0fe", + "last_modified": 1666727869001 + }, + { + "schema": 1666727448118, + "derHash": "UJLODj9w8v2VYcNGI7VG99Mz7xtjPBR9EpDijemGojA=", + "subject": "CN=Telekom Security ServerID EV Class 3 CA,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MGgxCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxMDAuBgNVBAMMJ1RlbGVrb20gU2VjdXJpdHkgU2VydmVySUQgRVYgQ2xhc3MgMyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "fb69758c6600c18b5dba70d05af48463d183d2ff66177c5cb5efddd3218e1ceb", + "size": 1967, + "filename": "sLVgLe1nM8JMIbUmVZO-i7BiMeC6eHi-ezBmAduziBM=.pem", + "location": "security-state-staging/intermediates/9ed8c27e-49e6-46f7-af3c-ffa5f93479b6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sLVgLe1nM8JMIbUmVZO+i7BiMeC6eHi+ezBmAduziBM=", + "crlite_enrolled": false, + "id": "6444a572-8d81-4739-990d-40d7f53f47be", + "last_modified": 1666727868987 + }, + { + "schema": 1666727397622, + "derHash": "lErOlh2zFr62lOAcMCxG/tQNwCkXKefa9YVQw8tV55E=", + "subject": "CN=Telekom Security ServerID OV Class 2 CA,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MGgxCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxMDAuBgNVBAMMJ1RlbGVrb20gU2VjdXJpdHkgU2VydmVySUQgT1YgQ2xhc3MgMiBDQQ==", + "whitelist": false, + "attachment": { + "hash": "70e43ea614f84f9dd411477e4adbb898f90e8e0efdcc88f3ee5de240120b1eb1", + "size": 1951, + "filename": "9K4RwKQgzd-IOm-vvVnHkFwvlV12A4LOuuuJ7_2Wo8Q=.pem", + "location": "security-state-staging/intermediates/1dc83dc9-3eb5-4f1a-b57c-b793fc38ab0c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9K4RwKQgzd+IOm+vvVnHkFwvlV12A4LOuuuJ7/2Wo8Q=", + "crlite_enrolled": false, + "id": "3f08d71a-ac2c-4364-8649-54709b155eac", + "last_modified": 1666727868974 + }, + { + "schema": 1666727351460, + "derHash": "5TppGT4i+uzIM/gGMb4V4CCgTCC7Z5/TUfDLlcAInlA=", + "subject": "CN=ANTIC DV CA,O=National Agency for Information and Communication Technologies,C=CM", + "subjectDN": "MGwxCzAJBgNVBAYTAkNNMUcwRQYDVQQKDD5OYXRpb25hbCBBZ2VuY3kgZm9yIEluZm9ybWF0aW9uIGFuZCBDb21tdW5pY2F0aW9uIFRlY2hub2xvZ2llczEUMBIGA1UEAwwLQU5USUMgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "836f3dfa4b79f6d065ef8167ee20614b60b2ebb956ddf14a551c18ba0eb30810", + "size": 1764, + "filename": "jbDVk1kQG6bLQ7ol0O08PnkeGy7Pjaz1Ez2qS_o__y0=.pem", + "location": "security-state-staging/intermediates/f422ae01-c4b2-4515-8808-5926402ec56a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jbDVk1kQG6bLQ7ol0O08PnkeGy7Pjaz1Ez2qS/o//y0=", + "crlite_enrolled": false, + "id": "e5e8a7bc-69f8-4b24-b673-017f81218771", + "last_modified": 1666727868960 + }, + { + "schema": 1666727370282, + "derHash": "tn5OkihuFoqRbz28rcPwuD28CtvkP5lcH6rTFt3sddg=", + "subject": "CN=HARICA Institutional TLS ECC,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MG0xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSUwIwYDVQQDDBxIQVJJQ0EgSW5zdGl0dXRpb25hbCBUTFMgRUND", + "whitelist": false, + "attachment": { + "hash": "69fdf558536961cc6ebb3d561bd8ab748fcf3d5d1fac6b6d203ca80228c802fa", + "size": 1333, + "filename": "-Ec0sIhFAVZsZ0KLzPuiGPCfJ2QZZXI3wE_cv2yp1GM=.pem", + "location": "security-state-staging/intermediates/1d4378a1-4d82-486b-b899-ef0248a5962a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+Ec0sIhFAVZsZ0KLzPuiGPCfJ2QZZXI3wE/cv2yp1GM=", + "crlite_enrolled": false, + "id": "b7d934aa-0f81-4c1f-8dfb-ba992f219852", + "last_modified": 1666727868933 + }, + { + "schema": 1666727412695, + "derHash": "GZqyqq//QEAeCjt7h+6ZZGWe/6lKH+y+kYrhNuS04Kg=", + "subject": "CN=D-TRUST BR CA 1-20-1 2020,O=D-Trust GmbH,C=DE", + "subjectDN": "MEgxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIjAgBgNVBAMTGUQtVFJVU1QgQlIgQ0EgMS0yMC0xIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "83449537aac8862f1ea8058fb335366b6fa8c7a51110de57bb5435ab54ab0804", + "size": 1662, + "filename": "kVO5KcVhLn-EO7KjAiRhLgxhA1OOzdUgODPbCEfZ-kQ=.pem", + "location": "security-state-staging/intermediates/75be562f-315d-4bd4-a243-fe93b83b194b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kVO5KcVhLn+EO7KjAiRhLgxhA1OOzdUgODPbCEfZ+kQ=", + "crlite_enrolled": false, + "id": "cc812a01-562a-4570-bb57-65b903984b64", + "last_modified": 1666727868920 + }, + { + "schema": 1666727372094, + "derHash": "QciXRzsDafp0sfT51/iRKUhcGjBcBxmoZ9yHFOCHAgA=", + "subject": "CN=D-TRUST EV CA 1-20-1 2020,O=D-Trust GmbH,C=DE", + "subjectDN": "MEgxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIjAgBgNVBAMTGUQtVFJVU1QgRVYgQ0EgMS0yMC0xIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "e7243885420768bcfaeaac83d278d2ecce102cec4ec1d4dfe7dcd96b7fd8729d", + "size": 1662, + "filename": "SJEHwcDvLp0G_-CfkfLyIPLHc6_k-E4CaepcoEBFq5s=.pem", + "location": "security-state-staging/intermediates/ff509022-df05-462c-8f12-7d582a910132.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SJEHwcDvLp0G/+CfkfLyIPLHc6/k+E4CaepcoEBFq5s=", + "crlite_enrolled": false, + "id": "5767e4fa-0b6e-4802-be9d-d4bf52538de8", + "last_modified": 1666727868905 + }, + { + "schema": 1666727349219, + "derHash": "zHJT696ffpLLope1ut7Rsi5c6spSXiAbTcQQ9PNQS14=", + "subject": "CN=CFCA EV OCA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFUxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFDASBgNVBAMMC0NGQ0EgRVYgT0NB", + "whitelist": false, + "attachment": { + "hash": "74214137d4d8e9fcca062824947ddab76b72e3385e4e90362d532dea748a76cf", + "size": 1898, + "filename": "Ub-bKk6h-qrzcElkX07080gHiz8CDNkXagA7GJtPu0I=.pem", + "location": "security-state-staging/intermediates/7b653369-09f2-4882-9c83-209a35b305e5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ub+bKk6h+qrzcElkX07080gHiz8CDNkXagA7GJtPu0I=", + "crlite_enrolled": false, + "id": "2f50d8ff-d71a-4851-afa7-4fb7a4e14792", + "last_modified": 1666727868891 + }, + { + "schema": 1666727426996, + "derHash": "As4WI1qIuX1hp8Fv9RRuT3ulgIga3CJlADNpi0kSyTk=", + "subject": "CN=cnTrus EV SSL CA,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGIxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEZMBcGA1UEAxMQY25UcnVzIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "8a2c62796de49738c6648befe89ee8f338658faaa444411e7f508ede4a3c145f", + "size": 2268, + "filename": "_Fk4GOAtmSo-xJLXixnH1A1pzonFgo4IAGSrf3wfR1k=.pem", + "location": "security-state-staging/intermediates/b20d43ab-f8ba-4334-9fe7-9761a8c8df5b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/Fk4GOAtmSo+xJLXixnH1A1pzonFgo4IAGSrf3wfR1k=", + "crlite_enrolled": false, + "id": "3b273f22-4f3a-4737-979e-0157ed65f0b3", + "last_modified": 1666727868878 + }, + { + "schema": 1666727411341, + "derHash": "TRLbPMknsmDb+PJ2SfCu0FEQBDAz5wk0TyY5MKnrVjE=", + "subject": "CN=cnWebTrust OV CA,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEExCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEZMBcGA1UEAxMQY25XZWJUcnVzdCBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ad8d62b9eeaa6ec4c309f35271597d3e672b84e5b2ee90d069611b3fb782f381", + "size": 2235, + "filename": "j4rCnPpdXzNAcnJQSrKiLlTLZZIJt4gZ9D6XOorTudM=.pem", + "location": "security-state-staging/intermediates/458b474e-7e16-4c87-8305-6937b4ca373d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "j4rCnPpdXzNAcnJQSrKiLlTLZZIJt4gZ9D6XOorTudM=", + "crlite_enrolled": false, + "id": "ca3bd4e6-3719-4f90-93be-f3bec8b1d133", + "last_modified": 1666727868864 + }, + { + "schema": 1666727369758, + "derHash": "aXhU6w6aR3nmKsoSR/WHlsxTTVSar+YdHvuKlpck9Fg=", + "subject": "CN=cnWebTrust EV CA,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEExCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEZMBcGA1UEAxMQY25XZWJUcnVzdCBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "40070392acdec705154cf7180e31b4d158c903b120ba125f7b9f88ea75c42d01", + "size": 2223, + "filename": "rr1Imgoyj61JA3E5r9kyBhIxOWojKf38FM0RZ8gze-8=.pem", + "location": "security-state-staging/intermediates/856df076-8668-4af6-a815-a46aefc6687e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rr1Imgoyj61JA3E5r9kyBhIxOWojKf38FM0RZ8gze+8=", + "crlite_enrolled": false, + "id": "3cd31ab6-a5a0-48c8-b8c6-bf3b52f1e812", + "last_modified": 1666727868851 + }, + { + "schema": 1666727355222, + "derHash": "NleScpC2H2zJgSsAJDFTm5c0y2WpaFdnUMdr8kll6Wo=", + "subject": "CN=Subordinate Advanced CA G2,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFkxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSMwIQYDVQQDExpTdWJvcmRpbmF0ZSBBZHZhbmNlZCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "355fc1cf56560cdcbf8000b8509f7b2fc16a064ba5dea3c43d58de658976aa33", + "size": 2743, + "filename": "dyr_8he3by16VTzT7yTET3jRYKwLMbcDBal3P6gRoZI=.pem", + "location": "security-state-staging/intermediates/0e978755-a60b-4319-b5ad-f0fcaf263c15.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dyr/8he3by16VTzT7yTET3jRYKwLMbcDBal3P6gRoZI=", + "crlite_enrolled": false, + "id": "d93d6c20-5a06-4148-afda-0f012b264687", + "last_modified": 1666727868824 + }, + { + "schema": 1666727364137, + "derHash": "vb8Gzp5WxFB/wk7X/h++uqm2yDpcbXwiw5kR2fUSXlo=", + "subject": "CN=Subordinate Advanced ECC CA G2,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQDEx5TdWJvcmRpbmF0ZSBBZHZhbmNlZCBFQ0MgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "9c5d31af488a6e6044a329488b4aa4e584216fba8e146cc8f01e162a53a1718d", + "size": 1638, + "filename": "1b-hdhDYKpJwO40m1GtAqNkn5luo7V8AYgKzz-dZ_YY=.pem", + "location": "security-state-staging/intermediates/90767168-53c0-470a-8f7c-94c792877b69.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1b+hdhDYKpJwO40m1GtAqNkn5luo7V8AYgKzz+dZ/YY=", + "crlite_enrolled": false, + "id": "d0f63dec-c1ea-4622-bb0a-f75cc3b93ee7", + "last_modified": 1666727868811 + }, + { + "schema": 1666727332142, + "derHash": "gc0DBnJS/+hJskDcwkVmZ4Z34/X+3ExUDXomytLAgcg=", + "subject": "CN=NII Open Domain CA - G7 ECC,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFoxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSQwIgYDVQQDExtOSUkgT3BlbiBEb21haW4gQ0EgLSBHNyBFQ0M=", + "whitelist": false, + "attachment": { + "hash": "bc81689e85d6b4f11be940e6725e2d51189ad0f9644a531e3d00192c0360bc71", + "size": 1353, + "filename": "9JNYM04BffEmh3OEDUF4Dq3ni7TNCF7Zx1ItQnZjbVY=.pem", + "location": "security-state-staging/intermediates/0fed38e6-b219-4f2b-835c-62affdd61546.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9JNYM04BffEmh3OEDUF4Dq3ni7TNCF7Zx1ItQnZjbVY=", + "crlite_enrolled": false, + "id": "9f3c8b96-34bf-40de-8fe2-c462ce561b7a", + "last_modified": 1666727868798 + }, + { + "schema": 1666727397462, + "derHash": "unmfMiWR3X0128WPSCByXwlPVDqhPLRYxVyR3Ia+Iv8=", + "subject": "CN=CERTDATA SSL EV CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MG4xCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExMDAuBgNVBAMMJ0NFUlREQVRBIFNTTCBFViBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "cbc72d1c9e1e80fae70fff1317f77c7f0df523f3e60a161d941a951d1184e87d", + "size": 2158, + "filename": "cVSKuRG7oLetITEBYy5dH5QBCUayslF78ETJPRfzwkU=.pem", + "location": "security-state-staging/intermediates/7bc5e53a-447b-4bbf-90e2-3c06f43c90cc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cVSKuRG7oLetITEBYy5dH5QBCUayslF78ETJPRfzwkU=", + "crlite_enrolled": false, + "id": "dfe35e95-c849-4d3d-b7d0-022978d28357", + "last_modified": 1666727868785 + }, + { + "schema": 1666727345092, + "derHash": "fg4WwAVvQan0xh9XFQPDvPB54r3bIovyIZrDEgBJa1w=", + "subject": "CN=COMODO RSA Extended Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "156d6c586c10f29dd5565d6e970da28ca5e4f685d16928689a29f193adc4c791", + "size": 2158, + "filename": "Fbr_5aSOo4KRal8YE49t4lc76IOnK_oto9NWV1cSKWM=.pem", + "location": "security-state-staging/intermediates/8330f1a2-2e8e-4670-bb57-42072e0fc3b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Fbr/5aSOo4KRal8YE49t4lc76IOnK/oto9NWV1cSKWM=", + "crlite_enrolled": false, + "id": "9f906cab-2fbd-45e3-9efb-9a9afd8adbad", + "last_modified": 1666727868772 + }, + { + "schema": 1666727453855, + "derHash": "m1P2nYRHsBsB5FZbhT4oi2y4IpHagqW0G7wVwvOtZUg=", + "subject": "CN=OneSignSSL RSA EV Secure Server CA,OU=Controlled by Sectigo exclusively for One Sign Pte. Ltd.,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MIHCMQswCQYDVQQGEwJTRzESMBAGA1UECBMJU2luZ2Fwb3JlMRIwEAYDVQQHEwlTaW5nYXBvcmUxGzAZBgNVBAoTEk9uZSBTaWduIFB0ZS4gTHRkLjFBMD8GA1UECxM4Q29udHJvbGxlZCBieSBTZWN0aWdvIGV4Y2x1c2l2ZWx5IGZvciBPbmUgU2lnbiBQdGUuIEx0ZC4xKzApBgNVBAMTIk9uZVNpZ25TU0wgUlNBIEVWIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "3d89c9e1a922444432fe3214dfe02fce91dc11364c41ff07786b9181b7ee3ac7", + "size": 2276, + "filename": "N8OD-hIuhMaCwngPwhlrWu8ly-IEZbRt_gL0S4FH9GU=.pem", + "location": "security-state-staging/intermediates/9bfa87c7-22ab-4a74-8b03-31a59d93146d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N8OD+hIuhMaCwngPwhlrWu8ly+IEZbRt/gL0S4FH9GU=", + "crlite_enrolled": false, + "id": "ca06ecee-27ae-45b5-b667-16da4f21b66b", + "last_modified": 1666727868759 + }, + { + "schema": 1666727445855, + "derHash": "cqNKwrQkrtP2sLBHVbiMwCfczIBv3bIrTNfEd3OXPsA=", + "subject": "CN=Sectigo RSA Organization Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGVMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxPTA7BgNVBAMTNFNlY3RpZ28gUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "4b46e556b0114fe3a779d0ce323ce86fee1630a3cb4ad8766f392875be4c7e04", + "size": 2174, + "filename": "RkhWTcfJAQN_YxOR12VkPo-PhmIoSfWd_JVkg44einY=.pem", + "location": "security-state-staging/intermediates/4ecbd37e-d6ce-4fa2-87b3-a0d6a31677d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RkhWTcfJAQN/YxOR12VkPo+PhmIoSfWd/JVkg44einY=", + "crlite_enrolled": false, + "id": "99eed771-a9a6-4e58-bb95-7008cb7f1439", + "last_modified": 1666727868744 + }, + { + "schema": 1666727395602, + "derHash": "J+/mgsdHnDgTOglDZm+8kQP8uxqRljTkOXEcvqfjz8c=", + "subject": "CN=WoTrus EV Server CA [Run by the Issuer],O=WoTrus CA Limited,C=CN", + "subjectDN": "MFwxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1RydXMgQ0EgTGltaXRlZDExMC8GA1UEAwwoV29UcnVzIEVWIFNlcnZlciBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "e122f748f32fbb3b48a624701e50fd5c4a27af62dc42c80b37d12eb30030829d", + "size": 2129, + "filename": "7gcte6Sz2PaLDgthd1WawzH-5AZvEHps9kxV7G1Z6nU=.pem", + "location": "security-state-staging/intermediates/25d1f373-2d95-4bde-a7d5-0c4e72dbde32.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7gcte6Sz2PaLDgthd1WawzH+5AZvEHps9kxV7G1Z6nU=", + "crlite_enrolled": false, + "id": "b9574c99-bbda-44bf-9142-a3a8723f041a", + "last_modified": 1666727868730 + }, + { + "schema": 1666727444178, + "derHash": "Q2/tMIldbH8NbcCdtiGrO4jsYyHFbKb9SdeYEeKlmMo=", + "subject": "CN=TBS X509 CA business 2,OU=TBS INTERNET CA,O=TBS INTERNET,L=Caen,ST=Calvados,C=FR", + "subjectDN": "MIGBMQswCQYDVQQGEwJGUjERMA8GA1UECBMIQ2FsdmFkb3MxDTALBgNVBAcTBENhZW4xFTATBgNVBAoTDFRCUyBJTlRFUk5FVDEYMBYGA1UECxMPVEJTIElOVEVSTkVUIENBMR8wHQYDVQQDExZUQlMgWDUwOSBDQSBidXNpbmVzcyAy", + "whitelist": false, + "attachment": { + "hash": "3c6ac3f26622a9520713c4c5f56b700a5568b11396c787937f1ec356bfd869d9", + "size": 2154, + "filename": "SSBQnpJB4S4xChF_ht4nYT6NSI2ORtzmPESVfewnepc=.pem", + "location": "security-state-staging/intermediates/b3871bec-dadc-4e7b-ae68-b7bc225b69f0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SSBQnpJB4S4xChF/ht4nYT6NSI2ORtzmPESVfewnepc=", + "crlite_enrolled": false, + "id": "4d972bd6-61ae-480e-b560-53f9e1c223c3", + "last_modified": 1666727868717 + }, + { + "schema": 1666727431197, + "derHash": "HyAocW4zWE8z+pIKgCR77s3D18smUZVUxkTOTExgd6k=", + "subject": "CN=Gandi Pro SSL CA 2,O=Gandi,L=Paris,ST=Paris,C=FR", + "subjectDN": "MFoxCzAJBgNVBAYTAkZSMQ4wDAYDVQQIEwVQYXJpczEOMAwGA1UEBxMFUGFyaXMxDjAMBgNVBAoTBUdhbmRpMRswGQYDVQQDExJHYW5kaSBQcm8gU1NMIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "6faf7f5ba7f16f7491ead642e0cfbaa35440f2c653b8dbb5a087ac9ec163a530", + "size": 2101, + "filename": "XQpYwYiHpVLml4eYccUVEZJpEOh5QrryTkXbWPum2FM=.pem", + "location": "security-state-staging/intermediates/89bc78dd-9df5-4eef-8388-f9c0404389b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XQpYwYiHpVLml4eYccUVEZJpEOh5QrryTkXbWPum2FM=", + "crlite_enrolled": false, + "id": "73046dfc-cf19-4186-82ef-495957df2484", + "last_modified": 1666727868704 + }, + { + "schema": 1666727351625, + "derHash": "TKCAJjzkvCTxgRzvP/rKWl7zmPk+vQxbz1t70LrmfUo=", + "subject": "CN=BitCert RSA Business Secure Site CA,O=BitCert,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdTaWNodWFuMRAwDgYDVQQHEwdDaGVuZ2R1MRAwDgYDVQQKEwdCaXRDZXJ0MSwwKgYDVQQDEyNCaXRDZXJ0IFJTQSBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "6316f70d39d4e72ffaa6257da1bea93a114191f7fa2f53dfdc1488322a362ae1", + "size": 2133, + "filename": "cz-x3ySlDQ6TKlRCvrhCFabpnRLASgrkOqGv8wQxW6A=.pem", + "location": "security-state-staging/intermediates/c1d38581-09c8-4632-b7fd-8fef7af079de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cz+x3ySlDQ6TKlRCvrhCFabpnRLASgrkOqGv8wQxW6A=", + "crlite_enrolled": false, + "id": "3a3be016-0c16-4a62-b10e-6c546a9a0c35", + "last_modified": 1666727868677 + }, + { + "schema": 1666727353673, + "derHash": "eO4jersjVKpnyo+14r+puRegv1Pi87PKs9rOweLDYeE=", + "subject": "CN=Gehirn Managed Certification Authority - ECC OV,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIGCMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTgwNgYDVQQDEy9HZWhpcm4gTWFuYWdlZCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDQyBPVg==", + "whitelist": false, + "attachment": { + "hash": "66713fc1ed9e0543e3920835b81b23d39775c4c736a85291a4495a0d0fc04e65", + "size": 1321, + "filename": "oARzv0l1Eo_Mizi_3scY3T6AQ1Zfy7sDSJO-oLT0O0g=.pem", + "location": "security-state-staging/intermediates/2f362e70-1aad-481b-92fc-d3512267c10e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oARzv0l1Eo/Mizi/3scY3T6AQ1Zfy7sDSJO+oLT0O0g=", + "crlite_enrolled": false, + "id": "08cfd806-5622-442f-8940-c0e5fc5b04d8", + "last_modified": 1666727868664 + }, + { + "schema": 1666727405639, + "derHash": "M8+wYs0pUeTdGiVr7RVr6lPykNA53u+4W+leGQEhajY=", + "subject": "CN=Trustico RSA DV CA,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MG4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIEwZMb25kb24xEDAOBgNVBAcTB0Nyb3lkb24xHzAdBgNVBAoTFlRoZSBUcnVzdGljbyBHcm91cCBMdGQxGzAZBgNVBAMTElRydXN0aWNvIFJTQSBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "7324a8522c5a773c75e46aa849977c2a916710a2eaffa3e359d413c373e33c35", + "size": 2113, + "filename": "B_bkhTDI5DJw6nXXzz0RfEQettWUxxESQlnc8VMZ4uk=.pem", + "location": "security-state-staging/intermediates/90ad777b-e189-4b7d-88e7-5865bcdd50f9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B/bkhTDI5DJw6nXXzz0RfEQettWUxxESQlnc8VMZ4uk=", + "crlite_enrolled": false, + "id": "3092fd49-e35f-4d39-8a7f-6aee24d25c3b", + "last_modified": 1666727868648 + }, + { + "schema": 1666727363637, + "derHash": "RcKL5DQvByzWIZaYw+8qxtunPxngTazyBE0vaZPjQrQ=", + "subject": "CN=eMudhra RSA Extended Validation Secure Server CA,OU=Controlled by Sectigo exclusively for eMudhra Technologies Ltd.,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIHhMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxSDBGBgNVBAsTP0NvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgZU11ZGhyYSBUZWNobm9sb2dpZXMgTHRkLjE5MDcGA1UEAxMwZU11ZGhyYSBSU0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "da857661dcb4b3f71a9584ca5f2ab090894112dd7be537a1a42e25ec51b922d9", + "size": 2316, + "filename": "xIsOtN5uZ5ZZQRiHSu6NrXg36kenjPgaYZeoj9lRsXk=.pem", + "location": "security-state-staging/intermediates/74cadf61-0c36-4e4e-94b0-05d97c29ab2e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xIsOtN5uZ5ZZQRiHSu6NrXg36kenjPgaYZeoj9lRsXk=", + "crlite_enrolled": false, + "id": "4853b80d-c09c-48e3-9f66-f8dccd0d2c30", + "last_modified": 1666727868634 + }, + { + "schema": 1666727399428, + "derHash": "mmlizYwhtdzx9ws5qDbH85fsSV+IC+m5tpuLJyiOyxQ=", + "subject": "CN=KICA RSA OV CA,O=KICA,C=KR", + "subjectDN": "MDUxCzAJBgNVBAYTAktSMQ0wCwYDVQQKEwRLSUNBMRcwFQYDVQQDEw5LSUNBIFJTQSBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "405c6fe796af433c64259453863af27dafcc8d93955b6cab9b46d6fe1c2343a7", + "size": 1569, + "filename": "404SflxwLnJCxfU7K35IbTgipMzslOzUuXBgFjCXwfk=.pem", + "location": "security-state-staging/intermediates/1d3d808f-3c59-4ee9-ac7e-e18487a872e9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "404SflxwLnJCxfU7K35IbTgipMzslOzUuXBgFjCXwfk=", + "crlite_enrolled": false, + "id": "b1052342-f8b8-479a-85f2-79e7c0ae9095", + "last_modified": 1666727868620 + }, + { + "schema": 1666727413552, + "derHash": "2OGKZa94RUhUyqKPsvXnm1fsFB8BBCO8yKcav8BJbCw=", + "subject": "CN=GENIOUS RSA Extended Validation Secure Server CA,OU=Controlled by Sectigo exclusively for Genious Communications,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIHYMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxRTBDBgNVBAsTPENvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgR2VuaW91cyBDb21tdW5pY2F0aW9uczE5MDcGA1UEAxMwR0VOSU9VUyBSU0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "d27fdb57c39eaed07acfbb6893264b26a768dd5cf09c9d0d6527dad739b0ebc3", + "size": 2304, + "filename": "ImErDX6kNNxXp53lr4QRD49owJiK0A8orzjdda8pDb0=.pem", + "location": "security-state-staging/intermediates/58f2cdb8-2552-4344-9823-54219a956adc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ImErDX6kNNxXp53lr4QRD49owJiK0A8orzjdda8pDb0=", + "crlite_enrolled": false, + "id": "0f5c1152-9ff7-4602-8f90-fa27a2c7ce3e", + "last_modified": 1666727868607 + }, + { + "schema": 1666727385767, + "derHash": "ZjpOZY+790/BmwFV8vrLEsybaXBY9EfveRF4/puNBz0=", + "subject": "CN=Trustico RSA EV CA,OU=Controlled by COMODO exclusively for The Trustico Group Ltd,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MIG0MQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMRAwDgYDVQQHEwdDcm95ZG9uMR8wHQYDVQQKExZUaGUgVHJ1c3RpY28gR3JvdXAgTHRkMUQwQgYDVQQLEztDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgVGhlIFRydXN0aWNvIEdyb3VwIEx0ZDEbMBkGA1UEAxMSVHJ1c3RpY28gUlNBIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "db00968a84b507dc8cbe0fdd621d5ed2d541345622a73c6f703cad1583a1f137", + "size": 2247, + "filename": "KwL2fukG72afNxr2mfIc-zRSfV9FvVkQd67wLyLhGLY=.pem", + "location": "security-state-staging/intermediates/f2773561-f2a2-40fa-ad5d-27944e0e90af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KwL2fukG72afNxr2mfIc+zRSfV9FvVkQd67wLyLhGLY=", + "crlite_enrolled": false, + "id": "3a5a271c-a832-45ef-b2c0-7c491f2fcb6d", + "last_modified": 1666727868594 + }, + { + "schema": 1666727390891, + "derHash": "sXdPmya04tX3UXE2AP/rFjs0y3oUwsB5cE6VNk+8ykk=", + "subject": "CN=InCommon RSA Server CA,OU=InCommon,O=Internet2,L=Ann Arbor,ST=MI,C=US", + "subjectDN": "MHYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMR8wHQYDVQQDExZJbkNvbW1vbiBSU0EgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "2582ac120eb47a5e3fffc97b01044c3212914fceed8932375ff038fac16bad3b", + "size": 2129, + "filename": "b1JA6-4svjmZnxGjAiQY3RS0A9FtjKLCWaRlVmCPM28=.pem", + "location": "security-state-staging/intermediates/fc8a57f0-b310-4796-802a-c2a5f730f254.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b1JA6+4svjmZnxGjAiQY3RS0A9FtjKLCWaRlVmCPM28=", + "crlite_enrolled": false, + "id": "f35a4859-a7a5-49bd-a611-5e0a360b4144", + "last_modified": 1666727868581 + }, + { + "schema": 1666727332478, + "derHash": "ERAGN4r76OmbsCuoc5DKQp/KJ3P3TX9+tXRPXd9oAUs=", + "subject": "CN=COMODO RSA Organization Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGWMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE8MDoGA1UEAxMzQ09NT0RPIFJTQSBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "0866ddbb3d41852c29b86743f83b209f9978a535bb8f273f96322c5b3e91f43f", + "size": 2158, + "filename": "EgNpQklEUNXn9Nl6RoIOC532j1g5-EFw0ZpLxxJq9Ms=.pem", + "location": "security-state-staging/intermediates/c4810cbd-83da-4542-9488-e64d95fd1ac4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EgNpQklEUNXn9Nl6RoIOC532j1g5+EFw0ZpLxxJq9Ms=", + "crlite_enrolled": false, + "id": "93cef3b3-7d88-43c1-a3dd-053c96105f9d", + "last_modified": 1666727868568 + }, + { + "schema": 1666727352981, + "derHash": "VJq1JOiGreznC92WoEtVSULDdL58KcDugYtjC98gDuE=", + "subject": "CN=COMODO ECC Organization Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGWMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE8MDoGA1UEAxMzQ09NT0RPIEVDQyBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "b2f0de87f5f773673b5675826fc479ea178ad2cbec8bd103f57f2ec1aa46a07f", + "size": 1321, + "filename": "ORuXcQB__xhdb52by_Vmo_qhrTuazztSjlMMhaafJZ0=.pem", + "location": "security-state-staging/intermediates/43740f29-584f-43b6-9482-c3c24513466d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ORuXcQB//xhdb52by/Vmo/qhrTuazztSjlMMhaafJZ0=", + "crlite_enrolled": false, + "id": "d5263b2f-8d24-47a3-afcd-ec5f673128d3", + "last_modified": 1666727868554 + }, + { + "schema": 1666727340612, + "derHash": "pKfQXyloVnnjwAoyLOa2zVf92l8u9CxsbnNYHIpHEHc=", + "subject": "CN=Trusted Secure ECC Certificate Authority DV,O=Corporation Service Company,L=Wilmington,ST=DE,C=US", + "subjectDN": "MIGLMQswCQYDVQQGEwJVUzELMAkGA1UECBMCREUxEzARBgNVBAcTCldpbG1pbmd0b24xJDAiBgNVBAoTG0NvcnBvcmF0aW9uIFNlcnZpY2UgQ29tcGFueTE0MDIGA1UEAxMrVHJ1c3RlZCBTZWN1cmUgRUNDIENlcnRpZmljYXRlIEF1dGhvcml0eSBEVg==", + "whitelist": false, + "attachment": { + "hash": "8138440a1c3f26dac0b9d8b825c751e1d3f69e3921da7fb091b740fdf06d9578", + "size": 1333, + "filename": "qaGgP6zdlL_vHNs7F1JoC4tyJv8eGtwjguHkPQZ2HYg=.pem", + "location": "security-state-staging/intermediates/18c23f9b-4d32-4990-ac2a-32121abff8ab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qaGgP6zdlL/vHNs7F1JoC4tyJv8eGtwjguHkPQZ2HYg=", + "crlite_enrolled": false, + "id": "8c08d7b9-7e7b-4427-8589-ba6ea10530b2", + "last_modified": 1666727868540 + }, + { + "schema": 1666727363808, + "derHash": "Or00nX/hQwXmlSQSNPn/lK6CGt9EZ7wWB6E+ES9ep2c=", + "subject": "CN=SSL.com Premium EV CA,OU=Controlled by COMODO exclusively for SSL.com+OU=www.ssl.com,O=SSL.com,C=US", + "subjectDN": "MIGMMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHU1NMLmNvbTE1MDMGA1UECxMsQ29udHJvbGxlZCBieSBDT01PRE8gZXhjbHVzaXZlbHkgZm9yIFNTTC5jb20xFDASBgNVBAsTC3d3dy5zc2wuY29tMR4wHAYDVQQDExVTU0wuY29tIFByZW1pdW0gRVYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "621fb09b600be83bd671ce763198a88e0d36dba88e3ad54918becd46e9e7e07a", + "size": 2231, + "filename": "q9kHBA4OE40-HVkOIjyKiKBED3yDP_uskMLt3i9Z7e8=.pem", + "location": "security-state-staging/intermediates/5580e1ae-418c-42ad-8a59-8d0d6a18993c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "q9kHBA4OE40+HVkOIjyKiKBED3yDP/uskMLt3i9Z7e8=", + "crlite_enrolled": false, + "id": "ccbc177b-530d-4075-8cda-7c1a84f5dfe0", + "last_modified": 1666727868525 + }, + { + "schema": 1666727438187, + "derHash": "AqtX5OZ6DLSN0v80gw6KxA9EdvsIymvj9c2Eb2RoQPA=", + "subject": "CN=COMODO RSA Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "e05720e51ca01968b8b3e4273a8729c271fe2fe18111b323fa133e2b880a0a9a", + "size": 2150, + "filename": "klO23nT2ehFDXCfx3eHTDRESMz3asj1muO-4aIdjiuY=.pem", + "location": "security-state-staging/intermediates/2fecec14-4555-43bd-ab49-8160916e38ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=", + "crlite_enrolled": false, + "id": "4a74b5c2-48f6-4d04-8ad6-e0a585aa6563", + "last_modified": 1666727868498 + }, + { + "schema": 1666727353494, + "derHash": "buBlr1i3BDyyNy/GY540hjBPzQlNnLaDjXaojOIMMN0=", + "subject": "CN=USERTrust RSA Domain Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE5MDcGA1UEAxMwVVNFUlRydXN0IFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "53d9fd192a6253434be5a2ceb2d6291bd343c51dcca6c4abaf0e8b3088e1c936", + "size": 2170, + "filename": "jPQXFCdXn0NxGIW3P3JUl22ASU1QS7BTE83baBiuZpc=.pem", + "location": "security-state-staging/intermediates/8f9b6c52-ea5d-416d-9057-9f2c263838fa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jPQXFCdXn0NxGIW3P3JUl22ASU1QS7BTE83baBiuZpc=", + "crlite_enrolled": false, + "id": "642fece3-a930-4f38-a52b-d498e8392dff", + "last_modified": 1666727868485 + }, + { + "schema": 1666727346277, + "derHash": "5/FSbqnVFHt6Xhrmq+MXN6RmbR6PA+YzcDvTw9mx43Y=", + "subject": "CN=Gehirn Managed Certification Authority - ECC DV,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIGCMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTgwNgYDVQQDEy9HZWhpcm4gTWFuYWdlZCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDQyBEVg==", + "whitelist": false, + "attachment": { + "hash": "d843127bfb3bef1d3f8447573c8fa9838209229355d03b74ee6108eaf49186a8", + "size": 1321, + "filename": "gXehP87ukwK1hI7ZuoCGUFVeee8oPwN9F_lSos9o0ns=.pem", + "location": "security-state-staging/intermediates/d68f7c23-5175-4e77-bf77-2f135c940bc7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gXehP87ukwK1hI7ZuoCGUFVeee8oPwN9F/lSos9o0ns=", + "crlite_enrolled": false, + "id": "2812d163-b80e-4cee-b232-b51cc536bcb2", + "last_modified": 1666727868472 + }, + { + "schema": 1666727393429, + "derHash": "K97vpzX/cAXf6C5mZBdy2CXno2GbvKyxPP4gEGYOPZ0=", + "subject": "CN=FujiSSL ECC Business Secure Site CA,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MHYxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzETMBEGA1UEBxMKU2hpYnV5YS1rdTEUMBIGA1UEChMLTmlqaW1vIEsuSy4xLDAqBgNVBAMTI0Z1amlTU0wgRUNDIEJ1c2luZXNzIFNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "ebae96515eb660526ae59e27dd82aae4e75b60a66d0bd4177622b8f049f44738", + "size": 1301, + "filename": "VSIhlyLVTq0piI3j7ajNA5ZmdmgumlgTW-MV_okesCc=.pem", + "location": "security-state-staging/intermediates/a695e1f3-fc54-4683-90b1-34d148981ce3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VSIhlyLVTq0piI3j7ajNA5ZmdmgumlgTW+MV/okesCc=", + "crlite_enrolled": false, + "id": "82c5dec4-2625-43cb-b42e-e1f825c22c30", + "last_modified": 1666727868458 + }, + { + "schema": 1666727416675, + "derHash": "p7dwK9Ju9/BnodOXXsFyC0bW4B/RAayTQi7rDW3iXzI=", + "subject": "CN=SecureCore RSA DV CA,O=SecureCore,L=OSAKA,ST=OSAKA,C=JP", + "subjectDN": "MGExCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVPU0FLQTEOMAwGA1UEBxMFT1NBS0ExEzARBgNVBAoTClNlY3VyZUNvcmUxHTAbBgNVBAMTFFNlY3VyZUNvcmUgUlNBIERWIENB", + "whitelist": false, + "attachment": { + "hash": "f15fd61069ce42b4e0909409354fd3b6896dd2506c3543b5ac7676e9004eb6cd", + "size": 2113, + "filename": "CrV-DCuu__rnw7hOV-VT2e3k97fReT_N7f4coJTynlM=.pem", + "location": "security-state-staging/intermediates/745fccff-2cb7-4d96-b214-712bb0212678.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CrV+DCuu//rnw7hOV+VT2e3k97fReT/N7f4coJTynlM=", + "crlite_enrolled": false, + "id": "d7b04dda-3aa4-4944-ac29-5b450792ebc4", + "last_modified": 1666727868445 + }, + { + "schema": 1666727374818, + "derHash": "DtmWPhu6BBfh/Be9OOkgYfllETuSHLNX8THxLwhTRVc=", + "subject": "CN=WoTrus OV Server CA [Run by the Issuer],O=WoTrus CA Limited,C=CN", + "subjectDN": "MFwxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1RydXMgQ0EgTGltaXRlZDExMC8GA1UEAwwoV29UcnVzIE9WIFNlcnZlciBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "22dab7a05d41815525fadef51e997822207a36089949db9c4320e85de840966d", + "size": 2097, + "filename": "B2o6KjBMvs-b5LG04FOX1d-Dw82KgV4aPdu-Q6Wq764=.pem", + "location": "security-state-staging/intermediates/000f7946-d2e3-479c-a02e-2bcfd25eb3bf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B2o6KjBMvs+b5LG04FOX1d+Dw82KgV4aPdu+Q6Wq764=", + "crlite_enrolled": false, + "id": "1a05d8c4-475c-4f3a-b60a-427c13103f66", + "last_modified": 1666727868431 + }, + { + "schema": 1666727361708, + "derHash": "G/2HAtj5uzQPNTggMwwLun5SLGMWTJHylUFNrHl/CGM=", + "subject": "CN=TI Trust Technologies OV CA,O=TI Trust Technologies S.R.L.,L=Pomezia,ST=Roma,C=IT", + "subjectDN": "MHsxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21hMRAwDgYDVQQHEwdQb21lemlhMSUwIwYDVQQKExxUSSBUcnVzdCBUZWNobm9sb2dpZXMgUy5SLkwuMSQwIgYDVQQDExtUSSBUcnVzdCBUZWNobm9sb2dpZXMgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "1caeb0e1afd1a030e95b03db4e43fa05de7ac42417d85801512cd9333bcd0aa9", + "size": 2150, + "filename": "nrqQiXIilvjVUSIRwooWc9ltG3vqAn5jSqfwOAWLI-I=.pem", + "location": "security-state-staging/intermediates/848f5df7-2bdf-4d2d-991e-4c4ee088b379.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nrqQiXIilvjVUSIRwooWc9ltG3vqAn5jSqfwOAWLI+I=", + "crlite_enrolled": false, + "id": "98d86f96-39bb-4798-8362-4b35be0bb427", + "last_modified": 1666727868415 + }, + { + "schema": 1666727355063, + "derHash": "ci1Qh02kVJbQKZYnQJd3YDqHNBpflDuInDLnuSgKj3E=", + "subject": "CN=MarketWare - Soluções para Mercados Digitais\\, Lda. RSA DV CA,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,L=Lisboa,ST=Lisboa,C=PT", + "subjectDN": "MIG3MQswCQYDVQQGEwJQVDEPMA0GA1UECBMGTGlzYm9hMQ8wDQYDVQQHEwZMaXNib2ExPTA7BgNVBAoMNE1hcmtldFdhcmUgLSBTb2x1w6fDtWVzIHBhcmEgTWVyY2Fkb3MgRGlnaXRhaXMsIExkYS4xRzBFBgNVBAMMPk1hcmtldFdhcmUgLSBTb2x1w6fDtWVzIHBhcmEgTWVyY2Fkb3MgRGlnaXRhaXMsIExkYS4gUlNBIERWIENB", + "whitelist": false, + "attachment": { + "hash": "364dc7ff0e34c494eeb6b74e5af48417d3bee36a3a3bc88c306402095454997e", + "size": 2231, + "filename": "0xvEIcXteNs-TYPZ7GyhN_WFNSBPpaBsyCLFCdhQQY0=.pem", + "location": "security-state-staging/intermediates/a9a16ec4-e9a6-4fc5-8368-10f052892a0b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0xvEIcXteNs+TYPZ7GyhN/WFNSBPpaBsyCLFCdhQQY0=", + "crlite_enrolled": false, + "id": "700dbfd1-9940-430d-bac5-80f5adaf4fcd", + "last_modified": 1666727868402 + }, + { + "schema": 1666727443828, + "derHash": "iyVXlJH4XPV37CFyz60tskyiJVdC5uU0HPBuLL7F2QY=", + "subject": "CN=EUNETIC - EuropeanSSL Extended Validation Server CA 2,OU=Controlled by COMODO exclusively for EUNETIC GmbH,O=EUNETIC GmbH,L=Durmersheim,ST=Baden-Württemberg,C=DE", + "subjectDN": "MIHTMQswCQYDVQQGEwJERTEbMBkGA1UECAwSQmFkZW4tV8O8cnR0ZW1iZXJnMRQwEgYDVQQHEwtEdXJtZXJzaGVpbTEVMBMGA1UEChMMRVVORVRJQyBHbWJIMTowOAYDVQQLEzFDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgRVVORVRJQyBHbWJIMT4wPAYDVQQDEzVFVU5FVElDIC0gRXVyb3BlYW5TU0wgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "c47c7d793fe9b357172de812fda6db8ed893185264b3194523595576b41b3875", + "size": 2284, + "filename": "nWdB0jAAGzhZiLNjLocqdxL16ZnFE4xjHkhnauNoK-A=.pem", + "location": "security-state-staging/intermediates/5168333b-0a9d-45be-9011-6097eff0da60.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nWdB0jAAGzhZiLNjLocqdxL16ZnFE4xjHkhnauNoK+A=", + "crlite_enrolled": false, + "id": "917dad42-ff25-4a3c-9939-309aedcf6437", + "last_modified": 1666727868389 + }, + { + "schema": 1666727356765, + "derHash": "KAu8NKmMx5YGqJaQ5Sq3L9xf/yddw2bEgnsWVjE+l5A=", + "subject": "CN=Don Dominio / MrDomain RSA DV CA,O=Soluciones Corporativas IP\\, SL,L=Manacor,ST=Illes Balears,C=ES", + "subjectDN": "MIGLMQswCQYDVQQGEwJFUzEWMBQGA1UECBMNSWxsZXMgQmFsZWFyczEQMA4GA1UEBxMHTWFuYWNvcjEnMCUGA1UEChMeU29sdWNpb25lcyBDb3Jwb3JhdGl2YXMgSVAsIFNMMSkwJwYDVQQDEyBEb24gRG9taW5pbyAvIE1yRG9tYWluIFJTQSBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "df1caa18ac42559ecdaaf751701f5eb16d143b610134964b5af4c5a78a516200", + "size": 2170, + "filename": "k5zZlzm-7Ha5WKka7Wov4zebiiAPJ0-9cRslSNnRFWA=.pem", + "location": "security-state-staging/intermediates/892dc635-4c44-4689-b1c6-94d3d6852a42.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k5zZlzm+7Ha5WKka7Wov4zebiiAPJ0+9cRslSNnRFWA=", + "crlite_enrolled": false, + "id": "f235b58c-5e9d-4d7e-9574-155ec50b7290", + "last_modified": 1666727868375 + }, + { + "schema": 1666727342147, + "derHash": "M55rkr5UWfJqjcPF83IJM8g44jZgGwUASMBHoSPm+Oc=", + "subject": "CN=Sectigo Qualified Website Authentication CA Natural R35,O=Sectigo (Europe) SL,C=ES", + "subjectDN": "MG0xCzAJBgNVBAYTAkVTMRwwGgYDVQQKExNTZWN0aWdvIChFdXJvcGUpIFNMMUAwPgYDVQQDEzdTZWN0aWdvIFF1YWxpZmllZCBXZWJzaXRlIEF1dGhlbnRpY2F0aW9uIENBIE5hdHVyYWwgUjM1", + "whitelist": false, + "attachment": { + "hash": "9f289cf2d9a31cc8b3bb166e088a3714fac7b2010d0160e5890b22cf722bc2cd", + "size": 2272, + "filename": "8SDm7JR9Lr5k8CRbSJ0R2_L6J0y30uLS_XkBNMdspCI=.pem", + "location": "security-state-staging/intermediates/41fd592c-48ce-48c2-bb94-5ed47795f68b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8SDm7JR9Lr5k8CRbSJ0R2/L6J0y30uLS/XkBNMdspCI=", + "crlite_enrolled": false, + "id": "eb92330a-1676-40ed-b839-e3f93759049e", + "last_modified": 1666727868362 + }, + { + "schema": 1666727390728, + "derHash": "BS8CbB5i5pekeRPgfOJN6DyIG/MAo8wkzAydRuyCR7s=", + "subject": "CN=TBS X509 CA pro hosting 2,OU=TBS INTERNET CA,O=TBS INTERNET,L=Caen,ST=Calvados,C=FR", + "subjectDN": "MIGEMQswCQYDVQQGEwJGUjERMA8GA1UECBMIQ2FsdmFkb3MxDTALBgNVBAcTBENhZW4xFTATBgNVBAoTDFRCUyBJTlRFUk5FVDEYMBYGA1UECxMPVEJTIElOVEVSTkVUIENBMSIwIAYDVQQDExlUQlMgWDUwOSBDQSBwcm8gaG9zdGluZyAy", + "whitelist": false, + "attachment": { + "hash": "8600fd5faf8d46cf045e41c1cb02882426e8db6506116315f927b9323814a2c2", + "size": 2162, + "filename": "XVLXL83TODHCORI1qq2fNoRUOUkHLj4GCQZqTjJ6v8Q=.pem", + "location": "security-state-staging/intermediates/9b7489bf-1a17-48c2-916f-f9034694b3d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XVLXL83TODHCORI1qq2fNoRUOUkHLj4GCQZqTjJ6v8Q=", + "crlite_enrolled": false, + "id": "703fcb58-2045-498e-8372-12eacd1a62f2", + "last_modified": 1666727868348 + }, + { + "schema": 1666727421514, + "derHash": "hGT/6ymjrRfp1vRuSMXHJ8nXho2sAmK/JW4gs8+1WII=", + "subject": "CN=Sectigo SHA-256 EV Secure Server CA 2,O=Sectigo Limited,C=GB", + "subjectDN": "MFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gU0hBLTI1NiBFViBTZWN1cmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "cdc38abcb47f09c937b5eb3ed32a230a1fc02eae4270fb497306252f60c160d4", + "size": 1674, + "filename": "ZFA27ke4TClASV6I0jW87lSWFo_b8drlh9D36Vzid1s=.pem", + "location": "security-state-staging/intermediates/e598453a-286a-4210-bd2d-a634aa6389bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZFA27ke4TClASV6I0jW87lSWFo/b8drlh9D36Vzid1s=", + "crlite_enrolled": false, + "id": "4baeb4c9-0512-4788-9c28-a605892d521a", + "last_modified": 1666727868335 + }, + { + "schema": 1666727399253, + "derHash": "EoYXPm8BAve90ywvgwkQlTSJvyLBYpXYTdkKPaE3Fko=", + "subject": "CN=COMODO SHA-2 Pro Series Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGLMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDExMC8GA1UEAxMoQ09NT0RPIFNIQS0yIFBybyBTZXJpZXMgU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "78d891f3a6803f34cfeb3d0156ffa523900ce69f70c5b9986aa0b9b239759a7d", + "size": 1707, + "filename": "4gSqIYIbYqiQ1NfKCAfvkzCzqmOipsGL9CFJJdH7Ms4=.pem", + "location": "security-state-staging/intermediates/aaccdd99-d4fa-456a-9294-0e979205269a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4gSqIYIbYqiQ1NfKCAfvkzCzqmOipsGL9CFJJdH7Ms4=", + "crlite_enrolled": false, + "id": "7a35c581-a818-4cfb-853b-5d57af7148bc", + "last_modified": 1666727868321 + }, + { + "schema": 1666727376852, + "derHash": "aQ7yFLwRTvHXJr1+8D5UbNbLe+W8I/rNORJjv/tXlwo=", + "subject": "CN=cPanel High Assurance RSA Certification Authority,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MH8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMTowOAYDVQQDEzFjUGFuZWwgSGlnaCBBc3N1cmFuY2UgUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "02ce58ad39af93c07684a4944d1ab5b2a0e3df52845ada7b0c7c801e79712796", + "size": 2138, + "filename": "btauesE0qNPMvYa1wCIsn_jg_om1G73FWer38Ur7kQE=.pem", + "location": "security-state-staging/intermediates/ba8ae507-f7c6-48ab-b7e8-5724a312717a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "btauesE0qNPMvYa1wCIsn/jg/om1G73FWer38Ur7kQE=", + "crlite_enrolled": false, + "id": "9807de89-f47f-4d05-8b83-418acc4330aa", + "last_modified": 1666727868307 + }, + { + "schema": 1666727416159, + "derHash": "YoBqPZQu1PbAkaEx5jY7E43WOlw830QT0AzIoMMzqfg=", + "subject": "CN=CERTDATA SSL OV CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MG4xCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExMDAuBgNVBAMMJ0NFUlREQVRBIFNTTCBPViBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "efcb5261a8c01fdab995cd7132c3af62c4131a2aa418f3c088ced17c5f8f28c1", + "size": 2129, + "filename": "j329Y60dfipsfu2WQjIJzo3bfYjWBMO0L6-erwtRMEM=.pem", + "location": "security-state-staging/intermediates/9185894f-c334-443f-a00e-5abbb5e69772.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "j329Y60dfipsfu2WQjIJzo3bfYjWBMO0L6+erwtRMEM=", + "crlite_enrolled": false, + "id": "cbdd61d4-cfd4-4cc5-bed0-c07b21c7b361", + "last_modified": 1666727868288 + }, + { + "schema": 1666727372741, + "derHash": "PF45H9jKOyFhRJViSuWynzntBmEktZRDj5SUs0NDx5U=", + "subject": "CN=EuropeanSSL High Assurance Server CA 2,O=EUNETIC GmbH,L=Durmersheim,ST=Baden-Württemberg,C=DE", + "subjectDN": "MIGIMQswCQYDVQQGEwJERTEbMBkGA1UECAwSQmFkZW4tV8O8cnR0ZW1iZXJnMRQwEgYDVQQHEwtEdXJtZXJzaGVpbTEVMBMGA1UEChMMRVVORVRJQyBHbWJIMS8wLQYDVQQDEyZFdXJvcGVhblNTTCBIaWdoIEFzc3VyYW5jZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "5464f66ed6e738614353c26984e502e03754193da19418723dbfbe3870e51502", + "size": 2166, + "filename": "HhU8wZ-rl8VgXaRRv_XdnUCKI8eLRVXfTeY77HdaQ3Y=.pem", + "location": "security-state-staging/intermediates/4a71f925-90b9-453b-9980-d39183584fa8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HhU8wZ+rl8VgXaRRv/XdnUCKI8eLRVXfTeY77HdaQ3Y=", + "crlite_enrolled": false, + "id": "78cebd0b-8491-4b2c-9cd1-7336300e2870", + "last_modified": 1666727868274 + }, + { + "schema": 1666727347850, + "derHash": "V9i9zViVWlWQpXxqr6WB7ZuWvtdvr+6WmxORh8Wocsc=", + "subject": "CN=Sectigo RSA Extended Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGRMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxOTA3BgNVBAMTMFNlY3RpZ28gUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0731b48fd8b6e6102210bf1fd0d98270be9195106a3c5ad81c0ffc71486eab12", + "size": 2211, + "filename": "hEJ5FNYP7ZpNILySZtJgiP6UtW3ClYUTRFxXGqWSWQ0=.pem", + "location": "security-state-staging/intermediates/e4022501-78ab-4377-b061-0999832c2a8d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hEJ5FNYP7ZpNILySZtJgiP6UtW3ClYUTRFxXGqWSWQ0=", + "crlite_enrolled": false, + "id": "a13ddd13-8e9c-4a33-9d19-dc557a327020", + "last_modified": 1666727868260 + }, + { + "schema": 1666727339046, + "derHash": "/yAcoSyHpvDLpkPpq7PJVMkVWt0xObLi6u0RS/n3XTE=", + "subject": "CN=COMODO RSA Domain Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "0e5888672dc235ecb49291515f1535331145bafac3937935291ca7a7503b3cfe", + "size": 2154, + "filename": "EULHwYvGhknyznoBvyvgbidiBH3JX3eFHHlIO3YK8Ek=.pem", + "location": "security-state-staging/intermediates/5c8056e2-7f02-452a-a514-695f16f72486.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EULHwYvGhknyznoBvyvgbidiBH3JX3eFHHlIO3YK8Ek=", + "crlite_enrolled": false, + "id": "4e1f664c-f28d-4b71-95e8-c89ac5edb71e", + "last_modified": 1666727868247 + }, + { + "schema": 1666727332312, + "derHash": "5/vS2obeMa5/4FHGQ/xF0x5nRUsqXNjkPG1lZyOujOo=", + "subject": "CN=FujiSSL SHA2 Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for Nijimo K.K.,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MIG+MQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTClNoaWJ1eWEta3UxFDASBgNVBAoTC05pamltbyBLLksuMTowOAYDVQQLEzFDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIE5pamltbyBLLksuMTgwNgYDVQQDEy9GdWppU1NMIFNIQTIgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a567dd5784eff01a33001e18df388f67b5dd570399381de60e4fe62728f2a255", + "size": 2268, + "filename": "yGo0I_1jRNcQKZcAyqa4MRLItXyEYd7pWVmh6m8y4C4=.pem", + "location": "security-state-staging/intermediates/bc139525-fefe-47c7-b8ea-766642f7cc43.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yGo0I/1jRNcQKZcAyqa4MRLItXyEYd7pWVmh6m8y4C4=", + "crlite_enrolled": false, + "id": "492de4ea-6076-4928-acd7-e2221e562cc9", + "last_modified": 1666727868233 + }, + { + "schema": 1666727371446, + "derHash": "UrbEsFs8Uhd0VXzVPupZVvNryUxktm2Z6KcpH8L8nDY=", + "subject": "CN=上海锐成信息科技有限公司 EV CA,O=上海锐成信息科技有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTkuIrmtbfplJDmiJDkv6Hmga/np5HmioDmnInpmZDlhazlj7gxMzAxBgNVBAMMKuS4iua1t+mUkOaIkOS/oeaBr+enkeaKgOaciemZkOWFrOWPuCBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "456340708d733577deea09386baf7b6fd717277f01b8273bdfc1a0d49cf3d3c2", + "size": 2158, + "filename": "9H6Ec6Gyc8uSo946YlOcaYTeoFYZA6Lk4LjvzAtZwR0=.pem", + "location": "security-state-staging/intermediates/43a81eb6-3a17-4fa9-8541-c938b45c9e82.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9H6Ec6Gyc8uSo946YlOcaYTeoFYZA6Lk4LjvzAtZwR0=", + "crlite_enrolled": false, + "id": "48f3c215-b393-45fe-b646-b68ca8693963", + "last_modified": 1666727868220 + }, + { + "schema": 1666727374460, + "derHash": "GtUWhBmK+R6iQ525x5ID9Hp0nr2JYnm3fcYn+D/+SdY=", + "subject": "CN=Network Solutions DV Server CA 2,O=Network Solutions L.L.C.,L=Herndon,ST=VA,C=US", + "subjectDN": "MHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJWQTEQMA4GA1UEBxMHSGVybmRvbjEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMSkwJwYDVQQDEyBOZXR3b3JrIFNvbHV0aW9ucyBEViBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "d4e844b1ea820ca81afe65e7e5eab090d6af0398d4f1d370cbf67cfe921959f1", + "size": 2150, + "filename": "G8g19iXohPy9KZGriQ807A59lcylMu81Zi8hXz5xxsM=.pem", + "location": "security-state-staging/intermediates/1c593191-2bde-410c-8ede-a0142de804b0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G8g19iXohPy9KZGriQ807A59lcylMu81Zi8hXz5xxsM=", + "crlite_enrolled": false, + "id": "7690b171-550a-4227-a93a-ff626b9aa157", + "last_modified": 1666727868206 + }, + { + "schema": 1666727346096, + "derHash": "puYUXPpzIrIt1vuvHfgzN4jbefGbEbaVz8kHtA0paQg=", + "subject": "CN=PSW GROUP (RSA) OV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChSU0EpIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "d8d7d33113a29a05f3c08cf4a94720d7ca7bdc256039cf99e2f2659b7bb9aa31", + "size": 2081, + "filename": "XJxD3hWuTDkFb4_QXFPChkZ3hlp9UIhypa5Cf40iwOE=.pem", + "location": "security-state-staging/intermediates/bca1cbc9-9f70-49d7-970e-bb746d783aee.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XJxD3hWuTDkFb4/QXFPChkZ3hlp9UIhypa5Cf40iwOE=", + "crlite_enrolled": false, + "id": "18bac00a-0100-4011-8837-7a88eda6f181", + "last_modified": 1666727868193 + }, + { + "schema": 1666727344032, + "derHash": "usTwPcY1va4Gf3v+d6sG2ItbpGaLD0YVflPWypMQB38=", + "subject": "CN=TrustSign RSA EV CA,OU=Controlled by Sectigo exclusively for Ziwit,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MIGZMQswCQYDVQQGEwJGUjEQMA4GA1UECBMHSGVyYXVsdDEUMBIGA1UEBxMLTW9udHBlbGxpZXIxDjAMBgNVBAoTBVppd2l0MTQwMgYDVQQLEytDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIFppd2l0MRwwGgYDVQQDExNUcnVzdFNpZ24gUlNBIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "bd9982b9e609f512f29dc84c817700aa3ab857c9760422ef8c7afeaefde20fbd", + "size": 2219, + "filename": "8o6U66RqF4Lhi_AFVcFBCBGggA7zxR6NC-m3h-ZT7LQ=.pem", + "location": "security-state-staging/intermediates/2aa98638-17b9-4220-a196-ca09194df22f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8o6U66RqF4Lhi/AFVcFBCBGggA7zxR6NC+m3h+ZT7LQ=", + "crlite_enrolled": false, + "id": "c5c35f82-5579-4f13-bc24-6e9a03dc2ae3", + "last_modified": 1666727868179 + }, + { + "schema": 1666727413203, + "derHash": "RZCrAULHhnEfVuh9+B6/mJGngEPNaNBwKUM/NRjsdss=", + "subject": "CN=WebSpace-Forum Essential CA II,O=WebSpace-Forum e.K.,L=Berlin,ST=Berlin,C=DE", + "subjectDN": "MHYxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEcMBoGA1UEChMTV2ViU3BhY2UtRm9ydW0gZS5LLjEnMCUGA1UEAxMeV2ViU3BhY2UtRm9ydW0gRXNzZW50aWFsIENBIElJ", + "whitelist": false, + "attachment": { + "hash": "dcfbd2676910e5589b335c5be968b4cb3dcb3d6c8f52742c1882baa24ad9642f", + "size": 2138, + "filename": "JLiByOVnWd2f8bAC0L_puQNtFwBfGt4XaQ6BRcqgrQ4=.pem", + "location": "security-state-staging/intermediates/04ef33c1-c720-4d0f-877c-45a3b0231711.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JLiByOVnWd2f8bAC0L/puQNtFwBfGt4XaQ6BRcqgrQ4=", + "crlite_enrolled": false, + "id": "a4afd380-aadd-42d1-bc83-e5e3fdf9f9ab", + "last_modified": 1666727868165 + }, + { + "schema": 1666727397297, + "derHash": "Fk4Ac6dSAKWBaZG9wXxSbrohJx9xNXRGmE7SyApZ/Vo=", + "subject": "CN=AlpiroSSL RSA OV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgUlNBIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "0953611231b9a449df1cdc37d6f9fc3956df6e7f399801d1333af19853486cf4", + "size": 2414, + "filename": "1hrgtfhRJ0sL7i_z-MTaLFvHn7lkcHEPtbsneJ6QMSY=.pem", + "location": "security-state-staging/intermediates/33b37bc4-a7cf-4345-93de-aa233d12e085.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1hrgtfhRJ0sL7i/z+MTaLFvHn7lkcHEPtbsneJ6QMSY=", + "crlite_enrolled": false, + "id": "2b178feb-295e-4a86-a46c-619f69548a88", + "last_modified": 1666727868150 + }, + { + "schema": 1666727446730, + "derHash": "wwvJZn4nOTeDPDlq2F7jJd5jP0/MxRSDQXqcik4zs50=", + "subject": "CN=Baidu\\, Inc. EV CA,O=Baidu\\, Inc.,C=CN", + "subjectDN": "MD8xCzAJBgNVBAYTAkNOMRQwEgYDVQQKEwtCYWlkdSwgSW5jLjEaMBgGA1UEAxMRQmFpZHUsIEluYy4gRVYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "89b5ae9e5e8beccfdc0e78210f4d4b07c2e70ba1ebf26ce9c3adda2e054e1b84", + "size": 2097, + "filename": "onoL2w__80DVMiDBAhLw1xZS_BfLNB7xpPDNOU-t5vE=.pem", + "location": "security-state-staging/intermediates/a4aadf00-b0fb-47a3-af3b-fee538364ce3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "onoL2w//80DVMiDBAhLw1xZS/BfLNB7xpPDNOU+t5vE=", + "crlite_enrolled": false, + "id": "82b69412-f85f-49a5-9ecb-bdc8865baa66", + "last_modified": 1666727868136 + }, + { + "schema": 1666727358448, + "derHash": "vaRnKkA8okUcdNfpPnpOcantIqKozNb5H4/prg5sqIE=", + "subject": "CN=Sectigo SHA-256 OV Secure Server CA 2,O=Sectigo Limited,C=GB", + "subjectDN": "MFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gU0hBLTI1NiBPViBTZWN1cmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "cd90be927b8b82f63d94bc710f94b15ae502edc046ec8a850e6036906b6fad9c", + "size": 1634, + "filename": "XB8O6m4KrSEF6ajZf52dFtzGpdc9UJGKYYbXFCuXf7c=.pem", + "location": "security-state-staging/intermediates/9e41ecb3-20c9-4bb7-9127-b426f9d84898.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XB8O6m4KrSEF6ajZf52dFtzGpdc9UJGKYYbXFCuXf7c=", + "crlite_enrolled": false, + "id": "0ccd3e49-f361-4f0f-adf4-1241c32a754f", + "last_modified": 1666727868122 + }, + { + "schema": 1666727383397, + "derHash": "26wvrk+MlJsaMM+HHqtYlX9kILeXpRJVBrjO3Uj114Q=", + "subject": "CN=GEANT EV RSA CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "MEQxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMRowGAYDVQQDExFHRUFOVCBFViBSU0EgQ0EgNA==", + "whitelist": false, + "attachment": { + "hash": "2c3d1eb12b83acc69c9e55cf9ff90e5ba7d32b24bfb94f7e40c6ad3d2471d299", + "size": 2450, + "filename": "BPjQDgce-sDBA1ay4Jv5JRXoEJZUq9x6xkxGPweoK8o=.pem", + "location": "security-state-staging/intermediates/6f75438b-fb40-46b7-8abc-a713c6d8dfeb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BPjQDgce+sDBA1ay4Jv5JRXoEJZUq9x6xkxGPweoK8o=", + "crlite_enrolled": false, + "id": "83a0342f-2377-4ea0-9425-b6778fa1bcf2", + "last_modified": 1666727868109 + }, + { + "schema": 1666727341290, + "derHash": "V8B4jlHXvRRHDPnk830z+8EEx3UB69lXFm0U9ExRbDA=", + "subject": "CN=eMudhra RSA Organization Validation Secure Server CA,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIGbMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxPTA7BgNVBAMTNGVNdWRocmEgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c4460c62c17acbf05928d10e4f51b3e01068ae50e190d566181fa632366539d9", + "size": 2190, + "filename": "D9NZ6oWac3feaXj7Ii5NCfiL5RG3s0UIV08ecGxPgtc=.pem", + "location": "security-state-staging/intermediates/9fdd9f38-fe57-45bf-b9a8-ca67f8482a87.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D9NZ6oWac3feaXj7Ii5NCfiL5RG3s0UIV08ecGxPgtc=", + "crlite_enrolled": false, + "id": "f74d8d45-b8fa-4317-8f10-3bb8527db68d", + "last_modified": 1666727868095 + }, + { + "schema": 1666727387445, + "derHash": "dFCEYqXCpPJb3LVcslkxCgVrfWI92OVTUfO0Lx0ySGs=", + "subject": "CN=TrustSign RSA OV CA,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MGMxCzAJBgNVBAYTAkZSMRAwDgYDVQQIEwdIZXJhdWx0MRQwEgYDVQQHEwtNb250cGVsbGllcjEOMAwGA1UEChMFWml3aXQxHDAaBgNVBAMTE1RydXN0U2lnbiBSU0EgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c7e0bc715517a5560ec51e4147a420b29c9f9bf21f1b51ebc516b09c134b7441", + "size": 2117, + "filename": "chB881BfDWAiJ-J0Xbjx2_V5kxVs4F8Bof_rFzQm8kw=.pem", + "location": "security-state-staging/intermediates/ffd1a0a7-7bce-4a66-a27c-a5df7c56bebc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "chB881BfDWAiJ+J0Xbjx2/V5kxVs4F8Bof/rFzQm8kw=", + "crlite_enrolled": false, + "id": "d94f00fe-6dc6-4354-8778-dc6d359bb6fd", + "last_modified": 1666727868082 + }, + { + "schema": 1666727345914, + "derHash": "w6qJpPn3DBiw/qXN/bNeSH2gLBCrbT9g3nmtTJSuKEw=", + "subject": "CN=GoGetSSL RSA OV CA,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MEwxCzAJBgNVBAYTAkxWMQ0wCwYDVQQHEwRSaWdhMREwDwYDVQQKEwhHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgUlNBIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "1a9eaa58a65921d02d8b5b41648c5e0405fbb585ad3428a310f709b0a6506998", + "size": 2085, + "filename": "hvk5zQ4_wW8fexIq-RQp19DbNh0mDLs7XtlPnYskzOc=.pem", + "location": "security-state-staging/intermediates/86fea854-2e75-44e4-9aec-67fda826e567.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hvk5zQ4/wW8fexIq+RQp19DbNh0mDLs7XtlPnYskzOc=", + "crlite_enrolled": false, + "id": "a52cd6a3-5d26-4822-83f6-17bddca5cec8", + "last_modified": 1666727868068 + }, + { + "schema": 1666727417516, + "derHash": "hopBQA1CWpONfsBsKQlzALEnBttHO2p5UFZfmNcE8ZY=", + "subject": "CN=sslTrus (RSA) OV CA,O=sslTrus,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMRAwDgYDVQQKEwdzc2xUcnVzMRwwGgYDVQQDExNzc2xUcnVzIChSU0EpIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "16ee822679e93c8bfd44da4e2ebab15646ed3c37dc1cc1818570090bea10f9f1", + "size": 2056, + "filename": "ih0R75GcT4FHFDlYK3QnWp_EQJYdcLVZ69u4d3mIJwE=.pem", + "location": "security-state-staging/intermediates/dead9989-c5e6-4ff8-bfbc-f613deccc0e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ih0R75GcT4FHFDlYK3QnWp/EQJYdcLVZ69u4d3mIJwE=", + "crlite_enrolled": false, + "id": "0a9ac0c5-27fb-4393-9cc3-fe51d343a943", + "last_modified": 1666727868055 + }, + { + "schema": 1666727381345, + "derHash": "8wHcxRgblBCQqRd+n/q0eVt66afSNg6m/aPM4M8N5Jo=", + "subject": "CN=TrustAsia RSA DV SSL Server CA,O=TrustAsia Technologies\\, Inc.,ST=Shanghai,C=CN", + "subjectDN": "MHAxCzAJBgNVBAYTAkNOMREwDwYDVQQIEwhTaGFuZ2hhaTElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEnMCUGA1UEAxMeVHJ1c3RBc2lhIFJTQSBEViBTU0wgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "d55aa58afdb470a7526b55f4c89dd6aa075d835ddb25b7666f716a3dc686e0bc", + "size": 2133, + "filename": "tBDWSaHOpgXo2ifK2nGQRAAdZWCfbS8D7IHof7Oj_VA=.pem", + "location": "security-state-staging/intermediates/13ba631b-2142-426f-81ac-5609bb202501.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tBDWSaHOpgXo2ifK2nGQRAAdZWCfbS8D7IHof7Oj/VA=", + "crlite_enrolled": false, + "id": "7c9d8f42-4058-482d-98a9-e4d088da5752", + "last_modified": 1666727868041 + }, + { + "schema": 1666727346452, + "derHash": "umub+7+WmK+R60xpNGZBQYGgACrVKP8VSUbVhqpdlEU=", + "subject": "CN=GoGetSSL ECC EV CA,OU=Controlled by COMODO CA exclusively for GoGetSSL,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MIGHMQswCQYDVQQGEwJMVjENMAsGA1UEBxMEUmlnYTERMA8GA1UEChMIR29HZXRTU0wxOTA3BgNVBAsTMENvbnRyb2xsZWQgYnkgQ09NT0RPIENBIGV4Y2x1c2l2ZWx5IGZvciBHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgRUNDIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "da2e49fc6c88f8026e7bf406dc6f33f19fae4387a8ebeba1fba5e19aa5c5370f", + "size": 1362, + "filename": "og9uiCTdRwcFKbx11dQHwWx4x4btYdfYz4pKq5LpcoY=.pem", + "location": "security-state-staging/intermediates/5f63133a-aa80-47b1-ad70-20a5c8fd6a13.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "og9uiCTdRwcFKbx11dQHwWx4x4btYdfYz4pKq5LpcoY=", + "crlite_enrolled": false, + "id": "b5ba2e29-12c1-49a0-b6f0-cb0cec5ada1b", + "last_modified": 1666727868027 + }, + { + "schema": 1666727447758, + "derHash": "F1LCGdAzcBm8jBk0fJ8rvjcIH6bMGxsJoDdY5gwwfgk=", + "subject": "CN=Gehirn Managed Certification Authority - RSA DV,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIGCMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTgwNgYDVQQDEy9HZWhpcm4gTWFuYWdlZCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIFJTQSBEVg==", + "whitelist": false, + "attachment": { + "hash": "f45447da4dda753e728c7f9a959cb7a987ab3a13f4dafaae6e345dbc26295f62", + "size": 2158, + "filename": "SWuXVKtSHOw5ZPbQM2Hw49GYwfpJkXf6azkkNwpZ9TQ=.pem", + "location": "security-state-staging/intermediates/83b2df18-a42c-41f9-91a8-4f6c0f342616.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SWuXVKtSHOw5ZPbQM2Hw49GYwfpJkXf6azkkNwpZ9TQ=", + "crlite_enrolled": false, + "id": "914c35dd-4fbe-4324-8b26-1ca26a95566c", + "last_modified": 1666727868013 + }, + { + "schema": 1666727379610, + "derHash": "mgaeR4zTQUSf8ot8d1LFSuz6usWOS1AoVhZJq0gggNs=", + "subject": "CN=DigitalTrust Secure CA G4 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MGUxCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjE3MDUGA1UEAwwuRGlnaXRhbFRydXN0IFNlY3VyZSBDQSBHNCAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "b5757ae6f157c6b3aeaba3e486cb53b3dd4fb4e504f71f3ab6b1d42817254f0a", + "size": 2467, + "filename": "R683Qs6upExakQVDH-vmwK67rfyDMxBotLmYh7TobVA=.pem", + "location": "security-state-staging/intermediates/abdca6cb-ec0b-43a3-9f0e-60a958965850.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "R683Qs6upExakQVDH+vmwK67rfyDMxBotLmYh7TobVA=", + "crlite_enrolled": false, + "id": "4e2db494-94b2-4cd4-989d-2ef9f52084a2", + "last_modified": 1666727867999 + }, + { + "schema": 1666727362933, + "derHash": "Ie5tT1/uSFrHoDXx1l3i/E3Q7SiXrNwqO2ecBNRZmug=", + "subject": "CN=DOMENY SSL EV Certification Authority,OU=Controlled by COMODO exclusively for DOMENY.PL sp. z o.o,O=DOMENY.PL sp. z o.o,L=Kraków,ST=Małopolskie,C=PL", + "subjectDN": "MIHHMQswCQYDVQQGEwJQTDEVMBMGA1UECAwMTWHFgm9wb2xza2llMRAwDgYDVQQHDAdLcmFrw7N3MRwwGgYDVQQKExNET01FTlkuUEwgc3AuIHogby5vMUEwPwYDVQQLEzhDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgRE9NRU5ZLlBMIHNwLiB6IG8ubzEuMCwGA1UEAxMlRE9NRU5ZIFNTTCBFViBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "f3d76494c4464919cce63ecb05bf14fb950df9c35a2cf3ec14c5145510e1f8a7", + "size": 2292, + "filename": "QKMgYhV5InRJvYzUJ1ylX-BvwC9fUdLVL_e6cCnbdGM=.pem", + "location": "security-state-staging/intermediates/8de6e42d-be0e-493e-aa2b-55eca748c560.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QKMgYhV5InRJvYzUJ1ylX+BvwC9fUdLVL/e6cCnbdGM=", + "crlite_enrolled": false, + "id": "47e09ae9-961e-4b44-84d8-2c5399f6d183", + "last_modified": 1666727867986 + }, + { + "schema": 1666727442801, + "derHash": "J38PyiY+EoVtuqTdCt8EIE9/mM/XLOnJOAfjd+NMiHY=", + "subject": "CN=GEANT eScience SSL CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "MEoxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMSAwHgYDVQQDExdHRUFOVCBlU2NpZW5jZSBTU0wgQ0EgNA==", + "whitelist": false, + "attachment": { + "hash": "e5cb12dfa1e08624cc4353261d43393fb397510f8120246c1a493092af4a2a71", + "size": 2458, + "filename": "aMvT7rta1tsjwjUiL86lNQhPARjlTSPwT-vUCzjJnTM=.pem", + "location": "security-state-staging/intermediates/6014506f-4f70-4159-bc59-1de9a8d1db3b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aMvT7rta1tsjwjUiL86lNQhPARjlTSPwT+vUCzjJnTM=", + "crlite_enrolled": false, + "id": "f514ceab-cecf-479f-9d02-3dc6a24fe7c4", + "last_modified": 1666727867973 + }, + { + "schema": 1666727364475, + "derHash": "FDY4IlF+KYmwh9DJpC7vE0p3S9CyoGZNRVr3hLwI9NA=", + "subject": "CN=Trusted Secure Certificate Authority 5,O=Corporation Service Company,L=Wilmington,ST=DE,C=US", + "subjectDN": "MIGGMQswCQYDVQQGEwJVUzELMAkGA1UECBMCREUxEzARBgNVBAcTCldpbG1pbmd0b24xJDAiBgNVBAoTG0NvcnBvcmF0aW9uIFNlcnZpY2UgQ29tcGFueTEvMC0GA1UEAxMmVHJ1c3RlZCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDU=", + "whitelist": false, + "attachment": { + "hash": "7458f386d39757872b85e956d2d7a16cb8599948efbd795f94e276b54fc5bed2", + "size": 2166, + "filename": "PzYLl5g5RZkQsPcG4YUX6vzXQf86rOUEcSAhn7JxzNo=.pem", + "location": "security-state-staging/intermediates/16cc4ad6-fd0a-4bd1-b425-0ed7e348bd1f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PzYLl5g5RZkQsPcG4YUX6vzXQf86rOUEcSAhn7JxzNo=", + "crlite_enrolled": false, + "id": "8d63c955-d419-4e2a-8ca5-c4a11735a8f7", + "last_modified": 1666727867960 + }, + { + "schema": 1666727420826, + "derHash": "G095TXcNxuBdx6QVhFD4JZVy621Z3mZ6PD++CNoBJIE=", + "subject": "CN=Global Trust CA - OV (RSA),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBPViAoUlNBKQ==", + "whitelist": false, + "attachment": { + "hash": "511f786505e7e1d59a19ac0d638f3174ac6afa6be8ecac88a6634e9d0338255e", + "size": 2089, + "filename": "K6VGZ_ktzZYufpj5GSQX1fCds5dLtjs5vlSRcV-Oe8U=.pem", + "location": "security-state-staging/intermediates/6b64ec36-5d65-40d2-a2ce-778dd4f8ebe8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "K6VGZ/ktzZYufpj5GSQX1fCds5dLtjs5vlSRcV+Oe8U=", + "crlite_enrolled": false, + "id": "952d0688-5510-45de-b63d-badbfa5083bf", + "last_modified": 1666727867947 + }, + { + "schema": 1666727406652, + "derHash": "NFcQZ1JAAhKQOjVFyjsu84SkVpcr2VHY2EDBsKN576E=", + "subject": "CN=Sectigo ECC Organization Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGVMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxPTA7BgNVBAMTNFNlY3RpZ28gRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "920416a7c8363586e1e4e1cd186f5cd60bbafe3112dbe05e77c143dcee8dceed", + "size": 1337, + "filename": "NodEgLRdUtj_Cm5LdQFzr6BIONhbXlzcV7NAqMo879U=.pem", + "location": "security-state-staging/intermediates/5f102271-46cb-45b9-9fa5-332266dea2af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NodEgLRdUtj/Cm5LdQFzr6BIONhbXlzcV7NAqMo879U=", + "crlite_enrolled": false, + "id": "09c8c9fe-00fc-4e04-ad73-908dd07f3a64", + "last_modified": 1666727867934 + }, + { + "schema": 1666727444830, + "derHash": "PtE/nRnlb1uhSSOFyoE/fdY7vrPPXFbbXDCc9OqpAXE=", + "subject": "CN=AlpiroSSL RSA EV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgUlNBIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "8f4c4cb0339ded214c0da6e42a512851fe2e0a20c8b7d05f114c65e6c28e1d2a", + "size": 2442, + "filename": "LDLs3_RixO6BHgKBiz13AqqAeJBFICakKMCnDQ1sQ04=.pem", + "location": "security-state-staging/intermediates/921ccf85-810d-4dac-aa21-7f10573415c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LDLs3/RixO6BHgKBiz13AqqAeJBFICakKMCnDQ1sQ04=", + "crlite_enrolled": false, + "id": "59adfb88-3b2d-4b3a-a8d5-1cdd21d4ea0f", + "last_modified": 1666727867921 + }, + { + "schema": 1666727398289, + "derHash": "Zc+Tj9n7NCAMn235Pe1zqltxw2nMdKfEkfDkeh1edL4=", + "subject": "CN=MarketWare Server CA 2,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,L=Lisboa,ST=Lisboa,C=PT", + "subjectDN": "MIGPMQswCQYDVQQGEwJQVDEPMA0GA1UECBMGTGlzYm9hMQ8wDQYDVQQHEwZMaXNib2ExPTA7BgNVBAoMNE1hcmtldFdhcmUgLSBTb2x1w6fDtWVzIHBhcmEgTWVyY2Fkb3MgRGlnaXRhaXMsIExkYS4xHzAdBgNVBAMTFk1hcmtldFdhcmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "360fccc13877914bb6cc44e726c3721d0ae013fa70446ce52f3cab127f1d149a", + "size": 2174, + "filename": "nDi1HIiT115x0MuCzNMX50C66I0SdrTtqTFxWKBs9TA=.pem", + "location": "security-state-staging/intermediates/a097c915-3a7b-4933-9e75-314b3d75bbc3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nDi1HIiT115x0MuCzNMX50C66I0SdrTtqTFxWKBs9TA=", + "crlite_enrolled": false, + "id": "7bca5e9e-3b2f-486e-aecd-dd57caae9675", + "last_modified": 1666727867908 + }, + { + "schema": 1666727375856, + "derHash": "myLn+gjT+MbW6fQg2PdWx5E15m0bLXCweQNyHwPb5bY=", + "subject": "CN=DOMENY SSL OV Certification Authority,O=DOMENY.PL sp. z o.o,L=Kraków,ST=Małopolskie,C=PL", + "subjectDN": "MIGEMQswCQYDVQQGEwJQTDEVMBMGA1UECAwMTWHFgm9wb2xza2llMRAwDgYDVQQHDAdLcmFrw7N3MRwwGgYDVQQKExNET01FTlkuUEwgc3AuIHogby5vMS4wLAYDVQQDEyVET01FTlkgU1NMIE9WIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "33d3f9f98124a1520b06103eebe50e0a09b8bb366086d1c8d77eda1100020b3d", + "size": 2154, + "filename": "y6RI9X1eeUkKidF2MrXM7RymKK9CUcax6TB8EJO15E4=.pem", + "location": "security-state-staging/intermediates/34069b00-c339-4852-bd44-2c39ed72658f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "y6RI9X1eeUkKidF2MrXM7RymKK9CUcax6TB8EJO15E4=", + "crlite_enrolled": false, + "id": "5c84d921-ec2c-4b16-9bf3-a681d07e7c21", + "last_modified": 1666727867895 + }, + { + "schema": 1666727418550, + "derHash": "7A6RbnSr8VDXJpuoha5sdB5IeFXP3QAhsfklDg8CQKQ=", + "subject": "CN=Digi-Sign CA Digi-SSL,O=Digi-Sign Limited,L=Dublin,ST=County Dublin,C=IE", + "subjectDN": "MHIxCzAJBgNVBAYTAklFMRYwFAYDVQQIEw1Db3VudHkgRHVibGluMQ8wDQYDVQQHEwZEdWJsaW4xGjAYBgNVBAoTEURpZ2ktU2lnbiBMaW1pdGVkMR4wHAYDVQQDExVEaWdpLVNpZ24gQ0EgRGlnaS1TU0w=", + "whitelist": false, + "attachment": { + "hash": "255f8ccc890b52f3a4ae2269e86976505b98807a97f2ca5c19edaa01b881f5a5", + "size": 2133, + "filename": "SjhfW8JOPoS_CGVE2In9gc0KODUuyPow9mwTaYtMEq0=.pem", + "location": "security-state-staging/intermediates/ebf8b4e4-74eb-4f1b-a908-599c391ef3da.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SjhfW8JOPoS/CGVE2In9gc0KODUuyPow9mwTaYtMEq0=", + "crlite_enrolled": false, + "id": "a6d65258-40ec-4881-8af4-6cbfae5e1975", + "last_modified": 1666727867882 + }, + { + "schema": 1666727382857, + "derHash": "cpVqHFql2RrGnNYxB6JaoYQ4Gd3CHFEI7B2X8yKWA84=", + "subject": "CN=USERTrust ECC Domain Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE5MDcGA1UEAxMwVVNFUlRydXN0IEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "47c195b1c6c0b23f6dc352acdf92058c4a9f9d37bfddcb59a2485881bc8bd16e", + "size": 1333, + "filename": "-orpYE2E52U2_GRvH4NTBMqU6WeiGaMHjrIRWT8JypY=.pem", + "location": "security-state-staging/intermediates/e3099de8-16db-482b-aabd-8df98a31a018.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+orpYE2E52U2/GRvH4NTBMqU6WeiGaMHjrIRWT8JypY=", + "crlite_enrolled": false, + "id": "c296c2c7-b45b-41b3-9f30-87653324b9a5", + "last_modified": 1666727867868 + }, + { + "schema": 1666727433381, + "derHash": "WWFWvCpDFet2jIJtfMoykVjK48b492BTaWW+58ra9u0=", + "subject": "CN=GENIOUS RSA Organization Validation Secure Server CA,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIGVMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxPTA7BgNVBAMTNEdFTklPVVMgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "cf0c09df35cb7acac3725bd47f6f914622953d1b04241034a2280df7977c78fd", + "size": 2186, + "filename": "TGPK3krjByA1NGzfFpJE6yAuNxJS1sOdosy48yS34bY=.pem", + "location": "security-state-staging/intermediates/487d47de-37f9-4a4c-85e2-57e7ac6ee20a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TGPK3krjByA1NGzfFpJE6yAuNxJS1sOdosy48yS34bY=", + "crlite_enrolled": false, + "id": "968a4ad7-7365-4c6e-acd4-c6ebced5104a", + "last_modified": 1666727867855 + }, + { + "schema": 1666727371604, + "derHash": "wt/7PBuwbeTBCSblF/82b5OJLV4C3G95CAJ121f9b8g=", + "subject": "CN=TI Trust Technologies EV CA,OU=Controlled by Sectigo exclusively for TI Trust Technologies,O=Telecom Italia Trust Technologies S.R.L.,L=Pomezia,ST=Roma,C=IT", + "subjectDN": "MIHNMQswCQYDVQQGEwJJVDENMAsGA1UECBMEUm9tYTEQMA4GA1UEBxMHUG9tZXppYTExMC8GA1UEChMoVGVsZWNvbSBJdGFsaWEgVHJ1c3QgVGVjaG5vbG9naWVzIFMuUi5MLjFEMEIGA1UECxM7Q29udHJvbGxlZCBieSBTZWN0aWdvIGV4Y2x1c2l2ZWx5IGZvciBUSSBUcnVzdCBUZWNobm9sb2dpZXMxJDAiBgNVBAMTG1RJIFRydXN0IFRlY2hub2xvZ2llcyBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "ff40fe6b7d8b5ac71ad0a753c583d3bbbd8349391eec0a6ae505b7e568c897c5", + "size": 2288, + "filename": "lG0jsaBDRslBBApGfnJu_ukJ5x3uoc841KUU1OMrO1E=.pem", + "location": "security-state-staging/intermediates/707d9b28-192f-4d89-88a6-90aca3a89d86.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lG0jsaBDRslBBApGfnJu/ukJ5x3uoc841KUU1OMrO1E=", + "crlite_enrolled": false, + "id": "0ca3c903-7710-434a-91d3-e883365b3718", + "last_modified": 1666727867841 + }, + { + "schema": 1666727359118, + "derHash": "vJI+nHvr5T9VyXMrFvUdU7dyAzmkSBtCgaQBP2Ypn7Y=", + "subject": "CN=SSL.com DV CA,OU=www.ssl.com,O=SSL.com,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdTU0wuY29tMRQwEgYDVQQLEwt3d3cuc3NsLmNvbTEWMBQGA1UEAxMNU1NMLmNvbSBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "cc7824c1ad989e39ca014a5f92fa4709a13e3de34ce7f067ecdb9b4c95f0323c", + "size": 2105, + "filename": "Csnhxeh1vsuMPZmVPaq9zufjZbtCA2WDcjROo3ZMJmY=.pem", + "location": "security-state-staging/intermediates/7ce8dabe-ff60-4885-9695-36590e3979d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Csnhxeh1vsuMPZmVPaq9zufjZbtCA2WDcjROo3ZMJmY=", + "crlite_enrolled": false, + "id": "6e3e2820-6bf0-4c1b-8408-6c10da3c0366", + "last_modified": 1666727867828 + }, + { + "schema": 1666727338533, + "derHash": "BPehwRJgflk5KP7HhsxgIb1uvZUAHxxpO4xLOE6xn2o=", + "subject": "CN=TrustSign BR Certification Authority (DV) 2,O=TrustSign Certificadora Dig. & Soluções Segurança da Inf. Ltda.,L=São José dos Campos,ST=São Paulo,C=BR", + "subjectDN": "MIHFMQswCQYDVQQGEwJCUjETMBEGA1UECAwKU8OjbyBQYXVsbzEeMBwGA1UEBwwVU8OjbyBKb3PDqSBkb3MgQ2FtcG9zMUswSQYDVQQKDEJUcnVzdFNpZ24gQ2VydGlmaWNhZG9yYSBEaWcuICYgU29sdcOnw7VlcyBTZWd1cmFuw6dhIGRhIEluZi4gTHRkYS4xNDAyBgNVBAMTK1RydXN0U2lnbiBCUiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAoRFYpIDI=", + "whitelist": false, + "attachment": { + "hash": "5ce5dd30457bde8708e84a8640eac9615ef70123626729952c867c8353aa36ed", + "size": 2247, + "filename": "BWKUCA6WpdRrjXTT5uzsuXJ6Gb81wQGRLjW5bslnp7k=.pem", + "location": "security-state-staging/intermediates/412937b3-4c01-460f-919e-91f7868ee734.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BWKUCA6WpdRrjXTT5uzsuXJ6Gb81wQGRLjW5bslnp7k=", + "crlite_enrolled": false, + "id": "5dc64f1b-9006-4ed5-82d0-52e326d82c8b", + "last_modified": 1666727867814 + }, + { + "schema": 1666727351979, + "derHash": "vf2E2QKq0BDE4IExd1r7c2VpTugcm4ri33ZBnz7fG+E=", + "subject": "CN=TrustSign BR Certification Authority (OV) 2,O=TrustSign Certificadora Dig. & Soluções Segurança da Inf. Ltda.,L=São José dos Campos,ST=São Paulo,C=BR", + "subjectDN": "MIHFMQswCQYDVQQGEwJCUjETMBEGA1UECAwKU8OjbyBQYXVsbzEeMBwGA1UEBwwVU8OjbyBKb3PDqSBkb3MgQ2FtcG9zMUswSQYDVQQKDEJUcnVzdFNpZ24gQ2VydGlmaWNhZG9yYSBEaWcuICYgU29sdcOnw7VlcyBTZWd1cmFuw6dhIGRhIEluZi4gTHRkYS4xNDAyBgNVBAMTK1RydXN0U2lnbiBCUiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAoT1YpIDI=", + "whitelist": false, + "attachment": { + "hash": "ac0f6646e6e159c0f132494d81240ae29ddfcf2954944473d1e91010b3363810", + "size": 2251, + "filename": "YH8nyVlvrD15SoA59nrCYN5KN2y2Xz-fJ5K-uBAfl1Y=.pem", + "location": "security-state-staging/intermediates/30fc0da8-fbce-4173-8b65-04302c315cb1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YH8nyVlvrD15SoA59nrCYN5KN2y2Xz+fJ5K+uBAfl1Y=", + "crlite_enrolled": false, + "id": "3b53f789-4bfc-430a-afa4-c2ea3dac27aa", + "last_modified": 1666727867800 + }, + { + "schema": 1666727360661, + "derHash": "djFLQcutTTf+2gYd//UKywPsgAfmXTYsZSPOGcQaaho=", + "subject": "CN=SSL Blindado 2,OU=Certificação Digital,O=Site Blindado S.A.,L=São Paulo,ST=SP,C=BR", + "subjectDN": "MIGGMQswCQYDVQQGEwJCUjELMAkGA1UECBMCU1AxEzARBgNVBAcMClPDo28gUGF1bG8xGzAZBgNVBAoTElNpdGUgQmxpbmRhZG8gUy5BLjEfMB0GA1UECwwWQ2VydGlmaWNhw6fDo28gRGlnaXRhbDEXMBUGA1UEAxMOU1NMIEJsaW5kYWRvIDI=", + "whitelist": false, + "attachment": { + "hash": "8265e0c7c71b80596278eaec5de11412ebd4eb175ac8016100097ae63f55b6b0", + "size": 2162, + "filename": "4-iqNnPo4Sq4xWzqc2uPKOdxlIAOrvZz-berOkTMMI4=.pem", + "location": "security-state-staging/intermediates/b756ba62-666b-45a3-a3b3-ba512a2f204e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4+iqNnPo4Sq4xWzqc2uPKOdxlIAOrvZz+berOkTMMI4=", + "crlite_enrolled": false, + "id": "f3fc4411-4a42-4afc-8717-54748cd4e378", + "last_modified": 1666727867785 + }, + { + "schema": 1666727427317, + "derHash": "ICi1Ih3id+8elh9OMYKjxQDuWqZ79bVE06bVil6md30=", + "subject": "CN=RU-CENTER High Assurance Services CA 2,O=RU-Center (ЗАО Региональный Сетевой Информационный Центр),L=Moscow,ST=Moscow,C=RU", + "subjectDN": "MIHNMQswCQYDVQQGEwJSVTEPMA0GA1UECBMGTW9zY293MQ8wDQYDVQQHEwZNb3Njb3cxazBpBgNVBAoMYlJVLUNlbnRlciAo0JfQkNCeINCg0LXQs9C40L7QvdCw0LvRjNC90YvQuSDQodC10YLQtdCy0L7QuSDQmNC90YTQvtGA0LzQsNGG0LjQvtC90L3Ri9C5INCm0LXQvdGC0YApMS8wLQYDVQQDEyZSVS1DRU5URVIgSGlnaCBBc3N1cmFuY2UgU2VydmljZXMgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "c3d5b50e9da6f5ea87c4018020ee825abef5d4ba7ef4d43a1bfec144b6bf3537", + "size": 2259, + "filename": "Y8qWv3kk8QX9ecEOiluI35bMi7sfrRJPiD5ZnUYgq-g=.pem", + "location": "security-state-staging/intermediates/d1085be6-1dd0-41e2-a1cc-13d4cc54b139.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y8qWv3kk8QX9ecEOiluI35bMi7sfrRJPiD5ZnUYgq+g=", + "crlite_enrolled": false, + "id": "ecdce316-7258-43ea-9af2-1a8d73dac1cb", + "last_modified": 1666727867771 + }, + { + "schema": 1666727388849, + "derHash": "4/NVu17BE8/cXhzSNAxlwm30xph5/vmAA27PQv0Jk3I=", + "subject": "CN=Network Solutions EV Server CA 3,O=Network Solutions L.L.C.,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhOZXR3b3JrIFNvbHV0aW9ucyBMLkwuQy4xKTAnBgNVBAMTIE5ldHdvcmsgU29sdXRpb25zIEVWIFNlcnZlciBDQSAz", + "whitelist": false, + "attachment": { + "hash": "84befaa8431c891c34c2c15b4cf3ad8f26d8d86016c34a232e55a05df6f87037", + "size": 2259, + "filename": "xQiXKX4gkAa0TLqAfjYeHbCD6uIQikdIAvDtjITPGTc=.pem", + "location": "security-state-staging/intermediates/7e772905-a27d-4e17-b2d5-1efe1b1f1638.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xQiXKX4gkAa0TLqAfjYeHbCD6uIQikdIAvDtjITPGTc=", + "crlite_enrolled": false, + "id": "3011f06b-7e2a-413e-9e40-e5ed7e742646", + "last_modified": 1666727867756 + }, + { + "schema": 1666727384751, + "derHash": "tm0ozXDm0bMhaI4cR+Y8Q8dqxQMSkJ1OMNePCLLtYFo=", + "subject": "CN=Gehirn Managed Certification Authority - RSA OV,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIGCMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTgwNgYDVQQDEy9HZWhpcm4gTWFuYWdlZCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIFJTQSBPVg==", + "whitelist": false, + "attachment": { + "hash": "e6e91378aa1fae5c39a7ff5b26b522feeeb79331cf9876e166caad6e99116fc3", + "size": 2158, + "filename": "sshJv9IEWk4-_PzdkseeN0rxfdOaGYVax8sZbKeQegY=.pem", + "location": "security-state-staging/intermediates/16b864b7-c11a-4f84-9684-2c41b291913a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sshJv9IEWk4+/PzdkseeN0rxfdOaGYVax8sZbKeQegY=", + "crlite_enrolled": false, + "id": "77c97703-dc62-4b47-bca7-fddc98cdf791", + "last_modified": 1666727867743 + }, + { + "schema": 1666727415130, + "derHash": "fOza/GvmGKQPG6wmRiRjhHQS1nalxtI4mVUO6Xa+Mv4=", + "subject": "CN=SSL Blindado EV 2,OU=Controlled by COMODO exclusively for Site Blindado S.A.+OU=Certificação Digital,O=Site Blindado S.A.,L=São Paulo,ST=SP,C=BR", + "subjectDN": "MIHLMQswCQYDVQQGEwJCUjELMAkGA1UECBMCU1AxEzARBgNVBAcMClPDo28gUGF1bG8xGzAZBgNVBAoTElNpdGUgQmxpbmRhZG8gUy5BLjFAMD4GA1UECxM3Q29udHJvbGxlZCBieSBDT01PRE8gZXhjbHVzaXZlbHkgZm9yIFNpdGUgQmxpbmRhZG8gUy5BLjEfMB0GA1UECwwWQ2VydGlmaWNhw6fDo28gRGlnaXRhbDEaMBgGA1UEAxMRU1NMIEJsaW5kYWRvIEVWIDI=", + "whitelist": false, + "attachment": { + "hash": "c82e01c724aae6a07109b4450194dba5f55bf0863041fccc2eceee36d9f3bf2e", + "size": 2280, + "filename": "oomAz8Bc3vA9i8i7dKzYpXEAA9hSHc_4c1uNOVpwL_s=.pem", + "location": "security-state-staging/intermediates/85592446-86f1-44d9-851f-857096e59694.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oomAz8Bc3vA9i8i7dKzYpXEAA9hSHc/4c1uNOVpwL/s=", + "crlite_enrolled": false, + "id": "7b4ae1e6-74d3-413e-b1f3-e04a0e541aa5", + "last_modified": 1666727867730 + }, + { + "schema": 1666727378589, + "derHash": "BsJ0YcBBfVE84PY0+rWt0wyI7w4lyE/eDkISW4jKBHU=", + "subject": "CN=COMODO ECC Extended Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "10bff8b6935e944d1554a191b019b596f05977105ce57198377eadb6de0c145f", + "size": 1321, + "filename": "YH3gQW7-dkqR106QZabQ1Aj-6wtLuy70aAZpWreNcBc=.pem", + "location": "security-state-staging/intermediates/b2f31954-4fc2-49aa-b47a-a4f8bc93b301.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YH3gQW7+dkqR106QZabQ1Aj+6wtLuy70aAZpWreNcBc=", + "crlite_enrolled": false, + "id": "323bbf53-f28a-4c44-98d1-30a4c14d7fd9", + "last_modified": 1666727867716 + }, + { + "schema": 1666727354531, + "derHash": "SytyugviemNHjCc6DFtS9puJ42marU86Gg6v5muZUmU=", + "subject": "CN=MarketWare - Soluções para Mercados Digitais\\, Lda. RSA EV CA,OU=Controlled by COMODO exclusively for MarketWare,O=MarketWare - Soluções para Mercados Digitais\\, Lda.,L=Lisboa,ST=Lisboa,C=PT", + "subjectDN": "MIHxMQswCQYDVQQGEwJQVDEPMA0GA1UECBMGTGlzYm9hMQ8wDQYDVQQHEwZMaXNib2ExPTA7BgNVBAoMNE1hcmtldFdhcmUgLSBTb2x1w6fDtWVzIHBhcmEgTWVyY2Fkb3MgRGlnaXRhaXMsIExkYS4xODA2BgNVBAsTL0NvbnRyb2xsZWQgYnkgQ09NT0RPIGV4Y2x1c2l2ZWx5IGZvciBNYXJrZXRXYXJlMUcwRQYDVQQDDD5NYXJrZXRXYXJlIC0gU29sdcOnw7VlcyBwYXJhIE1lcmNhZG9zIERpZ2l0YWlzLCBMZGEuIFJTQSBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "125446714e5452ed7280f9fe6cdc58e5963958feba36a8bd600c991fabb83e58", + "size": 2341, + "filename": "qCF-PPuByTYruFr6MIeAiLpr2ebZPZHBVpg73sPCJwI=.pem", + "location": "security-state-staging/intermediates/f2bbec94-183e-4537-bc0a-f49e5e7afbb1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qCF+PPuByTYruFr6MIeAiLpr2ebZPZHBVpg73sPCJwI=", + "crlite_enrolled": false, + "id": "4e1c2e5f-7a2e-4e96-b564-224f85d2097a", + "last_modified": 1666727867703 + }, + { + "schema": 1666727349915, + "derHash": "FD3+ZYMuID5NKOww+2C6c77jru4wij+NGUu2Hn0dm8A=", + "subject": "CN=FujiSSL ECC Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for Nijimo K.K.,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MIG9MQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTClNoaWJ1eWEta3UxFDASBgNVBAoTC05pamltbyBLLksuMTowOAYDVQQLEzFDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIE5pamltbyBLLksuMTcwNQYDVQQDEy5GdWppU1NMIEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "be2891a17b4b2dd6c2e6e6e3bbd4c82e9223451e780a0c6df047a1fbd776a4c3", + "size": 1427, + "filename": "2e_93k3ZMkd5hB-WURSs0iPYqDfeY-hXB4RKemS8IaE=.pem", + "location": "security-state-staging/intermediates/59c8e768-6036-4d32-bb6e-41bc3035aacb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2e/93k3ZMkd5hB+WURSs0iPYqDfeY+hXB4RKemS8IaE=", + "crlite_enrolled": false, + "id": "4529a09e-9474-40d7-92b6-43d7030ca3a2", + "last_modified": 1666727867690 + }, + { + "schema": 1666727398447, + "derHash": "CDeZ6LK5AW5EcC6/m/NpziU/4fvrZQ5d8Q70TYe/O64=", + "subject": "CN=GEANT OV ECC CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "MEQxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMRowGAYDVQQDExFHRUFOVCBPViBFQ0MgQ0EgNA==", + "whitelist": false, + "attachment": { + "hash": "e14bc2f75c4c68648138b0aeee5942cb1a3927e570e71f25447b6402a3913a4c", + "size": 1264, + "filename": "nMY4vuFVgfkmWBODcb-TSGhs4sivegdluGYPGdmc-ac=.pem", + "location": "security-state-staging/intermediates/6c3d651e-2d0f-45c6-a7b3-9e26e1af5a82.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nMY4vuFVgfkmWBODcb+TSGhs4sivegdluGYPGdmc+ac=", + "crlite_enrolled": false, + "id": "acf514c8-e6a7-4660-b14b-b5cef8358994", + "last_modified": 1666727867677 + }, + { + "schema": 1666727426827, + "derHash": "rPzAJDH0y4L1UsQ7vfj6AhSlMVxzuCyu+q2xBN4lE2A=", + "subject": "CN=ZoTrus EV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MEwxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMRkwFwYDVQQDExBab1RydXMgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "43b385094d7ae6820b2d70a02b6f5487c752c56a82a993e342bc7538a407d40b", + "size": 2239, + "filename": "VTirtCP86RRlS4YQdVifAZSzYVRDhWwJ9f4P42Sjo3A=.pem", + "location": "security-state-staging/intermediates/613d9dc4-15e0-4cb5-a32b-d19bb4c8cc2d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VTirtCP86RRlS4YQdVifAZSzYVRDhWwJ9f4P42Sjo3A=", + "crlite_enrolled": false, + "id": "05cd4ee1-e0f0-41c1-b98f-c4d9ab33edcb", + "last_modified": 1666727867664 + }, + { + "schema": 1666727438009, + "derHash": "E8a3OVvGT8Bs7yzlP2bUuYAMUs2IFu43Uy86skuBZcw=", + "subject": "CN=K Software Certificate Authority (DV) 2,O=K Software,L=Ashland,ST=KY,C=US", + "subjectDN": "MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJLWTEQMA4GA1UEBxMHQXNobGFuZDETMBEGA1UEChMKSyBTb2Z0d2FyZTEwMC4GA1UEAxMnSyBTb2Z0d2FyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgKERWKSAy", + "whitelist": false, + "attachment": { + "hash": "8a9e6bebf2554efd82de42d3c7090d73dc04c0146db9127f01bd780acb523629", + "size": 2138, + "filename": "fL2WqC2l5uf2-UsREkC4vpvX1l1w6N47XmmbDTOPHoM=.pem", + "location": "security-state-staging/intermediates/3b00536b-80c7-4c64-bba2-a88d43f70ca9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fL2WqC2l5uf2+UsREkC4vpvX1l1w6N47XmmbDTOPHoM=", + "crlite_enrolled": false, + "id": "79193cb6-7022-434a-a082-26cbef2aec89", + "last_modified": 1666727867651 + }, + { + "schema": 1666727340086, + "derHash": "N4NPpepA+/e2EZaVWWLhygVYhyQ15CBmU9P2IN2OmI4=", + "subject": "CN=GEANT OV RSA CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "MEQxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMRowGAYDVQQDExFHRUFOVCBPViBSU0EgQ0EgNA==", + "whitelist": false, + "attachment": { + "hash": "c399a283dff647782cefedbbe3d8838b4fa8b61f1244503d71634f5f8a6ddfc1", + "size": 2450, + "filename": "j0qRK9S0oUba9b4ttZdKp42Q4T2J8S4FFKPNG5FTFVA=.pem", + "location": "security-state-staging/intermediates/62baaff8-7cf1-4d21-8acf-43eb6db790d9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "j0qRK9S0oUba9b4ttZdKp42Q4T2J8S4FFKPNG5FTFVA=", + "crlite_enrolled": false, + "id": "20775ffb-1103-4907-b08a-0c493aeac016", + "last_modified": 1666727867638 + }, + { + "schema": 1666727364656, + "derHash": "kdwogfatCgATs9ROmTYaE4YvnLe+YiD8395g5IvdMXo=", + "subject": "CN=CerSign OV SSL CA,O=CerSign Technology Limited,C=CN", + "subjectDN": "ME4xCzAJBgNVBAYTAkNOMSMwIQYDVQQKExpDZXJTaWduIFRlY2hub2xvZ3kgTGltaXRlZDEaMBgGA1UEAxMRQ2VyU2lnbiBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "ab76afd7872abc782c88a96e41c3c4cf60a458fdda48c16234b9af74fdb4d66b", + "size": 2255, + "filename": "4MEFWPrhdB_pUSLUqOwqdeWoWeGI_9LylUEJnnPvwh4=.pem", + "location": "security-state-staging/intermediates/f5707965-6d23-47cc-97b4-8eb1d7e1beb8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4MEFWPrhdB/pUSLUqOwqdeWoWeGI/9LylUEJnnPvwh4=", + "crlite_enrolled": false, + "id": "46153284-3091-49a4-a840-6d9733a4ebb1", + "last_modified": 1666727867609 + }, + { + "schema": 1666727335833, + "derHash": "CgXEYnVjkN0fHV3YJ5TDAPBL54nc521+MS95DWj9OFo=", + "subject": "CN=InCommon RSA Server CA,OU=InCommon,O=Internet2,L=Ann Arbor,ST=MI,C=US", + "subjectDN": "MHYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMR8wHQYDVQQDExZJbkNvbW1vbiBSU0EgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "b12f6e751ef575ac1683efe1f647ce386bb42994433744cc73e49947b6df8ff0", + "size": 2129, + "filename": "b1JA6-4svjmZnxGjAiQY3RS0A9FtjKLCWaRlVmCPM28=.pem", + "location": "security-state-staging/intermediates/46790f31-f30a-4ebc-a8bd-affde0c28bbf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b1JA6+4svjmZnxGjAiQY3RS0A9FtjKLCWaRlVmCPM28=", + "crlite_enrolled": false, + "id": "37de3a14-4676-4734-8dba-62ddf13ebc00", + "last_modified": 1666727867596 + }, + { + "schema": 1666727334979, + "derHash": "hJZThEmAOcyUWGEDGDA/nM/EUuI2eA+93EdIVn8FoO4=", + "subject": "CN=GoGetSSL ECC DV CA,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MEwxCzAJBgNVBAYTAkxWMQ0wCwYDVQQHEwRSaWdhMREwDwYDVQQKEwhHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgRUNDIERWIENB", + "whitelist": false, + "attachment": { + "hash": "5a75e8035e419a5ae0f0099e17459edb6014ec95e7b62c3d5ceb181275e58f6f", + "size": 1244, + "filename": "v7s8gnnUIPgE_G0J6878I42tOsH3uq3oEiIBuxWKvb0=.pem", + "location": "security-state-staging/intermediates/10ca6461-fe6f-4402-8fc4-f6972c147bc2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v7s8gnnUIPgE/G0J6878I42tOsH3uq3oEiIBuxWKvb0=", + "crlite_enrolled": false, + "id": "0b3bf6a0-56f2-4b0d-a820-4bba8fff8038", + "last_modified": 1666727867582 + }, + { + "schema": 1666727392577, + "derHash": "CCf2v9u/MOoP6s8be9Bu72XbJxrNfptdJpoPLzcrIek=", + "subject": "CN=EuropeanSSL Server CA 2,O=EUNETIC GmbH,L=Durmersheim,ST=Baden-Württemberg,C=DE", + "subjectDN": "MHkxCzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRlbi1Xw7xydHRlbWJlcmcxFDASBgNVBAcTC0R1cm1lcnNoZWltMRUwEwYDVQQKEwxFVU5FVElDIEdtYkgxIDAeBgNVBAMTF0V1cm9wZWFuU1NMIFNlcnZlciBDQSAy", + "whitelist": false, + "attachment": { + "hash": "0777ef7e31ab4d9ebe86d4a6dc4821e28e3ee50c30293916120b70c77e31d7c6", + "size": 2142, + "filename": "voGNYzoU7vT-Rbv9zjL5yL6Z294ZkClnxHkt0bjiTWM=.pem", + "location": "security-state-staging/intermediates/56dc27c8-1780-43ce-8db7-d3e3f6a44dc9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "voGNYzoU7vT+Rbv9zjL5yL6Z294ZkClnxHkt0bjiTWM=", + "crlite_enrolled": false, + "id": "7bc85549-a6b2-4184-85b9-8603ff9a2a2d", + "last_modified": 1666727867569 + }, + { + "schema": 1666727403737, + "derHash": "Vi1qW0sGdGX/0Pv8m7BXVc2s9Vte5cb5ELi1PbEo9Xo=", + "subject": "CN=Sectigo Qualified Website Authentication CA E35,O=Sectigo (Europe) SL,C=ES", + "subjectDN": "MGUxCzAJBgNVBAYTAkVTMRwwGgYDVQQKExNTZWN0aWdvIChFdXJvcGUpIFNMMTgwNgYDVQQDEy9TZWN0aWdvIFF1YWxpZmllZCBXZWJzaXRlIEF1dGhlbnRpY2F0aW9uIENBIEUzNQ==", + "whitelist": false, + "attachment": { + "hash": "69b9e216fce7c5fbb78bbb7d476dbbb0d1c4cfbaa764f52c892a7e1db04d54da", + "size": 1248, + "filename": "LTu6tENFs1xZQP1NiYhwg6AMeqalq2jUuwpowf0rQuY=.pem", + "location": "security-state-staging/intermediates/047aed0f-4102-4193-9915-3c44b39c7d8b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LTu6tENFs1xZQP1NiYhwg6AMeqalq2jUuwpowf0rQuY=", + "crlite_enrolled": false, + "id": "cab24211-5bf7-4797-874a-c6627ab26cd0", + "last_modified": 1666727867555 + }, + { + "schema": 1666727331164, + "derHash": "DuACkMinHZheOL64TsrJ0Cm1WWcRnUfLy+NH8WTfS0o=", + "subject": "CN=GlobeSSL EV CA,O=CentralNic Luxembourg Sàrl,C=LU", + "subjectDN": "MEwxCzAJBgNVBAYTAkxVMSQwIgYDVQQKDBtDZW50cmFsTmljIEx1eGVtYm91cmcgU8OgcmwxFzAVBgNVBAMTDkdsb2JlU1NMIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "60a873dd05c5875ad6d0f733dce61044918b1943fbd09033401831a2de151f75", + "size": 2113, + "filename": "CxpahwRxk_rjFSnTogI-dPS7QQ16sQ0GM12A7ChwIS4=.pem", + "location": "security-state-staging/intermediates/e4fe0ba5-5fe0-4525-8aab-278ea8641c93.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CxpahwRxk/rjFSnTogI+dPS7QQ16sQ0GM12A7ChwIS4=", + "crlite_enrolled": false, + "id": "004bb13c-86c0-45a4-8a82-99ebcd525383", + "last_modified": 1666727867542 + }, + { + "schema": 1666727436452, + "derHash": "kaMhrX/TyXgSFNdvXijrDTGshml+33AOmvlMdIX1tSE=", + "subject": "CN=GoGetSSL RSA EV CA,OU=Controlled by COMODO CA exclusively for GoGetSSL,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MIGHMQswCQYDVQQGEwJMVjENMAsGA1UEBxMEUmlnYTERMA8GA1UEChMIR29HZXRTU0wxOTA3BgNVBAsTMENvbnRyb2xsZWQgYnkgQ09NT0RPIENBIGV4Y2x1c2l2ZWx5IGZvciBHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgUlNBIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "89b3c214abe9460c0c727ff1084e87f17253692f65bafb9f3006ece4f83947e1", + "size": 2198, + "filename": "kr1IBiy8TZumRDu-iU8HD2GILSEqTPUTw6G9jcHRek4=.pem", + "location": "security-state-staging/intermediates/cb63de13-8a0c-4e55-a8b5-9bb73418724e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kr1IBiy8TZumRDu+iU8HD2GILSEqTPUTw6G9jcHRek4=", + "crlite_enrolled": false, + "id": "c901cbad-1ff8-499d-9907-8843a03e2db3", + "last_modified": 1666727867529 + }, + { + "schema": 1666727425307, + "derHash": "7xULGjNxR1yLPtEdv5O/kIQL1Tlca1+mrju/p0b69Vs=", + "subject": "CN=sslTrus (ECC) OV CA,O=sslTrus,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMRAwDgYDVQQKEwdzc2xUcnVzMRwwGgYDVQQDExNzc2xUcnVzIChFQ0MpIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "c4cd661433e6286382eb00f08f7117b264f6f7e4c20afaf4c3f6f3370808cb39", + "size": 1215, + "filename": "pJxjodLJbnAgcHt9MNFsEIq5_2tWVNeIo2xYSRJE4SE=.pem", + "location": "security-state-staging/intermediates/cabd0fdf-67d0-4416-877c-56013ac22d05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pJxjodLJbnAgcHt9MNFsEIq5/2tWVNeIo2xYSRJE4SE=", + "crlite_enrolled": false, + "id": "423a7ac1-a678-4722-b682-4329a2f69a5c", + "last_modified": 1666727867515 + }, + { + "schema": 1666727391585, + "derHash": "S3BM2oDiRNQYaETw7yQrcMSxq02NiWFWjyjhLIl4TyM=", + "subject": "CN=Baidu\\, Inc. OV CA,O=Baidu\\, Inc.,C=CN", + "subjectDN": "MD8xCzAJBgNVBAYTAkNOMRQwEgYDVQQKEwtCYWlkdSwgSW5jLjEaMBgGA1UEAxMRQmFpZHUsIEluYy4gT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "6640ab717318f02a363504cd0ca9f136015c63ca090a37eeb5e3c302f387af34", + "size": 2056, + "filename": "YhO9UIj-GsxiTpBJJHAy3KMI3EVCiDbs7p2O02LNtXc=.pem", + "location": "security-state-staging/intermediates/8e5f1509-8eb4-4ed2-929f-2dc94aa4ce1a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YhO9UIj+GsxiTpBJJHAy3KMI3EVCiDbs7p2O02LNtXc=", + "crlite_enrolled": false, + "id": "4627074c-5b10-4a84-93b4-b84a6d421f42", + "last_modified": 1666727867498 + }, + { + "schema": 1666727417865, + "derHash": "dp9v8Jp0Yqwnpugt53ABPTwCEzr92+WBrvzKGwoCkYA=", + "subject": "CN=TrustSign BR Certification Authority (EV) 2,OU=Controlled by COMODO for TrustSign Certificadora Digital,O=TrustSign Certificadora Dig. & Soluções Segurança da Inf. Ltda.,L=São José dos Campos,ST=São Paulo,C=BR", + "subjectDN": "MIIBCDELMAkGA1UEBhMCQlIxEzARBgNVBAgMClPDo28gUGF1bG8xHjAcBgNVBAcMFVPDo28gSm9zw6kgZG9zIENhbXBvczFLMEkGA1UECgxCVHJ1c3RTaWduIENlcnRpZmljYWRvcmEgRGlnLiAmIFNvbHXDp8O1ZXMgU2VndXJhbsOnYSBkYSBJbmYuIEx0ZGEuMUEwPwYDVQQLEzhDb250cm9sbGVkIGJ5IENPTU9ETyBmb3IgVHJ1c3RTaWduIENlcnRpZmljYWRvcmEgRGlnaXRhbDE0MDIGA1UEAxMrVHJ1c3RTaWduIEJSIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IChFVikgMg==", + "whitelist": false, + "attachment": { + "hash": "53e1a6064d72cde24e9a929e83a1ec07595879fadd48df9cf8f4f91e06a15849", + "size": 2373, + "filename": "iKWK-FGvQxVzCQKwKOQ7Rd18KzTZXBBwxTbfJaG1qN0=.pem", + "location": "security-state-staging/intermediates/47329d36-da41-4576-a809-44f3e303a29a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iKWK+FGvQxVzCQKwKOQ7Rd18KzTZXBBwxTbfJaG1qN0=", + "crlite_enrolled": false, + "id": "beddfc3b-a262-4ab7-855b-150e374efd6e", + "last_modified": 1666727867481 + }, + { + "schema": 1666727446381, + "derHash": "3izLCHRurwKZjmyV4ko9LPtv9KPUHq+/g/1XAGby8qg=", + "subject": "CN=Network Solutions OV Server CA 2,O=Network Solutions L.L.C.,L=Herndon,ST=VA,C=US", + "subjectDN": "MHoxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJWQTEQMA4GA1UEBxMHSGVybmRvbjEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMSkwJwYDVQQDEyBOZXR3b3JrIFNvbHV0aW9ucyBPViBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "688202456d7e248919acc3a3df2daffbbf6b03ea813ff2d3427f7f1e68044a04", + "size": 2150, + "filename": "hIBbkSty62Y4PvvLLkYxEHhqoAK2s9JgMSFHh6HeR60=.pem", + "location": "security-state-staging/intermediates/d8d37306-7460-4acb-965e-ba66b1ad8f78.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hIBbkSty62Y4PvvLLkYxEHhqoAK2s9JgMSFHh6HeR60=", + "crlite_enrolled": false, + "id": "7dda0a21-f7c6-4923-83be-56706af07752", + "last_modified": 1666727867468 + }, + { + "schema": 1666727385276, + "derHash": "vbeqKPFk5LwV1pIHM7Ij7ZjlUiCj5W87Hs/QTofTC3E=", + "subject": "CN=InCommon ECC Server CA,OU=InCommon,O=Internet2,L=Ann Arbor,ST=MI,C=US", + "subjectDN": "MHYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNSTESMBAGA1UEBxMJQW5uIEFyYm9yMRIwEAYDVQQKEwlJbnRlcm5ldDIxETAPBgNVBAsTCEluQ29tbW9uMR8wHQYDVQQDExZJbkNvbW1vbiBFQ0MgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "6ba40ddb17a0c5e550d03cf27b415658441db787e87cc7efbaeacbe40e7954dd", + "size": 1293, + "filename": "8Ped1-_2NPqUB2Q-UJri8oBJaaIrldtkbw8LmkGTkrE=.pem", + "location": "security-state-staging/intermediates/b23bebad-6d65-4274-8012-f0cc243b967d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8Ped1+/2NPqUB2Q+UJri8oBJaaIrldtkbw8LmkGTkrE=", + "crlite_enrolled": false, + "id": "d876197d-e674-4e0b-8d72-41091e4b2fb3", + "last_modified": 1666727867454 + }, + { + "schema": 1666727366065, + "derHash": "51WunELBMV8n3NBkUcar4LxqrW7dwuVjXPxJGBktNEw=", + "subject": "CN=sslTrus (RSA) EV CA,O=sslTrus (上海锐成信息科技有限公司),C=CN", + "subjectDN": "MGQxCzAJBgNVBAYTAkNOMTcwNQYDVQQKDC5zc2xUcnVzICjkuIrmtbfplJDmiJDkv6Hmga/np5HmioDmnInpmZDlhazlj7gpMRwwGgYDVQQDExNzc2xUcnVzIChSU0EpIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "51291a73f466b48324d8adf66ef95553f962e87f46b667a6e5ffa84c650bc557", + "size": 2138, + "filename": "i8sF2qLB7tRF7sIqB2OmU30xW1URXbosyF-3an-ceXg=.pem", + "location": "security-state-staging/intermediates/d8030219-5a49-434f-bfbc-4e1c15244673.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i8sF2qLB7tRF7sIqB2OmU30xW1URXbosyF+3an+ceXg=", + "crlite_enrolled": false, + "id": "a2ddf25a-17ec-4605-a1b2-436d2f7b1932", + "last_modified": 1666727867441 + }, + { + "schema": 1666727384573, + "derHash": "H/7OCWgvhwJKKkSyyYex/M7jsrXPAh788jhpIkpMoVQ=", + "subject": "CN=Sectigo ECC Extended Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGRMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxOTA3BgNVBAMTMFNlY3RpZ28gRUNDIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d322692b7e1b9d1ca216bbd77e178d93796f295966e130e60932d2623f9a533e", + "size": 1374, + "filename": "tMfg7Fo2Cq1tZyZLF7qjiWh-5J3kMiUXlIyn4Vr5nX4=.pem", + "location": "security-state-staging/intermediates/85491fe0-b8b4-448f-a505-de9fe6810af4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tMfg7Fo2Cq1tZyZLF7qjiWh+5J3kMiUXlIyn4Vr5nX4=", + "crlite_enrolled": false, + "id": "057ea451-c972-4b2e-a64b-010d5c64b5ac", + "last_modified": 1666727867427 + }, + { + "schema": 1666727443480, + "derHash": "zWwQig5kHyyhIqqm0D+CZ1nK58b4AOq/dtxItnzQg84=", + "subject": "CN=COMODO ECC Domain Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "fdc9f35f8aa108a206630dc736519f9dfbfe8d377befd1434e04057351cb23b8", + "size": 1317, + "filename": "x9SZw6TwIqfmvrLZ_kz1o0Ossjmn728BnBKpUFqGNVM=.pem", + "location": "security-state-staging/intermediates/bd12ec7e-95c9-43a1-8ba0-bb79c0c971cb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x9SZw6TwIqfmvrLZ/kz1o0Ossjmn728BnBKpUFqGNVM=", + "crlite_enrolled": false, + "id": "0e9934e0-e8f1-4282-9296-be48b24cc9bf", + "last_modified": 1666727867412 + }, + { + "schema": 1666727338705, + "derHash": "WDRT7ArdhSaAHmL7hQePCySEXwtnYSgw3al+wOgp9B0=", + "subject": "CN=Trusted Secure Certificate Authority DV,O=Corporation Service Company,L=Wilmington,ST=DE,C=US", + "subjectDN": "MIGHMQswCQYDVQQGEwJVUzELMAkGA1UECBMCREUxEzARBgNVBAcTCldpbG1pbmd0b24xJDAiBgNVBAoTG0NvcnBvcmF0aW9uIFNlcnZpY2UgQ29tcGFueTEwMC4GA1UEAxMnVHJ1c3RlZCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IERW", + "whitelist": false, + "attachment": { + "hash": "6bcb6b12aac50732f84ab7b31db95cdfa7e6569d4ef32012af6ceff698cd544f", + "size": 2166, + "filename": "tMbtz8E47Nvp7rWcxBxJwWfSw3stezSqn-KyFA3dAqo=.pem", + "location": "security-state-staging/intermediates/7738a5eb-12d0-4372-a403-127c8574fdec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tMbtz8E47Nvp7rWcxBxJwWfSw3stezSqn+KyFA3dAqo=", + "crlite_enrolled": false, + "id": "b2beb690-f711-40e2-a60a-b1e9055fa86b", + "last_modified": 1666727867398 + }, + { + "schema": 1666727440073, + "derHash": "307LFhEO3qq6B6eB7Lk/41SWGzEx0tpQctNKvgt01bg=", + "subject": "CN=SecureCore RSA OV CA,O=SecureCore,L=OSAKA,ST=OSAKA,C=JP", + "subjectDN": "MGExCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVPU0FLQTEOMAwGA1UEBxMFT1NBS0ExEzARBgNVBAoTClNlY3VyZUNvcmUxHTAbBgNVBAMTFFNlY3VyZUNvcmUgUlNBIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "49467151fb7ae2c8a75b0d299a527107ef213c388761d4e23e850365c9378693", + "size": 2113, + "filename": "i9YotTNYIfvzcEXlwG2eor_v3IOO_LhM0dufRqOf7Dk=.pem", + "location": "security-state-staging/intermediates/1a44d6a0-db1a-449c-b613-01d7a5978309.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "i9YotTNYIfvzcEXlwG2eor/v3IOO/LhM0dufRqOf7Dk=", + "crlite_enrolled": false, + "id": "24044967-2370-4d7a-a62e-5f0e3609ae26", + "last_modified": 1666727867385 + }, + { + "schema": 1666727426325, + "derHash": "cQLZcsJOnKaNDm+f9TxfZYzZuGSfKOrY28M2wt9QSBE=", + "subject": "CN=CertCloud RSA OV TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "ME0xCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMSAwHgYDVQQDExdDZXJ0Q2xvdWQgUlNBIE9WIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "06f8793bccce4def2d12e509ec2db282cc0dc976354cfb8739afbce39ce99f61", + "size": 2081, + "filename": "ORYJiVkIJCqtrNqVHJ4iw_q-R2u46OEMcFExxBr8_5Y=.pem", + "location": "security-state-staging/intermediates/06ff1559-82b4-4470-acef-546ca0d2d7e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ORYJiVkIJCqtrNqVHJ4iw/q+R2u46OEMcFExxBr8/5Y=", + "crlite_enrolled": false, + "id": "00da2a29-55d4-4141-b618-98cac6e7b045", + "last_modified": 1666727867371 + }, + { + "schema": 1666727423605, + "derHash": "GUzCtbBkk8DvCwqfqDdvlYviQqJ0ral0rLyRAjNoeNw=", + "subject": "CN=DOMENY SSL DV Certification Authority,O=DOMENY.PL sp. z o.o,L=Kraków,ST=Małopolskie,C=PL", + "subjectDN": "MIGEMQswCQYDVQQGEwJQTDEVMBMGA1UECAwMTWHFgm9wb2xza2llMRAwDgYDVQQHDAdLcmFrw7N3MRwwGgYDVQQKExNET01FTlkuUEwgc3AuIHogby5vMS4wLAYDVQQDEyVET01FTlkgU1NMIERWIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "ad32d53b83760d3ca2c18007c1e9dd8203dc16f07bd3a8bac0b99701a5b796f3", + "size": 2154, + "filename": "c4Z4xm0MVOlxzaOy4OXC86lTgsofdZML4CYOmaSpWYM=.pem", + "location": "security-state-staging/intermediates/b332d168-1bb8-4058-95fd-56aaad43101b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "c4Z4xm0MVOlxzaOy4OXC86lTgsofdZML4CYOmaSpWYM=", + "crlite_enrolled": false, + "id": "de87f0b6-9d2d-4592-8c45-a439301c4776", + "last_modified": 1666727867357 + }, + { + "schema": 1666727419907, + "derHash": "hg+OSYT8qqp4yH8HE/IDGBtX17VW/tl5zhTcoB/6SlQ=", + "subject": "CN=GEANT EV ECC CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "MEQxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMRowGAYDVQQDExFHRUFOVCBFViBFQ0MgQ0EgNA==", + "whitelist": false, + "attachment": { + "hash": "3c61e7b9ee02d47dc2f8a41911c86a0d41e29cb2be3b6b70caaf18b1b105ca1d", + "size": 1264, + "filename": "FFXxLheN9s893pVXnMlRnGpl8sSJRMnaghLMByYYI1Y=.pem", + "location": "security-state-staging/intermediates/96fc6403-52e0-4638-b9dd-775c4f633906.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FFXxLheN9s893pVXnMlRnGpl8sSJRMnaghLMByYYI1Y=", + "crlite_enrolled": false, + "id": "ef31a762-f26e-43b1-aa34-1183bc4916a3", + "last_modified": 1666727867342 + }, + { + "schema": 1666727413893, + "derHash": "+OO/Hv4I73AMTtv9xP8xnVjst4DfsN2n1RBHs5socY4=", + "subject": "CN=WoTrus DV Server CA [Run by the Issuer],O=WoTrus CA Limited,C=CN", + "subjectDN": "MFwxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1RydXMgQ0EgTGltaXRlZDExMC8GA1UEAwwoV29UcnVzIERWIFNlcnZlciBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "d01d9500122bedeecb1a1ea9f68d7a15dea1ca61b9744ec9b49fdf9db5e12fd1", + "size": 2101, + "filename": "-VWlwEVcGawfAmx6yUXVECmp5xZpZ7uvqv3Jr-xEvE4=.pem", + "location": "security-state-staging/intermediates/848346b8-c721-46b5-850d-da51c599ee2e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+VWlwEVcGawfAmx6yUXVECmp5xZpZ7uvqv3Jr+xEvE4=", + "crlite_enrolled": false, + "id": "0cf125cb-2b8c-4b59-a24c-1ae2d4cb712b", + "last_modified": 1666727867329 + }, + { + "schema": 1666727399594, + "derHash": "EjXhOBK5dSFwzpvTo7YOZqyMDLRPnMtcdoWJ/Z8nufQ=", + "subject": "CN=GoGetSSL ECC OV CA,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MEwxCzAJBgNVBAYTAkxWMQ0wCwYDVQQHEwRSaWdhMREwDwYDVQQKEwhHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgRUNDIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "d00593de2ef18b22998637a264d18afafd03c3c0c67e1f877e84969a5e75f328", + "size": 1244, + "filename": "PrR6EH8Bm2wimAptoITpdhgeQfnUp4ASjI8FRS-Bq7E=.pem", + "location": "security-state-staging/intermediates/5dfb5405-6034-4d22-92b5-12881aad8440.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PrR6EH8Bm2wimAptoITpdhgeQfnUp4ASjI8FRS+Bq7E=", + "crlite_enrolled": false, + "id": "6c442ac4-66c1-432d-bf78-80ce046badd7", + "last_modified": 1666727867316 + }, + { + "schema": 1666727390556, + "derHash": "ufIWQyNjjc4LkiGLQ8QcGysmljiTKdsZ9c961Jtcs3I=", + "subject": "CN=Gandi Standard SSL CA 2,O=Gandi,L=Paris,ST=Paris,C=FR", + "subjectDN": "MF8xCzAJBgNVBAYTAkZSMQ4wDAYDVQQIEwVQYXJpczEOMAwGA1UEBxMFUGFyaXMxDjAMBgNVBAoTBUdhbmRpMSAwHgYDVQQDExdHYW5kaSBTdGFuZGFyZCBTU0wgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "8b1ceb14d5c25356214a73586739ce33690e5f47c00a76ed94647903e85e7611", + "size": 2109, + "filename": "WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV-nNm1r4=.pem", + "location": "security-state-staging/intermediates/4f8220db-8fc8-4d4e-b9ee-ac020e050d5d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4=", + "crlite_enrolled": false, + "id": "e9f19529-c655-4693-b307-095080dcd0a7", + "last_modified": 1666727867303 + }, + { + "schema": 1666727403214, + "derHash": "C9FHkYe6Aeo+k4p1DHveO28oXcEOu5lTA/ZmAbpbLSA=", + "subject": "CN=WebSpace-Forum Server CA II,O=WebSpace-Forum e.K.,L=Berlin,ST=Berlin,C=DE", + "subjectDN": "MHMxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCZXJsaW4xDzANBgNVBAcTBkJlcmxpbjEcMBoGA1UEChMTV2ViU3BhY2UtRm9ydW0gZS5LLjEkMCIGA1UEAxMbV2ViU3BhY2UtRm9ydW0gU2VydmVyIENBIElJ", + "whitelist": false, + "attachment": { + "hash": "65b7676339537a03054bbad626b8cb8d54125b5819f31bbe0f37a375bca49efb", + "size": 2133, + "filename": "5RYmGs6llHArO1X6YMpESuHfz0WJ0hjDErS2_5y9hWo=.pem", + "location": "security-state-staging/intermediates/c118334a-9f3b-48bb-b538-707da8727964.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5RYmGs6llHArO1X6YMpESuHfz0WJ0hjDErS2/5y9hWo=", + "crlite_enrolled": false, + "id": "15268d2e-197a-4e65-942b-23efa82b8c59", + "last_modified": 1666727867290 + }, + { + "schema": 1666727348871, + "derHash": "OYmjFwLp5qlIXzySp30hsYUsf2ci8j1EuMvnKpJuyFQ=", + "subject": "CN=Trustico ECC DV CA,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MG4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIEwZMb25kb24xEDAOBgNVBAcTB0Nyb3lkb24xHzAdBgNVBAoTFlRoZSBUcnVzdGljbyBHcm91cCBMdGQxGzAZBgNVBAMTElRydXN0aWNvIEVDQyBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "28fb5bef0d87bb2cf53084d9c1292345b10e9c16be15494008eade041709453f", + "size": 1272, + "filename": "siFWwL8GHh0KXgWfKS8Gt9laNL7edvX8IB7dl6zLe-o=.pem", + "location": "security-state-staging/intermediates/2889361f-0903-49b8-ae12-81556e971667.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "siFWwL8GHh0KXgWfKS8Gt9laNL7edvX8IB7dl6zLe+o=", + "crlite_enrolled": false, + "id": "0cbdce88-d64d-4599-af44-2f28f6c28e1f", + "last_modified": 1666727867276 + }, + { + "schema": 1666727452995, + "derHash": "4R4GhhxNMI/ZRL8Xvl6QcqA0xPkwNMtZwC1RLTD3/EU=", + "subject": "CN=COMODO SHA-2 Pro Series Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGLMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDExMC8GA1UEAxMoQ09NT0RPIFNIQS0yIFBybyBTZXJpZXMgU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "c0ba4ac065c211f41369956562bcd135e544be484490c840c2859a4bcc826350", + "size": 1691, + "filename": "4gSqIYIbYqiQ1NfKCAfvkzCzqmOipsGL9CFJJdH7Ms4=.pem", + "location": "security-state-staging/intermediates/d60ecac0-e137-48c9-815a-27c5011e87a4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4gSqIYIbYqiQ1NfKCAfvkzCzqmOipsGL9CFJJdH7Ms4=", + "crlite_enrolled": false, + "id": "f195075c-7448-4acd-ae0f-e82a470ff32a", + "last_modified": 1666727867263 + }, + { + "schema": 1666727388682, + "derHash": "ltxhXR892QAu5eYosoAe7Qe5Hw5FrK3t7na8YT2gbL0=", + "subject": "CN=Gehirn Managed Certification Authority - RSA EV,OU=Controlled by COMODO CA exclusively for Gehirn Inc.,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIHAMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTwwOgYDVQQLEzNDb250cm9sbGVkIGJ5IENPTU9ETyBDQSBleGNsdXNpdmVseSBmb3IgR2VoaXJuIEluYy4xODA2BgNVBAMTL0dlaGlybiBNYW5hZ2VkIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gUlNBIEVW", + "whitelist": false, + "attachment": { + "hash": "be0ede6fc99501f78a50bd371c697b30762b5735a23356a631f12af9fb12dade", + "size": 2276, + "filename": "MQrt-wrOan6w852uOuU2w7xe24wLLPV42Kyxv47Qm84=.pem", + "location": "security-state-staging/intermediates/048026a0-02af-48ff-8684-e0b66be52758.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MQrt+wrOan6w852uOuU2w7xe24wLLPV42Kyxv47Qm84=", + "crlite_enrolled": false, + "id": "a7fd7afc-2405-46d2-9054-f323e1a9c13b", + "last_modified": 1666727867249 + }, + { + "schema": 1666727376193, + "derHash": "vb9XvRAWOncbKNCdTJAYjVlgcsaZvYJ0/kYo0SUOSb0=", + "subject": "CN=DigitalTrust Secure CA G3 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MGUxCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjE3MDUGA1UEAwwuRGlnaXRhbFRydXN0IFNlY3VyZSBDQSBHMyAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "4f45fdcd92cf64e75142226782fce479b706e3b25b8f6ee2a94fdc3b8211bf61", + "size": 1317, + "filename": "n7S9CUGXp7reMdZAD7cwkt9AngliimrF0ya3qfi_Tn0=.pem", + "location": "security-state-staging/intermediates/0287fd56-c974-4c99-b3fa-2f5dd71da1ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "n7S9CUGXp7reMdZAD7cwkt9AngliimrF0ya3qfi/Tn0=", + "crlite_enrolled": false, + "id": "d2223a6b-0612-4edf-8a79-577b918f21f3", + "last_modified": 1666727867236 + }, + { + "schema": 1666727442459, + "derHash": "H5NmbaV+HuQ7Gec2f3IlFR1EqWZxhDrWJiKI20qoA3c=", + "subject": "CN=FujiSSL SHA2 Business Secure Site CA,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MHcxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzETMBEGA1UEBxMKU2hpYnV5YS1rdTEUMBIGA1UEChMLTmlqaW1vIEsuSy4xLTArBgNVBAMTJEZ1amlTU0wgU0hBMiBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "655627ce8be9e7dfb28d576ee44e18954aa421b5f27eab1537c1f4e8834ff86e", + "size": 2142, + "filename": "AapugBjS_xPRoLxwSH9ER41rNad1oyfyVH7OwZcxPkY=.pem", + "location": "security-state-staging/intermediates/7b849114-5825-4b3c-8b7d-8aef88e26781.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AapugBjS/xPRoLxwSH9ER41rNad1oyfyVH7OwZcxPkY=", + "crlite_enrolled": false, + "id": "abc517ec-da13-4416-ab04-8d0fd84836d2", + "last_modified": 1666727867222 + }, + { + "schema": 1666727439715, + "derHash": "Lkph5SOOVW6M7bzAtPPeIXgkVmSgvhlbOFW0yF297v8=", + "subject": "CN=USERTrust RSA Organization Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGZMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE/MD0GA1UEAxM2VVNFUlRydXN0IFJTQSBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "db7d31742bfa482640b0f61d16f31c158d1ccd1665b1af727ec69119c18d8b3b", + "size": 2178, + "filename": "qmZOfgEUaNPh43JyVsa1dSCvAFxPKkxkytll6bY4_4E=.pem", + "location": "security-state-staging/intermediates/2185c65c-d2c0-4ba3-8bf8-6da756924b30.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qmZOfgEUaNPh43JyVsa1dSCvAFxPKkxkytll6bY4/4E=", + "crlite_enrolled": false, + "id": "396b40a2-b39e-46e1-b5c6-34abcd1df1d2", + "last_modified": 1666727867209 + }, + { + "schema": 1666727429673, + "derHash": "fjI2jiSizEjT0p6IOyYOQM7aIq+XGJx4/FuShITpoBA=", + "subject": "CN=COMODO ECC Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "467d14fa1e5da312c401a3797dfe6a2e68e1085d920f9c86a09e0e679f10eede", + "size": 1313, + "filename": "EohwrK1N7rr3bRQphPj4j2cel-B2d0NNbM9PWHNDXpM=.pem", + "location": "security-state-staging/intermediates/bf3ccd0f-a3a8-4fb4-8efe-fe3fca180a23.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EohwrK1N7rr3bRQphPj4j2cel+B2d0NNbM9PWHNDXpM=", + "crlite_enrolled": false, + "id": "9a9a795e-2c26-43c3-91d2-10d1b807fd5d", + "last_modified": 1666727867196 + }, + { + "schema": 1666727341459, + "derHash": "+BMqOx0ZAlxebrPBt2tTRljBGlvmkETp1ER7V5Rju0U=", + "subject": "CN=SecureCore RSA EV CA,OU=Controlled by COMODO exclusively for SecureCore,O=SecureCore,L=OSAKA,ST=OSAKA,C=JP", + "subjectDN": "MIGbMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFT1NBS0ExDjAMBgNVBAcTBU9TQUtBMRMwEQYDVQQKEwpTZWN1cmVDb3JlMTgwNgYDVQQLEy9Db250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgU2VjdXJlQ29yZTEdMBsGA1UEAxMUU2VjdXJlQ29yZSBSU0EgRVYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "836dc627e14efa5d36dbebd164d0a9ec6864bc30dda700e4a2d1c6776b8f063c", + "size": 2227, + "filename": "4oLlji5ovjgkWA95bS8h_Ik9hO3Fy_mgx1aM3Jv7Bn0=.pem", + "location": "security-state-staging/intermediates/77a6c459-b99e-4d99-b07c-b1c8e90d24c0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4oLlji5ovjgkWA95bS8h/Ik9hO3Fy/mgx1aM3Jv7Bn0=", + "crlite_enrolled": false, + "id": "c982dc65-a605-4303-9f3a-017eed4aa6c6", + "last_modified": 1666727867183 + }, + { + "schema": 1666727433027, + "derHash": "ACOT+gpoJfd+9obiCGILlxd3kfAvB9slGEgPvjfOe9g=", + "subject": "CN=Sectigo Qualified Website Authentication CA R35,O=Sectigo (Europe) SL,C=ES", + "subjectDN": "MGUxCzAJBgNVBAYTAkVTMRwwGgYDVQQKExNTZWN0aWdvIChFdXJvcGUpIFNMMTgwNgYDVQQDEy9TZWN0aWdvIFF1YWxpZmllZCBXZWJzaXRlIEF1dGhlbnRpY2F0aW9uIENBIFIzNQ==", + "whitelist": false, + "attachment": { + "hash": "682a76ce2b94d1239894aa1cbb1af2c8932c262982f42465973482b66e9a5c8e", + "size": 2259, + "filename": "KxJdQKV5G03wpSUMzFPrE_WdnS5RnO4gex-GeXPUDnw=.pem", + "location": "security-state-staging/intermediates/07129048-5262-4517-b851-5a0b027347d2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KxJdQKV5G03wpSUMzFPrE/WdnS5RnO4gex+GeXPUDnw=", + "crlite_enrolled": false, + "id": "472efb80-09e6-4cc4-9723-321570fb3080", + "last_modified": 1666727867170 + }, + { + "schema": 1666727331677, + "derHash": "2VEjGV+gLBzEBXMzbmTI36IQsXc+PGEQtusm5UN+I6U=", + "subject": "CN=McAfee OV SSL CA 2,O=McAfee\\, Inc.,L=Santa Clara,ST=CA,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExFTATBgNVBAoTDE1jQWZlZSwgSW5jLjEbMBkGA1UEAxMSTWNBZmVlIE9WIFNTTCBDQSAy", + "whitelist": false, + "attachment": { + "hash": "57cb84d53e8a3cd439300a62db89c170f0cbb916e38c7ee179bd0114c2aac1be", + "size": 2117, + "filename": "KKKPHEdd8Vi-l7k78OoSVKsWZubT8CNhOr4ZCATqRXU=.pem", + "location": "security-state-staging/intermediates/8de7b884-3af8-4122-9fb0-9eb402f55e2e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KKKPHEdd8Vi+l7k78OoSVKsWZubT8CNhOr4ZCATqRXU=", + "crlite_enrolled": false, + "id": "a2a3a289-36e1-4286-a263-9755102af1e4", + "last_modified": 1666727867156 + }, + { + "schema": 1666727412860, + "derHash": "ghzFXOfsXHT+u0L2JOtqNsR4IVox7Wfjz3I6Z+jHXro=", + "subject": "CN=cPanel\\, Inc. Certification Authority,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "d855dc72522724ce4be2aeb325a952073468a5419e0ee106ec039a87df8af546", + "size": 2121, + "filename": "hOF1upiO-xevnDEQ13g3aY3M3Uqa44RN5rVlwvU2WC8=.pem", + "location": "security-state-staging/intermediates/b42b9ff8-8458-4087-95d1-ff8540b63d82.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hOF1upiO+xevnDEQ13g3aY3M3Uqa44RN5rVlwvU2WC8=", + "crlite_enrolled": false, + "id": "75ea0554-7134-4dd0-ab73-14bc528af786", + "last_modified": 1666727867141 + }, + { + "schema": 1666727451637, + "derHash": "vao4RyBgT/RlOwDaOHOoxUWZb/uKuR1hbxBJX8Am6Pw=", + "subject": "CN=cPanel\\, Inc. ECC Certification Authority,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MHYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMTEwLwYDVQQDEyhjUGFuZWwsIEluYy4gRUNDIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "90ffd326c72e193425c3971988103396754f940e9bf7a8391479d7e36e256f8b", + "size": 1284, + "filename": "1OWbvT5F8be0LIlW8C2KyzC-FMP8CzHyOolBvIgt6bc=.pem", + "location": "security-state-staging/intermediates/0b3cbaf8-d330-42bb-8f01-921600c76e1e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1OWbvT5F8be0LIlW8C2KyzC+FMP8CzHyOolBvIgt6bc=", + "crlite_enrolled": false, + "id": "db717607-cd21-46be-8ac1-3e25240f7ce6", + "last_modified": 1666727867128 + }, + { + "schema": 1666727422020, + "derHash": "4g3/JvPDlj83AL8uEI8fnp3uJZwk+K/Q27tvzZobxZo=", + "subject": "CN=E-SAFER EXTENDED SSL CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGDMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTE1MDMGA1UEAwwsRS1TQUZFUiBFWFRFTkRFRCBTU0wgQ0EgIFtSdW4gYnkgdGhlIElzc3Vlcl0=", + "whitelist": false, + "attachment": { + "hash": "34d7db2d7dd7c88a3e207db8bff79deef52c5a8c9900c47bc1a3ffabc466e5f9", + "size": 2316, + "filename": "plm_9YONwHj1IGtrwIqZq6BKWKFZfTMWD6vczJ53lUg=.pem", + "location": "security-state-staging/intermediates/2cc945f7-048e-49d1-9db7-21562f2a4653.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "plm/9YONwHj1IGtrwIqZq6BKWKFZfTMWD6vczJ53lUg=", + "crlite_enrolled": false, + "id": "5bdb9110-1c75-423a-a5ef-154888d717ff", + "last_modified": 1666727867115 + }, + { + "schema": 1666727408393, + "derHash": "RtO1D+3PjDa19nIjnMOi6eAu0V8zy9wMJiqGR4SUA/Q=", + "subject": "CN=USERTrust ECC Organization Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGZMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE/MD0GA1UEAxM2VVNFUlRydXN0IEVDQyBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "5ceaf46e2ac5dfb9fde535b5c898ac39fa2af36bbbe446f13ad9b6aec6874fd3", + "size": 1337, + "filename": "kRGhnbdY7rR4yOa8G3EFT4gmen6uTLPFQPPk_s1vl-M=.pem", + "location": "security-state-staging/intermediates/8d9e6036-afcb-4860-ab15-985f8bd573e5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kRGhnbdY7rR4yOa8G3EFT4gmen6uTLPFQPPk/s1vl+M=", + "crlite_enrolled": false, + "id": "d182b3c6-c3d6-4fe0-a6c2-0e3291824191", + "last_modified": 1666727867102 + }, + { + "schema": 1666727414593, + "derHash": "f1UnYRTdiWXbpbEIGf3hIcILEchISXf6OIFEI66CrAo=", + "subject": "CN=CerSign EV SSL CA,O=CerSign Technology Limited,C=CN", + "subjectDN": "ME4xCzAJBgNVBAYTAkNOMSMwIQYDVQQKExpDZXJTaWduIFRlY2hub2xvZ3kgTGltaXRlZDEaMBgGA1UEAxMRQ2VyU2lnbiBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "495acedde9beaa8a7d9ae6f2128649514591f3c2e26a3a483388dae353a235ef", + "size": 2243, + "filename": "yLwV6J-LJLdy54phxrtaZQEA77Z036OFtSoZ8ExHxms=.pem", + "location": "security-state-staging/intermediates/dd3d94a5-5538-4e6c-a9b4-4137f78e5573.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yLwV6J+LJLdy54phxrtaZQEA77Z036OFtSoZ8ExHxms=", + "crlite_enrolled": false, + "id": "041dd12d-8dc7-42b5-b753-f4688fb8af33", + "last_modified": 1666727867089 + }, + { + "schema": 1666727425971, + "derHash": "OKdSUCLJZYyq/GBgdk/VHGpcW+8jLT5sfkivB6CY3x4=", + "subject": "CN=Comodo Japan RSA DV CA,O=Comodo Japan\\, Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MHAxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzETMBEGA1UEBxMKQ2hpeW9kYS1rdTEbMBkGA1UEChMSQ29tb2RvIEphcGFuLCBJbmMuMR8wHQYDVQQDExZDb21vZG8gSmFwYW4gUlNBIERWIENB", + "whitelist": false, + "attachment": { + "hash": "15b2316522bc93a2d8ab24c55298c53ef30e6d7d564ecd2d5b54a9b86e87ab78", + "size": 1658, + "filename": "oGI3G9oW9P0H6HjREnI-teLBhK_5fC1XowTKkZ9VZXE=.pem", + "location": "security-state-staging/intermediates/6ffe65c4-36e7-4b51-8cdb-fb8236aa1d15.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oGI3G9oW9P0H6HjREnI+teLBhK/5fC1XowTKkZ9VZXE=", + "crlite_enrolled": false, + "id": "ee2ffeb3-1f3e-46dc-9075-791ac0d4ce8c", + "last_modified": 1666727867075 + }, + { + "schema": 1666727389014, + "derHash": "r+FZXnCXzN+0Ms6mOb3uhs8ldsJ982+caRzwPhMkwdQ=", + "subject": "CN=Trustico RSA OV CA,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MG4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIEwZMb25kb24xEDAOBgNVBAcTB0Nyb3lkb24xHzAdBgNVBAoTFlRoZSBUcnVzdGljbyBHcm91cCBMdGQxGzAZBgNVBAMTElRydXN0aWNvIFJTQSBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b882ceb4de2a252da6a3da1903b5df843ddfc9f27fe3fce13468ad505c7a8da7", + "size": 2113, + "filename": "hZa79Z70L3tWUM25OQ658Ld4Rg65tj7Rn6OQXFiJPkg=.pem", + "location": "security-state-staging/intermediates/c12a0ea9-da0b-4d95-811f-c39e7a877614.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hZa79Z70L3tWUM25OQ658Ld4Rg65tj7Rn6OQXFiJPkg=", + "crlite_enrolled": false, + "id": "df8bcbba-ca7d-43a8-a14e-ac1e7f02e712", + "last_modified": 1666727867061 + }, + { + "schema": 1666727375517, + "derHash": "Q8rDHvjouhtLFrggbkwKJsW62y/DqgnpAXDkG2bC/WQ=", + "subject": "CN=GoGetSSL RSA DV CA,O=GoGetSSL,L=Riga,C=LV", + "subjectDN": "MEwxCzAJBgNVBAYTAkxWMQ0wCwYDVQQHEwRSaWdhMREwDwYDVQQKEwhHb0dldFNTTDEbMBkGA1UEAxMSR29HZXRTU0wgUlNBIERWIENB", + "whitelist": false, + "attachment": { + "hash": "367f87cad467ddfcf71cabca71bc380360a7514fab7e1a526d6cef19cef32a8a", + "size": 2085, + "filename": "T-6uyT5C6WT480t2wqX2OJ3ZrNt2j8v52bWLj-XIvz8=.pem", + "location": "security-state-staging/intermediates/b8cc1db1-43ff-4891-acec-2345fd8dfd6e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "T+6uyT5C6WT480t2wqX2OJ3ZrNt2j8v52bWLj+XIvz8=", + "crlite_enrolled": false, + "id": "4af48306-1568-435b-87af-c6b92a9ecae2", + "last_modified": 1666727867047 + }, + { + "schema": 1666727330060, + "derHash": "8Up9JCetCgG01/152NUHctJSbiUkJfignDA3/fpjX1o=", + "subject": "CN=ZoTrus OV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MEwxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMRkwFwYDVQQDExBab1RydXMgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "2848d4896f5e05eae159751304e91e948e9f3c077825ced5d9f747fc086bdaf9", + "size": 2251, + "filename": "mic4JfOko8h5lsSjNABtsJRu4_nBErCNRFyxtEJc7SA=.pem", + "location": "security-state-staging/intermediates/d72844b2-0cdb-43da-be5b-273a81b87844.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mic4JfOko8h5lsSjNABtsJRu4/nBErCNRFyxtEJc7SA=", + "crlite_enrolled": false, + "id": "ad4ae7f5-99e9-4cca-9f7c-f63585c51826", + "last_modified": 1666727867034 + }, + { + "schema": 1666727437155, + "derHash": "ytA6WE5YU5uJGS7EnOp6bY7XJCrV3N+D+W0IfwnyMts=", + "subject": "CN=GEANT eScience SSL ECC CA 4,O=GEANT Vereniging,C=NL", + "subjectDN": "ME4xCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBHRUFOVCBWZXJlbmlnaW5nMSQwIgYDVQQDExtHRUFOVCBlU2NpZW5jZSBTU0wgRUNDIENBIDQ=", + "whitelist": false, + "attachment": { + "hash": "67fff97ed479e7b8f50a3ec8d6e6b332d764c44935757af9be5caa2e2c575119", + "size": 1276, + "filename": "nzYOlulDufoKXDgcAhC63BnT5t74_zG2ZFweTlq2pTA=.pem", + "location": "security-state-staging/intermediates/289d0bfa-f60b-4593-a2a6-fc2e5548b6cb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nzYOlulDufoKXDgcAhC63BnT5t74/zG2ZFweTlq2pTA=", + "crlite_enrolled": false, + "id": "58e54036-f4e7-4e13-b97b-edee5cb220f4", + "last_modified": 1666727867020 + }, + { + "schema": 1666727400793, + "derHash": "xjHKi5oSWUVaZh4sU4jNBppwS85U+l76cGvwsJYtj/M=", + "subject": "CN=E-SAFER ORGANIZATION SSL CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGHMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTE5MDcGA1UEAwwwRS1TQUZFUiBPUkdBTklaQVRJT04gU1NMIENBICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "e8c26ed3ecc253310199e14ca9644cbf795137570d62dbcd5815813c7b55a23a", + "size": 2333, + "filename": "gQ064qd4IEshlJkixJLrt4o9KEXL1XNDFPkbzRjuF0s=.pem", + "location": "security-state-staging/intermediates/c975f27c-fcb2-48ae-96b6-68bc39bb6f6d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gQ064qd4IEshlJkixJLrt4o9KEXL1XNDFPkbzRjuF0s=", + "crlite_enrolled": false, + "id": "7d324cc3-53f6-4869-8c36-c615b3290d68", + "last_modified": 1666727867007 + }, + { + "schema": 1666727453661, + "derHash": "2TUmaeKTSTKa9HRcVabBWdyXmrdFklFwoZgGaDgqy8A=", + "subject": "CN=Trusted Secure ECC Certificate Authority,O=Corporation Service Company,L=Wilmington,ST=DE,C=US", + "subjectDN": "MIGIMQswCQYDVQQGEwJVUzELMAkGA1UECBMCREUxEzARBgNVBAcTCldpbG1pbmd0b24xJDAiBgNVBAoTG0NvcnBvcmF0aW9uIFNlcnZpY2UgQ29tcGFueTExMC8GA1UEAxMoVHJ1c3RlZCBTZWN1cmUgRUNDIENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "ffcbd855285d11ec163f8b8807def500544559775b4ceb3d7fcc3fa8b6f09b67", + "size": 1325, + "filename": "qk3cnu1avJacTvFxrZGIIPiymx-oSzPLYsolI21B5Vc=.pem", + "location": "security-state-staging/intermediates/678ee2c5-5473-49e9-a5fa-1fe1a036ffe3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qk3cnu1avJacTvFxrZGIIPiymx+oSzPLYsolI21B5Vc=", + "crlite_enrolled": false, + "id": "c65b914c-20b3-45e9-b3b9-b71286f0b5c7", + "last_modified": 1666727866993 + }, + { + "schema": 1666727346614, + "derHash": "mSTm/A5VqfD/qZgpjzAQDBorOAFM5LE3fQjELuQkN2M=", + "subject": "CN=cnTrus OV SSL CA,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGIxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEZMBcGA1UEAxMQY25UcnVzIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "425808379815fb809c7db9dcffeea8544b6375be9dd70bed99890e0ae683b3cc", + "size": 2280, + "filename": "UvPptw6qfgD_o4kl84TncVTWChjvpEgjLjc6AEALwhY=.pem", + "location": "security-state-staging/intermediates/8b8219c8-a3a0-478e-a9a8-bf27621b9b37.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UvPptw6qfgD/o4kl84TncVTWChjvpEgjLjc6AEALwhY=", + "crlite_enrolled": false, + "id": "c1c36072-d3f6-41ef-9535-4773ad262386", + "last_modified": 1666727866980 + }, + { + "schema": 1666727408050, + "derHash": "NOveN3X8Y7boDw5H6wouJo9/5O/1Et1BEY2EBQJ0df4=", + "subject": "CN=XinNet RSA OV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBSU0EgT1Y=", + "whitelist": false, + "attachment": { + "hash": "61b4f1271f971b35dbb21efa65381c7a9a42b953a4f2a97ea248655b619c4e5b", + "size": 2247, + "filename": "9Xa_SPv1KNbKazcD_MKRMyHRIy068JKcVvOiCc_T5ec=.pem", + "location": "security-state-staging/intermediates/d929be9a-911a-4cd7-b4ef-68fe521f2f73.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9Xa/SPv1KNbKazcD/MKRMyHRIy068JKcVvOiCc/T5ec=", + "crlite_enrolled": false, + "id": "07ba494f-75ae-4540-b3d7-77addd473069", + "last_modified": 1666727866967 + }, + { + "schema": 1666727340265, + "derHash": "roIgH1ZeBDm39K1okydZTrl0pPeBsabL/tWb04IxfxM=", + "subject": "CN=Thawte TLS ECC CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFFRoYXd0ZSBUTFMgRUNDIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "57d89599988d7bc402c1ba123132fbf1a908a59550a9fc499d6c56a71b579771", + "size": 1142, + "filename": "Z2FuypjaQ1wWJ4YxsCzQZPBTleqi8I6cEO-bYvpiEQI=.pem", + "location": "security-state-staging/intermediates/70e34f90-78e8-45d4-ac31-585b43fe58fb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z2FuypjaQ1wWJ4YxsCzQZPBTleqi8I6cEO+bYvpiEQI=", + "crlite_enrolled": false, + "id": "9dee899c-9bcb-4d39-bc6c-85250c723859", + "last_modified": 1666727866952 + }, + { + "schema": 1666727426491, + "derHash": "sjopwxKnqAsP5rTnG5CcrpKtZJ6Idm5Wye6OHXwBOUU=", + "subject": "CN=Trust Provider B.V. TLS RSA EV CA G2,O=Trust Provider B.V.,C=NL", + "subjectDN": "MFoxCzAJBgNVBAYTAk5MMRwwGgYDVQQKExNUcnVzdCBQcm92aWRlciBCLlYuMS0wKwYDVQQDEyRUcnVzdCBQcm92aWRlciBCLlYuIFRMUyBSU0EgRVYgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "db5c41ad0878314cf616d0ce27defe4bb76a6bedbe7e50c09eef688c2272a4ff", + "size": 1756, + "filename": "tbS_1veJrHW-X3zzJhnpwaHOLn5kSHEHFyby1BZoZlg=.pem", + "location": "security-state-staging/intermediates/8b17a910-28e4-4245-9c0e-613dfc959dbe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tbS/1veJrHW+X3zzJhnpwaHOLn5kSHEHFyby1BZoZlg=", + "crlite_enrolled": false, + "id": "0ce0ee4a-1393-4431-abf3-d006cbb96f03", + "last_modified": 1666727866938 + }, + { + "schema": 1666727371761, + "derHash": "BwUxODzNEA0+nNlk2weqXoRaBoby6uO8imJ7GCBXsfE=", + "subject": "CN=WoTrus EV SSL Pro CA,O=WoTrus CA Limited,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1RydXMgQ0EgTGltaXRlZDEdMBsGA1UEAxMUV29UcnVzIEVWIFNTTCBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "eb4c0a82284e0aeb9ce6b91db33bee1985922cb215327f2e73e9fb3edbdaa292", + "size": 1845, + "filename": "rBTvvFbWGhKxAPrEAJ1x8vttBOVCvRznd5h2iXt2ATs=.pem", + "location": "security-state-staging/intermediates/f6452090-5838-4301-88db-8a0e4b236b13.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rBTvvFbWGhKxAPrEAJ1x8vttBOVCvRznd5h2iXt2ATs=", + "crlite_enrolled": false, + "id": "62dff257-444d-4410-b94f-61686e918ed5", + "last_modified": 1666727866925 + }, + { + "schema": 1666727342484, + "derHash": "15otXgMpXA6f6uNtAh69UglwCrGp6BekPzD6PGb3jSE=", + "subject": "CN=DigiCert TLS Hybrid ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBIeWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "bc2954442e7a77c973da8e56642f87d300ade42db9755d9dfb888b77d502b45b", + "size": 1536, + "filename": "e0IRz5Tio3GA1Xs4fUVWmH1xHDiH2dMbVtCBSkOIdqM=.pem", + "location": "security-state-staging/intermediates/8f52065f-5cee-4d08-b159-e6444aec8a36.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e0IRz5Tio3GA1Xs4fUVWmH1xHDiH2dMbVtCBSkOIdqM=", + "crlite_enrolled": false, + "id": "b8541d60-7ae4-4cdb-b65c-94c6ea2f9622", + "last_modified": 1666727866912 + }, + { + "schema": 1666727367394, + "derHash": "vrjv6bGnPIQbN1qQ5f/4BIhI46KvZvbE3XuTjW/oxdg=", + "subject": "CN=TERENA SSL CA 3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MGQxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEYMBYGA1UEAxMPVEVSRU5BIFNTTCBDQSAz", + "whitelist": false, + "attachment": { + "hash": "e4ac7ab94f676f44055d2f3516028ed222a17a914725519dd8ff9d3afb675e0d", + "size": 1788, + "filename": "8651wEkMkH5ftiaLp57oqmx3KHTFzDgp7ZeJXR0ToBs=.pem", + "location": "security-state-staging/intermediates/cf430545-b753-4480-94fd-67f3f032f9af.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8651wEkMkH5ftiaLp57oqmx3KHTFzDgp7ZeJXR0ToBs=", + "crlite_enrolled": false, + "id": "6a7c2942-3cb4-418e-9ebd-9506dda57955", + "last_modified": 1666727866898 + }, + { + "schema": 1666727336331, + "derHash": "QD4GKiZTBZETKFuvgKDUrkIshIyfePrQH8lLxbh/7xo=", + "subject": "CN=DigiCert SHA2 Extended Validation Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNDAyBgNVBAMTK0RpZ2lDZXJ0IFNIQTIgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "8c56cc97d9228cf4315abdb67ecbe6f845bb15c6bddfa4fc1b4bd41251669430", + "size": 1695, + "filename": "RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn_yOhI_y-ho=.pem", + "location": "security-state-staging/intermediates/078b10f3-5ab5-4378-ada1-201c314e5dc2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=", + "crlite_enrolled": false, + "id": "6928877d-4683-4f4c-8186-42ac4ab38543", + "last_modified": 1666727866885 + }, + { + "schema": 1666727339543, + "derHash": "tutPitGXBz/lID+Pzr/VxQnN6co6pl7FnSA4MUJBktQ=", + "subject": "CN=Secure Site Extended Validation CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLjAsBgNVBAMTJVNlY3VyZSBTaXRlIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "cd904a90ca202b5ad90cebb83e4d5c23de42eb5861cff29e373aa692f5cebac5", + "size": 1699, + "filename": "GQ1NxZwFxMspL6F7W34V3JpBp8emVEoKYr5gy_rpnLc=.pem", + "location": "security-state-staging/intermediates/9d68a7bf-267d-4ad1-a0e1-76c7bfd5f003.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GQ1NxZwFxMspL6F7W34V3JpBp8emVEoKYr5gy/rpnLc=", + "crlite_enrolled": false, + "id": "6dd778da-ae48-4e44-a711-9554a9bde292", + "last_modified": 1666727866869 + }, + { + "schema": 1666727363970, + "derHash": "cQAks3vZ8OFTfBikwg+aMcS0hdEkjGQ/ILTADzcWuoU=", + "subject": "CN=DigiCert EV Server CA G4,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1EaWdpQ2VydCAgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBFViBTZXJ2ZXIgQ0EgRzQ=", + "whitelist": false, + "attachment": { + "hash": "ed7edc9b1f494a5623657a2e420f2213a6a924fbe887f8b45d30a0ace12b994d", + "size": 1951, + "filename": "rSOteFVbBeX8qJ_1s6WFayDHdgaEKLjbWQS5PZDkdvg=.pem", + "location": "security-state-staging/intermediates/d7567855-d0ad-4791-b70c-ad1638c4b0b8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rSOteFVbBeX8qJ/1s6WFayDHdgaEKLjbWQS5PZDkdvg=", + "crlite_enrolled": false, + "id": "05859cd9-5da1-4707-afc7-8ae8f5dbe9e5", + "last_modified": 1666727866856 + }, + { + "schema": 1666727398131, + "derHash": "L2iJlhp8pwZ+i6EDws+bmpJPjKKT8RF44joZeNLxM9M=", + "subject": "CN=DigiCert Cloud Services CA-1,O=DigiCert Inc,C=US", + "subjectDN": "MEsxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJTAjBgNVBAMTHERpZ2lDZXJ0IENsb3VkIFNlcnZpY2VzIENBLTE=", + "whitelist": false, + "attachment": { + "hash": "d202b6061f57e425a4b7e1b2b848b30da87b4fa0eb8d5e4a70307ce5ff99228e", + "size": 1646, + "filename": "UgpUVparimk8QCjtWQaUQ7EGrtrykc_L8N66EhFY3VE=.pem", + "location": "security-state-staging/intermediates/0c3216d9-9fe7-4784-bccb-1927dd3ceff5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UgpUVparimk8QCjtWQaUQ7EGrtrykc/L8N66EhFY3VE=", + "crlite_enrolled": false, + "id": "d64cb01d-fa83-4092-845b-913f4d4061d6", + "last_modified": 1666727866842 + }, + { + "schema": 1666727365154, + "derHash": "j6xXZDnJ/T7xU7Ufnt0NOBtd97h1Wc6+ygQpfdRKY5s=", + "subject": "CN=DigiCert Global CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2JhbCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "52b1c1b08d18fc0370d8a9752dd7009dcb0db031a9495a435b240684ec2ba02d", + "size": 1634, + "filename": "njN4rRG-22dNXAi-yb8e3UMypgzPUPHlv4-foULwl1g=.pem", + "location": "security-state-staging/intermediates/00772084-a223-4f09-911c-f5ec083d339f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "njN4rRG+22dNXAi+yb8e3UMypgzPUPHlv4+foULwl1g=", + "crlite_enrolled": false, + "id": "0a341464-dd9a-4f0f-a855-d63fa887b2f4", + "last_modified": 1666727866829 + }, + { + "schema": 1666727372910, + "derHash": "m/7T2dyVI2vP/LNdSxINsOOGf2Kx/QFako2gswOWZoM=", + "subject": "CN=DigiCert High Assurance TLS RSA SHA256 2020 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE4MDYGA1UEAxMvRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTE=", + "whitelist": false, + "attachment": { + "hash": "5e142f2c8b2fe06316f53d76be5e837878e6537e01e66186d01bb4b6167dfc1b", + "size": 1748, + "filename": "KKzhMaY72_nD-ZVShAg153XyomvfRUrrKjaWxh3ZCRo=.pem", + "location": "security-state-staging/intermediates/b5ade449-4506-4a44-87eb-1f9505aa417a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KKzhMaY72/nD+ZVShAg153XyomvfRUrrKjaWxh3ZCRo=", + "crlite_enrolled": false, + "id": "324ecc15-aac9-473a-8fa5-04c69f2368f2", + "last_modified": 1666727866812 + }, + { + "schema": 1666727394590, + "derHash": "iTfpDhSVyaeO+xgYLUP9xz/NKSq2NHoSuQd96HhTDmI=", + "subject": "CN=GeoTrust EV RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxITAfBgNVBAMTGEdlb1RydXN0IEVWIFJTQSBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "6e6acba4497685370050fe22f00922d96466b366ec048389689c951f2bc20540", + "size": 1646, + "filename": "AKgAa8DfB2Gh8BJb5YJO995EeaGcs3vEecdkZw_OD7I=.pem", + "location": "security-state-staging/intermediates/54d08752-5c1b-49c6-8ecd-1cbdc144c4e3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AKgAa8DfB2Gh8BJb5YJO995EeaGcs3vEecdkZw/OD7I=", + "crlite_enrolled": false, + "id": "bcc2ec00-1bbb-49ff-9196-7453336027cc", + "last_modified": 1666727866796 + }, + { + "schema": 1666727450952, + "derHash": "+WkIgIGfBs3MCy8iSyB/KvYAP7VzObhnmhYPqVII1i0=", + "subject": "CN=DigiCert Baltimore CA-2 G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIzAhBgNVBAMTGkRpZ2lDZXJ0IEJhbHRpbW9yZSBDQS0yIEcy", + "whitelist": false, + "attachment": { + "hash": "8dc9d4aa138337848bff8373424db2e2dd239637f061a54c5dc8c5db87006e6f", + "size": 1581, + "filename": "56higu_MFWb_c2b0avLE5oN2ECS2C43RvzSUgx_2xIE=.pem", + "location": "security-state-staging/intermediates/1374f3d6-e2c4-481c-8658-733e97739f55.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "56higu/MFWb/c2b0avLE5oN2ECS2C43RvzSUgx/2xIE=", + "crlite_enrolled": false, + "id": "bd39e282-d3b0-4d50-ba66-de1d0121ebe8", + "last_modified": 1666727866783 + }, + { + "schema": 1666727359803, + "derHash": "CQM/4jmW/kpZxMD1I9JWDjHf5MF9jqFAPUKalx9L1lo=", + "subject": "CN=WoTrus OV SSL Pro CA,O=WoTrus CA Limited,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1RydXMgQ0EgTGltaXRlZDEdMBsGA1UEAxMUV29UcnVzIE9WIFNTTCBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "0496b3149e44ed6def4616b558d891efffb6b0d20710b857fed23934051aec68", + "size": 1861, + "filename": "zACNb3_D6r-GIH58_Q4EDalNSChXFVVFbUJICvq7SIQ=.pem", + "location": "security-state-staging/intermediates/f3b55e61-3d7d-4b0b-ac5f-0577a6dc5b2d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zACNb3/D6r+GIH58/Q4EDalNSChXFVVFbUJICvq7SIQ=", + "crlite_enrolled": false, + "id": "41d32923-8355-4d67-bfab-517aa0f3a571", + "last_modified": 1666727866770 + }, + { + "schema": 1666727356927, + "derHash": "S8xeI0/oHt5Or4g6oZwxM1sLJuheBmuZReTLYVPrIMI=", + "subject": "CN=Thawte TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFFRoYXd0ZSBUTFMgUlNBIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "c91c75d82301c3a18260ed11bc06f3c79a262ff7794711a32d4e0f6fae55fd16", + "size": 1634, + "filename": "42b9RNOnyb3tlC0KYtNPA3KKpJluskyU6aG-CipUmaM=.pem", + "location": "security-state-staging/intermediates/d89aea89-0644-488a-87ec-6fe32a4c8f6b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "42b9RNOnyb3tlC0KYtNPA3KKpJluskyU6aG+CipUmaM=", + "crlite_enrolled": false, + "id": "2b1fde2c-d19d-47aa-9dde-8355994571e0", + "last_modified": 1666727866757 + }, + { + "schema": 1666727409267, + "derHash": "wG4wf3z8HTL6cqTAM8h7kAGa8hbwd11kl4ouymyKIw4=", + "subject": "CN=GeoTrust TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFkdlb1RydXN0IFRMUyBSU0EgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "3700beab3bb15a8f5d9dc68032c04ba1abcb58e60c4fc02bcb9dbbbdbf31258a", + "size": 1638, + "filename": "SDG5orEv8iX6MNenIAxa8nQFNpROB_6-llsZdXHZNqs=.pem", + "location": "security-state-staging/intermediates/ced454b6-3f1c-47f2-8881-f95499846b83.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SDG5orEv8iX6MNenIAxa8nQFNpROB/6+llsZdXHZNqs=", + "crlite_enrolled": false, + "id": "b860e76d-2e99-4f11-8f34-c24d65fa9e26", + "last_modified": 1666727866744 + }, + { + "schema": 1666727372579, + "derHash": "GUAL5bejH7czkXcAeJ0vCiRxwMnVBsDlBMBsFtfLF8A=", + "subject": "CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLzAtBgNVBAMTJkRpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "fc49ef5499c2e3eebd0f3199498da32725362a16eed48e7b69a27d8f05901331", + "size": 1687, + "filename": "k2v657xBsOVe1PQRwOsHsw3bsGT2VzIqz5K-59sNQws=.pem", + "location": "security-state-staging/intermediates/0acde44b-2192-4511-8c5a-b0e50f180ccf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k2v657xBsOVe1PQRwOsHsw3bsGT2VzIqz5K+59sNQws=", + "crlite_enrolled": false, + "id": "c85675f4-4bb8-4759-909f-cea3b1729711", + "last_modified": 1666727866731 + }, + { + "schema": 1666727345420, + "derHash": "lRQaNVpaKqPgESIcq5/nOBD9tdiBNYZPA+TYUr0Lvts=", + "subject": "CN=Thawte RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHDAaBgNVBAMTE1RoYXd0ZSBSU0EgQ04gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "d0092989a33a059425f8fdb48fa5b67a27979e85599b87d5b3b4cdc9633acc51", + "size": 1792, + "filename": "hN3GzHyLopxOn8DxNQIEvpQa-NyS7R_1oQY0xsFsrOA=.pem", + "location": "security-state-staging/intermediates/fe06a439-e718-4f32-9f86-6496f932e53b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hN3GzHyLopxOn8DxNQIEvpQa+NyS7R/1oQY0xsFsrOA=", + "crlite_enrolled": false, + "id": "3718bfa1-b885-4d18-ba3c-c5d9ba546f46", + "last_modified": 1666727866718 + }, + { + "schema": 1666727423099, + "derHash": "48JCed1qM3+IGxvGkuh49KMa/pWFHyCPlIALDCTYjDg=", + "subject": "CN=GeoTrust RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFUdlb1RydXN0IFJTQSBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "77b33c8317779db8fa911e3a9f4b194cb8c14bf035a51538332572ba33b22407", + "size": 1813, + "filename": "LYxGolhhMxT6m_MXQhVdp6zkEP-xK8sxXN4I4pBqtFs=.pem", + "location": "security-state-staging/intermediates/14b69ed3-e7ed-4c49-bac3-fac74b07509f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LYxGolhhMxT6m/MXQhVdp6zkEP+xK8sxXN4I4pBqtFs=", + "crlite_enrolled": false, + "id": "6ddb3b1b-9a01-4257-b315-b29d2b88702d", + "last_modified": 1666727866704 + }, + { + "schema": 1666727436803, + "derHash": "E5rDa9hTmLu7RYhofCDcNoJ9A6X0Kq34fwK+zEvpv8I=", + "subject": "CN=DigiCert Secure Site EV CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFNlY3VyZSBTaXRlIEVWIENOIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "c30a8c2dba14a41fcf3c4466c0a37c568165ef661c1e847e1f42ba3523ae26e3", + "size": 1638, + "filename": "q0mT-mtnAchq7xXJEtVo25yl_8a1e08EqUu2Ww5ybfQ=.pem", + "location": "security-state-staging/intermediates/33ca1d04-2f29-43b2-bcb6-a67279e70854.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "q0mT+mtnAchq7xXJEtVo25yl/8a1e08EqUu2Ww5ybfQ=", + "crlite_enrolled": false, + "id": "7605906c-27c8-4817-9f0b-3ea342481f97", + "last_modified": 1666727866691 + }, + { + "schema": 1666727359455, + "derHash": "vzVY+HfonSfapg2pZxZ2Vw376yFdhKxaNxItZ3a3j24=", + "subject": "CN=GeoTrust EV ECC CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0dlb1RydXN0IEVWIEVDQyBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "fd09ba904b4aae46221e9b016749ea94a96f949958f1dd2f127e1155a6f0e083", + "size": 1390, + "filename": "KZiLkK8HMbpxJlPzQa3CEz5YMCWvdd0o1cov4_NrxCA=.pem", + "location": "security-state-staging/intermediates/02ea376f-2b25-48af-aa1a-bbc3ae4d927a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KZiLkK8HMbpxJlPzQa3CEz5YMCWvdd0o1cov4/NrxCA=", + "crlite_enrolled": false, + "id": "62aaa813-2caa-49f3-9a2f-e608096d7f84", + "last_modified": 1666727866677 + }, + { + "schema": 1666727358279, + "derHash": "umoMEXDhxzI4YHSbXnsO02WXXY/JB0CxXXD4Q6I5SUI=", + "subject": "CN=Secure Site Pro CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGzAZBgNVBAMTElNlY3VyZSBTaXRlIFBybyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "85f6a2b31ea1fb9dc7c6f10b9c4a06a72d0ee931bc7c241045f9c93994b8137f", + "size": 1674, + "filename": "yFzqzO6UfU8CG8Xw6hrhKs8Wc7kjwaPPHpMW5A4WT3k=.pem", + "location": "security-state-staging/intermediates/2ac911c5-7564-4f87-8226-5c4d8e843a63.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yFzqzO6UfU8CG8Xw6hrhKs8Wc7kjwaPPHpMW5A4WT3k=", + "crlite_enrolled": false, + "id": "763f32c5-dafd-4b5f-97fe-b84705fb1c14", + "last_modified": 1666727866664 + }, + { + "schema": 1666727419563, + "derHash": "x5C0cShEfsC2DyK/y3ldccMm3ZEO4Sy7TMWoYZHrkbw=", + "subject": "CN=RapidSSL RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFFJhcGlkU1NMIFJTQSBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "6dea21c7a180a2dd42927ed8ef9f67c44fe495ebe3a0b9f915212bdd00222efa", + "size": 1687, + "filename": "nKWcsYrc-y5I8vLf1VGByjbt-Hnasjl-9h8lNKJytoE=.pem", + "location": "security-state-staging/intermediates/45cec3c7-5837-4564-ae9a-ca159c42fdae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nKWcsYrc+y5I8vLf1VGByjbt+Hnasjl+9h8lNKJytoE=", + "crlite_enrolled": false, + "id": "3db8db3a-6520-45ae-bc8b-ee66c7826bf1", + "last_modified": 1666727866651 + }, + { + "schema": 1666727386274, + "derHash": "k1abJqpTXj4HyJHGvS+p3Ak5wk20s3Jq2FMe2xfEl8o=", + "subject": "CN=Thawte EV RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFVRoYXd0ZSBFViBSU0EgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "2afcbb41f3261b8de1c381ea87d94d71e912d4f0f133fc7def226d445a3d1fc9", + "size": 1662, + "filename": "LrLVqGD-UOnCQjaFUpgBUORdtTIaWwBeJtZ2JTpAm_U=.pem", + "location": "security-state-staging/intermediates/533a2e28-1d5c-4259-832a-fc9c84b4a612.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LrLVqGD+UOnCQjaFUpgBUORdtTIaWwBeJtZ2JTpAm/U=", + "crlite_enrolled": false, + "id": "18c0695e-ca93-4d3f-91c3-60819c6551f3", + "last_modified": 1666727866638 + }, + { + "schema": 1666727357259, + "derHash": "OPc2R/HsruwPxa/misMODCYXHp8jL0UwcpQSQI/VNiY=", + "subject": "CN=DigiCert Baltimore TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEJhbHRpbW9yZSBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "2895d14312fddb01cbe298e63afe2299e876f4c86692a4fc1ce2b81b4098621b", + "size": 1752, + "filename": "cxJYF2dxgQBCK6TeOX11kvdv3gHxeJgxQeT9-UzsuNw=.pem", + "location": "security-state-staging/intermediates/b367aa12-6056-49e2-b8bc-e3e1a2120b35.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cxJYF2dxgQBCK6TeOX11kvdv3gHxeJgxQeT9+UzsuNw=", + "crlite_enrolled": false, + "id": "390e7060-735e-480e-8acd-98737a0ee80b", + "last_modified": 1666727866624 + }, + { + "schema": 1666727420093, + "derHash": "pmyXrlnf+eoBXnPHRO5VIHRfEBo5XEl9GeHxaNOa/NA=", + "subject": "CN=DigiCert Secure Site Pro CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKjAoBgNVBAMTIURpZ2lDZXJ0IFNlY3VyZSBTaXRlIFBybyBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "fed0dc0203fd4a2fa009eba4aee1888c162e419b0c63be3155a472ad83b1b140", + "size": 1808, + "filename": "BbkOPUFIMuqBj5SBjChDvpb1ZCdk3b9ZNDWOnKRB_bo=.pem", + "location": "security-state-staging/intermediates/04307cc4-0014-46c3-ab8e-21b7b74b920c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BbkOPUFIMuqBj5SBjChDvpb1ZCdk3b9ZNDWOnKRB/bo=", + "crlite_enrolled": false, + "id": "d44f8523-49e6-4da8-9c0c-21f008f59251", + "last_modified": 1666727866611 + }, + { + "schema": 1666727332806, + "derHash": "29+pGsxNuK2D/MeXjjXWL24y5VEIJzyOyZjjEzWA1mQ=", + "subject": "CN=TrustAsia OV TLS Pro CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgT1YgVExTIFBybyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "09abfb488ce57ff55af2a8d48e0210dd09559b54b424afa704d5818fb498257b", + "size": 1821, + "filename": "cJhQtqu9DZRLxNRS1bDaM1E3d1GuJZmxfwKcfb-Gx3Y=.pem", + "location": "security-state-staging/intermediates/fc310196-a128-4383-818f-07e7f4229a9b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cJhQtqu9DZRLxNRS1bDaM1E3d1GuJZmxfwKcfb+Gx3Y=", + "crlite_enrolled": false, + "id": "b9f2b1b6-b5e8-4bb2-94a4-90b62bde54e7", + "last_modified": 1666727866598 + }, + { + "schema": 1666727377230, + "derHash": "bmLL4+QqQcUEBbPi8aRCV2gyZ4VhhK+cAhEgcKyDaTY=", + "subject": "CN=Thawte ECC CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGzAZBgNVBAMTElRoYXd0ZSBFQ0MgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "aaccd4f02bd25a9869feb7eaf00bf29254d846a3042440bc67f2dded4146ae98", + "size": 1358, + "filename": "3hiWxsgsNSgIwyt2ATwgEYbkxoaBEZb10lFKzoR0-ig=.pem", + "location": "security-state-staging/intermediates/80cb5267-95af-4561-94fa-159dffc7384b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3hiWxsgsNSgIwyt2ATwgEYbkxoaBEZb10lFKzoR0+ig=", + "crlite_enrolled": false, + "id": "c80dae20-2b2c-4555-9acd-802e86077d0e", + "last_modified": 1666727866585 + }, + { + "schema": 1666727419395, + "derHash": "XSh2HL8wTq/NEns01hT+F5rHdE8VUq8cMSmEJa0FonU=", + "subject": "CN=Aetna Inc. Secure CA2,O=Aetna Inc,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxHjAcBgNVBAMTFUFldG5hIEluYy4gU2VjdXJlIENBMg==", + "whitelist": false, + "attachment": { + "hash": "c2cdb82c5e1bf5ef4193f5ea83b80439de8011541cd0db0c76e1bb7ec0b468fd", + "size": 1683, + "filename": "NIgvyyhcGFo7vlL_K_w6cn8iFWO9ftJXWrIU77O5hMI=.pem", + "location": "security-state-staging/intermediates/ed4bff44-19e6-498e-9259-91ec361a7b9e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NIgvyyhcGFo7vlL/K/w6cn8iFWO9ftJXWrIU77O5hMI=", + "crlite_enrolled": false, + "id": "2d046223-ab4e-46b2-986d-e3da4c0832c8", + "last_modified": 1666727866572 + }, + { + "schema": 1666727412018, + "derHash": "FesKdcZzq/vc0vr8AoI8kf5svDbgB4hELIdU1yvsNxc=", + "subject": "CN=Encryption Everywhere DV TLS CA - G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "dde344d32d4e86413da0b7d37c26998d4ad3df3ca77e7f5f0053cbe4e746e064", + "size": 1678, + "filename": "GI75anSEdkuHj05mreE0Sd9jE6dVqUIzzXRHHlZBVbI=.pem", + "location": "security-state-staging/intermediates/07343162-9815-45f8-a95e-f2763cbe9c15.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GI75anSEdkuHj05mreE0Sd9jE6dVqUIzzXRHHlZBVbI=", + "crlite_enrolled": false, + "id": "aa3e61ad-a255-46e9-91c3-72520b55e7bf", + "last_modified": 1666727866559 + }, + { + "schema": 1666727435441, + "derHash": "2OJppusIy8M3rWV4xyE1eHSEyZ0vsIzO0CnoBr4QQPw=", + "subject": "CN=GeoTrust ECC CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFEdlb1RydXN0IEVDQyBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "e09d45c878751bf1ff0c329323a2fbfcf4ab034c73ea3028c6a001d090931e44", + "size": 1362, + "filename": "A0W8sBH3jBrKa3ukj8kse0jYSSeQM5e5tb8obnMQvak=.pem", + "location": "security-state-staging/intermediates/d38b536d-0613-4cda-96c9-e45d2dbfeae0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "A0W8sBH3jBrKa3ukj8kse0jYSSeQM5e5tb8obnMQvak=", + "crlite_enrolled": false, + "id": "9ae6e407-8a56-4149-b6a3-a866d64372ee", + "last_modified": 1666727866546 + }, + { + "schema": 1666727335491, + "derHash": "JXaHE9O0Wfk4LSpZT4XzRwn9KokwcxVCpBRv+yRr7Gk=", + "subject": "CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBSU0EgU0hBMjU2IDIwMjAgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "07cafa926a378703117e3b510b5d355851d64bae66fd9dcbc8f66da93cb60dcb", + "size": 1764, + "filename": "RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=.pem", + "location": "security-state-staging/intermediates/a5c4d407-50b5-4f8b-b512-1f94c112895a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=", + "crlite_enrolled": false, + "id": "edc74b28-6086-4e59-a786-7f11f3a68860", + "last_modified": 1666727866532 + }, + { + "schema": 1666727448849, + "derHash": "lYjvdBmeRazvzM/AxHAQ6fKjeh3UTGGk4cazNNpa9hQ=", + "subject": "CN=DigiCert EV RSA CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEVWIFJTQSBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "979717a08303b3bdf3cc094444c2d3f494028c214281cecf379dd35196298b15", + "size": 1873, + "filename": "w9I8WrpHr1YAS0DugvLGsrI9Vm1yEkZAyllKxemXXf4=.pem", + "location": "security-state-staging/intermediates/23e4caa9-34aa-417e-96d6-0cbe09fe2c1b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "w9I8WrpHr1YAS0DugvLGsrI9Vm1yEkZAyllKxemXXf4=", + "crlite_enrolled": false, + "id": "36053ff3-6dc0-49e3-a6a6-d96c46b3982b", + "last_modified": 1666727866519 + }, + { + "schema": 1666727354717, + "derHash": "oUSKAV4TmcWokYEvDojG2EeyIdIfkyamYmukOg62Etk=", + "subject": "CN=Aetna Inc. Secure EV CA,O=Aetna Inc,C=US", + "subjectDN": "MEMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxIDAeBgNVBAMTF0FldG5hIEluYy4gU2VjdXJlIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "b397cd1ec073303b2d5236a77c1446f20ccf4a85eaeca51549c6aa6108d92da2", + "size": 1804, + "filename": "0rv4XQwSpZni_0C8FcOJhSJNEzvghB5GUVNKhM-UmQE=.pem", + "location": "security-state-staging/intermediates/e66dd7a1-3bd1-42ac-98c6-daeefd436477.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0rv4XQwSpZni/0C8FcOJhSJNEzvghB5GUVNKhM+UmQE=", + "crlite_enrolled": false, + "id": "cc712069-0be1-4f40-9cbd-38ca7c1847a7", + "last_modified": 1666727866506 + }, + { + "schema": 1666727395258, + "derHash": "y1ez/yBAyyaUl2JbyQ+p17TtSTjG9g9C9pr99QisKZM=", + "subject": "CN=DigiCert Basic RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJDAiBgNVBAMTG0RpZ2lDZXJ0IEJhc2ljIFJTQSBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "6183d382e68c871db6d7256406cdd74001d595ef4bd736fcd8ffc633574569a7", + "size": 1821, + "filename": "ZckwC4fqIUCeiz1_ihqLY9TDek4tDc704HFPrhqylPo=.pem", + "location": "security-state-staging/intermediates/aabdf20d-d35a-4828-9c8e-d44fe1098547.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZckwC4fqIUCeiz1/ihqLY9TDek4tDc704HFPrhqylPo=", + "crlite_enrolled": false, + "id": "49f5824f-cdce-4d43-8e45-02e42e7c2f48", + "last_modified": 1666727866493 + }, + { + "schema": 1666727340785, + "derHash": "75KWA2ptfJhsjVnbk2CT546FxwrttP56VN3KqCEDHHI=", + "subject": "CN=DigiCert G5 TLS RSA4096 SHA384 2021 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEwMC4GA1UEAxMnRGlnaUNlcnQgRzUgVExTIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "33ab6bfa9d61060281c2ebbcb71780c80a5ac2b429c5cb932f9a540cd319b189", + "size": 2377, + "filename": "5R0B4UlPeqmGgtewU9-0QUYDvO9-UNN4YyL8SiHOYVo=.pem", + "location": "security-state-staging/intermediates/0e43578c-ef31-4c8e-b5b1-2529e35e4d96.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5R0B4UlPeqmGgtewU9+0QUYDvO9+UNN4YyL8SiHOYVo=", + "crlite_enrolled": false, + "id": "bed5a55d-5b9c-40fb-b5e4-1ee14b847e60", + "last_modified": 1666727866478 + }, + { + "schema": 1666727360317, + "derHash": "Go15Cvmys0191q9htapM8TgLhglcusK8qzW9Vm0BgMM=", + "subject": "CN=Secure Site Extended Validation CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIlNlY3VyZSBTaXRlIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0E=", + "whitelist": false, + "attachment": { + "hash": "840e93b7392e279f33dc1fc00f08d6429a4c62a6a952ed7455934769b1105c47", + "size": 1687, + "filename": "GQ1NxZwFxMspL6F7W34V3JpBp8emVEoKYr5gy_rpnLc=.pem", + "location": "security-state-staging/intermediates/adeeb09d-1c1b-4333-92ef-bcff5522fff3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GQ1NxZwFxMspL6F7W34V3JpBp8emVEoKYr5gy/rpnLc=", + "crlite_enrolled": false, + "id": "7987d8aa-24a9-48a4-bafd-eb28c3ed6e13", + "last_modified": 1666727866460 + }, + { + "schema": 1666727418217, + "derHash": "qDhAXrsD9d/Y1KlXKubg4/NW7E7BNDdaWdt7GV3D7EQ=", + "subject": "CN=GeoTrust TLS ECC CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFkdlb1RydXN0IFRMUyBFQ0MgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "c314d639f9d79bd1cc0fb4ad1de6c2d14f5a3552e4fb548497c2dfc4b048c3e8", + "size": 1146, + "filename": "5UDBgr_RVcJyUuNCNmS5AenhQ6TpdPBpf6CAGjUnD10=.pem", + "location": "security-state-staging/intermediates/5194afcf-779a-4b67-8a2c-d2d678abc8e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5UDBgr/RVcJyUuNCNmS5AenhQ6TpdPBpf6CAGjUnD10=", + "crlite_enrolled": false, + "id": "39bb1651-f937-42b1-a9c7-dbf371efada0", + "last_modified": 1666727866446 + }, + { + "schema": 1666727389532, + "derHash": "mZNeIEJFNewBbzN7K+aPE0neZszkylqzZ/jzc4IVuDM=", + "subject": "CN=DigiCert Secure Site ECC CA-1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJjAkBgNVBAMTHURpZ2lDZXJ0IFNlY3VyZSBTaXRlIEVDQyBDQS0x", + "whitelist": false, + "attachment": { + "hash": "1a5e1cfd28c5f42d5ec051123399c858ed20cf62dd5550bfff961a4aa3e7307b", + "size": 1374, + "filename": "Z5_2RtWXrCvvJsLXdZ4t9HEaVg21e9AhczULn9tmg4M=.pem", + "location": "security-state-staging/intermediates/4567866f-5a00-4281-8d39-96cd49487394.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z5/2RtWXrCvvJsLXdZ4t9HEaVg21e9AhczULn9tmg4M=", + "crlite_enrolled": false, + "id": "8d662803-b81e-4f7c-b977-0386af9091ea", + "last_modified": 1666727866433 + }, + { + "schema": 1666727442631, + "derHash": "/ciYbPrE818azVF+D2G4eYgq4HbiuoC3e9Pw/lzviGI=", + "subject": "CN=DigiCert ECC Extended Validation Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMzAxBgNVBAMTKkRpZ2lDZXJ0IEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "0563ca9171ec77c7dc031dc81fa7dd0cae759518c708ffd9626ac265013d5e29", + "size": 1414, + "filename": "WOINGBsGlG95B7s_6U7XB_KM7U73y-sXgtLqZpn3G88=.pem", + "location": "security-state-staging/intermediates/f2a467cf-852a-4877-8297-d067155b948c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WOINGBsGlG95B7s/6U7XB/KM7U73y+sXgtLqZpn3G88=", + "crlite_enrolled": false, + "id": "31a6dd57-062d-4f9e-8c49-c1e3b9ec2788", + "last_modified": 1666727866420 + }, + { + "schema": 1666727346774, + "derHash": "bo2VL9urrY3j1h4JQ5Nzm1pHNxpSvcsqPC+MQ2IvZA8=", + "subject": "CN=DigiCert Trusted Server CA G4,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IFRydXN0ZWQgU2VydmVyIENBIEc0", + "whitelist": false, + "attachment": { + "hash": "15dd9f28242cbc8c7c6e592f07d7aee34c14e229225f95df277eda489fb2144e", + "size": 2263, + "filename": "ATJixqKjjVXHRkLEgfX09sgN3kdNds3rCsOQC264qlM=.pem", + "location": "security-state-staging/intermediates/4b699f50-0bbe-4fae-b162-c628ceafd5f0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ATJixqKjjVXHRkLEgfX09sgN3kdNds3rCsOQC264qlM=", + "crlite_enrolled": false, + "id": "8b519983-0c91-4ee5-993d-bc9fee4a60c0", + "last_modified": 1666727866406 + }, + { + "schema": 1666727445682, + "derHash": "GZ7lgAlVXa4s2gYmkxxkOR1qiMzLH58LLugLZn9YHAY=", + "subject": "CN=Cisco Meraki CA,O=Cisco Systems\\, Inc.,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRwwGgYDVQQKExNDaXNjbyBTeXN0ZW1zLCBJbmMuMRgwFgYDVQQDEw9DaXNjbyBNZXJha2kgQ0E=", + "whitelist": false, + "attachment": { + "hash": "1528a4dab30891539c1d26c31940c77bb23d870302ee395db0e3c6edf0270cba", + "size": 1638, + "filename": "GUIjMNe--V5-Oj0iDybPHPoFZBo3KkQeXR-6l1vJW34=.pem", + "location": "security-state-staging/intermediates/b7c2edca-eecf-41ee-95ae-d460adebdd41.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GUIjMNe++V5+Oj0iDybPHPoFZBo3KkQeXR+6l1vJW34=", + "crlite_enrolled": false, + "id": "cedb8044-76ac-46bb-9df6-87fde0517cfe", + "last_modified": 1666727866393 + }, + { + "schema": 1666727343676, + "derHash": "igI8COShqsySWzTFx5ZajQUnVlrqEwq7kV5QjNM6+0U=", + "subject": "CN=GeoTrust RSA CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFUdlb1RydXN0IFJTQSBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "68bccf72a6fa1673b1b5cb1775d8943e1e8377e2b4a7f3c379126f64ec0dd89f", + "size": 1792, + "filename": "Z8gGiWQqJBlCkkMEDLNWwgA7ptFGYD644CqzC-fWQBE=.pem", + "location": "security-state-staging/intermediates/7a8ab771-37f3-457e-ae5c-8ffb63b6f29e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z8gGiWQqJBlCkkMEDLNWwgA7ptFGYD644CqzC+fWQBE=", + "crlite_enrolled": false, + "id": "1f28e67d-7ac2-46c1-ae7b-7c3d443054b7", + "last_modified": 1666727866379 + }, + { + "schema": 1666727377559, + "derHash": "mFFAkZNCI5yJCGsi38qkUIWQsRo94KQj4l68Pdr4kQ0=", + "subject": "CN=DigiCert Secure Site Pro ECC CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxLjAsBgNVBAMTJURpZ2lDZXJ0IFNlY3VyZSBTaXRlIFBybyBFQ0MgQ04gQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "59fabf6d371074df2fed2750d128dff31353b2691c777e5189ceae43ba1b2cb4", + "size": 1341, + "filename": "ljUnYjVBy0mvbfWJdhWfQmd_OGXEqZ6sBA-BZTbrSVY=.pem", + "location": "security-state-staging/intermediates/cb45983e-15d2-495d-a73a-01e52941fdb5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ljUnYjVBy0mvbfWJdhWfQmd/OGXEqZ6sBA+BZTbrSVY=", + "crlite_enrolled": false, + "id": "e18682ca-0440-47ad-9f9c-3eb3aa842b2a", + "last_modified": 1666727866366 + }, + { + "schema": 1666727362769, + "derHash": "jMNOEcFnBFgkreYcSQemRA7bLEOY6ZwRKoWdZh+OK8c=", + "subject": "CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFEdlb1RydXN0IFJTQSBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "b52c2405d007fa4609fce284e0ec8518bc3c2133009f36be18b2cc717cc58084", + "size": 1634, + "filename": "zUIraRNo-4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=.pem", + "location": "security-state-staging/intermediates/2e1a3cd7-0c11-46b6-ac97-522525cb0897.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zUIraRNo+4JoAYA7ROeWjARtIoN4rIEbCpfCRQT6N6A=", + "crlite_enrolled": false, + "id": "4ec89fca-6a6d-4154-9023-d7ec1ed235bd", + "last_modified": 1666727866353 + }, + { + "schema": 1666727330386, + "derHash": "l6BzlXxYEnJXV2cvIiaQZVoUB9cQEe38iy+txRKJEdo=", + "subject": "CN=DigiCert G5 TLS ECC SHA384 2021 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEsMCoGA1UEAxMjRGlnaUNlcnQgRzUgVExTIEVDQyBTSEEzODQgMjAyMSBDQTE=", + "whitelist": false, + "attachment": { + "hash": "4a57bc07702213ad8106224c4a6f736bc535fa974729592b97c20e276d0f15a2", + "size": 1244, + "filename": "LsOdqDFw3goo_G8jjeEPxe-JSJ7aFp1RF5Ih4_2ZvFY=.pem", + "location": "security-state-staging/intermediates/b922bf76-82c6-4d56-9df0-a9ce0bbc8904.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LsOdqDFw3goo/G8jjeEPxe+JSJ7aFp1RF5Ih4/2ZvFY=", + "crlite_enrolled": false, + "id": "62f940db-d5f1-4e34-b456-a57c2de3f9a8", + "last_modified": 1666727866325 + }, + { + "schema": 1666727391750, + "derHash": "X4hpRhXkxhaG4Qa4TDM4xnIMU19g029hKC7RXhl33UQ=", + "subject": "CN=DigiCert Cloud Services CA-1,O=DigiCert Inc,C=US", + "subjectDN": "MEsxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJTAjBgNVBAMTHERpZ2lDZXJ0IENsb3VkIFNlcnZpY2VzIENBLTE=", + "whitelist": false, + "attachment": { + "hash": "ddd476a218cddfdfa053e1aac1cd92bd85a4418fa86d82cbf1ecd3d7a85fe32b", + "size": 1760, + "filename": "UgpUVparimk8QCjtWQaUQ7EGrtrykc_L8N66EhFY3VE=.pem", + "location": "security-state-staging/intermediates/7bfa15b8-4267-4b8c-b430-23dddbb6404a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UgpUVparimk8QCjtWQaUQ7EGrtrykc/L8N66EhFY3VE=", + "crlite_enrolled": false, + "id": "ed2612e9-fdfc-412e-90c3-05ddee993b94", + "last_modified": 1666727866312 + }, + { + "schema": 1666727426147, + "derHash": "AMDLqVy/4eJwgUJ18ytQXW5ZFsK22mf69N0wnubobL4=", + "subject": "CN=DigiCert Secure Site ECC CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKjAoBgNVBAMTIURpZ2lDZXJ0IFNlY3VyZSBTaXRlIEVDQyBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "8607c668ffd111313d1bac37cc89373d3f0f37b454c9b4247d80bb41c7462d2a", + "size": 1337, + "filename": "mpDMzjSYAgr-a1VhfWcSLkk3xG29zzCRDJISnBodNVg=.pem", + "location": "security-state-staging/intermediates/d3d5ec25-e1b1-46da-ad86-9d0efdea52e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mpDMzjSYAgr+a1VhfWcSLkk3xG29zzCRDJISnBodNVg=", + "crlite_enrolled": false, + "id": "0cabb874-cc9f-4f3c-bbdb-b0096ee3ebc1", + "last_modified": 1666727866298 + }, + { + "schema": 1666727404068, + "derHash": "01M7cypRim2mjvJmCF4R39EUwOsAks1DUwpE1UuRPtE=", + "subject": "CN=Secure Site CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xFzAVBgNVBAMTDlNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "8503ea96cc4afe592dd276b2331616cdcab35f585437369264cdb10317b7c1f5", + "size": 1662, + "filename": "3mc_12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=.pem", + "location": "security-state-staging/intermediates/ed54c2d1-c94c-4f85-a594-1a321f6c6d09.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3mc/12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=", + "crlite_enrolled": false, + "id": "10ea447c-1348-4510-bf93-df74417f637c", + "last_modified": 1666727866285 + }, + { + "schema": 1666727342648, + "derHash": "OIPm3kkXpGtZTswtKsapXUPn6qjgiakfm8EE/xbfjeY=", + "subject": "CN=Secure Site Pro Extended Validation CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLzAtBgNVBAMTJlNlY3VyZSBTaXRlIFBybyBFeHRlbmRlZCBWYWxpZGF0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "d86c7a477e4365081d111dc0b7a87945d10a54dca6dbfa31bc016b6b7714eb55", + "size": 1695, + "filename": "C5iNVr6DMrTBS2Wvb08zPxdCQnB0DSWA-yu6a9MbQBI=.pem", + "location": "security-state-staging/intermediates/d15ee40f-7057-421f-a864-6e16847d31fe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "C5iNVr6DMrTBS2Wvb08zPxdCQnB0DSWA+yu6a9MbQBI=", + "crlite_enrolled": false, + "id": "17532cc5-7a32-494c-957b-330438017c68", + "last_modified": 1666727866271 + }, + { + "schema": 1666727446908, + "derHash": "Ut3on91vYQRz+TFMeosaxEL4qoM6pddzAG/adYyf8ew=", + "subject": "CN=GeoTrust ECC CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFUdlb1RydXN0IEVDQyBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "e1d421efc7701807b272be245a0e91e28951861f5275b8127b114b8bca7daab7", + "size": 1321, + "filename": "htzU8zBrdwmMNFLvW_ZzkFTZcn95rIZbtuXmmiR6prc=.pem", + "location": "security-state-staging/intermediates/e74cc07a-c43b-42f8-9563-ce8caf0521d5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "htzU8zBrdwmMNFLvW/ZzkFTZcn95rIZbtuXmmiR6prc=", + "crlite_enrolled": false, + "id": "cf269307-4b53-4d48-a114-bb6d80cba476", + "last_modified": 1666727866257 + }, + { + "schema": 1666727350944, + "derHash": "IQbMeQfGS4pdovwzjulN//EO9xHe27T8aU7gkuxTKx0=", + "subject": "CN=TrustCubes ICA G1,OU=www.trustcubes.com,O=TRUSTCUBES LIMITED,C=GB", + "subjectDN": "MIGhMQswCQYDVQQGEwJHQjEbMBkGA1UEChMSVFJVU1RDVUJFUyBMSU1JVEVEMRcwFQYIKwYBBAGEBwETCTIyMzAxMzcwMTEjMCEGCSsGAQQBg5gqARMUOTg0NTAwNTA1RkU4MENEME5FNTgxGzAZBgNVBAsTEnd3dy50cnVzdGN1YmVzLmNvbTEaMBgGA1UEAxMRVHJ1c3RDdWJlcyBJQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "4fd4f517ffa8aac9af19b9e801ab62735acff50caf86a036094e511433c5de0b", + "size": 1756, + "filename": "WQNVf79ywhoSIaihn1jQE-5z8UKENBfhCeJcnklN0YQ=.pem", + "location": "security-state-staging/intermediates/006f58e9-d885-42b4-b686-a26a127b9507.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WQNVf79ywhoSIaihn1jQE+5z8UKENBfhCeJcnklN0YQ=", + "crlite_enrolled": false, + "id": "a7b8fc53-0818-4870-aeb4-0fc4462bbd98", + "last_modified": 1666727866244 + }, + { + "schema": 1666727428316, + "derHash": "Ukz3MxxO41PusezXTh+AGg8fCN+gMiCS9CIFr8OhdnU=", + "subject": "CN=DigiCert SHA-2 RADIUS CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFNIQS0yIFJBRElVUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "aa9ab8aca60dc4901811836ab08219bf41a254f4a99c5a8df19a6e2e7a205861", + "size": 1674, + "filename": "yZwj8Xi_M8HwwIrk8zUJt2lZ5wy1IcLCTmo7r845-xQ=.pem", + "location": "security-state-staging/intermediates/22ac570b-4cc3-4c17-9e18-d971ddf56a48.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yZwj8Xi/M8HwwIrk8zUJt2lZ5wy1IcLCTmo7r845+xQ=", + "crlite_enrolled": false, + "id": "5b92eb31-eb03-4a75-85b7-54719a5a0329", + "last_modified": 1666727866230 + }, + { + "schema": 1666727406472, + "derHash": "oNFSOgv2Y1UOuQgem93toSgU+VcLdpfZXfLqU9T/deM=", + "subject": "CN=RapidSSL ECC CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHTAbBgNVBAMTFFJhcGlkU1NMIEVDQyBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "9a697f2608b2ac5c4f46062de1776c38440087ed4083ca828a3f55fafc471bcc", + "size": 1410, + "filename": "zJax49jXNWVm12ouj-dVmlW_r0Zw4NbjfsoCxV3-blM=.pem", + "location": "security-state-staging/intermediates/e9579747-90e1-476e-8584-f49cf81d5a9a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zJax49jXNWVm12ouj+dVmlW/r0Zw4NbjfsoCxV3+blM=", + "crlite_enrolled": false, + "id": "f489bd87-1084-4d96-89f4-68c1dc43202c", + "last_modified": 1666727866217 + }, + { + "schema": 1666727418046, + "derHash": "3cu08wUdbmATEOsORrW7mcXouZ96U542eph96gMOMY4=", + "subject": "CN=Aetna Inc. Secure CA2,O=Aetna Inc,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxHjAcBgNVBAMTFUFldG5hIEluYy4gU2VjdXJlIENBMg==", + "whitelist": false, + "attachment": { + "hash": "fef5a3bae1ed2ec43c7ac48315dd5848dcddc98565757b54e37a4e6a8cfd8a99", + "size": 1800, + "filename": "NIgvyyhcGFo7vlL_K_w6cn8iFWO9ftJXWrIU77O5hMI=.pem", + "location": "security-state-staging/intermediates/36301512-ecda-409a-9af9-1adcd3291550.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NIgvyyhcGFo7vlL/K/w6cn8iFWO9ftJXWrIU77O5hMI=", + "crlite_enrolled": false, + "id": "92ded146-e18a-48f3-b504-3ec6efe36e7d", + "last_modified": 1666727866203 + }, + { + "schema": 1666727341626, + "derHash": "A86bxxuR/bfLPFI1yuBwHLSGu9Yo1KreWEH8XwqjekY=", + "subject": "CN=DigiCert CN RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFURpZ2lDZXJ0IENOIFJTQSBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "c27c05d060655cb099a911109bba4a1d37d8af8f018dc4d2dfad6e5274c164ac", + "size": 1829, + "filename": "MIKoeatlSqVA3aCIrE0_JYoP9vF4XSCTPHy-c9vAsKk=.pem", + "location": "security-state-staging/intermediates/891ec495-a9ed-4e9c-a88e-e0bd528c5bcf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MIKoeatlSqVA3aCIrE0/JYoP9vF4XSCTPHy+c9vAsKk=", + "crlite_enrolled": false, + "id": "c71148c4-7b1e-41d9-ab5f-466795767fc5", + "last_modified": 1666727866190 + }, + { + "schema": 1666727425132, + "derHash": "sTGQXMciEnBhO1KayeeGqiMKv+FUoKy+RSvDUL0e/ks=", + "subject": "CN=DigiCert CN RSA EV CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IENOIFJTQSBFViBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "05e62f726d0dc50be765d984c585e938b40a504b1fcb88a02e90cca88e163ddb", + "size": 1683, + "filename": "mGDyZVimaL1gSwtTqcpINStgZay1F9wruRyUZy0wI3g=.pem", + "location": "security-state-staging/intermediates/bb88f2ec-ea56-4089-90a4-8d9259f9c264.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mGDyZVimaL1gSwtTqcpINStgZay1F9wruRyUZy0wI3g=", + "crlite_enrolled": false, + "id": "e988072a-6c66-46f0-b901-7fca54ca2ef5", + "last_modified": 1666727866177 + }, + { + "schema": 1666727439554, + "derHash": "tBpIZPDU7E6mMtAbPn8jJ3XlXiKzv9hkLuEpIoDQ5Ho=", + "subject": "CN=DigiCert Basic EV RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IEJhc2ljIEVWIFJTQSBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "2488500f03c3d8515238899ef2a1f17805b624993fc13c196c39ff9e4ccc8704", + "size": 1654, + "filename": "M8wsUw9qVjgbOPT0Gb0ZignTvgWTwtAotFBG4daZQ1w=.pem", + "location": "security-state-staging/intermediates/0b915143-abba-4380-9fa4-03f105da62d9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M8wsUw9qVjgbOPT0Gb0ZignTvgWTwtAotFBG4daZQ1w=", + "crlite_enrolled": false, + "id": "b82a5536-966e-4914-86dd-98d87f07e263", + "last_modified": 1666727866163 + }, + { + "schema": 1666727448511, + "derHash": "sm6zEPj68O9bDQtxqmXsBQ+jreKRNPtDirZEAoj6bmc=", + "subject": "CN=Secure Site CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xFzAVBgNVBAMTDlNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "4cf5a68da77fb6a3d9ca37398febfa6527ad886b3e86117420589375b33083c5", + "size": 1666, + "filename": "3mc_12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=.pem", + "location": "security-state-staging/intermediates/9f2f87aa-eb42-495d-a1f4-5b5d6bc4394f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3mc/12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=", + "crlite_enrolled": false, + "id": "e2f0b91f-d44f-42d2-ae2a-3b63eb6d7e9e", + "last_modified": 1666727866148 + }, + { + "schema": 1666727368560, + "derHash": "ml7s7px9iYvYHcO/Bm2vau+42xxZZ2IG0r/daCMSxvY=", + "subject": "CN=Thawte RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGzAZBgNVBAMTElRoYXd0ZSBSU0EgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "8a7b2ec3e21a1e59ebe4e10096741f36dc996f8c2901999550f7e629555361dc", + "size": 1634, + "filename": "S0mHTmqv2QhJEfy5vyPVERSnyMEliJzdC8RXduOjhAs=.pem", + "location": "security-state-staging/intermediates/a295584b-223a-4e07-a0cb-3953ab3f2e46.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S0mHTmqv2QhJEfy5vyPVERSnyMEliJzdC8RXduOjhAs=", + "crlite_enrolled": false, + "id": "4d1beba6-fa58-40c2-8d1d-086d634fa5bd", + "last_modified": 1666727866135 + }, + { + "schema": 1666727422551, + "derHash": "ChY2AGMb1mJn+3rq0lxTiyt9cq1kFqK70oX2VLtkL20=", + "subject": "CN=Aetna Inc. Secure EV CA,O=Aetna Inc,C=US", + "subjectDN": "MEMxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxIDAeBgNVBAMTF0FldG5hIEluYy4gU2VjdXJlIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "99945b8fb1ff89697427d104cf744cf70ff8fc7f21b7ea991a9d7a97932b449b", + "size": 1687, + "filename": "0rv4XQwSpZni_0C8FcOJhSJNEzvghB5GUVNKhM-UmQE=.pem", + "location": "security-state-staging/intermediates/1d1ab64c-f64a-4919-aace-65e8529d7d55.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0rv4XQwSpZni/0C8FcOJhSJNEzvghB5GUVNKhM+UmQE=", + "crlite_enrolled": false, + "id": "5958f121-2009-4512-9ba6-82063db5ad3a", + "last_modified": 1666727866121 + }, + { + "schema": 1666727389863, + "derHash": "rshjiY8oTWzUxqP2w+ZSNICjWcM9r2b60zgYSbi7AYs=", + "subject": "CN=Cloudflare Inc RSA CA-2,O=Cloudflare\\, Inc.,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZGZsYXJlLCBJbmMuMSAwHgYDVQQDExdDbG91ZGZsYXJlIEluYyBSU0EgQ0EtMg==", + "whitelist": false, + "attachment": { + "hash": "e6beb4b3ac790c49b23fd4e9e9e03e0d29f7200df762a6b737068f6a873e8f9a", + "size": 1654, + "filename": "hS5jJ4P-iQUErBkvoWBQOd1T7VOAYlOVegvv1iMzpxA=.pem", + "location": "security-state-staging/intermediates/e920545c-7b6c-4c76-9a6a-e2490d391424.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hS5jJ4P+iQUErBkvoWBQOd1T7VOAYlOVegvv1iMzpxA=", + "crlite_enrolled": false, + "id": "239072e9-3a93-488b-90f7-98aa49c2584b", + "last_modified": 1666727866091 + }, + { + "schema": 1666727406145, + "derHash": "t1rumDb++8RG8oiilwuE/GCq+e+9LLn4L3WBeQ3kuNw=", + "subject": "CN=DigiCert Secure Site Pro EV CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxLTArBgNVBAMTJERpZ2lDZXJ0IFNlY3VyZSBTaXRlIFBybyBFViBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "26af9fa1e7c77c446cb3a04828a0ea066545df24ccd3eb7388b4c284151f916f", + "size": 1642, + "filename": "2OVT1JHnI_dBAIq49zWtXoFiiSQRI-yC8zdls8oKK38=.pem", + "location": "security-state-staging/intermediates/4edbb396-edd5-4bfd-81b5-b537de90240a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2OVT1JHnI/dBAIq49zWtXoFiiSQRI+yC8zdls8oKK38=", + "crlite_enrolled": false, + "id": "cc215f58-abea-4b31-947a-f62a88abb6ed", + "last_modified": 1666727866071 + }, + { + "schema": 1666727451464, + "derHash": "RCLpY+5TzVjMn4XNQL9f/sAJX98aFUU1ZhwcBrytxps=", + "subject": "CN=RapidSSL TLS RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFlJhcGlkU1NMIFRMUyBSU0EgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "97c7e7dd9d4df6ec631b1e879fc5d5d001c2ee54c275b370c5bfe8b2075f995e", + "size": 1691, + "filename": "E3tYcwo9CiqATmKtpMLW5V-pzIq-ZoDmpXSiJlXGmTo=.pem", + "location": "security-state-staging/intermediates/633fabe3-e7ff-425b-9146-a18134188c21.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "E3tYcwo9CiqATmKtpMLW5V+pzIq+ZoDmpXSiJlXGmTo=", + "crlite_enrolled": false, + "id": "7e9c3ad7-edb5-4500-b004-a7bbc004a745", + "last_modified": 1666727866054 + }, + { + "schema": 1666727339207, + "derHash": "xicKFQaR++GQ2DH1E5vf7s97KYtPoMoXMGpp1+kee6I=", + "subject": "CN=DigiCert G5 TLS RSA4096 SHA384 2021 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEwMC4GA1UEAxMnRGlnaUNlcnQgRzUgVExTIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "2af8b3632cefcd23ee28c7ef0e26baf3aa1f6248ff86a03ee9c42664bdcaf856", + "size": 2393, + "filename": "5R0B4UlPeqmGgtewU9-0QUYDvO9-UNN4YyL8SiHOYVo=.pem", + "location": "security-state-staging/intermediates/939f632b-9b51-4b94-911d-78a3fe1b1429.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5R0B4UlPeqmGgtewU9+0QUYDvO9+UNN4YyL8SiHOYVo=", + "crlite_enrolled": false, + "id": "b5040b0c-ef3a-493c-a5eb-e334c81a0902", + "last_modified": 1666727866031 + }, + { + "schema": 1666727450056, + "derHash": "M+ik7UiTB2DOGtei1E8HmyL2YAUnU5dhCeb8dHUlUr0=", + "subject": "CN=TrustAsia OV TLS Pro CA,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSAwHgYDVQQDExdUcnVzdEFzaWEgT1YgVExTIFBybyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d696e8a02c7f0d746829d2b3318c91ce8daf2c9b2478c186e6765456f154da1f", + "size": 1666, + "filename": "cJhQtqu9DZRLxNRS1bDaM1E3d1GuJZmxfwKcfb-Gx3Y=.pem", + "location": "security-state-staging/intermediates/7c03ad6f-9302-4ef0-8cfa-c63b88ca2eb0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cJhQtqu9DZRLxNRS1bDaM1E3d1GuJZmxfwKcfb+Gx3Y=", + "crlite_enrolled": false, + "id": "422d4f55-855c-47d0-b608-19915a4013b4", + "last_modified": 1666727866011 + }, + { + "schema": 1666727360496, + "derHash": "fK7saA5kl/xRCQc/g4FueY8n+JaKJmPByVcRtUgZLjU=", + "subject": "CN=GeoTrust ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFUdlb1RydXN0IEVDQyBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "46e627fcefde31414651ca984396410cd372950426d973d114d2360dcf3cd38b", + "size": 1374, + "filename": "5hUeeQGevtQx_9LReJqSkj73Yv0TUcld9s9jPFCqJqA=.pem", + "location": "security-state-staging/intermediates/53ecee3d-ffe9-40eb-9a7c-8d3951ba457c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5hUeeQGevtQx/9LReJqSkj73Yv0TUcld9s9jPFCqJqA=", + "crlite_enrolled": false, + "id": "9077c165-1ccf-47cc-a76a-d560b2ee0cda", + "last_modified": 1666727865993 + }, + { + "schema": 1666727363462, + "derHash": "M47bBPuL6vB6EHSef05TjeBxXa+2R41YBj+3yL2www0=", + "subject": "CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMyBUTFMgRUNDIFNIQTM4NCAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "89f87e5ab2d8608c9772c9ee89bb1ff266577aa9247e7555e07215b5108cbd82", + "size": 1325, + "filename": "qBRjZmOmkSNJL0p70zek7odSIzqs_muR4Jk9xYyCP-E=.pem", + "location": "security-state-staging/intermediates/9e35054b-aef6-4258-8ef5-65899c7b0772.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qBRjZmOmkSNJL0p70zek7odSIzqs/muR4Jk9xYyCP+E=", + "crlite_enrolled": false, + "id": "751a26ff-6347-44ae-af17-142d6fcf49c7", + "last_modified": 1666727865974 + }, + { + "schema": 1666727350439, + "derHash": "8SJB7jTAOmCNNNvA6kZeG9GqEwkVVPnU0IYlP/POg9Q=", + "subject": "CN=DC Government SHA2 EV Intermediate CA,OU=Office of the Chief Technology Officer,O=Government of the District of Columbia,C=US", + "subjectDN": "MIGfMQswCQYDVQQGEwJVUzEvMC0GA1UEChMmR292ZXJubWVudCBvZiB0aGUgRGlzdHJpY3Qgb2YgQ29sdW1iaWExLzAtBgNVBAsTJk9mZmljZSBvZiB0aGUgQ2hpZWYgVGVjaG5vbG9neSBPZmZpY2VyMS4wLAYDVQQDEyVEQyBHb3Zlcm5tZW50IFNIQTIgRVYgSW50ZXJtZWRpYXRlIENB", + "whitelist": false, + "attachment": { + "hash": "90f3dceffcd69e1b48cfc958f2ea459f79c5ee69520f368de74c3a929c78a497", + "size": 2073, + "filename": "D6bVPPyHKUFV0WmvSS6nCMgaQId_SQKugGjOOT48QXE=.pem", + "location": "security-state-staging/intermediates/cad5f6b5-2734-4acd-93bf-834cf09b57f9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D6bVPPyHKUFV0WmvSS6nCMgaQId/SQKugGjOOT48QXE=", + "crlite_enrolled": false, + "id": "5d8690a3-f2cb-4d3f-8976-5ecde6bdf54b", + "last_modified": 1666727865958 + }, + { + "schema": 1666727388517, + "derHash": "LRQPILipbitNLxzFrKXloefcVqdJHlEJBpYPONLSGu8=", + "subject": "CN=GeoTrust EV RSA CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFUdlb1RydXN0IEVWIFJTQSBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "5e98e5f403ff3dddd032a6ff647e356e6da095ecfa1bac29e32e80471dcfbc19", + "size": 1873, + "filename": "VHUagq80gsWdvnfHL_-2XSLZrAJk4I9d8fx6N8UvWGM=.pem", + "location": "security-state-staging/intermediates/e8d73d97-e9d2-4d02-b887-8bb211ce06b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VHUagq80gsWdvnfHL/+2XSLZrAJk4I9d8fx6N8UvWGM=", + "crlite_enrolled": false, + "id": "d8b00e81-729c-44bc-9212-adbe0840cc08", + "last_modified": 1666727865936 + }, + { + "schema": 1666727355907, + "derHash": "5aHBkZ476u5ZNahIXdzg4/AaJhjbDweTs9s9msLZZ8c=", + "subject": "CN=DigiCert G5 TLS ECC SHA384 2021 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEsMCoGA1UEAxMjRGlnaUNlcnQgRzUgVExTIEVDQyBTSEEzODQgMjAyMSBDQTE=", + "whitelist": false, + "attachment": { + "hash": "a84ab90fee0d4f6fbf6c6e63fbbbd741df2406c735f923d2eca3ea94915e934f", + "size": 1223, + "filename": "LsOdqDFw3goo_G8jjeEPxe-JSJ7aFp1RF5Ih4_2ZvFY=.pem", + "location": "security-state-staging/intermediates/868dc36d-ee40-4c52-9e2f-6fa71dc32488.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LsOdqDFw3goo/G8jjeEPxe+JSJ7aFp1RF5Ih4/2ZvFY=", + "crlite_enrolled": false, + "id": "b6f346e1-b3e3-4cd2-a8d6-9c90e5bacb86", + "last_modified": 1666727865920 + }, + { + "schema": 1666727428643, + "derHash": "I93wiyI3PYYVjrnJn9tTZrGYBFYFMTct0g3OP7dm9Ww=", + "subject": "CN=GeoTrust CN RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFUdlb1RydXN0IENOIFJTQSBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "318d73015376f86e0506706af771be21a870ce1c70e5b3a7835733914bab3639", + "size": 1829, + "filename": "4H6OXny7MqJPbCOTpHyS0fSSUeHk_I5nKbIyuQwnfsA=.pem", + "location": "security-state-staging/intermediates/add22cce-aabf-4167-9d28-fb12c5e95054.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4H6OXny7MqJPbCOTpHyS0fSSUeHk/I5nKbIyuQwnfsA=", + "crlite_enrolled": false, + "id": "5145609d-03ae-46e6-a8f0-b43ceb48f201", + "last_modified": 1666727865905 + }, + { + "schema": 1666727430359, + "derHash": "wa13eHltILymXIiaJlUCEVZSi7Yv9fpD4bjlqD49Lqo=", + "subject": "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "8d5e0c561c61be2f2cef640011d673af8a75a82024f5bcf0348d3dd34002b5bd", + "size": 1760, + "filename": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=.pem", + "location": "security-state-staging/intermediates/16773900-8e85-42bf-896c-e0656df0b64d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=", + "crlite_enrolled": false, + "id": "4f169191-0e4c-45dc-8273-0115bdb1c24c", + "last_modified": 1666727865886 + }, + { + "schema": 1666727337336, + "derHash": "0OilGsqrmvgnZ+LvFlQ8LMY1VRtd4NriXWqsceeGKHA=", + "subject": "CN=TrustAsia OV TLS Pro CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgT1YgVExTIFBybyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "ebd10662baa82cc7f2d221c4534c1a6175a301fec27e3ce8f3f99347e99e5f24", + "size": 1821, + "filename": "tv1pIXiJEYFUrzxhRqxCjTL7S9s7y_PZbxYVnDOXeCI=.pem", + "location": "security-state-staging/intermediates/d38f4ed5-7362-454c-9ad2-0f8d47c58d11.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tv1pIXiJEYFUrzxhRqxCjTL7S9s7y/PZbxYVnDOXeCI=", + "crlite_enrolled": false, + "id": "8ed1f085-086f-402a-afa7-893c057aa162", + "last_modified": 1666727865864 + }, + { + "schema": 1666727352318, + "derHash": "H4656ajgZsxbODPgazEpdktiJjnVsWP2AOHHkSC/Pu0=", + "subject": "CN=DigiCert Global G2 TLS RSA SHA256 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2JhbCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "2f0f7bd3aa5d887df233421b1862144f48f1e78aaf6bde77dd04a92bf7b1f74e", + "size": 1776, + "filename": "Wec45nQiFwKvHtuHxSAMGkt19k-uPSw9JlEkxhvYPHk=.pem", + "location": "security-state-staging/intermediates/68708c8c-138c-4ee0-a5c4-66532b7b641e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wec45nQiFwKvHtuHxSAMGkt19k+uPSw9JlEkxhvYPHk=", + "crlite_enrolled": false, + "id": "30d6dd2b-af53-4e50-93c4-e7f3e0caa778", + "last_modified": 1666727865848 + }, + { + "schema": 1666727416841, + "derHash": "nbCiDDBVQeFegYqIDQZXmHapyQG47s2WhILFjZun8Kc=", + "subject": "CN=TrustAsia EV TLS Pro CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRVYgVExTIFBybyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "ed5d563d514f39499b71d7a6f0a248b4c61e380f9e9dfca14d38306123d7bb76", + "size": 1650, + "filename": "qPCsIegto8XzD_ooUOSN32SyAwuprv0BX_QDRc0K3aI=.pem", + "location": "security-state-staging/intermediates/9c01f138-1562-4fe5-a690-f1a5bdbb13ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qPCsIegto8XzD/ooUOSN32SyAwuprv0BX/QDRc0K3aI=", + "crlite_enrolled": false, + "id": "07245742-ecdb-4dc2-ace6-9a64038f11a1", + "last_modified": 1666727865834 + }, + { + "schema": 1666727361167, + "derHash": "91Qc9p0d4ayVOrwfrW94B6NO3+nhLBHmahlZMMI61sY=", + "subject": "CN=DigiCert Global CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2JhbCBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "636a98faff2529c5913204d3e3fd962544a257b8fbf941b8f31bc0b8e6cb4f13", + "size": 1106, + "filename": "lh6fRVDi7gDQENRcjLNsiTdmqn9awnJtmhUMzPTx6FM=.pem", + "location": "security-state-staging/intermediates/6db8a6bf-95cd-4548-a6f5-fbdf1e92811e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lh6fRVDi7gDQENRcjLNsiTdmqn9awnJtmhUMzPTx6FM=", + "crlite_enrolled": false, + "id": "24e37e3a-1aa7-4c1d-946e-a1b956996fca", + "last_modified": 1666727865818 + }, + { + "schema": 1666727419742, + "derHash": "9hSuKxAUhPFfZvTdpW7m60InKPF52UOZ7eGawdhai9M=", + "subject": "CN=Secure Site CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGjAYBgNVBAMTEVNlY3VyZSBTaXRlIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "7b53c5585cb213e67ffc75e0e207d7df7736a01e94405c05fcadcda57a4dd6c5", + "size": 1825, + "filename": "3mc_12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=.pem", + "location": "security-state-staging/intermediates/b9c0be27-667a-4073-b981-b75d445af54d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3mc/12C2pwvtV5JjfcxLmbbWYQSbj9yqSWLXKfZcMUw=", + "crlite_enrolled": false, + "id": "3cc06076-083d-4333-bdaf-0a479cc50aa0", + "last_modified": 1666727865801 + }, + { + "schema": 1666727375325, + "derHash": "avXE6sGAKJuUp3pdIx44Zf/pNPPmokv0h+tOK/2Amks=", + "subject": "CN=DigiCert Secure Site CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IFNlY3VyZSBTaXRlIENOIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "8bd898f10684eae3810bae34d31c153bf823daf9bb8e90ef85a30c9afbd870bb", + "size": 1804, + "filename": "TbrK7tI1CsyZLKNdMvoHsV863GbcuERLt4LWrjChCv0=.pem", + "location": "security-state-staging/intermediates/0990a5c9-b0a9-4df1-87f7-b59af54fd8c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TbrK7tI1CsyZLKNdMvoHsV863GbcuERLt4LWrjChCv0=", + "crlite_enrolled": false, + "id": "bcfabcdf-0aaa-4ab8-8330-e4afa00417e7", + "last_modified": 1666727865769 + }, + { + "schema": 1666727370940, + "derHash": "90w7LH5FXWVNnuWqpxJ4nMsme5YcGhzkimnIYM0ZNng=", + "subject": "CN=Thawte EV ECC CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFVRoYXd0ZSBFViBFQ0MgQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "ac60a878b837ac5acde21931778e37420c87650e995c87cd0c4cffb2ec59178d", + "size": 1390, + "filename": "sHnhNNcEZAQ0U5qyqTUj5LjSA8SG8NfF7zS_jwKtxLc=.pem", + "location": "security-state-staging/intermediates/633972ce-a88d-43f3-b835-8d832f1bde0c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sHnhNNcEZAQ0U5qyqTUj5LjSA8SG8NfF7zS/jwKtxLc=", + "crlite_enrolled": false, + "id": "923ae836-8f12-4700-a373-5060905c7289", + "last_modified": 1666727865756 + }, + { + "schema": 1666727398937, + "derHash": "k8OBywezU6kgwqe+1r6/GVxoJ53QUn038gvdDZnDMPo=", + "subject": "CN=DigiCert Assured ID CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEgxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "f6c8112ef8614332fdca113714480ab9d504ac5a161140b3a03b70156fe90951", + "size": 1654, + "filename": "dnPPE-JM_ZaNCC1Q198LELhQiAdfMsEPXi1LVYU-aPQ=.pem", + "location": "security-state-staging/intermediates/625fb1bf-2f17-4e4f-adbb-8ec1cedc9725.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dnPPE+JM/ZaNCC1Q198LELhQiAdfMsEPXi1LVYU+aPQ=", + "crlite_enrolled": false, + "id": "992eec46-81fd-4ec5-9b30-0701ceb2c4de", + "last_modified": 1666727865744 + }, + { + "schema": 1666727425471, + "derHash": "TjD4AEwY09eY7Fp3NDRrWl6+jVRCPOmCV/xXc1pvc40=", + "subject": "CN=DigiCert High Assurance CA-3b,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIENBLTNi", + "whitelist": false, + "attachment": { + "hash": "94cc514696f4cc8b6732c63dc89f44d422cc8ba162a3ed5b61cadf555ab85772", + "size": 2048, + "filename": "1jqJVFODXrZURu0Yd9r3uRS24OAQ3A4Crn2vR8KpNT8=.pem", + "location": "security-state-staging/intermediates/8810131b-cfb6-4b4b-8251-b94be87ed9c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1jqJVFODXrZURu0Yd9r3uRS24OAQ3A4Crn2vR8KpNT8=", + "crlite_enrolled": false, + "id": "1c454263-3cfa-4316-a32c-594e2ca911f6", + "last_modified": 1666727865731 + }, + { + "schema": 1666727394766, + "derHash": "AJhxw6TGBzEeWukvAQlfm/dhALh5SrCppSEOZ5TIYHw=", + "subject": "CN=Trust Provider B.V. TLS RSA CA G1,OU=Domain Validated SSL,O=Trust Provider B.V.,C=NL", + "subjectDN": "MHYxCzAJBgNVBAYTAk5MMRwwGgYDVQQKExNUcnVzdCBQcm92aWRlciBCLlYuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEqMCgGA1UEAxMhVHJ1c3QgUHJvdmlkZXIgQi5WLiBUTFMgUlNBIENBIEcx", + "whitelist": false, + "attachment": { + "hash": "e68c00c6a2d7a94a049d03b406000c58140c0e8f2bdaf4ec4dee420488b19972", + "size": 1687, + "filename": "DZ2CNIcIqZj3arOg4dqY2nkxRPJTRypLcTMN_FbGZLM=.pem", + "location": "security-state-staging/intermediates/d68b50e2-f640-4c14-8f8d-3d06895fa5dd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DZ2CNIcIqZj3arOg4dqY2nkxRPJTRypLcTMN/FbGZLM=", + "crlite_enrolled": false, + "id": "310ba4f7-752f-4547-b995-bc2bc7591c0a", + "last_modified": 1666727865719 + }, + { + "schema": 1666727407517, + "derHash": "Av7Tui5qeEOjGKmBvIRwYf0oLZ6IR/+p9U14W2uB1vM=", + "subject": "CN=Secure Site Pro CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFVNlY3VyZSBTaXRlIFBybyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "d3d8388c4d24d13d47226c4ec8f107db463d925badd0130e0d7ca2db6a707355", + "size": 1829, + "filename": "yFzqzO6UfU8CG8Xw6hrhKs8Wc7kjwaPPHpMW5A4WT3k=.pem", + "location": "security-state-staging/intermediates/05064024-e55d-4a24-8af1-a623cb42e66d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yFzqzO6UfU8CG8Xw6hrhKs8Wc7kjwaPPHpMW5A4WT3k=", + "crlite_enrolled": false, + "id": "5c3d331a-3364-4c1a-9b1a-65d7a57f5af3", + "last_modified": 1666727865706 + }, + { + "schema": 1666727371109, + "derHash": "2jvitqbZcVwSlaQr5SbgAB0Q5ddUDwbnYxs05kSTSEg=", + "subject": "CN=Secure Site Pro ECC CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIjAgBgNVBAMTGVNlY3VyZSBTaXRlIFBybyBFQ0MgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "e35a825f1808b4a4b8cd9d0e951d3af60b0df0ec46fec0960740c2f92566aa5b", + "size": 1394, + "filename": "SM9Xyw9hNxw5i17xxTmtGrBPfKQFpQaYXQuRAEx9ygs=.pem", + "location": "security-state-staging/intermediates/8164957e-941c-40a6-bafc-b00219396da0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SM9Xyw9hNxw5i17xxTmtGrBPfKQFpQaYXQuRAEx9ygs=", + "crlite_enrolled": false, + "id": "b108471a-d610-4b25-9eb1-fa54cd948643", + "last_modified": 1666727865692 + }, + { + "schema": 1666727364303, + "derHash": "MoZpFBLzDs5cBl/WL0OSpHYtHneBUzg1nfOQKZ+9Ye8=", + "subject": "CN=Thawte EV RSA CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHDAaBgNVBAMTE1RoYXd0ZSBFViBSU0EgQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "a8197f401360acfc75a18e45c5c77d5d4e77dcd084fcf0258a8e91bed3346eeb", + "size": 1873, + "filename": "BLOV1UJgeWCwCAR8qU9OWMMcRY8vXk1zqu0szJamQxw=.pem", + "location": "security-state-staging/intermediates/53198829-280c-44c6-a8d5-2b3d0141d4d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BLOV1UJgeWCwCAR8qU9OWMMcRY8vXk1zqu0szJamQxw=", + "crlite_enrolled": false, + "id": "e01f49af-1b36-46a6-8852-c945934fa1f7", + "last_modified": 1666727865679 + }, + { + "schema": 1666727396960, + "derHash": "OrvmPa91bFAWtrhfUgFf2Oisvid8UIexJ6YFY6hB7Yo=", + "subject": "CN=Cloudflare Inc ECC CA-3,O=Cloudflare\\, Inc.,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZGZsYXJlLCBJbmMuMSAwHgYDVQQDExdDbG91ZGZsYXJlIEluYyBFQ0MgQ0EtMw==", + "whitelist": false, + "attachment": { + "hash": "f7e471ccaa5a049df60367b77d98d07cd5db968d7e9fbcc638ccf2b08d6572f7", + "size": 1378, + "filename": "FEzVOUp4dF3gI0ZVPRJhFbSJVXR-uQmMH65xhs1glH4=.pem", + "location": "security-state-staging/intermediates/94388457-922f-4508-8094-bb10ab4f11d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FEzVOUp4dF3gI0ZVPRJhFbSJVXR+uQmMH65xhs1glH4=", + "crlite_enrolled": false, + "id": "ab828172-9f33-49df-8d8b-f17174f9e641", + "last_modified": 1666727865665 + }, + { + "schema": 1666727336841, + "derHash": "yHzgOv+13mMZwhmXHy7S2Pb1OJ4tU7It0sVipcmCf8A=", + "subject": "CN=GeoTrust EV CN RSA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHjAcBgNVBAMTFUdlb1RydXN0IEVWIENOIFJTQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "3ef397109e23513d25c637f4f4d25aca8102f0de50dd9b8c29ef06860be3147c", + "size": 1678, + "filename": "_AbtYXa2JlKxQyB1FnBObb2Wv8Q-bUxjsGRIXvzcvpQ=.pem", + "location": "security-state-staging/intermediates/12597c35-fd1e-4f3b-8136-4c8419a62439.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/AbtYXa2JlKxQyB1FnBObb2Wv8Q+bUxjsGRIXvzcvpQ=", + "crlite_enrolled": false, + "id": "f1968ec8-bf26-495e-99a2-2fa3c73f81e6", + "last_modified": 1666727865652 + }, + { + "schema": 1666727432527, + "derHash": "dWqrkA4/XHYnNLZGH8MqndNB6h1KBCg0CSM8OXh6hn4=", + "subject": "CN=TrustAsia ECC OV TLS Pro CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMScwJQYDVQQDEx5UcnVzdEFzaWEgRUNDIE9WIFRMUyBQcm8gQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "8564399a58a7811f26e479caae21b5f77ae21c29c2437ce4beb5513b9e3de582", + "size": 1353, + "filename": "tbnWlmtVSEavKTtABU3v0aeisdNi3iLXhpU10YWnzRw=.pem", + "location": "security-state-staging/intermediates/cbe6cf22-b14f-456d-a243-29fcbe5c9a1d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tbnWlmtVSEavKTtABU3v0aeisdNi3iLXhpU10YWnzRw=", + "crlite_enrolled": false, + "id": "1c958fea-9fe7-4d5d-9426-7e71145da173", + "last_modified": 1666727865627 + }, + { + "schema": 1666727441116, + "derHash": "Qi+dTmgTTjYrdWnlKYoXPOxAx8cnSyJjqbyK2h0aI/o=", + "subject": "CN=Data Management Intermediate CA2,O=Fresenius Kabi AG,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFGcmVzZW5pdXMgS2FiaSBBRzEpMCcGA1UEAxMgRGF0YSBNYW5hZ2VtZW50IEludGVybWVkaWF0ZSBDQTI=", + "whitelist": false, + "attachment": { + "hash": "b839f43495e3f321b12bbfe130f086ea35ee864d4a5ceca9ed004b7e9dbbc308", + "size": 1788, + "filename": "nhS3FkZCk36iI2tjewAoFwSeq0mVjOvTE5pZNBi0jqs=.pem", + "location": "security-state-staging/intermediates/8c91bc67-eed9-4106-b8ff-3e5ecf5c9a42.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nhS3FkZCk36iI2tjewAoFwSeq0mVjOvTE5pZNBi0jqs=", + "crlite_enrolled": false, + "id": "7b8e9431-837e-4864-85de-908b4ef19fdb", + "last_modified": 1666727865614 + }, + { + "schema": 1666727363290, + "derHash": "TzXGucEAkF/yW7AcCiDr4zW87vJJzp/6HhHgYu1m/NE=", + "subject": "CN=DigiCert Trusted G4 TLS RSA SHA384 2020 CA1,O=DigiCert Inc,C=US", + "subjectDN": "MFoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxNDAyBgNVBAMTK0RpZ2lDZXJ0IFRydXN0ZWQgRzQgVExTIFJTQSBTSEEzODQgMjAyMCBDQTE=", + "whitelist": false, + "attachment": { + "hash": "5aa778206c7c0b1741ee853b137b342e94f534e7dc76cb2cb633e765794482aa", + "size": 2479, + "filename": "rjm2hHKNhSnI_7YDoWFLmDSpnIwnr9n9bbNU0dhDiJg=.pem", + "location": "security-state-staging/intermediates/f16ac849-dc79-4e52-86e4-1d37645c80e1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rjm2hHKNhSnI/7YDoWFLmDSpnIwnr9n9bbNU0dhDiJg=", + "crlite_enrolled": false, + "id": "1da54348-1704-4aa9-9838-0b4eaf56c560", + "last_modified": 1666727865601 + }, + { + "schema": 1666727427646, + "derHash": "GFwK5HBCO51GeKfBBVtbSNkHBVBbeU4hXAY4UTNpgfQ=", + "subject": "CN=GeoTrust EV RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0dlb1RydXN0IEVWIFJTQSBDQSAyMDE4", + "whitelist": false, + "attachment": { + "hash": "7ce3818537ba21f0f1da583e8a25789be009298c8e9c98b1d577c9037018b892", + "size": 1666, + "filename": "yWulDX8E5Q0XG4-9jVDljmO2FvAVzIRhn2MppW4vyUM=.pem", + "location": "security-state-staging/intermediates/32aafacb-ac6b-4e0a-b1e4-74b264fd4d66.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yWulDX8E5Q0XG4+9jVDljmO2FvAVzIRhn2MppW4vyUM=", + "crlite_enrolled": false, + "id": "8851be6c-6d3b-4b1c-9ca9-48820b97907f", + "last_modified": 1666727865588 + }, + { + "schema": 1666727333476, + "derHash": "z3z6T528y8ptIO/evq1OFzs052vaHrHmGfROBulfwgg=", + "subject": "CN=Wells Fargo Public Trust Certification Authority 01 G2,OU=Organization Validated TLS,O=Wells Fargo & Company,C=US", + "subjectDN": "MIGTMQswCQYDVQQGEwJVUzEeMBwGA1UECgwVV2VsbHMgRmFyZ28gJiBDb21wYW55MSMwIQYDVQQLExpPcmdhbml6YXRpb24gVmFsaWRhdGVkIFRMUzE/MD0GA1UEAxM2V2VsbHMgRmFyZ28gUHVibGljIFRydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IDAxIEcy", + "whitelist": false, + "attachment": { + "hash": "a8a1ec42719c782ae44d78633be9e4daddc709bf1127ec155bae81e252f5ae6f", + "size": 1727, + "filename": "piL8cJbKkJ2PJeful08_TUBtZu6QcnuC-5qgNu13n_c=.pem", + "location": "security-state-staging/intermediates/3560b6a7-ac40-4c0b-ab01-ba7caf4f697e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "piL8cJbKkJ2PJeful08/TUBtZu6QcnuC+5qgNu13n/c=", + "crlite_enrolled": false, + "id": "2bc76535-9629-4fc1-bc41-f1ed5be342aa", + "last_modified": 1666727865575 + }, + { + "schema": 1666727434414, + "derHash": "QNaZIOoFRW+gEX3mCLioATeQtUIJfjQ+wcvOLfuXE7A=", + "subject": "CN=Secure Site Pro Extended Validation CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMjAwBgNVBAMTKVNlY3VyZSBTaXRlIFBybyBFeHRlbmRlZCBWYWxpZGF0aW9uIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "3ecbe428b438a6837ddb6322b9cc1b5b106b0455f84524312b3c78988886d32c", + "size": 1703, + "filename": "C5iNVr6DMrTBS2Wvb08zPxdCQnB0DSWA-yu6a9MbQBI=.pem", + "location": "security-state-staging/intermediates/f0c0b7b4-8372-40d6-966a-c4db8084c76c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "C5iNVr6DMrTBS2Wvb08zPxdCQnB0DSWA+yu6a9MbQBI=", + "crlite_enrolled": false, + "id": "ad0355f7-e30a-4644-b92c-d6853b951ef5", + "last_modified": 1666727865563 + }, + { + "schema": 1666727428480, + "derHash": "/BbVMgO9kYfWnMmdLaVRB2tNyymBQNZ1H3oBKWbJn90=", + "subject": "CN=RapidSSL TLS ECC CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFlJhcGlkU1NMIFRMUyBFQ0MgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "40dd078660f4e1d87d7b968c85f05309892ef31efbc7a7838b082de02aed4837", + "size": 1199, + "filename": "cBtUiO-mwdgMfNeEp_qKuEHswRn2nHp2FDUKVi_aTDw=.pem", + "location": "security-state-staging/intermediates/1dd32622-340e-4668-88c0-f66a190b9524.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cBtUiO+mwdgMfNeEp/qKuEHswRn2nHp2FDUKVi/aTDw=", + "crlite_enrolled": false, + "id": "4263bbf6-d71d-487f-a3e5-52d926e62fca", + "last_modified": 1666727865550 + }, + { + "schema": 1666727427479, + "derHash": "XqOFfqzUx8pay8qcRifibzByA40ZGinUw/lGSy5fAMY=", + "subject": "CN=Microsoft RSA TLS Issuing EOC CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBSU0EgVExTIElzc3VpbmcgRU9DIENBIDAx", + "whitelist": false, + "attachment": { + "hash": "c4cf08af2dd2b1f29bdfb63d83a5482200edf35471b1453a302294b55f3068d5", + "size": 2694, + "filename": "Hx2OdoXOglUIj3kb_T-5J_UKwQBwkbW7wzR--8XZqAw=.pem", + "location": "security-state-staging/intermediates/81ce4311-98c7-4368-b741-d59fdfeee0b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Hx2OdoXOglUIj3kb/T+5J/UKwQBwkbW7wzR++8XZqAw=", + "crlite_enrolled": false, + "id": "85bb3b93-bc7b-4813-bd54-54de9d3a4778", + "last_modified": 1666727865537 + }, + { + "schema": 1666727350775, + "derHash": "TSfvY1e8kpeDV1wMjJNAtn++/ARf3CY7jIg2Lm06228=", + "subject": "CN=DigiCert G5 TLS EU RSA4096 SHA384 2022 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MGUxCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxMzAxBgNVBAMTKkRpZ2lDZXJ0IEc1IFRMUyBFVSBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "eb4d4318326da322347772ff734c14c459e839156f777a04ad72a4cacbc53b2d", + "size": 2410, + "filename": "XS9PxSX-bSbqQaF0ahMe-9fiIseIbQLaQ5ENRo-lfsM=.pem", + "location": "security-state-staging/intermediates/29618e88-ae37-4fa6-a83c-0c71f569be81.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XS9PxSX+bSbqQaF0ahMe+9fiIseIbQLaQ5ENRo+lfsM=", + "crlite_enrolled": false, + "id": "e4c9df67-646b-4a03-853c-f94ecfd8a2ab", + "last_modified": 1666727865524 + }, + { + "schema": 1666727416329, + "derHash": "v4ppAnvMjS1CpubSW91Ic/ajS4+Q7fB+hsXWkW2guTM=", + "subject": "CN=Amazon RSA 2048 M03,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgMjA0OCBNMDM=", + "whitelist": false, + "attachment": { + "hash": "7e0ddf74af77639eb14ff4608979cf3539ee193c8d27f436eca0b6c068f39f7c", + "size": 1573, + "filename": "vxRon_El5KuI4vx5ey1DgmsYmRY0nDd5Cg4GfJ8S-bg=.pem", + "location": "security-state-staging/intermediates/6dc00bc6-c455-409b-a24c-790b8cf09e7f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vxRon/El5KuI4vx5ey1DgmsYmRY0nDd5Cg4GfJ8S+bg=", + "crlite_enrolled": false, + "id": "e6f0f6d1-50db-4673-8606-c86cfa51ffdc", + "last_modified": 1666727865511 + }, + { + "schema": 1666727407169, + "derHash": "Uzjr7I+yrGCZYSbT52qjT9DzMYrHjrt6yPbxNh9ISzM=", + "subject": "CN=Amazon RSA 2048 M01,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgMjA0OCBNMDE=", + "whitelist": false, + "attachment": { + "hash": "9960f53f3a4d362a258e972a50b6d094af8a8eb71887908dc69ece672b1da701", + "size": 1573, + "filename": "DxH4tt40L-eduF6szpY6TONlxhZhBd-pJ9wbHlQ2fuw=.pem", + "location": "security-state-staging/intermediates/3bbbdd93-4637-465f-933c-b59df5c30125.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DxH4tt40L+eduF6szpY6TONlxhZhBd+pJ9wbHlQ2fuw=", + "crlite_enrolled": false, + "id": "efefd1fe-25c9-48a0-acda-ad37cb8c5d63", + "last_modified": 1666727865498 + }, + { + "schema": 1666727359981, + "derHash": "pK38KRe7FFHTEjcdRBrNtoYQf7wLMVbVVw6Z4RIZdPo=", + "subject": "CN=DigiCert G2 TLS EU RSA4096 SHA384 2022 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MGUxCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxMzAxBgNVBAMTKkRpZ2lDZXJ0IEcyIFRMUyBFVSBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "edba399b22ede3f049df94893943a485684e8bfd87d89069d139e823a88af580", + "size": 2081, + "filename": "lXt3ip5lkns-fBxV_S9MSfUx0UUdhBEmhXz5PkrAWGg=.pem", + "location": "security-state-staging/intermediates/fd9cc291-cb5e-418d-a7bb-7482141d2748.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lXt3ip5lkns+fBxV/S9MSfUx0UUdhBEmhXz5PkrAWGg=", + "crlite_enrolled": false, + "id": "091f1be8-6f2b-4984-9dee-a97723f94409", + "last_modified": 1666727865485 + }, + { + "schema": 1666727432857, + "derHash": "wSYqxkEgdbJROuXlY/zO20s+WbgSaV7gzYciOiwtBIs=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIyIFE0", + "whitelist": false, + "attachment": { + "hash": "e4b7ca4a4107f6b883160c4b2e48c0c3eb94d853c4ab6a8caa50711dcf221712", + "size": 2333, + "filename": "RoTNEm9v9N7RLcLj-67DjkpRLenPKTrJrJfPAZ-2k0I=.pem", + "location": "security-state-staging/intermediates/bcac0eea-3b40-4ea0-82cb-0593ef39e464.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RoTNEm9v9N7RLcLj+67DjkpRLenPKTrJrJfPAZ+2k0I=", + "crlite_enrolled": false, + "id": "8cc67029-18ef-477c-a500-953246d7319c", + "last_modified": 1666727865472 + }, + { + "schema": 1666727454369, + "derHash": "o0sVyNHClhBqy8FGgBYAJ7H6SRFMIb2Zs65ZvYxMe1Q=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "d7f8fe7148825699b71b1c6c4312280c66b6bacf5ca14e4666ad88db9de44184", + "size": 1179, + "filename": "UMszM-L2_W9CVUkts4RbrbvwIdUas6zfNZJukTUPtrs=.pem", + "location": "security-state-staging/intermediates/8023b685-327a-469d-b227-31bd392d3060.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UMszM+L2/W9CVUkts4RbrbvwIdUas6zfNZJukTUPtrs=", + "crlite_enrolled": false, + "id": "c27458ce-37a4-4dbe-ad91-07b23c715768", + "last_modified": 1666727865458 + }, + { + "schema": 1666727427813, + "derHash": "sFAADPdJ7GWKa57Y1MOjk0NWRKMnYD+wNjJ+MqR7+6Q=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "3e3aeb23cb3b237dc9714b96b4e2bf124140a690983ea47e848d7c415ed7d8b8", + "size": 2324, + "filename": "Kd7RTI6sKwBTnFP02S0-FUYxE-cvHcrZlopNycP1fwk=.pem", + "location": "security-state-staging/intermediates/67c80d30-cec4-4cec-9c5d-f0116584df4b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Kd7RTI6sKwBTnFP02S0+FUYxE+cvHcrZlopNycP1fwk=", + "crlite_enrolled": false, + "id": "cf4d39ff-6614-443c-9d84-53196607e955", + "last_modified": 1666727865445 + }, + { + "schema": 1666727420265, + "derHash": "8Hu73gdvm0DFfMS+/t6Xyh9Tua4UfwNdKEy/U/NDL7g=", + "subject": "CN=CFCA OV OCA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFUxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFDASBgNVBAMMC0NGQ0EgT1YgT0NB", + "whitelist": false, + "attachment": { + "hash": "bc37658d1568750ae385b92be54ede64df31899d30f67211fd1e015c9dc7803b", + "size": 1963, + "filename": "uc0FPx73f1ObHGmGZOcevy371Uo9asVjdabpiS0lKgQ=.pem", + "location": "security-state-staging/intermediates/8a832b56-3f0a-4b5f-8966-8a1da5242863.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uc0FPx73f1ObHGmGZOcevy371Uo9asVjdabpiS0lKgQ=", + "crlite_enrolled": false, + "id": "b2b776e5-2b2f-4c8e-819d-026b8d3855d2", + "last_modified": 1666727865432 + }, + { + "schema": 1666727373073, + "derHash": "IjreDkXMxL7LJV2wCd8eI5ql1xsXzz2wpYLCksJezFI=", + "subject": "CN=HARICA EV TLS ECC,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgRVYgVExTIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "613d2bc47a0062ad07f0dd5fc643880c8bf83ee36e4e3c2539e1ddc87cbae37e", + "size": 1256, + "filename": "Sb-JCz292ZwEB6r9mixsDVi5qskw_XMeyEgejlo3rWQ=.pem", + "location": "security-state-staging/intermediates/f00127f5-3cfb-4428-a321-23126b47ef34.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Sb+JCz292ZwEB6r9mixsDVi5qskw/XMeyEgejlo3rWQ=", + "crlite_enrolled": false, + "id": "ab80eae2-35d7-4169-8a83-23fc84d0afed", + "last_modified": 1666727865419 + }, + { + "schema": 1666727425639, + "derHash": "ymIzVKMceHiBXg2QH6R+MNEKYfaUXTy+/EdX9Ef6iuo=", + "subject": "CN=HARICA EV TLS RSA,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgRVYgVExTIFJTQQ==", + "whitelist": false, + "attachment": { + "hash": "f21020b590225c57981c6989b288f36f75190334f614c0629fd5bc41ac0cc9c3", + "size": 2402, + "filename": "cpUNvd6AbHU70x0H2HTgteMXHmEM7LkiSlG5Qv-cWbQ=.pem", + "location": "security-state-staging/intermediates/90570e44-8c03-4ff7-b6f5-e06e50cee699.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cpUNvd6AbHU70x0H2HTgteMXHmEM7LkiSlG5Qv+cWbQ=", + "crlite_enrolled": false, + "id": "85bf819b-0410-48e1-8796-fe482c08d77b", + "last_modified": 1666727865406 + }, + { + "schema": 1666727412364, + "derHash": "8PVzdjxhb3YqpquSfL3nifqO9Oaa+KJ+xjxi7vMp7KE=", + "subject": "CN=DigiCert G3 TLS EU ECC P-384 SHA384 2022 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MGcxCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxNTAzBgNVBAMTLERpZ2lDZXJ0IEczIFRMUyBFVSBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "d27937a474a0a361ceb81ad3cd8ab0ac9b7d7197162326d10e0792d396a6342c", + "size": 1284, + "filename": "jNgunfyjXRyuZo0MEOLRQXcemIuUv3Qx7QTwaIDmxK0=.pem", + "location": "security-state-staging/intermediates/ca164ccc-5429-4cde-b994-2b1fe78848a2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jNgunfyjXRyuZo0MEOLRQXcemIuUv3Qx7QTwaIDmxK0=", + "crlite_enrolled": false, + "id": "ee603017-cde5-4dbb-beda-8719a62726a0", + "last_modified": 1666727865393 + }, + { + "schema": 1666727417007, + "derHash": "E4vfbiOslx605iayed1qJvBXUQ8d45QpOl7qKGDeAZs=", + "subject": "CN=Amazon RSA 2048 M04,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgMjA0OCBNMDQ=", + "whitelist": false, + "attachment": { + "hash": "3fd6a67306830c682f642e1458bbe239ac874cfb208e513d2d69a7a408cc3fe8", + "size": 1573, + "filename": "G9LNNAql897egYsabashkzUCTEJkWBzgoEtk8X_678c=.pem", + "location": "security-state-staging/intermediates/8d8a1a2b-4e4e-42cf-86ad-f0bddf6dd18b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G9LNNAql897egYsabashkzUCTEJkWBzgoEtk8X/678c=", + "crlite_enrolled": false, + "id": "70ed5476-f737-4dba-bb87-563abc6d0075", + "last_modified": 1666727865380 + }, + { + "schema": 1666727449009, + "derHash": "sPMwoxoMUJh+HDp7sCwt2mgpkdMWW1F71E+6SmAgvZQ=", + "subject": "CN=Amazon RSA 2048 M02,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgMjA0OCBNMDI=", + "whitelist": false, + "attachment": { + "hash": "2b97f25007709fabcfdc7f1421c6c2619d3aa9b5d5b9406531af231a111d647a", + "size": 1573, + "filename": "18tkPyr2nckv4fgo0dhAkaUtJ2hu2831xlO2SKhq8dg=.pem", + "location": "security-state-staging/intermediates/428b12bc-6cd5-4015-9246-8998285520ab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "18tkPyr2nckv4fgo0dhAkaUtJ2hu2831xlO2SKhq8dg=", + "crlite_enrolled": false, + "id": "9f31f9c2-c59b-4b3a-bd5c-580178033148", + "last_modified": 1666727865367 + }, + { + "schema": 1666727331338, + "derHash": "ST547ujMofJuZJTtkkmFrz+p5hEOqmHDIU6Nc7QEcxY=", + "subject": "CN=Amazon ECDSA 256 M03,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAyNTYgTTAz", + "whitelist": false, + "attachment": { + "hash": "36c68a3ea49bef4f1fa2c32a364339daac12b83fdaa6cbfbf194c06029b62b14", + "size": 1041, + "filename": "EeanMg3Yqy6aN2LhC28wADhHDB_czZ9k4RcepvoGwRg=.pem", + "location": "security-state-staging/intermediates/e758a729-50b0-4eca-a11d-6aaa41a9672d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EeanMg3Yqy6aN2LhC28wADhHDB/czZ9k4RcepvoGwRg=", + "crlite_enrolled": false, + "id": "c4316e4b-fd16-4947-93cd-4451816e1922", + "last_modified": 1666727865353 + }, + { + "schema": 1666727431368, + "derHash": "yeQPToM5bzSnyGGBe07as9wfi6xpn9UMsmH6kSPVXvQ=", + "subject": "CN=SwissSign Personal Silver CA 2014 - G22,O=SwissSign AG,C=CH", + "subjectDN": "MFYxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxMDAuBgNVBAMTJ1N3aXNzU2lnbiBQZXJzb25hbCBTaWx2ZXIgQ0EgMjAxNCAtIEcyMg==", + "whitelist": false, + "attachment": { + "hash": "8b3dff1c600553b50452dcbd5783cbfaa219a177d66705c2de0f41eb51b439a5", + "size": 2398, + "filename": "m0Mz3XVaph4DGN-UfcVV7i83DtSCwW0evxwDvZf_j18=.pem", + "location": "security-state-staging/intermediates/2b6e11e8-fc81-45eb-9865-4123414df1e4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "m0Mz3XVaph4DGN+UfcVV7i83DtSCwW0evxwDvZf/j18=", + "crlite_enrolled": false, + "id": "59ced421-04b9-4022-85c6-56f10093da01", + "last_modified": 1666727865340 + }, + { + "schema": 1666727353157, + "derHash": "XLiObv8xXidXRbPHKIBkpWFCqz75rHBn8bs9KY+675U=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "366133abe72d731e28c755a7f25c087d2ec24e6630766b30afd25267f5fdfb53", + "size": 1642, + "filename": "MBDIWmCHtaXMQrff2GtzR55OKMslHveMxMPT1AY1XOE=.pem", + "location": "security-state-staging/intermediates/e375bc4e-f009-4334-9651-d8fd2934b83f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MBDIWmCHtaXMQrff2GtzR55OKMslHveMxMPT1AY1XOE=", + "crlite_enrolled": false, + "id": "88da76a9-9190-4281-a996-90f17e160e30", + "last_modified": 1666727865327 + }, + { + "schema": 1666727434065, + "derHash": "1UzQ2to2gIlF0eTkFDIuAKsJuagA2jLWwrNLLKErnOo=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIyIFE0", + "whitelist": false, + "attachment": { + "hash": "4b04a3169266e90304e62bf8489cfa493b049c211529faf9364c0b215b18524d", + "size": 1199, + "filename": "LTVa62pAnIv2EfGFZJVZUwsUgrxbCyCGO555QS9v2PY=.pem", + "location": "security-state-staging/intermediates/54230558-38b2-4ff3-8b84-30d1b4b86ba5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LTVa62pAnIv2EfGFZJVZUwsUgrxbCyCGO555QS9v2PY=", + "crlite_enrolled": false, + "id": "fe0323dc-ec17-4ad2-9740-bb23e7e80741", + "last_modified": 1666727865314 + }, + { + "schema": 1666727378419, + "derHash": "k6wnHAdYomOmA1Z822UX2P3qE4ghVODEGnUTfFYToiI=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIyIFE0", + "whitelist": false, + "attachment": { + "hash": "a4ccf971823533d4fdd934b44c48eaf9e1480e14d40a42a19c3f22a7196b546f", + "size": 1642, + "filename": "yNGjqBwGjuxKmg-UrZWJF4ebyR3YrxML9ALFQczJEX4=.pem", + "location": "security-state-staging/intermediates/96b5681d-2aaa-4dac-9827-d0a896818604.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yNGjqBwGjuxKmg+UrZWJF4ebyR3YrxML9ALFQczJEX4=", + "crlite_enrolled": false, + "id": "330d93b8-b87c-4eea-baf5-a44e0093f12e", + "last_modified": 1666727865299 + }, + { + "schema": 1666727409624, + "derHash": "gUbqtzpiN9nwZoupyLtCJuglelT6xB25Ym6k9WftTRo=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2022 Q4,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjIgUTQ=", + "whitelist": false, + "attachment": { + "hash": "3194e4d2277c0fa68e36c0e8226755c0254651f32544f7af4161945de4586449", + "size": 1642, + "filename": "nCud_5CXIJAevZiY1MOcLQeN91icjJP5IaFOSesPOJI=.pem", + "location": "security-state-staging/intermediates/53ae73e0-b383-48e8-acb7-bcdeae7b1fd9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nCud/5CXIJAevZiY1MOcLQeN91icjJP5IaFOSesPOJI=", + "crlite_enrolled": false, + "id": "028f1982-56b9-41f3-8206-7875c750eb12", + "last_modified": 1666727865285 + }, + { + "schema": 1666727396304, + "derHash": "t90+EdtvBO9r0emHaMaAXMzNHTf4vYWLbSFkV5cUd4Q=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIyIFE0", + "whitelist": false, + "attachment": { + "hash": "6dcfe0fb28d8f9024575fb510d7ff291f71886b34c9cc1faae8a605ebf1fbcc8", + "size": 1642, + "filename": "LvLdkXrl1JWSRZCQbnD2VJs1V9b-Ar9hLxEch3hEy-w=.pem", + "location": "security-state-staging/intermediates/8a7aae07-132c-450f-b2a6-ab534790df49.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LvLdkXrl1JWSRZCQbnD2VJs1V9b+Ar9hLxEch3hEy+w=", + "crlite_enrolled": false, + "id": "072d4173-5a0c-4a82-be49-a91e47fbf533", + "last_modified": 1666727865271 + }, + { + "schema": 1666727341801, + "derHash": "MGv4CZY2pE//te7c5uMMDzbH1D9syloso6txZo81MyA=", + "subject": "SERIALNUMBER=G63287510,CN=ANF Secure Server CA,OU=ANF Autoridad intermedia tecnicos,O=ANF Autoridad de Certificacion,C=ES", + "subjectDN": "MIGVMRIwEAYDVQQFEwlHNjMyODc1MTAxCzAJBgNVBAYTAkVTMScwJQYDVQQKEx5BTkYgQXV0b3JpZGFkIGRlIENlcnRpZmljYWNpb24xKjAoBgNVBAsTIUFORiBBdXRvcmlkYWQgaW50ZXJtZWRpYSB0ZWNuaWNvczEdMBsGA1UEAxMUQU5GIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "94f2679da6fc663782dece4baeb031bb22bd3de9a11c9b24ed20b914260e0d4c", + "size": 2645, + "filename": "uiEcUi6rH4hr3LVqMdSOFpFfUPBYYzPvgxeYIKOOeGk=.pem", + "location": "security-state-staging/intermediates/39894355-15d9-4bf9-aa00-9b376067ec88.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uiEcUi6rH4hr3LVqMdSOFpFfUPBYYzPvgxeYIKOOeGk=", + "crlite_enrolled": false, + "id": "bc4939de-dfcf-4dba-ba83-ac8dada019cc", + "last_modified": 1666727865258 + }, + { + "schema": 1666727439040, + "derHash": "huh/XKOqljjTIXPm3B3lkZ/mM82XAynTP2llAcBz9b0=", + "subject": "CN=cnWebTrust OV CA - ECC,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEcxCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEfMB0GA1UEAxMWY25XZWJUcnVzdCBPViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "3925020180a8564572e7fd866537761924dcf5c7f6a81e2bb64ac43bb8064275", + "size": 1232, + "filename": "Zznygcd02s-GRBZdVzzLeU95XTr_tfE49F-r4OGfszM=.pem", + "location": "security-state-staging/intermediates/1b2ec3e5-6406-43f1-b844-af3bb80b55d8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Zznygcd02s+GRBZdVzzLeU95XTr/tfE49F+r4OGfszM=", + "crlite_enrolled": false, + "id": "b934396e-4172-49e2-97f4-c680f60b9532", + "last_modified": 1666727865138 + }, + { + "schema": 1666727428145, + "derHash": "p8CB4fjWyAxPPa5X3vlNEastamQ5+Yd14BACKpDW0eY=", + "subject": "CN=TrustAsia ECC EV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIEVWIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "99502d77687be7445ef67afc31ea03c048c1846f96cd5dd63db403a76482e5ec", + "size": 1244, + "filename": "rV9R8mlMRJRagxoykbSDDNA73L2G7T4llh3vYjKsx60=.pem", + "location": "security-state-staging/intermediates/3a640792-8333-4d08-aac9-9484e165ae78.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rV9R8mlMRJRagxoykbSDDNA73L2G7T4llh3vYjKsx60=", + "crlite_enrolled": false, + "id": "4a6491aa-4966-43ff-a10a-46acbc77c021", + "last_modified": 1666727865122 + }, + { + "schema": 1666727349573, + "derHash": "2lRiUmoMLphSqGGGsCU5AVh1nNymriHwn3E8pqzN0fE=", + "subject": "CN=QuoVadis Enterprise Trust CA 3 G3,O=QuoVadis Limited,C=BM", + "subjectDN": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDMgRzM=", + "whitelist": false, + "attachment": { + "hash": "0b01ec78286d184607dd09e4cac9c4992d4af9f58534eb2cb8fea437481a6197", + "size": 2333, + "filename": "smsafMf1m1b-3NN_jssl3RMKHXok-GELWWNtG9vZEmA=.pem", + "location": "security-state-staging/intermediates/c87c6dee-16e3-4bb0-8541-6cf2e215df90.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "smsafMf1m1b+3NN/jssl3RMKHXok+GELWWNtG9vZEmA=", + "crlite_enrolled": false, + "id": "4f58238b-464f-49f2-87a6-905bf5e2256b", + "last_modified": 1666727865103 + }, + { + "schema": 1666727405291, + "derHash": "smjRaTSrW6Iy8XnNn1x/wH6oWDpWqafB1stY/ggjv1o=", + "subject": "CN=D-TRUST BR CA 1-20-2 2020,O=D-Trust GmbH,C=DE", + "subjectDN": "MEgxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIjAgBgNVBAMTGUQtVFJVU1QgQlIgQ0EgMS0yMC0yIDIwMjA=", + "whitelist": false, + "attachment": { + "hash": "f0c02de6205d137c443f7dfaca0471f9e5055ebab902816844871afd5048a50e", + "size": 1666, + "filename": "mYMhLRmTQhVj2rn5bt1WUUNesYqFtN-rN6zvKEu8akY=.pem", + "location": "security-state-staging/intermediates/a9d2887c-fa0f-4b64-80b4-b9d290b7c78a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mYMhLRmTQhVj2rn5bt1WUUNesYqFtN+rN6zvKEu8akY=", + "crlite_enrolled": false, + "id": "9f5f757a-0664-40de-9df0-ddb2f904aa4b", + "last_modified": 1666727865089 + }, + { + "schema": 1666727434770, + "derHash": "269Y3r+7od6OBNyueiwWOwnTnn5zLpHeqGVcK9cyqwA=", + "subject": "CN=Verokey Secure Web,O=Verokey,C=AU", + "subjectDN": "MDwxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MRswGQYDVQQDExJWZXJva2V5IFNlY3VyZSBXZWI=", + "whitelist": false, + "attachment": { + "hash": "c5f50c22da86ef7b7de5af316d9e01bee66bb2b8b74913b1b1b1ddca99ad5b8a", + "size": 1678, + "filename": "BLGkEY8QPCtAg4Yd-57Aiig-74Lh325P803f8vLhBoc=.pem", + "location": "security-state-staging/intermediates/36c3a520-0118-469c-a721-cfd21fcfa174.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BLGkEY8QPCtAg4Yd+57Aiig+74Lh325P803f8vLhBoc=", + "crlite_enrolled": false, + "id": "8d5e7e22-7c97-431c-b156-e2e7c90dbc77", + "last_modified": 1666727865074 + }, + { + "schema": 1666727444002, + "derHash": "lzpBJ2/9AeAnoqrUnjTDeEbT6Xb/amILZxLjODIEGqY=", + "subject": "CN=Go Daddy Secure Certificate Authority - G2,OU=http://certs.godaddy.com/repository/,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIG0MQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy", + "whitelist": false, + "attachment": { + "hash": "a111efc20329e87b541cfc3e7f526fc54bb24624fddf210f4347b9fadfb1ab6b", + "size": 1727, + "filename": "8Rw90Ej3Ttt8RRkrg-WYDS9n7IS03bk5bjP_UXPtaY8=.pem", + "location": "security-state-staging/intermediates/a2a04dfe-6201-4d0a-b8cf-80d2975bfb51.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8=", + "crlite_enrolled": false, + "id": "549c92e2-3738-4c8c-ae37-407e4e6158ce", + "last_modified": 1666727865014 + }, + { + "schema": 1666727411681, + "derHash": "MysQFTn6icpOIocZ/lKHq2hprzGrIcvxEPXdPFmUwcc=", + "subject": "CN=Amazon ECDSA 256 M01,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAyNTYgTTAx", + "whitelist": false, + "attachment": { + "hash": "bc9627b077f8e49740f4a758d95cf104d557357e7ff2d32a3d1b71ff7f66af51", + "size": 1041, + "filename": "Qn2rVbyC3wQTe4rKJB9XU1WaCtwtBNpusM7f5eHjj9M=.pem", + "location": "security-state-staging/intermediates/b355804b-4052-46bc-8216-e19067687e45.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Qn2rVbyC3wQTe4rKJB9XU1WaCtwtBNpusM7f5eHjj9M=", + "crlite_enrolled": false, + "id": "5cc801d8-6a8e-49a7-8156-422a38db6b13", + "last_modified": 1666727864995 + }, + { + "schema": 1666400223579, + "derHash": "DSzH5pc/gPoiU/uIgo72rLs7lf+nfEiWUMNWc19UtwY=", + "subject": "CN=AlpiroSSL ECC OV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgRUNDIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "660704e30efa63fe14a6055be29e0064227817f3e4921c304bfb45606557a9d9", + "size": 1264, + "filename": "X_kxO0BscKQlU9_xYyElfuh-DkcVP6lXeRrOBQPEQoo=.pem", + "location": "security-state-staging/intermediates/1020a8f8-4a5a-47ee-9eb9-9d138fbefe60.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "X/kxO0BscKQlU9/xYyElfuh+DkcVP6lXeRrOBQPEQoo=", + "crlite_enrolled": false, + "id": "6a660601-87ea-408d-a641-5343680f0cca", + "last_modified": 1666486623083 + }, + { + "schema": 1666270087163, + "derHash": "2nOKR07nRzyWmey6jrX0g62pZ5iBhaBZdcS6DAGzlVk=", + "subject": "CN=CFCA DV OCA,O=China Financial Certification Authority,C=CN", + "subjectDN": "MFUxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFDASBgNVBAMMC0NGQ0EgRFYgT0NB", + "whitelist": false, + "attachment": { + "hash": "7f0388d9dbe44b8309b49af6bc57cbb9d6bcdecd52e63a0224fe0aaa48f21fd9", + "size": 2020, + "filename": "a3jzvw9CT6-ZtuS70fb8B8cu5T5zTulOjkR9ebqiaxc=.pem", + "location": "security-state-staging/intermediates/da5b57f9-5a82-4c18-a7b9-af8d2654de55.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "a3jzvw9CT6+ZtuS70fb8B8cu5T5zTulOjkR9ebqiaxc=", + "crlite_enrolled": false, + "id": "bd831e0b-483b-4c5f-98d9-156c61ccb3cd", + "last_modified": 1666270623205 + }, + { + "schema": 1665967729988, + "derHash": "9fGowE5c8P22eN0nmZRGPkhQjENJZDsNksv9dSP2xAo=", + "subject": "CN=Bloomberg TLS CA,O=Bloomberg LP,C=US", + "subjectDN": "MD8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxCbG9vbWJlcmcgTFAxGTAXBgNVBAMTEEJsb29tYmVyZyBUTFMgQ0E=", + "whitelist": false, + "attachment": { + "hash": "92f10638e3de8bb93a7e73213b2a67a0ca09c2736f06dfeb656d60d0a1f6153a", + "size": 1683, + "filename": "wR5GLfX8HG7FhvZ7hnbmrGu1dPd6dkKlKYnCvd1A-v0=.pem", + "location": "security-state-staging/intermediates/5fb90803-186b-4986-b9bb-44560e5d7c67.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wR5GLfX8HG7FhvZ7hnbmrGu1dPd6dkKlKYnCvd1A+v0=", + "crlite_enrolled": false, + "id": "b15cf09b-70a9-42d8-a898-16bbdc49ac3e", + "last_modified": 1665968223348 + }, + { + "schema": 1665860224090, + "derHash": "RAs3QW8t6FLKOGFQ1hHsCvMd1Y2KJ/iCgVp+o+5oSO4=", + "subject": "CN=Bloomberg TLS CA,O=Bloomberg LP,C=US", + "subjectDN": "MD8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxCbG9vbWJlcmcgTFAxGTAXBgNVBAMTEEJsb29tYmVyZyBUTFMgQ0E=", + "whitelist": false, + "attachment": { + "hash": "4a92e0f439e5ac3728da149e9dfcdac4443b6ce9ce1c908e2d20a150ad801b73", + "size": 1743, + "filename": "wR5GLfX8HG7FhvZ7hnbmrGu1dPd6dkKlKYnCvd1A-v0=.pem", + "location": "security-state-staging/intermediates/607ed211-fb20-4f94-983e-4f28c788decb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wR5GLfX8HG7FhvZ7hnbmrGu1dPd6dkKlKYnCvd1A+v0=", + "crlite_enrolled": false, + "id": "40e80e12-a4fe-4188-8ab3-6b02b2355943", + "last_modified": 1665968223336 + }, + { + "schema": 1665708517697, + "derHash": "jYPWn6YVqiaDsT14lEgsVCsLzxi7ktMPAV+0JHGvu2Y=", + "subject": "CN=TrustAsia ECC EV TLS Pro CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMScwJQYDVQQDEx5UcnVzdEFzaWEgRUNDIEVWIFRMUyBQcm8gQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "4366255797b83166920a1e824ec09a03600ab8b2d5cec5d642855221c5ac5f8e", + "size": 1382, + "filename": "xgkVHlf2XyODr7l-8b1HSx2m_0yRG8ZF5HdfiYwDwaY=.pem", + "location": "security-state-staging/intermediates/74b980e9-95c9-430f-b226-a87543fc64de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xgkVHlf2XyODr7l+8b1HSx2m/0yRG8ZF5HdfiYwDwaY=", + "crlite_enrolled": false, + "id": "db1eb71a-d600-4007-9d9b-621f20217241", + "last_modified": 1665709023819 + }, + { + "schema": 1665665355822, + "derHash": "ui9XSAn1/TY48Vp+Qts7RlyNZil7JkUUV/FjO392LzU=", + "subject": "CN=GlobalSign Atlas ECCR5 OV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIFRMUyBDQSAyMDIzIFEx", + "whitelist": false, + "attachment": { + "hash": "2be5c08269fc2d2e247946f55f4017f35aa1dc882427030286d5c8cfd1ce1cb4", + "size": 1195, + "filename": "XrPo3NhNRXIH-AOZzg3a4F-mQbtZnOKvKewGMDga97E=.pem", + "location": "security-state-staging/intermediates/76618edb-9869-4be8-a696-3d9f85077234.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XrPo3NhNRXIH+AOZzg3a4F+mQbtZnOKvKewGMDga97E=", + "crlite_enrolled": false, + "id": "b8c80eb1-b0b3-4dbb-aa9f-0772a33b2666", + "last_modified": 1665665823393 + }, + { + "schema": 1665665356847, + "derHash": "fE6QIHsrfK7AgEJsxGmQjLJ7kl7jscmZwiuFaIEv2ow=", + "subject": "CN=AlphaSSL CA - SHA256 - G4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSIwIAYDVQQDExlBbHBoYVNTTCBDQSAtIFNIQTI1NiAtIEc0", + "whitelist": false, + "attachment": { + "hash": "aa4faaefa822f9902a0520d55cd275a1c3b1d694d9c2e9cd500c49b957267cf0", + "size": 1634, + "filename": "BbrVIhEYvvBL6FiyC7nzVKLLDU3GPYdqHWAfk0ev_80=.pem", + "location": "security-state-staging/intermediates/ebfbc51e-c5f5-4a43-a385-003d492b64c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BbrVIhEYvvBL6FiyC7nzVKLLDU3GPYdqHWAfk0ev/80=", + "crlite_enrolled": false, + "id": "a0f8459f-e1e7-407b-9866-c60cf2cadfc3", + "last_modified": 1665665823381 + }, + { + "schema": 1665665357824, + "derHash": "oCwE2rCzF6SRB1Py9048eLOa0hDly3KHCyeBIq01smU=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "ce7b4c20f4e9e4470a2a23a39f627e7e0e4dbd8019e8b89faad9c1f96709a897", + "size": 2345, + "filename": "lCk7U0B7GxmG-G3zG9NMZhBW5zX9WG2OOMQdcEoaIM0=.pem", + "location": "security-state-staging/intermediates/113f55d4-d85f-4efa-8421-b8afc89a88a9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lCk7U0B7GxmG+G3zG9NMZhBW5zX9WG2OOMQdcEoaIM0=", + "crlite_enrolled": false, + "id": "0b7d6f08-9f90-4b4b-acde-d14b51fa1c04", + "last_modified": 1665665823370 + }, + { + "schema": 1665665358790, + "derHash": "TEFwfYhNfn0/3j33wkx/9uRvkWZlHYLD2nxHmNeggk8=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIzIFEx", + "whitelist": false, + "attachment": { + "hash": "2b7e6c6842847b59bd5c9cefd82a4cd3209f978c7e1bd92c00c7ac9aca8e1838", + "size": 1195, + "filename": "36ah7WHdx2k9G2lh1Edcsr6lVjSR6H_n7R7FWf5yAxM=.pem", + "location": "security-state-staging/intermediates/90e34889-c979-44fb-b56e-273636207523.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "36ah7WHdx2k9G2lh1Edcsr6lVjSR6H/n7R7FWf5yAxM=", + "crlite_enrolled": false, + "id": "c9391699-679e-4399-abaf-d9d4605dd990", + "last_modified": 1665665823359 + }, + { + "schema": 1665665359721, + "derHash": "TQSHEyQoie/39pwhNZxqnyEd1SwZ6b8+rz/PXI0Xiss=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "0f049e3399da779362e1c1a0a334654dfb6bc77648e5ef79afefdef8cf4e1220", + "size": 1199, + "filename": "fdIC1sV5JYPMcR-pbQXe_6mG2io0LdhYtAMMDw4MRto=.pem", + "location": "security-state-staging/intermediates/752e1c11-d50e-4b64-89d3-6b3476155ceb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fdIC1sV5JYPMcR+pbQXe/6mG2io0LdhYtAMMDw4MRto=", + "crlite_enrolled": false, + "id": "44b8382f-50c1-49df-afff-84f74e504a53", + "last_modified": 1665665823347 + }, + { + "schema": 1665665360678, + "derHash": "fsT97Qd83XmaQ5Y3/IiqyVRqexrp9NsK1xrMvHUufok=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIzIFEx", + "whitelist": false, + "attachment": { + "hash": "3e80d1e7b256e7a90a661e54c5215c9902baafc56aea084cffb581682a949621", + "size": 1642, + "filename": "yRbh4Znu93MwWptb4h9bPhtLIP-1ydlFLip9SIGcNv0=.pem", + "location": "security-state-staging/intermediates/f3789e12-dea4-43f2-a4be-f308c2343eb2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yRbh4Znu93MwWptb4h9bPhtLIP+1ydlFLip9SIGcNv0=", + "crlite_enrolled": false, + "id": "1ae1e49d-f8f5-4802-bfa5-6cd87c6a79a6", + "last_modified": 1665665823334 + }, + { + "schema": 1665665361626, + "derHash": "EIPVdGT9USi4CyKWX6IF4TFo3fzN8XpwlfO+DtCrOpE=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIzIFEx", + "whitelist": false, + "attachment": { + "hash": "5a8071ed5d1796cf3ae87eaaf01d46b71e3745859f8f6dcaaecea41db5e7016d", + "size": 2349, + "filename": "idTINMkGcX3vkPReA3MkuQwSq5m9WXT2_SC8n_NAoOg=.pem", + "location": "security-state-staging/intermediates/3169fa44-7ab4-4cbe-9aaa-4d2e10113290.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "idTINMkGcX3vkPReA3MkuQwSq5m9WXT2/SC8n/NAoOg=", + "crlite_enrolled": false, + "id": "c2ff5b98-10e4-4977-9a7d-6348e7ab3783", + "last_modified": 1665665823322 + }, + { + "schema": 1665665362586, + "derHash": "cdfWARwQGFk5xpCH39ROWsKNpaIOp35lC/mD5N+5nRw=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "eb270cdc1a15a9506b5447fa73a613013cb7fb9aeb2010b3506c5a9defd54557", + "size": 1642, + "filename": "vDHLXe8DZrVsrPdEp_vwpQ3M4jYDEV5YBlgnvCq76VY=.pem", + "location": "security-state-staging/intermediates/d8d360da-465f-45b5-9edf-15cfa235d94f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vDHLXe8DZrVsrPdEp/vwpQ3M4jYDEV5YBlgnvCq76VY=", + "crlite_enrolled": false, + "id": "284a8b4a-128b-4bff-b531-0cf7870dee6d", + "last_modified": 1665665823311 + }, + { + "schema": 1665665363521, + "derHash": "jP364cBga444Guai1K/R5QzqY7ZSwstzFFYdM1MYM2k=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "7a7339ac628ba733ca72d3dbc627c7b71c3864b8246ee6230e43e248ad8025b3", + "size": 1199, + "filename": "M0n504M7d3qEMVt-SxyNOHIEBWSe51OGsCG_NKA9HQs=.pem", + "location": "security-state-staging/intermediates/1fb55888-e9d4-43a2-a9fd-c59962fad4b1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M0n504M7d3qEMVt+SxyNOHIEBWSe51OGsCG/NKA9HQs=", + "crlite_enrolled": false, + "id": "abbf2f61-7d58-4265-aa1e-9fb7a6e29939", + "last_modified": 1665665823300 + }, + { + "schema": 1665665364448, + "derHash": "0txardwktCyOLP3nVq9rekPMrvt1ub0prufCjN6M8bQ=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIDIwMjMgUTE=", + "whitelist": false, + "attachment": { + "hash": "b5dcafe0e6d047493f56ae880e71b36759e0a0dce3a6e5e84a571ffd2feb4b43", + "size": 1642, + "filename": "YijRnBf8QQCIr2rnSYZ5X8lvtGvsYKRR899qmn4ZQTw=.pem", + "location": "security-state-staging/intermediates/3e70cfa5-5d61-458a-9eae-f6f1185e50b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YijRnBf8QQCIr2rnSYZ5X8lvtGvsYKRR899qmn4ZQTw=", + "crlite_enrolled": false, + "id": "4a6e9d47-e390-410a-b7f1-e55102e95ff8", + "last_modified": 1665665823288 + }, + { + "schema": 1665665365380, + "derHash": "si/0i5CgfArmXdCauA3USu8ZEkId/7cwFYPCTctjdpI=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "7a29465a5118485a545c9201c6592deff14c7da1fe7c9a9792eaa5fe789644c6", + "size": 1642, + "filename": "npizXpwRIYW_f4thHDYKq2yHR7-gHEI4SPn_6MBPDDk=.pem", + "location": "security-state-staging/intermediates/ffb8cd8a-c812-462f-bb35-59e57477ae98.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "npizXpwRIYW/f4thHDYKq2yHR7+gHEI4SPn/6MBPDDk=", + "crlite_enrolled": false, + "id": "85dc6898-b61c-42ed-a4aa-c1e32794436f", + "last_modified": 1665665823277 + }, + { + "schema": 1665665366320, + "derHash": "jR7jHgR8nmBM8GA6kHbcRae9TUr0WE0hVvA3UmJtim4=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMyBRMQ==", + "whitelist": false, + "attachment": { + "hash": "3728443f6678eb400f95c2591927bb49f951917dfcb8b26a1dcebac7c87de2fc", + "size": 1199, + "filename": "oygZmz_vZg7JYIqtCoKZYqUtQyZlq4nQ_T_vXCTnw30=.pem", + "location": "security-state-staging/intermediates/4686bbc6-c36f-4ef7-bd85-3aa1c62216ea.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oygZmz/vZg7JYIqtCoKZYqUtQyZlq4nQ/T/vXCTnw30=", + "crlite_enrolled": false, + "id": "d6d0b8aa-20ed-4745-a08d-4292cf8399cd", + "last_modified": 1665665823266 + }, + { + "schema": 1665665367298, + "derHash": "1BPFHkPGiA2fOaKVxJbYtQsfakRjY6fBr6yptRDlwwA=", + "subject": "CN=GlobalSign Atlas R3 OV TLS CA 2023 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIE9WIFRMUyBDQSAyMDIzIFEx", + "whitelist": false, + "attachment": { + "hash": "600855be2917f7e88e786b625dac3e2a1fbae11ae176bcc3fb3cef298aa4c25c", + "size": 1642, + "filename": "U-lJFQTcz2X0e14FAISlROsPC3mrKMaavqb0kVA5NR4=.pem", + "location": "security-state-staging/intermediates/b4662a9d-42c8-4b80-a96b-dfc5d52e4bf7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "U+lJFQTcz2X0e14FAISlROsPC3mrKMaavqb0kVA5NR4=", + "crlite_enrolled": false, + "id": "5c698bdb-ba48-4930-a3a8-831f93b497a4", + "last_modified": 1665665823256 + }, + { + "schema": 1665578919085, + "derHash": "ROaDV24clg14TRe7IyXCXPG+TiBHDDHZ1zys7Ulslck=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgSDIgMjAyMQ==", + "whitelist": false, + "attachment": { + "hash": "6cd9e29057bbe5b64edf3377a6113b0eaccbd584246dad3b5bd7e028d89f27c9", + "size": 1252, + "filename": "M57F0Wnxzruxx2skIABzM3bhMc1XFcJ5OmPMfoX_2Kw=.pem", + "location": "security-state-staging/intermediates/1bbcc3cd-f827-4861-a10e-95361d1c9110.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M57F0Wnxzruxx2skIABzM3bhMc1XFcJ5OmPMfoX/2Kw=", + "crlite_enrolled": false, + "id": "a7237377-ea8a-421a-9e89-eb5e456457e7", + "last_modified": 1665579462842 + }, + { + "schema": 1665557823626, + "derHash": "Crsc3lPMrBcEg9r808258mIrp+R9MewP09snG0qqUwM=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSBIMiAyMDIx", + "whitelist": false, + "attachment": { + "hash": "d6a647a904b2296b2de421924aee6694ee9acef681494c12c051da05b878733b", + "size": 2406, + "filename": "0mvzohE9hsg2CJwQhrPuumD2oI7MvADSjcdM3IhnqV4=.pem", + "location": "security-state-staging/intermediates/2e408e2e-ba52-4bfd-8fce-a7fcfe0075be.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0mvzohE9hsg2CJwQhrPuumD2oI7MvADSjcdM3IhnqV4=", + "crlite_enrolled": false, + "id": "8ebb9dc6-96b6-48c7-a893-297551984569", + "last_modified": 1665579462830 + }, + { + "schema": 1665578919278, + "derHash": "OFqgChhI+Af2ZVYeiIsNT2lhQFLYJfPDOXDgD0w2WN0=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgSDIgMjAyMQ==", + "whitelist": false, + "attachment": { + "hash": "6af7c086768ea998be52398129400eb39d0f5d4eb9230e1c2c7a9f228d5f6d58", + "size": 2398, + "filename": "6kj_jfSE7hoAQjieaM0YreYi-NU25dfVW4X5EvPet-c=.pem", + "location": "security-state-staging/intermediates/5719a806-1c18-46c8-9229-0f467fd98968.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6kj/jfSE7hoAQjieaM0YreYi+NU25dfVW4X5EvPet+c=", + "crlite_enrolled": false, + "id": "51e492fd-63e9-4386-9e4c-107cf4cc16b1", + "last_modified": 1665579462816 + }, + { + "schema": 1665168564306, + "derHash": "NkeqwrKCvJQf56ZC49y5nPxbPG3OlEoelvgCjom3sJA=", + "subject": "CN=GTS CA 2P2,O=Google Trust Services LLC,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRMwEQYDVQQDEwpHVFMgQ0EgMlAy", + "whitelist": false, + "attachment": { + "hash": "3d6c30b5ecd86000cf065014ec131cec711a8b846d98e249cb208c6c92a3c220", + "size": 1146, + "filename": "dVRFNZcK5NW65k3TVPqCdirBZSLZbQ8FI7w4pg9zcWM=.pem", + "location": "security-state-staging/intermediates/b0684b44-214a-4a04-9507-dc9307ca21d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dVRFNZcK5NW65k3TVPqCdirBZSLZbQ8FI7w4pg9zcWM=", + "crlite_enrolled": false, + "id": "a7696b55-65d3-4838-b71b-8a845e20e08c", + "last_modified": 1665169023249 + }, + { + "schema": 1664891326687, + "derHash": "2FmCjk6MNezDjkFeHQz/1KnfwRYgVAHRKY/BM0fcAYc=", + "subject": "CN=SSL Secure Site CA,O=SSL Limited,C=GB", + "subjectDN": "MEAxCzAJBgNVBAYTAkdCMRQwEgYDVQQKDAtTU0wgTGltaXRlZDEbMBkGA1UEAwwSU1NMIFNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "1cbf517d05902fa781d9ca1babe1269e24dafbbff2ab524bb102a9bd41604a70", + "size": 1678, + "filename": "FMV2Nt-BwJTHkVoED60m_qJ_6h5_PcVDCANGYPuITIw=.pem", + "location": "security-state-staging/intermediates/7033adb1-aa08-477e-939e-ac09e5a8cc12.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FMV2Nt+BwJTHkVoED60m/qJ/6h5/PcVDCANGYPuITIw=", + "crlite_enrolled": false, + "id": "df75a865-7d14-4a07-bd27-76ff9a5d3634", + "last_modified": 1664891823141 + }, + { + "schema": 1664585320096, + "derHash": "SKfJxaNnNPyeIE1jzmu7zZ4hwZeGBHYM2NMNb0xntnw=", + "subject": "CN=Plex Devices High Assurance CA,O=Plex\\, Inc.,C=US", + "subjectDN": "MEsxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpQbGV4LCBJbmMuMScwJQYDVQQDEx5QbGV4IERldmljZXMgSGlnaCBBc3N1cmFuY2UgQ0E=", + "whitelist": false, + "attachment": { + "hash": "02be526c7cf8b8327b244d8abd905b5da27d108eee4a719d225004abd4209f2f", + "size": 1670, + "filename": "vLeOEXjDgoNqSXwuYXRAIm7pApi-S-w8e2zLIwsC76A=.pem", + "location": "security-state-staging/intermediates/4e7685be-f9c0-4d17-b8f3-865d25b5d33f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vLeOEXjDgoNqSXwuYXRAIm7pApi+S+w8e2zLIwsC76A=", + "crlite_enrolled": false, + "id": "452205f3-07bd-412c-a0f3-3fed26c0eeb0", + "last_modified": 1664585823072 + }, + { + "schema": 1664585320982, + "derHash": "UNPXH8DNfjatrjIiH+++jMKbJna6MmwJuPobJNvnVRQ=", + "subject": "CN=Plex Devices High Assurance CA2,O=Plex\\, Inc.,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpQbGV4LCBJbmMuMSgwJgYDVQQDEx9QbGV4IERldmljZXMgSGlnaCBBc3N1cmFuY2UgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "b9ec4c02a550f9a56f28784144351318a6a3cb42352706073353594b30831f29", + "size": 1699, + "filename": "ymL44ll2Q9SIPAQZkomkZ5uFqCHuOYoXpsbrXWfyZkg=.pem", + "location": "security-state-staging/intermediates/b1dae52e-fa30-4d0b-bfd3-410952ab066e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ymL44ll2Q9SIPAQZkomkZ5uFqCHuOYoXpsbrXWfyZkg=", + "crlite_enrolled": false, + "id": "5a85e7b2-ce77-42e7-acfb-9ef72026acf9", + "last_modified": 1664585823063 + }, + { + "schema": 1664326321968, + "derHash": "vRk8R15OZ5OL7zQSK5i1WKKILn7ZQ2mmywETIO8VYjw=", + "subject": "CN=AlwaysOnSSL TLS ECC CA G1,OU=Domain Validated SSL,O=CertCenter AG,C=DE", + "subjectDN": "MGgxCzAJBgNVBAYTAkRFMRYwFAYDVQQKEw1DZXJ0Q2VudGVyIEFHMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZQWx3YXlzT25TU0wgVExTIEVDQyBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "6a6ab99ca26b4ea85f6b0ebff3e05be9589783e1fd7c0869984977e5eee35bf2", + "size": 1175, + "filename": "0W9t3LKpNLN0CxCis6AJXuaxxouX1FIp8IEuXgow59U=.pem", + "location": "security-state-staging/intermediates/7b309fea-ab29-4cd2-be7f-2b1085a4c3e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0W9t3LKpNLN0CxCis6AJXuaxxouX1FIp8IEuXgow59U=", + "crlite_enrolled": false, + "id": "de57018b-48a7-4a6a-a8ce-0b1b15e57d19", + "last_modified": 1664326626002 + }, + { + "schema": 1664326318466, + "derHash": "wCLVzqonXypiaPp5rDVlOzpzDe+kH5zYgX1tFZvTMJc=", + "subject": "CN=DigiCert Secure Site Korea EV CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKTAnBgNVBAMTIERpZ2lDZXJ0IFNlY3VyZSBTaXRlIEtvcmVhIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "9e5a746ac77a20cd8a2433a0ccf3dcca6a47d3f6602b22588154fbfcff3c111c", + "size": 1699, + "filename": "GU23iW3XanBnTnSB-qHY60G9W7OWfde0SLEvQJL9stY=.pem", + "location": "security-state-staging/intermediates/7d5c253a-8a9d-47b0-b14d-a935652bb1c7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GU23iW3XanBnTnSB+qHY60G9W7OWfde0SLEvQJL9stY=", + "crlite_enrolled": false, + "id": "3fb82116-362e-4282-982a-db1d110f0d11", + "last_modified": 1664326625994 + }, + { + "schema": 1664326309829, + "derHash": "DVG2zc80qfIhRHh/P9kg3IgA+2BJC/yNKJoZU+D72k0=", + "subject": "CN=TrustAsia EV TLS Pro CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRVYgVExTIFBybyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "ce686ad6e53606937f47bb538464e59bb83f5d67b4f21eee779132956645376c", + "size": 1670, + "filename": "JTqDYh_WILx03HF-qMSr0wy2uDSoWh4uqNea4MnbzZ8=.pem", + "location": "security-state-staging/intermediates/8f378669-0201-4a76-9394-84b5d9f4d4bb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JTqDYh/WILx03HF+qMSr0wy2uDSoWh4uqNea4MnbzZ8=", + "crlite_enrolled": false, + "id": "f9fda149-0c99-483d-87d9-ede54b3cdd66", + "last_modified": 1664326625932 + }, + { + "schema": 1664326319338, + "derHash": "25mk8oTM8Qsm3nt6XWUXJbhXy8hx67MwKNZ7VVEO/Nk=", + "subject": "CN=Abbott Laboratories Secure Authentication CA,O=Abbott Laboratories Inc.,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhBYmJvdHQgTGFib3JhdG9yaWVzIEluYy4xNTAzBgNVBAMTLEFiYm90dCBMYWJvcmF0b3JpZXMgU2VjdXJlIEF1dGhlbnRpY2F0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "d51732ac0b1ce932777f09ace232f20e4ab6fdc2e4be70d3441e83f23115c318", + "size": 1772, + "filename": "S-zMrWUPK-hCylswDO1ieeWHUSws1lexaXMxp-YEjWA=.pem", + "location": "security-state-staging/intermediates/0ee918aa-5ec3-4097-9959-8e60637a737e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S+zMrWUPK+hCylswDO1ieeWHUSws1lexaXMxp+YEjWA=", + "crlite_enrolled": false, + "id": "21fb8eda-586a-49f6-a362-1c12e465ff21", + "last_modified": 1664326625925 + }, + { + "schema": 1664326312437, + "derHash": "75E4mTZU35LS+1hg4o3ogYqfSdtW7LaJpnp/wtWIHdI=", + "subject": "CN=DigiCert Secure Site Korea ECC CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGsxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKjAoBgNVBAMTIURpZ2lDZXJ0IFNlY3VyZSBTaXRlIEtvcmVhIEVDQyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1cebaeaf42603c1ecacaaf80e014cd47e7847cb05d26b160ee9968c87797e04d", + "size": 1410, + "filename": "DqywqWwTJGFOkcrnV9IPNGtEO_GJMB1Y28mAQyChmRY=.pem", + "location": "security-state-staging/intermediates/18453308-5296-495f-9985-552e1584e920.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DqywqWwTJGFOkcrnV9IPNGtEO/GJMB1Y28mAQyChmRY=", + "crlite_enrolled": false, + "id": "c1c62fba-bd9f-4dd1-91b4-5a7272e6906d", + "last_modified": 1664326625917 + }, + { + "schema": 1664326313287, + "derHash": "vmoNnh0RXyKT9qvxGz7I6ILiRCbu6wmqpQNZeZPneiU=", + "subject": "CN=TERENA SSL High Assurance CA 3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MHMxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEnMCUGA1UEAxMeVEVSRU5BIFNTTCBIaWdoIEFzc3VyYW5jZSBDQSAz", + "whitelist": false, + "attachment": { + "hash": "8d237604a604b6c114352fe34117c1f80898239bdf21d1f81d2411892a0eec27", + "size": 1752, + "filename": "XaQOs7GKv4Gx4JRA8ZmihabSl9wxIPx-hQBmJ54WmCs=.pem", + "location": "security-state-staging/intermediates/8a739887-2661-4e6d-8937-838e82255c56.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XaQOs7GKv4Gx4JRA8ZmihabSl9wxIPx+hQBmJ54WmCs=", + "crlite_enrolled": false, + "id": "4060e977-0298-41b1-8f3d-af34a82c520d", + "last_modified": 1664326625909 + }, + { + "schema": 1664326308953, + "derHash": "JOnyCsFnu48J3ooemWjMU/C186SUj1G4ZHtAsYbHXr4=", + "subject": "CN=DigiCert Secure Auth CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IFNlY3VyZSBBdXRoIENB", + "whitelist": false, + "attachment": { + "hash": "cda72d22a79f77e8f6bf5a1839b886065eb2ff2b353c100287cc8a32e4b0b773", + "size": 1792, + "filename": "tdoo0C0wiaEcgnWAQOcm5NMBHjL1VfpZl4892op9W2U=.pem", + "location": "security-state-staging/intermediates/6b4698ed-1e53-434f-b7d4-fe4692c7b107.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tdoo0C0wiaEcgnWAQOcm5NMBHjL1VfpZl4892op9W2U=", + "crlite_enrolled": false, + "id": "b69f8920-3d83-43c3-8b24-6edf10da5eef", + "last_modified": 1664326625894 + }, + { + "schema": 1664326303736, + "derHash": "bGniAWVkQOuYzQh1dkoe0ZAV7YxEJ2Aaypxor6iXOVk=", + "subject": "CN=Abbott Laboratories Secure Server CA,O=Abbott Laboratories Inc.,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhBYmJvdHQgTGFib3JhdG9yaWVzIEluYy4xLTArBgNVBAMTJEFiYm90dCBMYWJvcmF0b3JpZXMgU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "eab39e7e333afe852aeb6f2575bf8e963df5990e6948452d109ad50c947571df", + "size": 1670, + "filename": "WLek4rSUF1hqMCGoR58aNhDKwFpMOcmhWVSaGRqkM5Y=.pem", + "location": "security-state-staging/intermediates/e4fb4ed4-bee3-40bb-9993-79934cb2d8f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WLek4rSUF1hqMCGoR58aNhDKwFpMOcmhWVSaGRqkM5Y=", + "crlite_enrolled": false, + "id": "981cf10f-0a58-4f74-ab2c-3dada26a9d3e", + "last_modified": 1664326625871 + }, + { + "schema": 1664326307214, + "derHash": "fAkS5d6EeLuG6OpGulrmXcOHC878vC9GeV7uz2SM++c=", + "subject": "CN=DigiCert Extended Validation CA G3,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "79aef4deadba4e5527967cc9603d5b5f0168cc397952725b9f9163d98b117b70", + "size": 1158, + "filename": "26Ut6iLcm-3yJllopC51FzYrG4EvCSFIv9j5aRSvwvk=.pem", + "location": "security-state-staging/intermediates/5774f69e-4cb5-4196-aa07-a4dbb507ab58.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "26Ut6iLcm+3yJllopC51FzYrG4EvCSFIv9j5aRSvwvk=", + "crlite_enrolled": false, + "id": "e384f427-9be3-40cc-a194-d79fb73446fa", + "last_modified": 1664326625863 + }, + { + "schema": 1664326305512, + "derHash": "d81ReEppPZSC5+ppTkBTvP2d9lWA+84UsqdSfAgYA+E=", + "subject": "CN=DigiCert Assured ID TLS CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEFzc3VyZWQgSUQgVExTIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "4047153b7f6291ee88b059494c6c59bd5962070832386ef7274d1050ec7cce55", + "size": 1918, + "filename": "7DHaE3Q3dQ9k5S7DaYbXog4BK-Oj492ta3kc2xRJ04s=.pem", + "location": "security-state-staging/intermediates/87c5aea0-3786-4e58-acc2-9575e56b1073.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7DHaE3Q3dQ9k5S7DaYbXog4BK+Oj492ta3kc2xRJ04s=", + "crlite_enrolled": false, + "id": "ed8651b1-33ff-4f92-bc75-55a696fe8d1b", + "last_modified": 1664326625855 + }, + { + "schema": 1664326298513, + "derHash": "wSP1r6zJ+QloCYUDVeW/eMqTdzSBEbUWepZN3twETek=", + "subject": "CN=TERENA Personal CA 3 G3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MGwxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEgMB4GA1UEAxMXVEVSRU5BIFBlcnNvbmFsIENBIDMgRzM=", + "whitelist": false, + "attachment": { + "hash": "ceaf99503d454e0bfc760c2d329942b0bb190599c2bd759df53ac0525ba6632e", + "size": 1561, + "filename": "YT82u8O68j48N4ShltJlLRPwW9t8uCpdZikMmWEu9ro=.pem", + "location": "security-state-staging/intermediates/9181fd99-fc3d-43be-ad3e-d1efb285d5ae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YT82u8O68j48N4ShltJlLRPwW9t8uCpdZikMmWEu9ro=", + "crlite_enrolled": false, + "id": "2d2d71d8-63a8-4ebf-b9ff-41fc7de6502f", + "last_modified": 1664326625832 + }, + { + "schema": 1664326299399, + "derHash": "A2oY9fDrndXuAreFTfXDOEVgHYk5z7e2B/adFCwB2Qk=", + "subject": "CN=TrustAsia TLS RSA CA G8,OU=Domain Validated SSL,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MHUxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXVHJ1c3RBc2lhIFRMUyBSU0EgQ0EgRzg=", + "whitelist": false, + "attachment": { + "hash": "baed94d2114fb277ac21313dba187c25e023b4b709456260c83533348987d326", + "size": 1687, + "filename": "rnv8EixdQFjoJF2hhedxVmsIlmFhW2jkp1llCWfHEvw=.pem", + "location": "security-state-staging/intermediates/05a943d4-dfca-4c89-8f44-c716b11303c8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rnv8EixdQFjoJF2hhedxVmsIlmFhW2jkp1llCWfHEvw=", + "crlite_enrolled": false, + "id": "ca450936-4851-4b5d-a03a-c68130f84102", + "last_modified": 1664326625808 + }, + { + "schema": 1664326297600, + "derHash": "ogZkT1ev0QuUFpSYhYmBwW1jOFjODIi1fPFPoqkqrP0=", + "subject": "CN=Secure Site Pro ECC CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFlNlY3VyZSBTaXRlIFBybyBFQ0MgQ0E=", + "whitelist": false, + "attachment": { + "hash": "b8140b2eeec8b54f38c8ab41c570c0475ea16dda43f8e2d9069082f2a48ba178", + "size": 1443, + "filename": "jqgpMe3J72LhpK4BfQvcmwg-Oh-RwdZH-swdUvq8F9k=.pem", + "location": "security-state-staging/intermediates/c8e1aca2-fc45-4263-bae9-d89eaec2fbd3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jqgpMe3J72LhpK4BfQvcmwg+Oh+RwdZH+swdUvq8F9k=", + "crlite_enrolled": false, + "id": "d444c5a6-804a-46e3-bbbb-8d03f923219f", + "last_modified": 1664326625800 + }, + { + "schema": 1664326301128, + "derHash": "t9/cJ+X/nzXv7J9LxTLDX3J3ibackKBIm0AkcpnZcDg=", + "subject": "CN=NCC Group Secure Server CA G4,O=NCC Group,C=US", + "subjectDN": "MEkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlOQ0MgR3JvdXAxJjAkBgNVBAMTHU5DQyBHcm91cCBTZWN1cmUgU2VydmVyIENBIEc0", + "whitelist": false, + "attachment": { + "hash": "20fff621c66f045f49bcfe13bb5d0083c6a6e1e44def86dbf3b99e2ccfc4c3dc", + "size": 2259, + "filename": "MVk5FViYCK7Pj5OrMzdLFaJfz4vqivN-THyz125ar_w=.pem", + "location": "security-state-staging/intermediates/3b467786-5cab-4a90-ac31-fa620d5a01e0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MVk5FViYCK7Pj5OrMzdLFaJfz4vqivN+THyz125ar/w=", + "crlite_enrolled": false, + "id": "06d88a0b-9af9-4681-ab32-9e94f802a262", + "last_modified": 1664326625785 + }, + { + "schema": 1664326296721, + "derHash": "WOifT3BBDaALQUc8+kE/oHN+0nKfPtd74pp/RwU3cUc=", + "subject": "CN=Thawte ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHDAaBgNVBAMTE1RoYXd0ZSBFQ0MgQ04gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "03c396058ce8fa96e5b49b97d333b5074a6fc5235a9a4154a8f1c2b82693ef48", + "size": 1317, + "filename": "0dJZS0XpXAP9EPdQcU6sdSVE8gQj1YnY-X4H-exCnC0=.pem", + "location": "security-state-staging/intermediates/d63c7368-5bb3-4fa5-a650-01451d8a5990.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0dJZS0XpXAP9EPdQcU6sdSVE8gQj1YnY+X4H+exCnC0=", + "crlite_enrolled": false, + "id": "8724cf17-17f4-450b-9635-1eacada68535", + "last_modified": 1664326625769 + }, + { + "schema": 1664326289637, + "derHash": "Y0/fJsmU52opGNnvxMq5xvyzRO9kKnnIkZK82g7VL0w=", + "subject": "CN=DigiCert Assured ID CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MEgxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "abd08758e317706405d73bf65d2507a5410f6ff635faffdeaa04c342401d0797", + "size": 1118, + "filename": "PvaHuwE2jRHMyjTCq1y7W0C__z4Wjsb057DegXU0Ars=.pem", + "location": "security-state-staging/intermediates/d9278d38-64f1-4628-b185-a021cc542517.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PvaHuwE2jRHMyjTCq1y7W0C//z4Wjsb057DegXU0Ars=", + "crlite_enrolled": false, + "id": "34273cc6-ee7f-4a26-913c-03824bc48005", + "last_modified": 1664326625739 + }, + { + "schema": 1664326288722, + "derHash": "8mmsALQQAD9y3GKK+z2VAnljDHxdDIIUig/STfTaQwE=", + "subject": "CN=DigiCert Trust Service ECC CA,O=DigiCert Inc,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IFRydXN0IFNlcnZpY2UgRUNDIENB", + "whitelist": false, + "attachment": { + "hash": "aa171f11c4fc2ce99557f16f4732690df2f37aefee0e9b5fc01d10bf016d1aee", + "size": 1410, + "filename": "447EOyTQJY3PyAbUMHr-SyrdqoPSJsxpM0Nf26eUpV0=.pem", + "location": "security-state-staging/intermediates/21d55dea-31a3-4e8f-8e16-5c79c28af097.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "447EOyTQJY3PyAbUMHr+SyrdqoPSJsxpM0Nf26eUpV0=", + "crlite_enrolled": false, + "id": "2cc65175-28ff-4d4e-b161-e4914fca272a", + "last_modified": 1664326625731 + }, + { + "schema": 1664326290556, + "derHash": "6VKbQo+2c5C8ZFXXm6JDToFsVP5PNZkwy3Cdslb935Q=", + "subject": "CN=DigiCert High Assurance Trust Service ECC EV CA,O=DigiCert Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxODA2BgNVBAMTL0RpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIFRydXN0IFNlcnZpY2UgRUNDIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "e1d33fc3556c65f83a31b2b6b1d28f6afb5af11d1824d26493f7b5811a803f95", + "size": 1431, + "filename": "MMkARzLCDka9T1FV6sAvs-cZUAaL2QbYfVCCeg3D0W8=.pem", + "location": "security-state-staging/intermediates/6eca4338-f97b-4342-9f65-4e9381d38460.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MMkARzLCDka9T1FV6sAvs+cZUAaL2QbYfVCCeg3D0W8=", + "crlite_enrolled": false, + "id": "f3ac32ed-8109-4dab-8813-83e4bf7925d9", + "last_modified": 1664326625715 + }, + { + "schema": 1664326286917, + "derHash": "1hnzJXuYdW0ogR087pra2LyuG0Nnu7DHP2teVY+6RWM=", + "subject": "CN=DigiCert Basic EV ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IEJhc2ljIEVWIEVDQyBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "09521230a03b590672f671bfd4e3d8e071b2ff923e8259b3d73a1094105c0708", + "size": 1378, + "filename": "yE_naogfFzNf_bv3qdwlxZJxaARkFaKc5DZu2mp6Q34=.pem", + "location": "security-state-staging/intermediates/2cb2ac6b-d8eb-4647-bc20-a412d247958c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yE/naogfFzNf/bv3qdwlxZJxaARkFaKc5DZu2mp6Q34=", + "crlite_enrolled": false, + "id": "ebaaa8ba-2509-429c-b8fa-1401e752026c", + "last_modified": 1664326625708 + }, + { + "schema": 1664326285963, + "derHash": "aNCy6Mhb8Am02zmsi14vqOH9n9HlAocE6pKIx+Ryqus=", + "subject": "CN=TrustAsia TLS ECC CA,OU=Domain Validated SSL,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MHIxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEdMBsGA1UEAxMUVHJ1c3RBc2lhIFRMUyBFQ0MgQ0E=", + "whitelist": false, + "attachment": { + "hash": "3e0e2d990132f8d357622f7b20cd687f510bd7c3674c06f211c12b87317f1eb1", + "size": 1406, + "filename": "x-B4VfwG1Q5TkeQLpvUFHvsaT9cd8FIf8sfKwgA4sCE=.pem", + "location": "security-state-staging/intermediates/18e448e1-105d-4af6-aed2-c3b148c16b31.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x+B4VfwG1Q5TkeQLpvUFHvsaT9cd8FIf8sfKwgA4sCE=", + "crlite_enrolled": false, + "id": "5c2915b3-a16a-4f4f-9add-c7fc0882a381", + "last_modified": 1664326625700 + }, + { + "schema": 1664326285104, + "derHash": "Sv/k/vOUZNF4jGYK9ZHV5gGyYcSBHfCj3Z1hyv6OXtg=", + "subject": "CN=TrustAsia EV TLS Pro CA,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSAwHgYDVQQDExdUcnVzdEFzaWEgRVYgVExTIFBybyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a1a1ff80a1c8f43fc45d793dbaeaf32d05314f8eda44363ad6ea301f01d86c3f", + "size": 1658, + "filename": "JTqDYh_WILx03HF-qMSr0wy2uDSoWh4uqNea4MnbzZ8=.pem", + "location": "security-state-staging/intermediates/b087580d-0f78-4679-9ce6-07922d3ed4f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JTqDYh/WILx03HF+qMSr0wy2uDSoWh4uqNea4MnbzZ8=", + "crlite_enrolled": false, + "id": "065a1355-6bb1-455f-9bd7-81c86fafb74d", + "last_modified": 1664326625692 + }, + { + "schema": 1664326279871, + "derHash": "6h87+00ylrRn1LWCVZ1lGNZ21a/H0cJL6AJS95GRBG8=", + "subject": "CN=Thawte EV ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHzAdBgNVBAMTFlRoYXd0ZSBFViBFQ0MgQ04gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "33ae53a1dd37eb7264d34c624d9db19e86d364c1ed5789dc5876fb9ddcd38667", + "size": 1349, + "filename": "3GFGMg3yja_Gu8_Fov8gNPJ7b50vsniti0_Jy-xvY9A=.pem", + "location": "security-state-staging/intermediates/82fe6169-f611-4dbf-840c-605b8227052e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3GFGMg3yja/Gu8/Fov8gNPJ7b50vsniti0/Jy+xvY9A=", + "crlite_enrolled": false, + "id": "f122e3af-5d95-4945-98ea-5f528a3d5347", + "last_modified": 1664326625645 + }, + { + "schema": 1664326281652, + "derHash": "cDV7nlbT+zxsAJw4xxgUVMRikI37zm1U1g3+HlBuFP0=", + "subject": "CN=TrustAsia ECC OV TLS Pro CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMScwJQYDVQQDEx5UcnVzdEFzaWEgRUNDIE9WIFRMUyBQcm8gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "21240a41277b34c6a1189b4e59d1447d01a97e239d72efa20016aec9ed60ada1", + "size": 1406, + "filename": "zQ_SLNg_h_U_OTLMb3XQLYnyHZ7Y7Hk6_xyTVTSQekc=.pem", + "location": "security-state-staging/intermediates/b10cbda6-5d12-47d2-b428-377602c98dfd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zQ/SLNg/h/U/OTLMb3XQLYnyHZ7Y7Hk6/xyTVTSQekc=", + "crlite_enrolled": false, + "id": "3339ef7d-c87d-4d8d-b482-1257cf9a8f9a", + "last_modified": 1664326625630 + }, + { + "schema": 1664326274418, + "derHash": "D7bOgJCdGa/uaojJWenpqmRmcVvOBcZEn+tTCghtrxQ=", + "subject": "CN=GeoTrust SupremeSSL EV 1 CA,OU=www.supremessl.com,O=SupremeSSL,C=NL", + "subjectDN": "MGUxCzAJBgNVBAYTAk5MMRMwEQYDVQQKEwpTdXByZW1lU1NMMRswGQYDVQQLExJ3d3cuc3VwcmVtZXNzbC5jb20xJDAiBgNVBAMTG0dlb1RydXN0IFN1cHJlbWVTU0wgRVYgMSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "06e8ce6e80d24faa00adc58c2bc94093f41b82ea0000a194035528ceadda9270", + "size": 1691, + "filename": "uG3aQXLPd55q_z1RlGHWJQcDYD5HzCoVUz8Q5eD3Rlw=.pem", + "location": "security-state-staging/intermediates/da439913-8a40-4db9-b9b5-d510a42754d9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uG3aQXLPd55q/z1RlGHWJQcDYD5HzCoVUz8Q5eD3Rlw=", + "crlite_enrolled": false, + "id": "4701752d-4c67-402a-8bde-1e5b1d71c950", + "last_modified": 1664326625607 + }, + { + "schema": 1664326278130, + "derHash": "eGeq6QXrjVVjX/67v4zwWmO5s5BmXeI2esEHPpKRNyg=", + "subject": "CN=Thawte CN RSA CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHDAaBgNVBAMTE1RoYXd0ZSBDTiBSU0EgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "1674ecb890e170849faaede2b2c9416bbe036a07a6ee1fd61848524807f952ea", + "size": 1825, + "filename": "VMXglf2ljsL4aRTMSwEqaTOKsyr2QltUubbk10mUCT0=.pem", + "location": "security-state-staging/intermediates/2339cef0-106c-4e85-bdf5-1ea426315d30.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VMXglf2ljsL4aRTMSwEqaTOKsyr2QltUubbk10mUCT0=", + "crlite_enrolled": false, + "id": "4317d153-8a03-4ae1-bd0b-5443aab13e7d", + "last_modified": 1664326625600 + }, + { + "schema": 1664326277264, + "derHash": "sFBb8pR/CAerri1CwZND6vCNHd4/h0WwWJpXNieS5HA=", + "subject": "CN=DigiCert Secure Site Korea EV ECC CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLTArBgNVBAMTJERpZ2lDZXJ0IFNlY3VyZSBTaXRlIEtvcmVhIEVWIEVDQyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "4bb9308e9c9bddb21616d43a79954c102783b7e356fc087715ae3f321cf74383", + "size": 1427, + "filename": "AdP_iRzhlqZTNB5kWPVQycXqelrhzOIKeur3cseH-6k=.pem", + "location": "security-state-staging/intermediates/d4cb7ca6-62ba-4418-809b-88501dfd11b9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AdP/iRzhlqZTNB5kWPVQycXqelrhzOIKeur3cseH+6k=", + "crlite_enrolled": false, + "id": "45e5c84b-5a43-4463-8f9c-5e760840f90c", + "last_modified": 1664326625592 + }, + { + "schema": 1664326271796, + "derHash": "NU/PxhYqDuIgXb72iTHngDPs6dEl4IJOSbrr+C390Wc=", + "subject": "CN=GeoTrust SupremeSSL 1 CA,OU=www.supremessl.com,O=SupremeSSL,C=NL", + "subjectDN": "MGIxCzAJBgNVBAYTAk5MMRMwEQYDVQQKEwpTdXByZW1lU1NMMRswGQYDVQQLExJ3d3cuc3VwcmVtZXNzbC5jb20xITAfBgNVBAMTGEdlb1RydXN0IFN1cHJlbWVTU0wgMSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a0e114086535d0b0d6b6a9bf3b81ebfd13b7ecf4a82fee4469b8cde3ad7d1310", + "size": 1707, + "filename": "SM-CBbHgVoJj8KYqNoXbUd-IGEbfvpomZR5lGHQ3qng=.pem", + "location": "security-state-staging/intermediates/6e65d316-4fd9-4970-84b7-ab5cf7e4c2c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SM+CBbHgVoJj8KYqNoXbUd+IGEbfvpomZR5lGHQ3qng=", + "crlite_enrolled": false, + "id": "8fbe73ff-0669-4a65-9666-f706486e0e55", + "last_modified": 1664326625584 + }, + { + "schema": 1664326270928, + "derHash": "l8S0QxYFXyalKh9mTOOFgAqWSoVSyZ0ryv1hjm2AdKA=", + "subject": "CN=DigiCert Basic ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJDAiBgNVBAMTG0RpZ2lDZXJ0IEJhc2ljIEVDQyBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "b611e95bd602406b97a2c91d967caf70fcd7b56437d690cba228735942277f67", + "size": 1382, + "filename": "Mg4fdox_AdxNXF8FVxl956AK4z_M3V3fNH5JYxAVfu4=.pem", + "location": "security-state-staging/intermediates/b0f1290c-6a0d-47d7-8d45-59fcbb70158b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Mg4fdox/AdxNXF8FVxl956AK4z/M3V3fNH5JYxAVfu4=", + "crlite_enrolled": false, + "id": "2a71bf1d-54ea-43e1-bfad-fbd30e500409", + "last_modified": 1664326625561 + }, + { + "schema": 1664326265639, + "derHash": "3U4MF5APP8Klt7dzrkAhitcyFrXOXShev/zogw0PA0o=", + "subject": "CN=TERENA Personal CA 3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MGkxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEdMBsGA1UEAxMUVEVSRU5BIFBlcnNvbmFsIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "5f70131626f90ff117b3f4e144a4104cefc2a25ad3b7b24ebbe7571cb6e9d37a", + "size": 1792, + "filename": "HOqz19sqhyR0ujgIsU9ml1UZS9cRaphqC_kcr3CVOg0=.pem", + "location": "security-state-staging/intermediates/5519208e-d7cc-40b7-9360-9aaec642f07a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HOqz19sqhyR0ujgIsU9ml1UZS9cRaphqC/kcr3CVOg0=", + "crlite_enrolled": false, + "id": "70744846-a620-4a6e-8867-00bb0c2bd5dd", + "last_modified": 1664326625515 + }, + { + "schema": 1664326264787, + "derHash": "ctD3y/RSm4DDSny7Q4vR0OH8JugOWcvU/XMU/N3w6ZQ=", + "subject": "CN=DigiCert High Assurance Trust Service EV CA,O=DigiCert Inc,C=US", + "subjectDN": "MFoxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxNDAyBgNVBAMTK0RpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIFRydXN0IFNlcnZpY2UgRVYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "4b30b178e2fe81ae9cb867ac5f6e36b60573c72a0961a9f88fc1db7d61d0aaf0", + "size": 1699, + "filename": "HA5OXoZpETZhlPuW6jLvs2pyRrgxtQPRGRxa5tAQugE=.pem", + "location": "security-state-staging/intermediates/70f38582-98bc-4752-8269-190f59635d8e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HA5OXoZpETZhlPuW6jLvs2pyRrgxtQPRGRxa5tAQugE=", + "crlite_enrolled": false, + "id": "973ff8a7-ce57-46f6-850e-c9c6e0a5f5aa", + "last_modified": 1664326625506 + }, + { + "schema": 1664326263917, + "derHash": "ZVXWYdN/JJSiPC1fg0eeeAUbbedqFHtQbWvqKIK00GY=", + "subject": "CN=DigiCert Secure Site Korea CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJjAkBgNVBAMTHURpZ2lDZXJ0IFNlY3VyZSBTaXRlIEtvcmVhIENB", + "whitelist": false, + "attachment": { + "hash": "03f9bb50867bac0516935740dfc891853da3c7d2b1a601308326b82ef732c23c", + "size": 1646, + "filename": "trjGmNMo7m2JbQME1aNrIcNXkG-fscWHZcrs2zCZOHg=.pem", + "location": "security-state-staging/intermediates/75549463-d317-4276-bf3a-19e056508361.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "trjGmNMo7m2JbQME1aNrIcNXkG+fscWHZcrs2zCZOHg=", + "crlite_enrolled": false, + "id": "27862dc6-0ed2-4f38-8d1a-9d7993fbfd82", + "last_modified": 1664326625490 + }, + { + "schema": 1664326260410, + "derHash": "/AG1j8eLnFkhF4TC4lvx0BKh4j8zchiEemLrQUXuSrY=", + "subject": "CN=GeoTrust EV ECC CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxITAfBgNVBAMTGEdlb1RydXN0IEVWIEVDQyBDTiBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "9c7da6adba261fff8165aad9c1207192fab15a42739528904513d61318490095", + "size": 1370, + "filename": "aDb4btQIVH2hsyd4IxUsJGinBpyh-phY6PF4IHasdAw=.pem", + "location": "security-state-staging/intermediates/3b6fca76-7f63-404d-9abf-7ab592b983fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aDb4btQIVH2hsyd4IxUsJGinBpyh+phY6PF4IHasdAw=", + "crlite_enrolled": false, + "id": "3afa2a26-d0c7-40b9-9948-ae8248fe87f4", + "last_modified": 1664326625482 + }, + { + "schema": 1664326259469, + "derHash": "YHXaXOzRXWWExVYDItXAn8IZnlLep5IdkQQKp1JIZy4=", + "subject": "CN=NCC Group Secure Server CA G2,O=NCC Group,C=US", + "subjectDN": "MEkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlOQ0MgR3JvdXAxJjAkBgNVBAMTHU5DQyBHcm91cCBTZWN1cmUgU2VydmVyIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "6d28f77859c98bdf554102992dfa0363d577b6bce1ebcf342078dfd78b069f71", + "size": 1593, + "filename": "I00VnH3kFOOpwcnmh-WRlnA_EFwJgSkqGVgxV5O2veg=.pem", + "location": "security-state-staging/intermediates/29e39210-9eef-4ea4-82d4-50cb9917783e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "I00VnH3kFOOpwcnmh+WRlnA/EFwJgSkqGVgxV5O2veg=", + "crlite_enrolled": false, + "id": "0eed32a3-d320-4426-abdc-81c28d7e1a64", + "last_modified": 1664326625475 + }, + { + "schema": 1664326262148, + "derHash": "KMu04NnE7m0ErI8UcXYFrjpL2Mv40IGyevbtsvPXajI=", + "subject": "CN=DigiCert Grid Trust CA G2,OU=www.digicert.com,O=DigiCert Grid,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1EaWdpQ2VydCBHcmlkMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSIwIAYDVQQDExlEaWdpQ2VydCBHcmlkIFRydXN0IENBIEcy", + "whitelist": false, + "attachment": { + "hash": "544c3726fd3951912634094ca6df5aa1090a31a80fdb5edf0277e9abd81bea8a", + "size": 1796, + "filename": "5TmPqy9bKSAy0zKN0Mz_yBIgKGk_pgZ4TMaSTFRh9xw=.pem", + "location": "security-state-staging/intermediates/227d5c5c-7f90-49be-aefa-d65b6fb1cfc2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5TmPqy9bKSAy0zKN0Mz/yBIgKGk/pgZ4TMaSTFRh9xw=", + "crlite_enrolled": false, + "id": "e6868543-147b-405d-9626-7cae3e7d6c0c", + "last_modified": 1664326625459 + }, + { + "schema": 1664326258603, + "derHash": "DhC93udRLb156/C09I/u18g8K9PdgXZVZfT/EQt7+kI=", + "subject": "CN=Cybertrust Japan Extended Validation Server CA,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MGsxCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjE3MDUGA1UEAxMuQ3liZXJ0cnVzdCBKYXBhbiBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "18920d33058e8f17a6bdee31b23b81bd7e4ed21bbc1448eb16b82585be4bd4ca", + "size": 1678, + "filename": "VfKdH3KeIRG8iIljlHEf2ck5OXG4Z8_yf2AOohCgqgk=.pem", + "location": "security-state-staging/intermediates/30c66e60-3606-4569-9301-45e409642137.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VfKdH3KeIRG8iIljlHEf2ck5OXG4Z8/yf2AOohCgqgk=", + "crlite_enrolled": false, + "id": "de62212d-97c9-4786-a039-945ab84ef95b", + "last_modified": 1664326625452 + }, + { + "schema": 1664326253292, + "derHash": "CFoHFFJN966ZGab1l4hEOeEqUz5hVS0p1qGMNQlvxUg=", + "subject": "CN=DigiCert SHA2 Extended Validation Server CA-3,OU=www.digicert.com,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MHkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTE2MDQGA1UEAxMtRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlcnZlciBDQS0z", + "whitelist": false, + "attachment": { + "hash": "a538f5544f44d3ccf60e286bcc16d6d426e8a756b01f468df88bf18c0d4a122e", + "size": 1719, + "filename": "lwTPN61Qg5-1qAU-Mik9sFaDX5hLo2AHP80YR-IgN6M=.pem", + "location": "security-state-staging/intermediates/0486c6f8-9242-436a-ac4b-f79e0190ac35.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lwTPN61Qg5+1qAU+Mik9sFaDX5hLo2AHP80YR+IgN6M=", + "crlite_enrolled": false, + "id": "31d059dc-e13a-4958-a089-4f6e29658616", + "last_modified": 1664326625437 + }, + { + "schema": 1664326257758, + "derHash": "aZGdgg7fWCrbz44JKpKE2QF3LuK0Geqd4fWHLHkfxvo=", + "subject": "CN=Thawte EV RSA CN CA G2,O=DigiCert Inc,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHzAdBgNVBAMTFlRoYXd0ZSBFViBSU0EgQ04gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "805c41e7ce5efb3db0c012649e6317b9b3c5665f8fe363f8c493ccac544b82a2", + "size": 1626, + "filename": "5jGxIiaHNj15fBcUBvvAsMfKNtiK_podXNf0YoRUoeI=.pem", + "location": "security-state-staging/intermediates/011f252e-4d0f-4be1-9170-fface44ce0a9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5jGxIiaHNj15fBcUBvvAsMfKNtiK/podXNf0YoRUoeI=", + "crlite_enrolled": false, + "id": "a6b40e00-91a8-4c6a-bfa0-e54dbf745822", + "last_modified": 1664326625421 + }, + { + "schema": 1664326255960, + "derHash": "8crKarI1CnZowT5BlgkIaB2vzH42jcuNR/7PljE5BIE=", + "subject": "CN=TrustAsia ECC OV TLS Pro CA,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtUcnVzdEFzaWEgRUNDIE9WIFRMUyBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "c4c5e5a057734e12651756064c71f1ba18b9e7ca77b90e5d922b8140a2117211", + "size": 1435, + "filename": "Gk60bmqo_Mr2nOHuGjR4JqFa8B4v-ZsjwYEWTGOxLZ8=.pem", + "location": "security-state-staging/intermediates/6c26b65e-0f80-4751-914d-277116a56bcb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Gk60bmqo/Mr2nOHuGjR4JqFa8B4v+ZsjwYEWTGOxLZ8=", + "crlite_enrolled": false, + "id": "4530dd5a-f084-4e4d-a29f-01b4434e4f6e", + "last_modified": 1664326625390 + }, + { + "schema": 1664326249700, + "derHash": "my3U++6UH2UQ9xbASWmzZCiJJ2VGFwHvR9QbMolHXVA=", + "subject": "CN=DigiCert Global CA-3 G2,OU=www.digicert.com,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEgMB4GA1UEAxMXRGlnaUNlcnQgR2xvYmFsIENBLTMgRzI=", + "whitelist": false, + "attachment": { + "hash": "250bc9edff1ff7853bd9af21cb2f9748dcf10461c9384c16bd18ed0a7d7ee7b3", + "size": 1662, + "filename": "Ydb2Ke2rGfceHSy4YbkTNE4A8k1rexJ2dXAXXYa9sgk=.pem", + "location": "security-state-staging/intermediates/fd14ed5b-3c47-4f72-875c-eb17e4792ece.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ydb2Ke2rGfceHSy4YbkTNE4A8k1rexJ2dXAXXYa9sgk=", + "crlite_enrolled": false, + "id": "1c706d4e-94e8-476d-9d2d-323ce4b4efcc", + "last_modified": 1664326625367 + }, + { + "schema": 1664326246090, + "derHash": "zby+8W43I3cC+sxLVdOrkqF8+heDXhM3eqTSug/UegI=", + "subject": "CN=Cisco Meraki CA2,O=Cisco Systems\\, Inc.,C=US", + "subjectDN": "MEYxCzAJBgNVBAYTAlVTMRwwGgYDVQQKExNDaXNjbyBTeXN0ZW1zLCBJbmMuMRkwFwYDVQQDExBDaXNjbyBNZXJha2kgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "03fef49ccd3c137144e9ce38dcbde01b3abd7d1352674e90f7758145de269fa4", + "size": 1752, + "filename": "UNvyYoiHE-TJr4GNRJRQQVYLfL7m_X0ULyrRNeeQaKE=.pem", + "location": "security-state-staging/intermediates/190982c6-808b-490a-bac0-48048e0d26a0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UNvyYoiHE+TJr4GNRJRQQVYLfL7m/X0ULyrRNeeQaKE=", + "crlite_enrolled": false, + "id": "43464ec3-b91e-478c-b7cb-45af7465b042", + "last_modified": 1664326625352 + }, + { + "schema": 1664326246953, + "derHash": "2n5XePYvh2Vbj9JKieZR5XMp3JwHZacRWM5WRDykTpU=", + "subject": "CN=DigiCert Extended Validation CA-3 G3,OU=www.digicert.com,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MHAxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEtMCsGA1UEAxMkRGlnaUNlcnQgRXh0ZW5kZWQgVmFsaWRhdGlvbiBDQS0zIEcz", + "whitelist": false, + "attachment": { + "hash": "daa2ce59cd5cd31c65c5ad2e7d7f1866f114717d332ca08ea30fd99a87088783", + "size": 1187, + "filename": "epA52XxQ9Mrum63GbS8dL1dkTIzeOueiCw6jHr7t_u8=.pem", + "location": "security-state-staging/intermediates/8cd6df9d-888e-4ce3-b6c5-a73d66888bb7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "epA52XxQ9Mrum63GbS8dL1dkTIzeOueiCw6jHr7t/u8=", + "crlite_enrolled": false, + "id": "464b0870-6d2a-4bb0-945c-c5ba1d987e67", + "last_modified": 1664326625329 + }, + { + "schema": 1664326238997, + "derHash": "KkG6gZ62Elr1y0uLDp6VTs55jCp+5D3Nr305WYfE1VI=", + "subject": "CN=AlwaysOnSSL TLS RSA CA G1,OU=Domain Validated SSL,O=CertCenter AG,C=DE", + "subjectDN": "MGgxCzAJBgNVBAYTAkRFMRYwFAYDVQQKEw1DZXJ0Q2VudGVyIEFHMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZQWx3YXlzT25TU0wgVExTIFJTQSBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "c12d96ada3ee7291ef29f2b18ab971c813e8a2be138651971e1a518c88863e98", + "size": 1670, + "filename": "DOdL2thyQw2ljWo5qhqUbm1_3EZBtYXxLKw14G9PO30=.pem", + "location": "security-state-staging/intermediates/40e88993-405f-440f-86bf-003848717116.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DOdL2thyQw2ljWo5qhqUbm1/3EZBtYXxLKw14G9PO30=", + "crlite_enrolled": false, + "id": "a2f9858d-6717-421d-ada6-d08073c87767", + "last_modified": 1664326625306 + }, + { + "schema": 1664326243370, + "derHash": "Y1r2iJ9JBg/g57q8DyMxT7EY87GCQ90X9H2GR6aWhLE=", + "subject": "CN=Thawte Partner CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xGjAYBgNVBAMTEVRoYXd0ZSBQYXJ0bmVyIENB", + "whitelist": false, + "attachment": { + "hash": "517f47202cc97d6d6f41abf2c0fb2acc7fbb25207419b62568dbcda5a17b8dd3", + "size": 1630, + "filename": "W91BaGXmCLPVN1dHiQP6qf3bRnkklNI0e470b1Yl6Hc=.pem", + "location": "security-state-staging/intermediates/faf3f16b-6ec5-4b45-919d-46fd8871e952.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W91BaGXmCLPVN1dHiQP6qf3bRnkklNI0e470b1Yl6Hc=", + "crlite_enrolled": false, + "id": "fdd54c2e-e355-4800-9d81-527ddf3dbe00", + "last_modified": 1664326625298 + }, + { + "schema": 1664326239850, + "derHash": "wN0GD/jOVW81aD1WTg5mspBxeICGk/g/OqZCMmv20Mg=", + "subject": "CN=TrustAsia ECC EV TLS Pro CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMScwJQYDVQQDEx5UcnVzdEFzaWEgRUNDIEVWIFRMUyBQcm8gQ0EgRzI=", + "whitelist": false, + "attachment": { + "hash": "15ecb4d2bfb652859fe461ec4d56ef2af5aabdec1b7048209da418993219d311", + "size": 1398, + "filename": "6lJqHj6Al12YiHTPXbfLj9_MNtnpjUBiUvHLHqoVkTA=.pem", + "location": "security-state-staging/intermediates/007b303a-64fa-40b6-bbc5-b1e1c4daf0f0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6lJqHj6Al12YiHTPXbfLj9/MNtnpjUBiUvHLHqoVkTA=", + "crlite_enrolled": false, + "id": "bc77ca51-b829-47ac-83b4-8d36fa8fdb7d", + "last_modified": 1664326625291 + }, + { + "schema": 1664326238164, + "derHash": "GR4LSLeLfvpIIqRlrWmzRAW4eNEL2FPY5Xy4udnlC4s=", + "subject": "CN=Trust Technologies Global CA,OU=Digital Identity and Security,O=Telecom Italia Trust Technologies S.r.l.,C=IT", + "subjectDN": "MIGPMQswCQYDVQQGEwJJVDExMC8GA1UEChMoVGVsZWNvbSBJdGFsaWEgVHJ1c3QgVGVjaG5vbG9naWVzIFMuci5sLjEmMCQGA1UECxMdRGlnaXRhbCBJZGVudGl0eSBhbmQgU2VjdXJpdHkxJTAjBgNVBAMTHFRydXN0IFRlY2hub2xvZ2llcyBHbG9iYWwgQ0E=", + "whitelist": false, + "attachment": { + "hash": "94a4e9c26aa1b7c676a213b4813818710a403a4e8c106d27113bba7c5ed0b0a7", + "size": 1683, + "filename": "kCvviSGuWkulE1Rv-GhY_fjUuT622_2qjrGk96XfS6A=.pem", + "location": "security-state-staging/intermediates/f7048242-ec1b-463f-9ca3-9c6e493c4a6d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kCvviSGuWkulE1Rv+GhY/fjUuT622/2qjrGk96XfS6A=", + "crlite_enrolled": false, + "id": "a7cfef6c-2fd8-463f-919d-6c648941fe56", + "last_modified": 1664326625283 + }, + { + "schema": 1664326237322, + "derHash": "EDLGAB7WZKDNNDsTi8toYOIREBHD1fBlQPW+QRR4EqU=", + "subject": "CN=Legacy Technologies Intermediate,O=Legacy Technologies GmbH,C=DE", + "subjectDN": "MFsxCzAJBgNVBAYTAkRFMSEwHwYDVQQKExhMZWdhY3kgVGVjaG5vbG9naWVzIEdtYkgxKTAnBgNVBAMTIExlZ2FjeSBUZWNobm9sb2dpZXMgSW50ZXJtZWRpYXRl", + "whitelist": false, + "attachment": { + "hash": "9f15931b004051236bc8ed4b1fa4f90b6457dcf766a3079669bce0ff128bfb11", + "size": 1906, + "filename": "1IN_wrqE9VVL4qRI4B5j8xzAQgZ1Wap_tLlGGVYhcEc=.pem", + "location": "security-state-staging/intermediates/7a4e3658-6b03-4543-9e0b-5aba562bebeb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1IN/wrqE9VVL4qRI4B5j8xzAQgZ1Wap/tLlGGVYhcEc=", + "crlite_enrolled": false, + "id": "366c5429-2c2a-48e2-86a4-c28c1a3a5f54", + "last_modified": 1664326625276 + }, + { + "schema": 1664326240711, + "derHash": "cNyG+fd1C3Sx3sjNNS7CWDfDbmQPcUjghGTvWQHlpYk=", + "subject": "CN=Secure Site Pro Extended Validation ECC CA G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNjA0BgNVBAMTLVNlY3VyZSBTaXRlIFBybyBFeHRlbmRlZCBWYWxpZGF0aW9uIEVDQyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "577ac346ffebe99d6da32d317208c24b599e73364354c65a1e968d1a0e249679", + "size": 1435, + "filename": "XyodbDLzN0yp63I67N9JKajdWDFEOhrdzgg3hIfeExM=.pem", + "location": "security-state-staging/intermediates/92de2a7d-3a4e-4193-9a4b-62f0f3fbb871.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XyodbDLzN0yp63I67N9JKajdWDFEOhrdzgg3hIfeExM=", + "crlite_enrolled": false, + "id": "50e57f50-8348-4ada-9ad8-b73758d5b387", + "last_modified": 1664326625237 + }, + { + "schema": 1664326228551, + "derHash": "qK0cJcxYCyEx/Mb8bWUTdG846pm9FiyBtcU5NJUXWxg=", + "subject": "CN=STRATO TLS RSA CA,OU=Domain Validated SSL,O=STRATO AG,C=DE", + "subjectDN": "MFwxCzAJBgNVBAYTAkRFMRIwEAYDVQQKEwlTVFJBVE8gQUcxHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMRowGAYDVQQDExFTVFJBVE8gVExTIFJTQSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b99df27262454afaed628c736471aba39f9cf60fbc452c5077c426d184145b16", + "size": 1654, + "filename": "_MWDzTF0gP61A7lqJx1MUWKutcFHBAsAnAXlMwxjhKA=.pem", + "location": "security-state-staging/intermediates/2650b60d-7122-4e1a-ae65-fec4e96bb1be.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/MWDzTF0gP61A7lqJx1MUWKutcFHBAsAnAXlMwxjhKA=", + "crlite_enrolled": false, + "id": "d3436f68-7673-4573-887e-10435f7c9b60", + "last_modified": 1664326625222 + }, + { + "schema": 1664326220594, + "derHash": "ydaRPz/t3v8YTJ7h1+F8WuyQiG7tXMPW6YEFgxyMDgs=", + "subject": "CN=TERENA SSL CA 3 G3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MGcxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEbMBkGA1UEAxMSVEVSRU5BIFNTTCBDQSAzIEcz", + "whitelist": false, + "attachment": { + "hash": "c34e8e1124aac0817764c399d00072c6d60abb9d90bfcb222a2db90df6f1633f", + "size": 1557, + "filename": "CZ7AmTfjAN5e7IHESKlD7oZew2o25fDM7eqsgsH2OSk=.pem", + "location": "security-state-staging/intermediates/18fe6fa8-0078-4269-8b40-c399a6523419.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CZ7AmTfjAN5e7IHESKlD7oZew2o25fDM7eqsgsH2OSk=", + "crlite_enrolled": false, + "id": "6f73b567-3c48-499d-a69e-5da61f1aafb8", + "last_modified": 1664326625160 + }, + { + "schema": 1664326217988, + "derHash": "iR7i4jKC5QdsmukEfejqkA4Gb4HW3Nm4Q8WQeLDxBbw=", + "subject": "CN=WoSign EV SSL Pro CA,O=WoSign CA Limited,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEdMBsGA1UEAxMUV29TaWduIEVWIFNTTCBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "2d79fd64b625d2f52cb5592212200817f868f152816de091bc2240f2994a6fdb", + "size": 1634, + "filename": "pH1dud9XORPnyq8wyLWWIS773gVEO_1UuyQKQdv4xKg=.pem", + "location": "security-state-staging/intermediates/102cb544-d2a1-4bd3-8ce4-a805eeee5aa3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pH1dud9XORPnyq8wyLWWIS773gVEO/1UuyQKQdv4xKg=", + "crlite_enrolled": false, + "id": "08aff822-4688-4856-98f3-f38310a21244", + "last_modified": 1664326625105 + }, + { + "schema": 1664326215354, + "derHash": "9/veuCvpnUHNykGfyRhZ0+UobAdiBODpA9Z4uyE+2Js=", + "subject": "CN=Hostpoint TLS RSA CA,OU=Domain Validated SSL,O=Hostpoint AG,C=CH", + "subjectDN": "MGIxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxIb3N0cG9pbnQgQUcxHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR0wGwYDVQQDExRIb3N0cG9pbnQgVExTIFJTQSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "13a44adeec67b57fe7198a7a70254d249d13d8968de9bd06caa41653638867fd", + "size": 1662, + "filename": "Q5l5uNBWZnOf3EnCFMK3ciNhPZboa_YPtUtL6yZ_DvU=.pem", + "location": "security-state-staging/intermediates/badd3199-2552-4fa5-9559-73d968f90197.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Q5l5uNBWZnOf3EnCFMK3ciNhPZboa/YPtUtL6yZ/DvU=", + "crlite_enrolled": false, + "id": "a82975c0-4bf0-4cfa-836b-909f5cd3e3ca", + "last_modified": 1664326625089 + }, + { + "schema": 1664326216237, + "derHash": "L7qP6RVBUEmqlChXy1MTf/09nlpHsntXgtq5tNp9Yk0=", + "subject": "CN=GeoTrust SupremeSSL EV 1 ECC CA,OU=www.supremessl.com,O=SupremeSSL,C=NL", + "subjectDN": "MGkxCzAJBgNVBAYTAk5MMRMwEQYDVQQKEwpTdXByZW1lU1NMMRswGQYDVQQLExJ3d3cuc3VwcmVtZXNzbC5jb20xKDAmBgNVBAMTH0dlb1RydXN0IFN1cHJlbWVTU0wgRVYgMSBFQ0MgQ0E=", + "whitelist": false, + "attachment": { + "hash": "becde922e69097b5a8130c98daf7d342285a21c614dc8f8f4ecccff35b337305", + "size": 1423, + "filename": "A97Lx6chFTH9FkG_Io5Uc5oemCnN0d8O2NjR9d-Pr0w=.pem", + "location": "security-state-staging/intermediates/944e3b52-2ab2-49df-8597-3493ca03777f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "A97Lx6chFTH9FkG/Io5Uc5oemCnN0d8O2NjR9d+Pr0w=", + "crlite_enrolled": false, + "id": "036957de-97b5-494b-baac-8f7f7dbf8861", + "last_modified": 1664326625081 + }, + { + "schema": 1664326210742, + "derHash": "dOq1c9p9sZUJe+DpDzNJNMekyJ6Ag3WdtDM6AO3SQ9k=", + "subject": "CN=DigiCert High Assurance TLS Hybrid ECC SHA256 2020 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE/MD0GA1UEAxM2RGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgVExTIEh5YnJpZCBFQ0MgU0hBMjU2IDIwMjAgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "999c9bf267d1157f83cee072ff3e458370831d98c64688771c80dc9c55c61174", + "size": 1483, + "filename": "vnCogm4QYze_Bc9r88xdA6NTQY74p4BAz2w5gxkLG2M=.pem", + "location": "security-state-staging/intermediates/8e947df6-4c32-4098-ade0-96dc5f789020.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vnCogm4QYze/Bc9r88xdA6NTQY74p4BAz2w5gxkLG2M=", + "crlite_enrolled": false, + "id": "5fe4444f-8130-4fe0-8be1-2e1465d50ab4", + "last_modified": 1664326625043 + }, + { + "schema": 1664326213595, + "derHash": "Q49HPr/IiE710+DVLSZM2+Vso4LZ6/xonXdIlAn1Wm4=", + "subject": "CN=Google CA1,O=Google\\, Inc.,L=Mountain View,ST=CA,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEVMBMGA1UEChMMR29vZ2xlLCBJbmMuMRMwEQYDVQQDEwpHb29nbGUgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "92179477e55d28f30f18aaf3fdc5144697ec2c7aebbe195309940fc4167c9d99", + "size": 1695, + "filename": "JqNL7R3G-I-oNR0Uoa1_QE7RwLsPo10K2QlsnoYQ8eg=.pem", + "location": "security-state-staging/intermediates/f1cb5a52-c3a4-4be8-a157-a235238513ca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JqNL7R3G+I+oNR0Uoa1/QE7RwLsPo10K2QlsnoYQ8eg=", + "crlite_enrolled": false, + "id": "9ef04497-4d5e-48bb-865f-c7627ba2ff7a", + "last_modified": 1664326625028 + }, + { + "schema": 1664326211603, + "derHash": "R1nW9O1t8OCPxMqAGYbivhBZTbrzQbTEWrr6N0iIfSU=", + "subject": "CN=DigiCert Secure Auth CA2,O=DigiCert Inc,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxITAfBgNVBAMTGERpZ2lDZXJ0IFNlY3VyZSBBdXRoIENBMg==", + "whitelist": false, + "attachment": { + "hash": "ab00a00ff3bbaee6458f8005af74030281b46c4c038c33d7039a01d5968b4fd1", + "size": 1772, + "filename": "ZZR--hRloC8riD7MvWAr-1lIuMoIu6Nr3T5xJPP6HWw=.pem", + "location": "security-state-staging/intermediates/7152e9d3-3a75-4a64-a243-62e63233c96e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZZR++hRloC8riD7MvWAr+1lIuMoIu6Nr3T5xJPP6HWw=", + "crlite_enrolled": false, + "id": "3f097f22-60fb-4a8c-b940-9602d83fab74", + "last_modified": 1664326625011 + }, + { + "schema": 1664326207982, + "derHash": "h38kznD0owR+TqcL7BvDG+m2Uzrf86OT/5v7PIEClEY=", + "subject": "CN=TrustAsia TLS ECC CA G9,OU=Domain Validated SSL,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MHUxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXVHJ1c3RBc2lhIFRMUyBFQ0MgQ0EgRzk=", + "whitelist": false, + "attachment": { + "hash": "213a8df28e7c3c0f36e3b517ad6b17c6286cc3b6ed28853c76b534b8a3a14474", + "size": 1191, + "filename": "z_Y0aMrBs7J0RBthUJnRbIqFivDK0vBrLe1Q3OF0GjQ=.pem", + "location": "security-state-staging/intermediates/30de5808-b0a9-4a94-9445-61d877cf87f7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z/Y0aMrBs7J0RBthUJnRbIqFivDK0vBrLe1Q3OF0GjQ=", + "crlite_enrolled": false, + "id": "f10d75dc-ab04-495d-90a1-c8a8e8d0af4f", + "last_modified": 1664326625003 + }, + { + "schema": 1664326205340, + "derHash": "qmHCkn3InbIlypoX1gA3PQWPaW2G0Q4r17Xo9EqX7tE=", + "subject": "CN=WoSign OV SSL Pro CA,O=WoSign CA Limited,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEdMBsGA1UEAxMUV29TaWduIE9WIFNTTCBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "8a650458c45daf0f355521940f4cb2d85e34c0cc71d7dae9339adbaeb2f3f9bd", + "size": 1666, + "filename": "YgNpzwCbB-5jnp9Vn5xVC090y79oIjE_2P8pF1_4DMI=.pem", + "location": "security-state-staging/intermediates/a9cbd897-053a-41e4-8393-6d04c9bc1206.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YgNpzwCbB+5jnp9Vn5xVC090y79oIjE/2P8pF1/4DMI=", + "crlite_enrolled": false, + "id": "6149ff16-482f-4deb-b5e3-c7e9461390ef", + "last_modified": 1664326624964 + }, + { + "schema": 1664326199136, + "derHash": "rRSmi+yUnoT2BjQZ1jRl0TfCrdPjqF4A6ePuguW0AY8=", + "subject": "CN=DigiCert Secure Site Japan CA,OU=www.digicert.co.jp,O=DigiCert Japan G.K.,C=JP", + "subjectDN": "MHAxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNEaWdpQ2VydCBKYXBhbiBHLksuMRswGQYDVQQLExJ3d3cuZGlnaWNlcnQuY28uanAxJjAkBgNVBAMTHURpZ2lDZXJ0IFNlY3VyZSBTaXRlIEphcGFuIENB", + "whitelist": false, + "attachment": { + "hash": "704aec98d2d7480b5a48932114c1651471e99f1b14b3d85195169801f91a351d", + "size": 1658, + "filename": "RuOIM5KDx0FCWTkQrZiunZ5syXM7en8mBJBWgs2ZzpE=.pem", + "location": "security-state-staging/intermediates/a6e61ee2-41d7-44e4-8e27-9adb2e4d4b97.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RuOIM5KDx0FCWTkQrZiunZ5syXM7en8mBJBWgs2ZzpE=", + "crlite_enrolled": false, + "id": "6654ecd6-ce34-4540-b598-3c9c74265a99", + "last_modified": 1664326624933 + }, + { + "schema": 1664326203592, + "derHash": "ljBWsNlB2dviesd4BT2F5DzHn0dq00z915nCfjgYQOs=", + "subject": "CN=NCC Group Secure Server CA G3,O=NCC Group,C=US", + "subjectDN": "MEkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlOQ0MgR3JvdXAxJjAkBgNVBAMTHU5DQyBHcm91cCBTZWN1cmUgU2VydmVyIENBIEcz", + "whitelist": false, + "attachment": { + "hash": "9e28901777feba1e901463fa788510b1aee8a5be43288dd998f5adb2c216567f", + "size": 1110, + "filename": "o054ddOknAfLTeuaAC1q42pNk8GmJ-eXYrMMmlOPyUc=.pem", + "location": "security-state-staging/intermediates/23241c83-542b-4cec-b106-4bd44c660312.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o054ddOknAfLTeuaAC1q42pNk8GmJ+eXYrMMmlOPyUc=", + "crlite_enrolled": false, + "id": "dcce2c9f-77dc-4d7f-8f66-e61882e0078b", + "last_modified": 1664326624925 + }, + { + "schema": 1664326198291, + "derHash": "Hgo6uZMVdxcoHUKr+AHrZN7tUA5BaMpwbWpx2BA8c6I=", + "subject": "CN=DigiCert Grid Trust CA,OU=www.digicert.com,O=DigiCert Grid,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1EaWdpQ2VydCBHcmlkMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMR8wHQYDVQQDExZEaWdpQ2VydCBHcmlkIFRydXN0IENB", + "whitelist": false, + "attachment": { + "hash": "da4e397039d98baa39ac2d9fe30c7be0e57f8d32e62bbe09d45426bf2df5ae2d", + "size": 2316, + "filename": "TBKoZNHPiEWnnKpVqLc_CHX3Z3iNpZdilsVXHJhcqkw=.pem", + "location": "security-state-staging/intermediates/c44d1d33-b524-4062-965b-fe6dd07b118c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TBKoZNHPiEWnnKpVqLc/CHX3Z3iNpZdilsVXHJhcqkw=", + "crlite_enrolled": false, + "id": "e5406e58-cea2-4e83-bf5b-5707b41137af", + "last_modified": 1664326624918 + }, + { + "schema": 1664326193057, + "derHash": "JT48lzLfiHTD1U2lIsFxEULJjCzqdmRjUVKomgPuk2Q=", + "subject": "CN=Optum Public Trust CA 1,O=Optum,L=Minneapolis,ST=Minnesota,C=US", + "subjectDN": "MGkxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlNaW5uZXNvdGExFDASBgNVBAcTC01pbm5lYXBvbGlzMQ4wDAYDVQQKEwVPcHR1bTEgMB4GA1UEAxMXT3B0dW0gUHVibGljIFRydXN0IENBIDE=", + "whitelist": false, + "attachment": { + "hash": "7d110bc6ad3efd8fd7bbcab1436ecaee6b8ceaf40a87936c938ff3a558bd2e59", + "size": 1796, + "filename": "vKi3BXeuhSDWdRuvjranv-RWto4k9Z05PXzhtTz0LnA=.pem", + "location": "security-state-staging/intermediates/d39a90f2-a2c1-4b96-bbc3-7aa70afff93b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vKi3BXeuhSDWdRuvjranv+RWto4k9Z05PXzhtTz0LnA=", + "crlite_enrolled": false, + "id": "8618bd62-b0b1-40f7-ad87-92c0da8618d5", + "last_modified": 1664326624887 + }, + { + "schema": 1664326191302, + "derHash": "+2BghIRAqkWU/oEJdB76tAxWcysmxnw21tyUW7L+E88=", + "subject": "CN=Plex Devices High Assurance CA3,O=Plex\\, Inc.,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpQbGV4LCBJbmMuMSgwJgYDVQQDEx9QbGV4IERldmljZXMgSGlnaCBBc3N1cmFuY2UgQ0Ez", + "whitelist": false, + "attachment": { + "hash": "cd28540e93a3e7eada94060afc6916529a05233243a206dfe1a370ff5b0563f8", + "size": 1817, + "filename": "7vFogPWQjRRrBULu9CTSZG05Zp5wI5psu-SVeew9nqo=.pem", + "location": "security-state-staging/intermediates/43a54629-f407-4965-9395-d247c9d23517.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7vFogPWQjRRrBULu9CTSZG05Zp5wI5psu+SVeew9nqo=", + "crlite_enrolled": false, + "id": "7d8027f5-c921-4f5d-bbbf-de30773d90a2", + "last_modified": 1664326624872 + }, + { + "schema": 1664326189590, + "derHash": "13N+Xy0//KQpkC6fOIz9bFlZzTWg/BA87i9+k9HGalI=", + "subject": "CN=DigiCert Assured ID TLS CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIzAhBgNVBAMTGkRpZ2lDZXJ0IEFzc3VyZWQgSUQgVExTIENB", + "whitelist": false, + "attachment": { + "hash": "0447be26ffed9fcf5fd85dadbd96a4ba02719d4c6314f079cc94fea2fe3ccd09", + "size": 1735, + "filename": "wcEQuH5mngysd8OrgXuw0roT-SkrGkVZxVSpoNafgws=.pem", + "location": "security-state-staging/intermediates/045b4d0f-c83e-49b0-b4d3-6f9f64529e61.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wcEQuH5mngysd8OrgXuw0roT+SkrGkVZxVSpoNafgws=", + "crlite_enrolled": false, + "id": "a7971f88-2e3c-48df-933b-1187e892478a", + "last_modified": 1664326624849 + }, + { + "schema": 1664326186972, + "derHash": "Hk/DTSbgeT1VnjIv4RHx4ZqdLjS8JdPEJ7AypI0sW2o=", + "subject": "CN=Encryption Everywhere ECC DV TLS CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MG0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLDAqBgNVBAMTI0VuY3J5cHRpb24gRXZlcnl3aGVyZSBFQ0MgRFYgVExTIENB", + "whitelist": false, + "attachment": { + "hash": "bd127e9ea89932ba68684ffc1be7ef8c10924c73c3c8c3fb210e768858e7a708", + "size": 1402, + "filename": "Y08hSYf9OdhKAGME14XuWUxmQaPENh7XTciOu54_cJM=.pem", + "location": "security-state-staging/intermediates/c33abfe2-4130-438c-9bb4-f48a133e03ce.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y08hSYf9OdhKAGME14XuWUxmQaPENh7XTciOu54/cJM=", + "crlite_enrolled": false, + "id": "19d1eba6-116e-400e-a75a-87b35951def6", + "last_modified": 1664326624834 + }, + { + "schema": 1664326183286, + "derHash": "vJE3miHnveEbRVvx1RGGMxaCgFpKNTqO4lj3oDcGxmQ=", + "subject": "CN=DigiCert Secure Site Pro EV ECC CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFcxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMTAvBgNVBAMTKERpZ2lDZXJ0IFNlY3VyZSBTaXRlIFBybyBFViBFQ0MgQ04gQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "3c4b989bded7b9912a9b1b0f826da0d683072540c585b2bee8fdf873f6c364ba", + "size": 1374, + "filename": "ABohyUv2yKwrj07ViEB9SgfSMpe6-G4RvaB7869gYaQ=.pem", + "location": "security-state-staging/intermediates/21f99515-dc4d-47ea-9ccb-0492a2058411.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ABohyUv2yKwrj07ViEB9SgfSMpe6+G4RvaB7869gYaQ=", + "crlite_enrolled": false, + "id": "79639afa-3ee1-4fe4-8531-6191289ee51e", + "last_modified": 1664326624795 + }, + { + "schema": 1664326181523, + "derHash": "kuN3Cx60T4TC8ssAl8L9cSa9IStBwmEOeN39iUZ2Fzg=", + "subject": "CN=Cybertrust Japan ECC EV CA,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MFcxCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEjMCEGA1UEAxMaQ3liZXJ0cnVzdCBKYXBhbiBFQ0MgRVYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "1daf5084cd06736b07b49baee68114937ff6311a4104382645aa9cb5e07573f7", + "size": 1171, + "filename": "DR7QtcOJm6feIAao37U2Se9iaEVNLbTu7mKFz-uWIQs=.pem", + "location": "security-state-staging/intermediates/d895d3d2-5442-4881-a48c-ae38a7c0d9be.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DR7QtcOJm6feIAao37U2Se9iaEVNLbTu7mKFz+uWIQs=", + "crlite_enrolled": false, + "id": "8d3d797d-93c7-4e4c-95e2-fcdb27a14ed2", + "last_modified": 1664326624788 + }, + { + "schema": 1664326178045, + "derHash": "/alHIIv6MgOmxXuHFKZHtwCeUWjoiVE0VFCx0tP5Gn0=", + "subject": "CN=TERENA eScience Personal CA 3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MHIxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEmMCQGA1UEAxMdVEVSRU5BIGVTY2llbmNlIFBlcnNvbmFsIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "a948cf8f30ccb800e35bb069018109f31bedbe3743067ced32cb2e79edefc532", + "size": 1804, + "filename": "D4s14JIoSBMzTNeUUcWADRb_-SykkRSe9uuLuoU5ots=.pem", + "location": "security-state-staging/intermediates/fef5346e-9b0a-48da-a507-622c76da1104.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D4s14JIoSBMzTNeUUcWADRb/+SykkRSe9uuLuoU5ots=", + "crlite_enrolled": false, + "id": "0478cf5c-b32a-4100-a960-ec6421916beb", + "last_modified": 1664326624749 + }, + { + "schema": 1664326176323, + "derHash": "raGI+DDDE/YEZIjsNB8e1K95PG3CjFhgBEXfvrQWN0Y=", + "subject": "CN=Thawte CN RSA EV CA G1,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xHzAdBgNVBAMTFlRoYXd0ZSBDTiBSU0EgRVYgQ0EgRzE=", + "whitelist": false, + "attachment": { + "hash": "e02463fc55522ad7b1e55d94442c9b9e0d7fe94f931562f95c40b173b423e4e8", + "size": 1678, + "filename": "Hn_AvJkahgLnROxUs8e4tPKOI2wS-n9H5P_jk0Cf2T4=.pem", + "location": "security-state-staging/intermediates/1bbfa46c-dfe1-4ed3-8f9b-eaa1fe2e12d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Hn/AvJkahgLnROxUs8e4tPKOI2wS+n9H5P/jk0Cf2T4=", + "crlite_enrolled": false, + "id": "f6ff9269-2497-4464-9adb-1cfa1ca71e7e", + "last_modified": 1664326624726 + }, + { + "schema": 1664326171899, + "derHash": "M9VzWYMfh3VObnVda1tW5+cSl93f6h1jlwhmBCgPb/w=", + "subject": "CN=Cybertrust Japan Secure Server CA,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MF4xCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEqMCgGA1UEAxMhQ3liZXJ0cnVzdCBKYXBhbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "d41fc55ada95fd61e6fe844c3f6490f2db299bd6a149d9861dc209e9938f43e2", + "size": 1711, + "filename": "bVyC1r63wwzb47rnbZ46v-604DOoFUXTp4JA0srlZRE=.pem", + "location": "security-state-staging/intermediates/35070f8c-d9da-4f65-9382-bf8ea4bb7376.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bVyC1r63wwzb47rnbZ46v+604DOoFUXTp4JA0srlZRE=", + "crlite_enrolled": false, + "id": "82d8e2c5-9576-44f8-aae2-cfcc4c7e1734", + "last_modified": 1664326624718 + }, + { + "schema": 1664326170107, + "derHash": "vxyw4hPY08cLrolCn8Ft4sdPdVlj0bm0iL0CYNvJG5w=", + "subject": "CN=DigiCert Baltimore CA-1 G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIzAhBgNVBAMTGkRpZ2lDZXJ0IEJhbHRpbW9yZSBDQS0xIEcy", + "whitelist": false, + "attachment": { + "hash": "6e8a14353fd1d1bb9476fd46f1aca8687f2288716f64828ce95fe48b2a7f4b30", + "size": 1581, + "filename": "EppRiUi9P-L8-u3LNh2t2P4La8TbSH4Xh_DCYKPwqts=.pem", + "location": "security-state-staging/intermediates/89f5a0eb-9314-41fc-8fae-ea97d024063a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EppRiUi9P+L8+u3LNh2t2P4La8TbSH4Xh/DCYKPwqts=", + "crlite_enrolled": false, + "id": "f91c2704-ae75-473f-9d97-fccf15e253e0", + "last_modified": 1664326624710 + }, + { + "schema": 1664326175457, + "derHash": "MZiW45VNUQ2jpLdTh+jIcLO8LDIo2FUJFuvpq9y3+SE=", + "subject": "CN=Oracle TLS RSA CA G1,OU=Organization Validated SSL,O=Oracle Corporation,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPcmFjbGUgQ29ycG9yYXRpb24xIzAhBgNVBAsTGk9yZ2FuaXphdGlvbiBWYWxpZGF0ZWQgU1NMMR0wGwYDVQQDExRPcmFjbGUgVExTIFJTQSBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "f5677aab4a4a5595d2fb7ec90b64027c9dae3fbc2d40c94a69a50ab45b582d86", + "size": 1678, + "filename": "92UUNzSFrSZZ2mG20rm7eFX2MV_e3zCq8ck0jTA9zMw=.pem", + "location": "security-state-staging/intermediates/ccdaebea-c9e8-4b7d-b412-1f34dfc85162.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "92UUNzSFrSZZ2mG20rm7eFX2MV/e3zCq8ck0jTA9zMw=", + "crlite_enrolled": false, + "id": "73b570ff-00cd-459f-8fc9-6c1d2398c857", + "last_modified": 1664326624702 + }, + { + "schema": 1664326174566, + "derHash": "w4BN5R6MFwUiIK4crT04PlTVt9wohDxC8NvZkTwehlg=", + "subject": "CN=Secure Site Pro Extended Validation ECC CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MHQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMzAxBgNVBAMTKlNlY3VyZSBTaXRlIFBybyBFeHRlbmRlZCBWYWxpZGF0aW9uIEVDQyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "9a2067a61b58bd7732cfbbfb1b50c9fd59c6f466ab5e85edbf86065a9b4c21bc", + "size": 1463, + "filename": "_Nqzn2CbpC436KXdkcnLpL18IA4yR1xWHjRW06xB_j8=.pem", + "location": "security-state-staging/intermediates/9df340b9-5581-4319-93d7-03e7df97b435.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/Nqzn2CbpC436KXdkcnLpL18IA4yR1xWHjRW06xB/j8=", + "crlite_enrolled": false, + "id": "c5bed312-b190-402f-8aa2-e8f337ebb2b8", + "last_modified": 1664326624695 + }, + { + "schema": 1664326171019, + "derHash": "1Gkx4Bgt1lXqDBbm3Zn45hr/5AH3NMbKjqAFapaOr4E=", + "subject": "CN=DigiCert Baltimore EV CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEJhbHRpbW9yZSBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "97e070c494e675a685508925e7c9f7bd9330064b1658256296b84022edf16b7a", + "size": 1577, + "filename": "VOipUY1UDQFkU3SLv8yqRqnSAXgf2u64AGItxQco2LY=.pem", + "location": "security-state-staging/intermediates/632f91d1-09ef-498e-b828-94c76c27331d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VOipUY1UDQFkU3SLv8yqRqnSAXgf2u64AGItxQco2LY=", + "crlite_enrolled": false, + "id": "4cde6e6f-b699-4797-81ed-c43195526adc", + "last_modified": 1664326624678 + }, + { + "schema": 1664326168323, + "derHash": "pLK0dUVVn1//t83aehIg5i+3TRt6mk1NrLKyqDnOdFY=", + "subject": "CN=DigiCert Secure Site EV ECC CN CA G3,O=DigiCert Inc,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxLTArBgNVBAMTJERpZ2lDZXJ0IFNlY3VyZSBTaXRlIEVWIEVDQyBDTiBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "a7fa291c8bd1e5f516449f8bf1e2e1e6ed6a6787c5bb8d4c1f598dbdca33c9b0", + "size": 1370, + "filename": "k53DKcmdvTrcweDtMvf_MKTy3iBxavrlU-riK2LCoiQ=.pem", + "location": "security-state-staging/intermediates/21e6a7af-a62b-4d7d-bc91-ac39edf72464.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k53DKcmdvTrcweDtMvf/MKTy3iBxavrlU+riK2LCoiQ=", + "crlite_enrolled": false, + "id": "65a20db5-6aec-4128-becd-1f0785604e3f", + "last_modified": 1664326624670 + }, + { + "schema": 1664326177186, + "derHash": "Hd/d+IPjlFsssk+luDeIN5xasFhCKrl532bHdHOYhoc=", + "subject": "CN=Aetna Inc. Secure EV CA2,O=Aetna Inc,C=US", + "subjectDN": "MEQxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlBZXRuYSBJbmMxITAfBgNVBAMTGEFldG5hIEluYy4gU2VjdXJlIEVWIENBMg==", + "whitelist": false, + "attachment": { + "hash": "859a201e0fac44519f77ae49c0f51dee2114dc1024e819f2e0334a11277777b6", + "size": 1626, + "filename": "u6NyIqjq8NgZ5VkAyKFRk6mJ-QeTmwgG2SNmXrkK7vE=.pem", + "location": "security-state-staging/intermediates/1e7b3b95-827d-4d57-82a5-e199120f0c80.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "u6NyIqjq8NgZ5VkAyKFRk6mJ+QeTmwgG2SNmXrkK7vE=", + "crlite_enrolled": false, + "id": "619763a5-936b-43f0-a9b5-02ee67969939", + "last_modified": 1664326624662 + }, + { + "schema": 1664326173666, + "derHash": "VIN+97WsSqI2BqFe8w3kbpu34j5g9u1PJhIJK5Ttxo8=", + "subject": "CN=Data Management Intermediate Certificate Authority,OU=Fresenius Kabi USA,O=Fresenius Kabi AG,C=US", + "subjectDN": "MIGDMQswCQYDVQQGEwJVUzEaMBgGA1UEChMRRnJlc2VuaXVzIEthYmkgQUcxGzAZBgNVBAsTEkZyZXNlbml1cyBLYWJpIFVTQTE7MDkGA1UEAxMyRGF0YSBNYW5hZ2VtZW50IEludGVybWVkaWF0ZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "5f33b8e38e004c1bcf92ca76d838dc3425c380684d74f76383bf32507d3b373b", + "size": 1748, + "filename": "6tOYk51VDHXklgQiMTHvUHAqQsHRj8do_RweZi812do=.pem", + "location": "security-state-staging/intermediates/1e03ffa2-edb9-4ff0-8da4-3b5f2e8d2adb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6tOYk51VDHXklgQiMTHvUHAqQsHRj8do/RweZi812do=", + "crlite_enrolled": false, + "id": "74784f65-5c79-4f63-a6ad-550cca4f2f09", + "last_modified": 1664326624655 + }, + { + "schema": 1664326165731, + "derHash": "jNco+cM5G6Q2ChDGbKSEyAdlHWIH8QYzZp7YiB/pG/U=", + "subject": "CN=CrowdStrike Global EV CA,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEhMB8GA1UEAxMYQ3Jvd2RTdHJpa2UgR2xvYmFsIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "9b5c4a4e56ffd2b307e3d885b93974df259d6d3078a6cb8b62255938c5699acc", + "size": 1853, + "filename": "jbtEGKuN8b5BZ9dJqvvW9XPxqqghhmKxB6nuThNyJd8=.pem", + "location": "security-state-staging/intermediates/71284bdb-5ea9-45fd-b39d-5d2276fd9a69.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jbtEGKuN8b5BZ9dJqvvW9XPxqqghhmKxB6nuThNyJd8=", + "crlite_enrolled": false, + "id": "9f2181fb-5b32-47d6-bf49-0be6c67443bc", + "last_modified": 1664326624639 + }, + { + "schema": 1664326162226, + "derHash": "sayM+xgbnJNU4Xdfy9/P54mMXMmhfXYxW1fBEu7lUjQ=", + "subject": "CN=Encryption Everywhere DV TLS CA - G2,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MG4xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBHMg==", + "whitelist": false, + "attachment": { + "hash": "49370e3f9e272d741b015bb0f388957f4be2003f0e4924f2f5cebddcd1b42d0c", + "size": 1678, + "filename": "gxeKFFaZ2HFJIsTdTjEl6nVo3ckTCX-qzRMqb9Xoa1w=.pem", + "location": "security-state-staging/intermediates/34d38b55-811e-4b86-a485-32393b601f16.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gxeKFFaZ2HFJIsTdTjEl6nVo3ckTCX+qzRMqb9Xoa1w=", + "crlite_enrolled": false, + "id": "b2ad50e8-e510-4188-a4f8-c309b7af13bd", + "last_modified": 1664326624624 + }, + { + "schema": 1664326160413, + "derHash": "dayOQdmnzHWNOZj+Aw9jjP0ohVgj2k6bVpVM+94FTrY=", + "subject": "CN=DigiCert Trust Service CA,O=DigiCert Inc,C=US", + "subjectDN": "MEgxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFRydXN0IFNlcnZpY2UgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2cdb77056933d8338bc8866b5ecff0e170017a177389a08f90e006944f493763", + "size": 1849, + "filename": "gEh9s2cJ3MLV50ZxAV4m1LscTzkQpFmUa1bCYZBMYEM=.pem", + "location": "security-state-staging/intermediates/c3d8c6c2-c88d-4044-bbab-49790345e058.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gEh9s2cJ3MLV50ZxAV4m1LscTzkQpFmUa1bCYZBMYEM=", + "crlite_enrolled": false, + "id": "fe1d12b7-de18-4a6e-97ce-f5e1549c8d94", + "last_modified": 1664326624609 + }, + { + "schema": 1664326159535, + "derHash": "efH1q2l96/GV9bfaZflTmWgu2uuAEVudQqauXi+piAI=", + "subject": "CN=TrustAsia TLS RSA CA,OU=Domain Validated SSL,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MHIxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEdMBsGA1UEAxMUVHJ1c3RBc2lhIFRMUyBSU0EgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2183fa940cb572e989ab42bf28fb1650ee490ea6b489a8a33ee79f8da8469c5f", + "size": 1683, + "filename": "jzqM6_58ozsPRvxUzg0hzjM-GcfwhTbU_G0TCDvL7hU=.pem", + "location": "security-state-staging/intermediates/d080816c-1351-444e-aade-24ed8f586175.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jzqM6/58ozsPRvxUzg0hzjM+GcfwhTbU/G0TCDvL7hU=", + "crlite_enrolled": false, + "id": "81c5622c-3704-493d-99fb-ed0d34e22d8b", + "last_modified": 1664326624594 + }, + { + "schema": 1664326163121, + "derHash": "LarG/frBbFSMU/8RmCVNfpN3YdIqHnzFwalGLpcUYfU=", + "subject": "CN=TrustAsia ECC EV TLS Pro CA,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDExtUcnVzdEFzaWEgRUNDIEVWIFRMUyBQcm8gQ0E=", + "whitelist": false, + "attachment": { + "hash": "a6577abedadd81611cdd084175ee766bb0beb264bf534578885e388b60a5d03c", + "size": 1427, + "filename": "FM2DBzyPjxqgoESMdjtIkhgTqwpjNM94Fncf5hoKBAg=.pem", + "location": "security-state-staging/intermediates/3a6083c3-d893-43ac-bd9f-fe6697293aa1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FM2DBzyPjxqgoESMdjtIkhgTqwpjNM94Fncf5hoKBAg=", + "crlite_enrolled": false, + "id": "965bbebb-8d9d-4cf8-9ae2-c42ab5322276", + "last_modified": 1664326624578 + }, + { + "schema": 1664326158618, + "derHash": "TFbKejwQ61h2Xg/8+ANcV8nzvbAUhi9nZ1bPeJGT8Q4=", + "subject": "CN=Sonavation IoT CA,OU=sonavation.com,O=Sonavation\\, Inc.,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBTb25hdmF0aW9uLCBJbmMuMRcwFQYDVQQLEw5zb25hdmF0aW9uLmNvbTEaMBgGA1UEAxMRU29uYXZhdGlvbiBJb1QgQ0E=", + "whitelist": false, + "attachment": { + "hash": "9435c16f54620272aec7d7abc3dd2a3ba91eb56527a23274814bcb29a44b2632", + "size": 1695, + "filename": "vVP8FQS5b7BRuCncy0juht2sjHUUGqg1uuyGap7EMKI=.pem", + "location": "security-state-staging/intermediates/d3c84332-b918-41de-8190-89370390db4d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vVP8FQS5b7BRuCncy0juht2sjHUUGqg1uuyGap7EMKI=", + "crlite_enrolled": false, + "id": "8d518eb7-5f35-46c4-b927-8b7aac8c0ff9", + "last_modified": 1664326624555 + }, + { + "schema": 1664326155179, + "derHash": "w2g/fZF1Qhna2k6Nww5LGL05KLU9Ork9BzhLxYcc41U=", + "subject": "CN=Cybertrust Japan Secure Server ECC CA,O=Cybertrust Japan Co.\\, Ltd.,C=JP", + "subjectDN": "MGIxCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEuMCwGA1UEAxMlQ3liZXJ0cnVzdCBKYXBhbiBTZWN1cmUgU2VydmVyIEVDQyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "337fa733767ef0fd0ae9adf061a38f5935257e34b75ef029ff02def23a16f3b3", + "size": 1187, + "filename": "QNrgssEKlwJV3LCOAwSPpkc3S6pOvGY2WT_YKfPuvVU=.pem", + "location": "security-state-staging/intermediates/cb286769-4d82-4701-9ab8-f98e717410d3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QNrgssEKlwJV3LCOAwSPpkc3S6pOvGY2WT/YKfPuvVU=", + "crlite_enrolled": false, + "id": "03b85d50-3c41-4be8-9a58-73e7c7b953a6", + "last_modified": 1664326624540 + }, + { + "schema": 1664326154269, + "derHash": "4b5ru7cPWiQec2/ETGohYL9s4ZuV7dZ7976JboN3h0U=", + "subject": "CN=TERENA eScience SSL CA 3,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MG0xCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEhMB8GA1UEAxMYVEVSRU5BIGVTY2llbmNlIFNTTCBDQSAz", + "whitelist": false, + "attachment": { + "hash": "a08076a84bc8fe3155ddd8623e80fc94436c26827325f7ef4693220388fe0c0f", + "size": 1800, + "filename": "mNPoBotkTew6fh29COIk7FfA9rKbI3N7z9F5Tl9pcMQ=.pem", + "location": "security-state-staging/intermediates/e3b599ad-6468-4e78-8fb0-d42803115117.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mNPoBotkTew6fh29COIk7FfA9rKbI3N7z9F5Tl9pcMQ=", + "crlite_enrolled": false, + "id": "a1dac4ca-f74a-4c2a-82ab-dbee042b2786", + "last_modified": 1664326624531 + }, + { + "schema": 1664304522729, + "derHash": "51J+MNRzrsOxYq/cRwlUSdLdVJTOhi4v5PQ2wIEmL2Q=", + "subject": "CN=OCLC TLS Issuing RSA SubCA R1,O=OCLC\\, Inc.,C=US", + "subjectDN": "MEoxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApPQ0xDLCBJbmMuMSYwJAYDVQQDDB1PQ0xDIFRMUyBJc3N1aW5nIFJTQSBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "74519fb4995ca1b2049180b68d90a199ba04b9dabb7186f66757c28da53399c0", + "size": 2259, + "filename": "tTLcUlxB7HbXqllccmThZ_W_11UBXG64FVmg6DZPdxk=.pem", + "location": "security-state-staging/intermediates/d9f43f3d-7853-4c74-809e-967155bfb50e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tTLcUlxB7HbXqllccmThZ/W/11UBXG64FVmg6DZPdxk=", + "crlite_enrolled": false, + "id": "d935f49f-3ba2-4703-828e-55e1c411f582", + "last_modified": 1664305023209 + }, + { + "schema": 1663959423669, + "derHash": "tl/mob8tUv9ot9Pp0PBrMAocPySDCbcEBcp73sBUvd0=", + "subject": "CN=GlobalSign Atlas R3 DV TLS CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFUxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSswKQYDVQQDEyJHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAyMDIw", + "whitelist": false, + "attachment": { + "hash": "f6ba4b244d0f4d8748bdeb3905b09e20d0bb70b28d5fe8f7a5d9f622031a27cd", + "size": 1687, + "filename": "xW7Bujpo5E-lsgAwIN5FP5ndgl7TMJJdKhiprnoOP6k=.pem", + "location": "security-state-staging/intermediates/aa3b1898-e758-4640-86d7-e62b0f9a21b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xW7Bujpo5E+lsgAwIN5FP5ndgl7TMJJdKhiprnoOP6k=", + "crlite_enrolled": false, + "id": "91782dd2-8264-4d7f-ac0c-485fc46cf10c", + "last_modified": 1664110623090 + }, + { + "schema": 1663958993415, + "derHash": "2S6TJS6rypUIcLlDMZkJY6LdXbltgzyCsI5Br9FxkXg=", + "subject": "CN=DigiCert TLS ECC P384 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEmMCQGA1UEAxMdRGlnaUNlcnQgVExTIEVDQyBQMzg0IFJvb3QgRzU=", + "whitelist": false, + "attachment": { + "hash": "ed10796477978449a52e68fab1f652a531a5cf20b18994448a25913f6b184ff9", + "size": 1142, + "filename": "oC-voZLIy4HLE0FVT5wFtxzKKokLDRKY1oNkfJYe-98=.pem", + "location": "security-state-staging/intermediates/c9e97989-3b6c-46f1-a53a-15ed5801bbc1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oC+voZLIy4HLE0FVT5wFtxzKKokLDRKY1oNkfJYe+98=", + "crlite_enrolled": false, + "id": "0cdd0fbd-bbce-40ea-8ae0-d89dcbe1ba12", + "last_modified": 1663959423212 + }, + { + "schema": 1663958992520, + "derHash": "pLzaMtSc3wXwzdCF5zw6LmeIC9SFef7U31lA33agdtc=", + "subject": "CN=DigiCert TLS RSA4096 Root G5,O=DigiCert\\, Inc.,C=US", + "subjectDN": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "whitelist": false, + "attachment": { + "hash": "85ea63b3039a3c48381df5362693185cb6db4a0e270bf1895a7ca8e764008607", + "size": 1943, + "filename": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=.pem", + "location": "security-state-staging/intermediates/8b2e52bc-c88a-4492-bf43-71e770c9410f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ape1HIIZ6T5d7GS61YBs3rD4NVvkfnVwELcCRW4Bqv0=", + "crlite_enrolled": false, + "id": "49a4c45d-de83-4855-8f2a-738215d6d2ba", + "last_modified": 1663959423204 + }, + { + "schema": 1663786393273, + "derHash": "6kI/GzsbUp0cfbmiGvh9x43nJZVV4pi6JsY88SdakSw=", + "subject": "CN=Baidu\\, Inc. DV CA,O=Baidu\\, Inc.,C=CN", + "subjectDN": "MD8xCzAJBgNVBAYTAkNOMRQwEgYDVQQKEwtCYWlkdSwgSW5jLjEaMBgGA1UEAxMRQmFpZHUsIEluYy4gRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "08909724ed23bb59c79ae1c3bbaef3aaa2479b57cc7ad9d8c2a3365f265b1dbb", + "size": 2056, + "filename": "XDCvV-xbAMAx9ilt0xDgWpOhUuTMeTe4IRR7qYMYfJA=.pem", + "location": "security-state-staging/intermediates/32548071-4c14-4e6e-b0b1-1d150dbe3cc5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XDCvV+xbAMAx9ilt0xDgWpOhUuTMeTe4IRR7qYMYfJA=", + "crlite_enrolled": false, + "id": "da177207-371a-40f7-9dde-cf2d5ba90e5e", + "last_modified": 1663786626567 + }, + { + "schema": 1663786388875, + "derHash": "KaTsi8oy3ZHxEg3hT1u0OTVtSO2AEg3xo6hmF+pIS8g=", + "subject": "CN=KeyNet Systems ECC DV CA,O=KeyNet Systems LLC,L=Las Vegas,ST=Nevada,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEbMBkGA1UEChMSS2V5TmV0IFN5c3RlbXMgTExDMSEwHwYDVQQDExhLZXlOZXQgU3lzdGVtcyBFQ0MgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "8a42fb51eac969e531af5298d9799c4a092fc6da5d13bda2d943ba1a028cab2e", + "size": 1297, + "filename": "BUAXj7DRPY9FwGYzytth18HC8DBDa4DvU_tIIX5dMkI=.pem", + "location": "security-state-staging/intermediates/66e54031-e71f-4b05-addb-e0c71a9466de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BUAXj7DRPY9FwGYzytth18HC8DBDa4DvU/tIIX5dMkI=", + "crlite_enrolled": false, + "id": "df8ab0be-9d64-4982-9a0f-ff93661d01ec", + "last_modified": 1663786626546 + }, + { + "schema": 1663786386277, + "derHash": "1YwU+8AQDhHv5yCN1pFpYCVzPheG8acwOwYz4H9mHDk=", + "subject": "CN=CERTDATA SSL OV ECC CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MHIxCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExNDAyBgNVBAMMK0NFUlREQVRBIFNTTCBPViBFQ0MgQ0EgIFtSdW4gYnkgdGhlIElzc3Vlcl0=", + "whitelist": false, + "attachment": { + "hash": "dbc2dd8483bf8da3713460965550c6c85284350adf301b5d6d6af96c27c381f2", + "size": 1297, + "filename": "3fq1AiHgm5wPjtliqEAe6Hqe_TUKwGTHlpm_w85y2o4=.pem", + "location": "security-state-staging/intermediates/04429fe5-6aab-403d-a6bd-41e3fe233fe9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3fq1AiHgm5wPjtliqEAe6Hqe/TUKwGTHlpm/w85y2o4=", + "crlite_enrolled": false, + "id": "72c3d346-21af-4299-ad01-736af9f66751", + "last_modified": 1663786626525 + }, + { + "schema": 1663786384605, + "derHash": "8bQTkri3zVpFay1Kkbe89szvIpS4tuYs/zj5w6J5bwY=", + "subject": "CN=GlobeSSL OV CA,O=CentralNic Luxembourg Sàrl,C=LU", + "subjectDN": "MEwxCzAJBgNVBAYTAkxVMSQwIgYDVQQKDBtDZW50cmFsTmljIEx1eGVtYm91cmcgU8OgcmwxFzAVBgNVBAMTDkdsb2JlU1NMIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "738b3ab18d18b5bb3a5c61e85dff01fcb2e9d7ba7bfc106be0cf2788ce6d162d", + "size": 2085, + "filename": "mfCBWjYYEugo1yhASbIoRwohL9WPkl5owslIdpTfCno=.pem", + "location": "security-state-staging/intermediates/58e032c9-3739-4306-b3be-d1a171c32836.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mfCBWjYYEugo1yhASbIoRwohL9WPkl5owslIdpTfCno=", + "crlite_enrolled": false, + "id": "3c4bb69b-7e2a-4e34-b89a-2fa8fb72f9cc", + "last_modified": 1663786626503 + }, + { + "schema": 1663786387127, + "derHash": "71ZjFC9bLWuCdFKbFX3y+WWnGHOlrlql4HICRPI5i28=", + "subject": "CN=CertCenter Enterprise RSA EV CA,OU=Controlled by COMODO CA exclusively for CertCenter AG,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MIGyMQswCQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRAwDgYDVQQHEwdHaWVzc2VuMRYwFAYDVQQKEw1DZXJ0Q2VudGVyIEFHMT4wPAYDVQQLEzVDb250cm9sbGVkIGJ5IENPTU9ETyBDQSBleGNsdXNpdmVseSBmb3IgQ2VydENlbnRlciBBRzEoMCYGA1UEAxMfQ2VydENlbnRlciBFbnRlcnByaXNlIFJTQSBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "737cb69cefa3d9ca3b829a4dbe9ae24a5c693c71c66cf9aeedaa6c08eff2ecf9", + "size": 2255, + "filename": "CaGpyyfpmNg5jcDd5cWMfcPMbbqa5_5gKWvNRcBjqoA=.pem", + "location": "security-state-staging/intermediates/29f7887f-da2a-41cc-bbe0-b585b5ec8399.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CaGpyyfpmNg5jcDd5cWMfcPMbbqa5/5gKWvNRcBjqoA=", + "crlite_enrolled": false, + "id": "8e3aed7f-a815-4e9a-81e9-fe4c073e05d3", + "last_modified": 1663786626496 + }, + { + "schema": 1663786383741, + "derHash": "kUfDjgO0bIzl3dsCyqKTLx4WdcCgAVKRODNu0Q2ZmDo=", + "subject": "CN=COMODO RSA Domain Validation Secure Server CA 3,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "76b55e319beb0a0b3a30c9556ebb53fcad7f327f6c14a4903a10bfc2c9cfe8b8", + "size": 2158, + "filename": "InQsVwxFxCARGa9oRQJcZVg2VPAXkUYSVkDc3jhuVUM=.pem", + "location": "security-state-staging/intermediates/ea431013-e9c9-46d0-b171-4bfaa692ba0b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "InQsVwxFxCARGa9oRQJcZVg2VPAXkUYSVkDc3jhuVUM=", + "crlite_enrolled": false, + "id": "1e04130f-d460-40fd-9186-6bd19a55934d", + "last_modified": 1663786626468 + }, + { + "schema": 1663786375695, + "derHash": "DjTMNfZt4MBuHZAeJYCWFULsLi+rF1eDfQnP42VHlmw=", + "subject": "CN=ISSAuth RSA EV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIFJTQSBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1c462f7e5e5f2ae7309231654dcb1fe3528f2543d6ee6d48aeb69e0e8700aa05", + "size": 2117, + "filename": "JuVSbm2PtOMQrBijLX2uh7DV87h-dofG6_8CWgsoUso=.pem", + "location": "security-state-staging/intermediates/1a267ba4-0090-44e3-ac3b-1374aeca442f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JuVSbm2PtOMQrBijLX2uh7DV87h+dofG6/8CWgsoUso=", + "crlite_enrolled": false, + "id": "261d2a7a-7e13-4ffd-bf3e-4dbe09c11bc8", + "last_modified": 1663786626447 + }, + { + "schema": 1663786372899, + "derHash": "a+fQTiD/wo8ZjKiPwMNkR4T9FWkDvweBMPuWTAJY26A=", + "subject": "CN=United Parcel Service\\, Inc. RSA OV CA,O=United Parcel Service\\, Inc.,L=Atlanta,ST=GA,C=US", + "subjectDN": "MIGCMQswCQYDVQQGEwJVUzELMAkGA1UECBMCR0ExEDAOBgNVBAcTB0F0bGFudGExJDAiBgNVBAoTG1VuaXRlZCBQYXJjZWwgU2VydmljZSwgSW5jLjEuMCwGA1UEAxMlVW5pdGVkIFBhcmNlbCBTZXJ2aWNlLCBJbmMuIFJTQSBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "5f2f917f15eb321eb42931857ac7ab983a47c6ee7bd3ae860d471c9af6b89cb5", + "size": 2142, + "filename": "SwXmbcFTOEV-82sxJhAl0R-uGVTqV9eWCnYAM12xQYs=.pem", + "location": "security-state-staging/intermediates/4ae82de2-f623-4295-9237-d34d5d6630b1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SwXmbcFTOEV+82sxJhAl0R+uGVTqV9eWCnYAM12xQYs=", + "crlite_enrolled": false, + "id": "b2a58c9b-ce5d-4635-94ac-0b3d767ad225", + "last_modified": 1663786626419 + }, + { + "schema": 1663786379170, + "derHash": "X6SbNsctA+QtqkMCUA1kbJSMh6w27TDMPb3bviTGRjM=", + "subject": "CN=WebNIC ECC Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for WebNIC,O=WebNIC (Web Commerce Communications (Singapore) Pte. Ltd.),L=Singapore,C=SG", + "subjectDN": "MIHVMQswCQYDVQQGEwJTRzESMBAGA1UEBxMJU2luZ2Fwb3JlMUMwQQYDVQQKEzpXZWJOSUMgKFdlYiBDb21tZXJjZSBDb21tdW5pY2F0aW9ucyAoU2luZ2Fwb3JlKSBQdGUuIEx0ZC4pMTUwMwYDVQQLEyxDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIFdlYk5JQzE2MDQGA1UEAxMtV2ViTklDIEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "16a07a5e056296529c314d3d556c9af76e27ff8a8f15cd42b04bdd0b74498ff3", + "size": 1443, + "filename": "wJUIwtfbQyb8AQ8i7WNkx5y7MTHHrQ3ILlCihfqdp-I=.pem", + "location": "security-state-staging/intermediates/581ad6f1-197b-4266-8998-31bfac7b024e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wJUIwtfbQyb8AQ8i7WNkx5y7MTHHrQ3ILlCihfqdp+I=", + "crlite_enrolled": false, + "id": "41782a4c-2f64-49ab-982f-d3b6b7081ee9", + "last_modified": 1663786626405 + }, + { + "schema": 1663786371097, + "derHash": "L/GDLeb5UGqsnSx3V+oHV2TsaMyccKDs4z7MYWB8vkM=", + "subject": "CN=TERENA SSL CA 2,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MGQxCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEYMBYGA1UEAxMPVEVSRU5BIFNTTCBDQSAy", + "whitelist": false, + "attachment": { + "hash": "baa36caebfdea55db8cf3498d94750076346aec56fd669ec04eafeaf63890516", + "size": 2129, + "filename": "PYHJ7Ok9y2OoV3yMZFAcH45HI64yll_qcT9kRYmQFTY=.pem", + "location": "security-state-staging/intermediates/be369640-ebcd-436b-9e2a-d8eb78f0a074.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PYHJ7Ok9y2OoV3yMZFAcH45HI64yll/qcT9kRYmQFTY=", + "crlite_enrolled": false, + "id": "eda33ed0-a35f-477e-b707-1579063b6a7b", + "last_modified": 1663786626392 + }, + { + "schema": 1663786374768, + "derHash": "JEobcwyh11pppS9eX0IbtUfHl+JK++nPuZyZNIjNwnE=", + "subject": "CN=SSLs.com RSA OV Secure Server CA,O=SSLs.com,L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRAwDgYDVQQHEwdQaG9lbml4MREwDwYDVQQKEwhTU0xzLmNvbTEpMCcGA1UEAxMgU1NMcy5jb20gUlNBIE9WIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "de15a4d6d9d7ca26a955f687c46b7fad2f1466fb1e844a776a74d2dffea0a398", + "size": 2129, + "filename": "Zyd1gFi_OihMqmE83KKHmgjVpD3p-B1OSbDcm-Mh1HI=.pem", + "location": "security-state-staging/intermediates/7fd94a32-ec78-4e58-b29a-dac8c5be1b24.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Zyd1gFi/OihMqmE83KKHmgjVpD3p+B1OSbDcm+Mh1HI=", + "crlite_enrolled": false, + "id": "b635f69c-ed64-4330-97a9-09c7ad828441", + "last_modified": 1663786626378 + }, + { + "schema": 1663786368428, + "derHash": "tfTF0YWoyHx4tvZ9/c/U4buwc+d6JkRV2pYd2b6R0c4=", + "subject": "CN=ZeroSSL ECC Business Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "ME0xCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSwwKgYDVQQDEyNaZXJvU1NMIEVDQyBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b30c5d75b143b3a40e4cb6b343c9be7563d389417de3bc95c6c7465803311a52", + "size": 1284, + "filename": "joN60utJv0YtzK4dit_eZatsv9Qz7o9doV9ZOgCp2Y8=.pem", + "location": "security-state-staging/intermediates/a1ed91a3-f011-4b67-80e2-2d9cda92eccc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "joN60utJv0YtzK4dit/eZatsv9Qz7o9doV9ZOgCp2Y8=", + "crlite_enrolled": false, + "id": "7bb4ec74-5906-4325-9593-b74756cfc1fd", + "last_modified": 1663786626371 + }, + { + "schema": 1663786367559, + "derHash": "fp3eEfdWaw+sYiigCQWoTmNg6VAacBs+f8gy6juf2ng=", + "subject": "CN=Don Dominio / MrDomain RSA EV CA,OU=Controlled by COMODO CA for Soluciones Corporativas IP\\, SL,O=Soluciones Corporativas IP\\, SL,L=Manacor,ST=Illes Balears,C=ES", + "subjectDN": "MIHQMQswCQYDVQQGEwJFUzEWMBQGA1UECBMNSWxsZXMgQmFsZWFyczEQMA4GA1UEBxMHTWFuYWNvcjEnMCUGA1UEChMeU29sdWNpb25lcyBDb3Jwb3JhdGl2YXMgSVAsIFNMMUMwQQYDVQQLEzpDb250cm9sbGVkIGJ5IENPTU9ETyBDQSBmb3IgU29sdWNpb25lcyBDb3Jwb3JhdGl2YXMgSVAsIFNMMSkwJwYDVQQDEyBEb24gRG9taW5pbyAvIE1yRG9tYWluIFJTQSBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1157cc07d37d1a32ff1cc8627a68b36b0dcde5fb4f06aabb12221282e524eef5", + "size": 2296, + "filename": "w0Shysnt57XAAwS7oiySGBbwyEZ1Xlrn5_wYfb_pOfc=.pem", + "location": "security-state-staging/intermediates/8505036d-a5d1-4d73-b372-2f916ee138e2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "w0Shysnt57XAAwS7oiySGBbwyEZ1Xlrn5/wYfb/pOfc=", + "crlite_enrolled": false, + "id": "670d17ce-0f54-41f0-a7d3-f96727e9ae13", + "last_modified": 1663786626364 + }, + { + "schema": 1663786361311, + "derHash": "0QEGJDi/sK8Fq6ed8A9N1J54uM79XtkpVlwjNhExAZE=", + "subject": "CN=Global Trust CA - OV (ECC),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBPViAoRUNDKQ==", + "whitelist": false, + "attachment": { + "hash": "f70ca0a06b7c82d32150f26ecdfe462e5b427d9a3c3f72f7d8456bdd11657b4f", + "size": 1252, + "filename": "oWD3L7oqjz3vUhAVyhQoyubwAtUDmmdDOlkahsKwP6M=.pem", + "location": "security-state-staging/intermediates/9c145580-394f-4fa2-a6f7-4b69b25217ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oWD3L7oqjz3vUhAVyhQoyubwAtUDmmdDOlkahsKwP6M=", + "crlite_enrolled": false, + "id": "3c597962-3dc2-4de5-82fe-2897023b80eb", + "last_modified": 1663786626309 + }, + { + "schema": 1663786359609, + "derHash": "JJvmXwx6sP47dkizDODrPPaRQCxy2stAzxRinGA8ujY=", + "subject": "CN=BlackCert\\, Inc. RSA DV Certification Authority,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRgwFgYDVQQKEw9CbGFja0NlcnQsIEluYy4xNzA1BgNVBAMTLkJsYWNrQ2VydCwgSW5jLiBSU0EgRFYgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "ffb41132fe60448dca99fed8dc36b06e864ec803804810ddb99ded957920e092", + "size": 2154, + "filename": "dnTrbfgvJOGlJse4RxYg30jhwWfQfKyNYkL6SpGs_1I=.pem", + "location": "security-state-staging/intermediates/c4c47a34-c4f5-4229-ae72-be617d7272f9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dnTrbfgvJOGlJse4RxYg30jhwWfQfKyNYkL6SpGs/1I=", + "crlite_enrolled": false, + "id": "b3de065f-383e-4062-984d-582c5ee787af", + "last_modified": 1663786626302 + }, + { + "schema": 1663786358729, + "derHash": "6+ABXvZBxICVTtKmiEL8zSqKeSE67tg23fpigujqj2o=", + "subject": "CN=BlackCert\\, Inc. RSA EV Certification Authority,OU=Controlled by COMODO exclusively for BlackCert\\, Inc.,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MIG9MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ08xDzANBgNVBAcTBkRlbnZlcjEYMBYGA1UEChMPQmxhY2tDZXJ0LCBJbmMuMT0wOwYDVQQLEzRDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgQmxhY2tDZXJ0LCBJbmMuMTcwNQYDVQQDEy5CbGFja0NlcnQsIEluYy4gUlNBIEVWIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "102f83bddea759d6a13c4385f9633aba85ef379cd52dde79f7d2c10f4d463dcb", + "size": 2272, + "filename": "nq1PHRuOp_qQ-vlMQDWIjBXQS9CPEsH5CLd-dlkam7U=.pem", + "location": "security-state-staging/intermediates/ead2cc1f-3b5a-4901-9bef-7454e11f15e1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nq1PHRuOp/qQ+vlMQDWIjBXQS9CPEsH5CLd+dlkam7U=", + "crlite_enrolled": false, + "id": "3b4cd15a-23cd-4507-bbc7-c4eed10c1309", + "last_modified": 1663786626295 + }, + { + "schema": 1663786357889, + "derHash": "qv3O5Xk7XPS/LQxfYELYpCK8wzs5Zaxig9JzzGm0g3U=", + "subject": "CN=Sectigo SHA-256 EV Secure Server CA,O=Sectigo Limited,C=GB", + "subjectDN": "MFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gU0hBLTI1NiBFViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "de41c4507fbe5c06587856e67211d8552d72595465bdf5493469076042f4ff4b", + "size": 1654, + "filename": "KlGkFBm4cI6bwYi3-GIwgmgYzH3wAO3Gtlqk0GMInns=.pem", + "location": "security-state-staging/intermediates/c32e55d9-6b40-4d00-a2c2-e572e3a1cfbf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KlGkFBm4cI6bwYi3+GIwgmgYzH3wAO3Gtlqk0GMInns=", + "crlite_enrolled": false, + "id": "72b7a497-0e85-46fe-b154-4f0298081a88", + "last_modified": 1663786626288 + }, + { + "schema": 1663786360454, + "derHash": "00GZhqSHZYJdE7MBVHsn3Fag7NMJGYr+mn1/qtZJaao=", + "subject": "CN=Verokey High Assurance Business,O=Verokey,C=AU", + "subjectDN": "MEkxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSgwJgYDVQQDEx9WZXJva2V5IEhpZ2ggQXNzdXJhbmNlIEJ1c2luZXNz", + "whitelist": false, + "attachment": { + "hash": "c17ed3f8451ba37004ae35c46d4b78114d7c83d9ebed89a21f9a3a9865bba26c", + "size": 2235, + "filename": "t5JC49VKZVju3OQDEz1v_ZMSizmc8S5_c7cqHzZHgyw=.pem", + "location": "security-state-staging/intermediates/072b1ac9-aea6-4d72-a3dc-c492cc9b613e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t5JC49VKZVju3OQDEz1v/ZMSizmc8S5/c7cqHzZHgyw=", + "crlite_enrolled": false, + "id": "924bf07e-9988-4022-8261-505e33eece30", + "last_modified": 1663786626281 + }, + { + "schema": 1663786357045, + "derHash": "O4u3vuWk6yHqjZJ58/RWyvO4nFbW3Uxpo/vX+eTtnNM=", + "subject": "CN=SSLs.com ECC EV Secure Server CA,OU=Controlled by Sectigo exclusively for SSLs.com,O=SSLs.com (Namecheap\\, Inc.),L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTEQMA4GA1UEBxMHUGhvZW5peDEjMCEGA1UEChMaU1NMcy5jb20gKE5hbWVjaGVhcCwgSW5jLikxNzA1BgNVBAsTLkNvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgU1NMcy5jb20xKTAnBgNVBAMTIFNTTHMuY29tIEVDQyBFViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "4f65e8f28bf260a126bbc6a18cd00a45b28024305f0b2f901010154e2e4c52a2", + "size": 1423, + "filename": "Ym-0KV9EAV6PC8waJvh5eI-QjQ_3STgXc-UH0obWlKM=.pem", + "location": "security-state-staging/intermediates/d38a0584-d71f-4b7c-9c16-a136b244e237.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ym+0KV9EAV6PC8waJvh5eI+QjQ/3STgXc+UH0obWlKM=", + "crlite_enrolled": false, + "id": "b2b0b450-3320-4595-b06e-c8074adc38b7", + "last_modified": 1663786626274 + }, + { + "schema": 1663786356150, + "derHash": "h1b6/vsKedj/Nhx/46TbP5rVIGVS5xpJXfmKM85laTw=", + "subject": "CN=CertAssure RSA DV Secure Server Certification Authority,O=CertAssure Inc.,L=San Jose,ST=CA,C=US", + "subjectDN": "MIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRgwFgYDVQQKEw9DZXJ0QXNzdXJlIEluYy4xQDA+BgNVBAMTN0NlcnRBc3N1cmUgUlNBIERWIFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "a9d68aeb2fe12cc37079f829b7e259aea865ce732fae13195cbe702ebfbbb6e9", + "size": 2166, + "filename": "XUK4_mOFBsai_iVZvPajZozACtYX_1Rgy6ZjFieNpck=.pem", + "location": "security-state-staging/intermediates/5fd49685-d2ea-477c-8d14-b88d4c913d46.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XUK4/mOFBsai/iVZvPajZozACtYX/1Rgy6ZjFieNpck=", + "crlite_enrolled": false, + "id": "f588c0ff-5c81-4fa8-95d0-aa50aa7248a4", + "last_modified": 1663786626267 + }, + { + "schema": 1663786351283, + "derHash": "zx+PmJ+XXQUabw2VI65XMqOn4jRObFQPu2Q94pVRnmc=", + "subject": "CN=CSP SSL Service CA 4,O=CENTRAL SECURITY PATROLS CO.\\, LTD.,L=Shinjuku-ku,ST=Tokyo,C=JP", + "subjectDN": "MH8xCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEBxMLU2hpbmp1a3Uta3UxKzApBgNVBAoTIkNFTlRSQUwgU0VDVVJJVFkgUEFUUk9MUyBDTy4sIExURC4xHTAbBgNVBAMTFENTUCBTU0wgU2VydmljZSBDQSA0", + "whitelist": false, + "attachment": { + "hash": "109d1f5cb034f92419a9a77b50ff079b19b72ad8400fcb071f3b8b22792477ab", + "size": 2154, + "filename": "6cmFGKSet-uN9lExNlA28Wy9f_epSW1jDhpODTW1QyI=.pem", + "location": "security-state-staging/intermediates/c3019c3d-b69f-4835-afcf-5b1aa9e4150c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6cmFGKSet+uN9lExNlA28Wy9f/epSW1jDhpODTW1QyI=", + "crlite_enrolled": false, + "id": "10512fab-e974-42a5-8402-9a01fd00122c", + "last_modified": 1663786626246 + }, + { + "schema": 1663786349594, + "derHash": "iF6OHcrQdxTzuiysmtv2hxH7LlY2g/xOM0z5JNl8wkU=", + "subject": "CN=DNEncrypt SHA2 EV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE3MDUGA1UEAwwuRE5FbmNyeXB0IFNIQTIgRVYgU1NML1RMUyAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "f7816d0e6d5ac4c35a76506e636a2d12adc7f4077569ce1d541eff25416ee242", + "size": 2487, + "filename": "EonOFaZgJpBcxWk-Gu5D6SKzi_Nbfzub6RHPOQrObVs=.pem", + "location": "security-state-staging/intermediates/eff5f2ec-6664-4877-97b7-8aaa6ba36e8b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EonOFaZgJpBcxWk+Gu5D6SKzi/Nbfzub6RHPOQrObVs=", + "crlite_enrolled": false, + "id": "46f43a31-5f60-4414-b31a-455a0f727d8f", + "last_modified": 1663786626232 + }, + { + "schema": 1663786355234, + "derHash": "ksdI7NEnrmBVsYCEZPO7ETzFGuAHNZGzaB7FJ8YXzIA=", + "subject": "CN=cPanel\\, Inc. Certification Authority 2,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MHQxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS8wLQYDVQQDEyZjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMg==", + "whitelist": false, + "attachment": { + "hash": "76c3656cc128f16e9c2bdda27896aada178d0902423bb4f4014ddc101502277c", + "size": 2138, + "filename": "HVDLzoiDeh9hx-Qx4o0Fjy44bVZy8Qv7zwbUSS7r4dQ=.pem", + "location": "security-state-staging/intermediates/be588bfd-7606-4782-a2ef-57c5cf57d025.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HVDLzoiDeh9hx+Qx4o0Fjy44bVZy8Qv7zwbUSS7r4dQ=", + "crlite_enrolled": false, + "id": "08dfe46a-33c8-44ee-bc8a-6e02fe995980", + "last_modified": 1663786626225 + }, + { + "schema": 1663786354329, + "derHash": "kdAFBjP2JXL2eWFIhnZE7jEUCtciZuD5MFp8b9QED58=", + "subject": "CN=COMODO ECC Domain Validation Secure Server CA 3,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "a61e122807ad3a61a777dc8f4c749d88f4409f31804006b1eb5a70944c7efc57", + "size": 1317, + "filename": "yRx6JkdTon5X6I6GmE6-8BL0QUZRKbcRvDM_vt2Beyg=.pem", + "location": "security-state-staging/intermediates/85aef9ad-5fb3-41c1-8be0-97335974816d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yRx6JkdTon5X6I6GmE6+8BL0QUZRKbcRvDM/vt2Beyg=", + "crlite_enrolled": false, + "id": "8949c218-e8ec-4e70-a3ef-6a4f7c989fe7", + "last_modified": 1663786626211 + }, + { + "schema": 1663786346195, + "derHash": "ZeeA3/y1OHDqTtnhhLz5v8pGSEi00n5e7fY5GOabO4c=", + "subject": "CN=Don Dominio / MrDomain RSA OV CA,O=Soluciones Corporativas IP\\, SL,L=Manacor,ST=Illes Balears,C=ES", + "subjectDN": "MIGLMQswCQYDVQQGEwJFUzEWMBQGA1UECBMNSWxsZXMgQmFsZWFyczEQMA4GA1UEBxMHTWFuYWNvcjEnMCUGA1UEChMeU29sdWNpb25lcyBDb3Jwb3JhdGl2YXMgSVAsIFNMMSkwJwYDVQQDEyBEb24gRG9taW5pbyAvIE1yRG9tYWluIFJTQSBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "93018aaec0a8c6199018c655a8612e5f3cd1eafb5d0f7f5d467ec1fec6b7774a", + "size": 2170, + "filename": "PDA9PcTMXiEjxDxKYS1P_SinvLjX1xMCDWv1_fCl8_Q=.pem", + "location": "security-state-staging/intermediates/d7247e2c-5eea-4769-b7ec-02c236f55d77.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PDA9PcTMXiEjxDxKYS1P/SinvLjX1xMCDWv1/fCl8/Q=", + "crlite_enrolled": false, + "id": "47019c69-022d-4a74-9f02-b018b83a93d1", + "last_modified": 1663786626204 + }, + { + "schema": 1663786348701, + "derHash": "6xRHA3Gwku0ggB4h6jllObTkKMFwirjJtwaDQjmdPIE=", + "subject": "CN=Global Trust CA - EV (RSA),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBFViAoUlNBKQ==", + "whitelist": false, + "attachment": { + "hash": "f6ba53a086261a1595315875b91471856e44fc26c583cfab24d7a21687d02500", + "size": 2117, + "filename": "V2odnnJSCZapUukBa0XBQ7E-peZxS4qRJ22CftbyIG4=.pem", + "location": "security-state-staging/intermediates/8d909c18-b333-46d9-a69b-a4669d72b9ae.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "V2odnnJSCZapUukBa0XBQ7E+peZxS4qRJ22CftbyIG4=", + "crlite_enrolled": false, + "id": "1f588451-7643-4346-8ea8-35b35719c2e8", + "last_modified": 1663786626197 + }, + { + "schema": 1663786347835, + "derHash": "0kGVxh+c2oVizXXpVWUZcJe9dN/AfOLR30FIi2mdxek=", + "subject": "CN=ZeroSSL ECC EV Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "MEcxCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSYwJAYDVQQDEx1aZXJvU1NMIEVDQyBFViBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "4124aadd36e62b2b57c55a9cfec42347a7945c101372b8022491d28a168fa1de", + "size": 1309, + "filename": "A8f81KNlDx2NqB55PbT84Ofjg0xfNreLGekgTvDP594=.pem", + "location": "security-state-staging/intermediates/25accb9b-4ced-4acd-8210-5ae8893aa712.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "A8f81KNlDx2NqB55PbT84Ofjg0xfNreLGekgTvDP594=", + "crlite_enrolled": false, + "id": "4bae7d0c-ed7c-45e1-b8a1-425400033942", + "last_modified": 1663786626163 + }, + { + "schema": 1663786338590, + "derHash": "65T44sjQyDOLuLpA4erWIkuELLr8mfJp7vB2HoOcQaY=", + "subject": "CN=Western Digital Technologies Certification Authority,O=Western Digital Technologies,L=Irvine,ST=CA,C=US", + "subjectDN": "MIGRMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExDzANBgNVBAcTBklydmluZTElMCMGA1UEChMcV2VzdGVybiBEaWdpdGFsIFRlY2hub2xvZ2llczE9MDsGA1UEAxM0V2VzdGVybiBEaWdpdGFsIFRlY2hub2xvZ2llcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "f2be72d533dedd996e9388594f2f181d219b061b2eb3b725862347646ca940f3", + "size": 2483, + "filename": "w5ZKTAj16cKbFb_ByP_gQe9CQupFYdX6HVmRkZZtm9w=.pem", + "location": "security-state-staging/intermediates/d2dc2695-70b2-4a2c-88fe-147f4ebd3ee7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "w5ZKTAj16cKbFb/ByP/gQe9CQupFYdX6HVmRkZZtm9w=", + "crlite_enrolled": false, + "id": "fff8110d-e61d-4438-8147-0c962383fd34", + "last_modified": 1663786626142 + }, + { + "schema": 1663786341944, + "derHash": "SIVck1lFJzOdmESWifq3wxX/VGUGbFFsKdvc8YU5AdM=", + "subject": "CN=WebNIC RSA Business Secure Site CA,O=WebNIC,L=Singapore,C=SG", + "subjectDN": "MF8xCzAJBgNVBAYTAlNHMRIwEAYDVQQHEwlTaW5nYXBvcmUxDzANBgNVBAoTBldlYk5JQzErMCkGA1UEAxMiV2ViTklDIFJTQSBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "e5e9419726d6e7ead1f4cbb98eb850c65061867ef9ec73e7f4faf3e585aca30b", + "size": 2093, + "filename": "VEf8vFVDUxjsP3LYyFy549557yb431jkLHXGaJeeg0s=.pem", + "location": "security-state-staging/intermediates/4d9a3627-f2bf-46be-8d65-0d6e16607a5c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VEf8vFVDUxjsP3LYyFy549557yb431jkLHXGaJeeg0s=", + "crlite_enrolled": false, + "id": "35b8a920-b6ba-485b-87ca-2947efe2e4c2", + "last_modified": 1663786626128 + }, + { + "schema": 1663786336867, + "derHash": "6BRDFB48EjLQZGXhG70fbKcYGj4Df4FbuPqurea+vtE=", + "subject": "CN=USERTrust ECC Extended Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGVMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE7MDkGA1UEAxMyVVNFUlRydXN0IEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c2f4c8d69f5d843519515af24e6e46c7e0fa042a483886f596e16b5d53ee699b", + "size": 1362, + "filename": "H-5ErRMlCt-Y6SWx-LsLzsfdqO6rR8VCeSlgzeC0WN4=.pem", + "location": "security-state-staging/intermediates/03e735b7-5513-4295-bd41-9e532eb5783e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "H+5ErRMlCt+Y6SWx+LsLzsfdqO6rR8VCeSlgzeC0WN4=", + "crlite_enrolled": false, + "id": "f9048616-d2af-4b7a-be26-157a7668c53a", + "last_modified": 1663786626121 + }, + { + "schema": 1663786335089, + "derHash": "PzsNaiB8UKWCnxsf4ca8O2YQYIV3RhTBIie3ZnKO0q4=", + "subject": "CN=ZeroSSL RSA EV Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "MEcxCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSYwJAYDVQQDEx1aZXJvU1NMIFJTQSBFViBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "7cfbccc6af23ad8b5efc12abf6b6ae1b933dde4d782076d4c17e04694489837a", + "size": 2454, + "filename": "25iQFhK1cn8vc_vird2SyolWQqiVsnbjTu9eZlX690I=.pem", + "location": "security-state-staging/intermediates/404ebdbd-c737-4e93-acfa-762582f112a0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "25iQFhK1cn8vc/vird2SyolWQqiVsnbjTu9eZlX690I=", + "crlite_enrolled": false, + "id": "76d96002-848d-4ac2-b533-b4cdcd75e34a", + "last_modified": 1663786626114 + }, + { + "schema": 1663786334209, + "derHash": "xic1XV18Ay6NBW37JSzmxsFUlfc2LUeuX3ySr+s7YrI=", + "subject": "CN=Verokey Verified Business (ECC),O=Verokey,C=AU", + "subjectDN": "MEkxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSgwJgYDVQQDEx9WZXJva2V5IFZlcmlmaWVkIEJ1c2luZXNzIChFQ0Mp", + "whitelist": false, + "attachment": { + "hash": "96f256fb0d654f43f542d7d73da3a72a3ebe7cc25e44f294090f0ea71157f4f1", + "size": 1236, + "filename": "qc4sSFb8jst71NCzSxKNGPWxFa79c8-sP8AfecBlAto=.pem", + "location": "security-state-staging/intermediates/305e72a6-20ec-4962-88da-eaacfacbf0d3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qc4sSFb8jst71NCzSxKNGPWxFa79c8+sP8AfecBlAto=", + "crlite_enrolled": false, + "id": "d177b415-a735-49d3-acf1-d483a8cf8445", + "last_modified": 1663786626107 + }, + { + "schema": 1663786337728, + "derHash": "mHpclYS0a1mHjkJGD4Pk1h9MJyI2aUbU0hbzNqbxKPc=", + "subject": "CN=BlackCert\\, Inc. ECC DV Certification Authority,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRgwFgYDVQQKEw9CbGFja0NlcnQsIEluYy4xNzA1BgNVBAMTLkJsYWNrQ2VydCwgSW5jLiBFQ0MgRFYgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "1f922e42ebd0e6bf8d650a099283a6fce1dbaaa0d43e103c152fb431a9173a7c", + "size": 1313, + "filename": "UHhlKE9aTixVYvtmCIINV9nOgVj-gicuzrl4dZJypeA=.pem", + "location": "security-state-staging/intermediates/4a348e85-a781-41c4-b9f5-7cc66956acaf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UHhlKE9aTixVYvtmCIINV9nOgVj+gicuzrl4dZJypeA=", + "crlite_enrolled": false, + "id": "b7188189-ae9f-4f81-a602-7a6df6946a84", + "last_modified": 1663786626094 + }, + { + "schema": 1663786332479, + "derHash": "2dgcAVBdsZNxAhDZtCe2Uynv/yVhNfb7L+2+U+WTvwI=", + "subject": "CN=ISSAuth ECC OV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIEVDQyBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "c52133d6c3fab6164ac2d0e33ae0a1f321040c74a0321fff187869b84b53364f", + "size": 1248, + "filename": "GE-HA2pOP4xPkalzLKDvBn9-H2GvpzI92tTTxyspNKk=.pem", + "location": "security-state-staging/intermediates/a0d5da82-e91c-4971-9281-c3cef675f183.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GE+HA2pOP4xPkalzLKDvBn9+H2GvpzI92tTTxyspNKk=", + "crlite_enrolled": false, + "id": "2e3a05ca-28f4-4d96-a3a7-a3caa1a90f5e", + "last_modified": 1663786626087 + }, + { + "schema": 1663786335987, + "derHash": "zPSXrgDaWh5agidWuOjwOOANUtxD7S9dM2J5CXMr4L0=", + "subject": "CN=CertCenter Enterprise ECC OV CA,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MHIxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xEDAOBgNVBAcTB0dpZXNzZW4xFjAUBgNVBAoTDUNlcnRDZW50ZXIgQUcxKDAmBgNVBAMTH0NlcnRDZW50ZXIgRW50ZXJwcmlzZSBFQ0MgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "cff537b6b429b5b2149cca7dfe561f333e52dfe7c6404299a365a05bbeb66f99", + "size": 1297, + "filename": "bzjbLG4lOPyzYixFbEQ90vuRtQ04e0wlcXN7_CMDiqo=.pem", + "location": "security-state-staging/intermediates/c9fd523d-b93d-41cc-ab52-188336686dc3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bzjbLG4lOPyzYixFbEQ90vuRtQ04e0wlcXN7/CMDiqo=", + "crlite_enrolled": false, + "id": "b1de5c18-5b01-4982-81e2-4234f49315af", + "last_modified": 1663786626080 + }, + { + "schema": 1663786330779, + "derHash": "O/fPS2GSO651p8JX9VBwAD7Pcn4c2mufy4VR/GSdb+s=", + "subject": "CN=GlobeSSL DV Certification Authority 2,O=Globe Hosting\\, Inc.,L=Wilmington,ST=DE,C=US", + "subjectDN": "MH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJERTETMBEGA1UEBxMKV2lsbWluZ3RvbjEcMBoGA1UEChMTR2xvYmUgSG9zdGluZywgSW5jLjEuMCwGA1UEAxMlR2xvYmVTU0wgRFYgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMg==", + "whitelist": false, + "attachment": { + "hash": "2a7d89cbf6797cab18a6def1a2d2bf34df20c56b6bbcf3455fee150d6914598d", + "size": 2150, + "filename": "lQ_FKBR5nsUo6XzQqIOg6_wickZMgocia2x_uINY_uw=.pem", + "location": "security-state-staging/intermediates/c0fc7db3-2be8-492d-a2dc-cca6f8a62b09.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lQ/FKBR5nsUo6XzQqIOg6/wickZMgocia2x/uINY/uw=", + "crlite_enrolled": false, + "id": "8a0d8253-3697-441a-b920-4a6a9f602da5", + "last_modified": 1663786626073 + }, + { + "schema": 1663786329097, + "derHash": "kMZx4I5oI7hkbC7BgCN2msD8NDijPBAfZoO5XeMYBSQ=", + "subject": "CN=COMODO ECC Extended Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGUMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE6MDgGA1UEAxMxQ09NT0RPIEVDQyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "2d0f4b22b6568077c96779daabd7d06484ff21f2d47d3b497b237d1e36faaf6f", + "size": 1366, + "filename": "8BVB_xN70KohDkSwXqSXWyZjE3Wl2yUIqPlb8CShV54=.pem", + "location": "security-state-staging/intermediates/c9f2ed62-6fd3-482a-8672-f51217ff5dd7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8BVB/xN70KohDkSwXqSXWyZjE3Wl2yUIqPlb8CShV54=", + "crlite_enrolled": false, + "id": "c61ce58d-5a36-4cd6-b9fd-ebb5d8bcb7ee", + "last_modified": 1663786626059 + }, + { + "schema": 1663786324767, + "derHash": "yjd7GIZrI48xqASd/RNgnlSbdROQ7vzbmpoTUxJ/+zU=", + "subject": "CN=DNEncrypt ECC OV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE2MDQGA1UEAwwtRE5FbmNyeXB0IEVDQyBPViBTU0wvVExTICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "7c2bacaba566bae80ebb209b80664616980b611770a7e78acdf9b60c396fee5a", + "size": 1309, + "filename": "Vqfldlh0sPrGbhkt70MsWwISyFbsgHXV3Scyfh6rREM=.pem", + "location": "security-state-staging/intermediates/7fc45852-8386-45e4-a6c5-700aff0ccdc3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vqfldlh0sPrGbhkt70MsWwISyFbsgHXV3Scyfh6rREM=", + "crlite_enrolled": false, + "id": "0b9c8da8-c599-4ba0-aeed-c52aedeab5c0", + "last_modified": 1663786626024 + }, + { + "schema": 1663786323845, + "derHash": "lp4yAFIOssaalmdYfm3U0LI/KPUQzBMXZwqO02E+vq0=", + "subject": "CN=DigitalTrust High Assurance CA G4 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MG0xCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjE/MD0GA1UEAww2RGlnaXRhbFRydXN0IEhpZ2ggQXNzdXJhbmNlIENBIEc0ICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "da841597ba22d1599704d4f40b216c7a8ec0848a232cd1c6f90064f89e31604d", + "size": 2507, + "filename": "LFyh5Bg1aFs3y7w1j2dwtnxqDbovX0i8cL9gYJC6PXw=.pem", + "location": "security-state-staging/intermediates/e30e8909-3c75-4e15-8f38-692e15929faf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LFyh5Bg1aFs3y7w1j2dwtnxqDbovX0i8cL9gYJC6PXw=", + "crlite_enrolled": false, + "id": "48f425e3-3522-44ae-9bda-39e541c6a670", + "last_modified": 1663786626010 + }, + { + "schema": 1663786322966, + "derHash": "Ot3JgkDiGnLZxA61BYkFqkDt+jRa3t3lhEzLD0YthPc=", + "subject": "CN=Global Trust CA - EV (ECC),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBFViAoRUNDKQ==", + "whitelist": false, + "attachment": { + "hash": "a9df27e4c19b4a1f76319ecedc65ac9c1df4361bf0bb9101f547a027486a8782", + "size": 1280, + "filename": "PRcljLZm-PUuo_QCHUFdjCyCwTjglb9Jg7caPjv8bI0=.pem", + "location": "security-state-staging/intermediates/7b871b32-2bee-4783-9a82-71abb59b9707.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PRcljLZm+PUuo/QCHUFdjCyCwTjglb9Jg7caPjv8bI0=", + "crlite_enrolled": false, + "id": "f5101c77-f135-445c-b549-626c031f1d3d", + "last_modified": 1663786626001 + }, + { + "schema": 1663786319550, + "derHash": "TJ55n+pib17TXY+RVI+2o5AFAwmfMrxJ3A+1ZrCg7zc=", + "subject": "CN=上海锐成信息科技有限公司 OV CA,O=上海锐成信息科技有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTkuIrmtbfplJDmiJDkv6Hmga/np5HmioDmnInpmZDlhazlj7gxMzAxBgNVBAMMKuS4iua1t+mUkOaIkOS/oeaBr+enkeaKgOaciemZkOWFrOWPuCBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "8b7a55b9706551951960b11e537a8b3bcddd894744169e04ce5a0b740276c9a2", + "size": 2125, + "filename": "0I7ImSIwMSH6GsuawHstmkZIicesm0pcLLm6TH6V8Gg=.pem", + "location": "security-state-staging/intermediates/a9a2423c-0475-4e83-bee7-963fd4d5b6a1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0I7ImSIwMSH6GsuawHstmkZIicesm0pcLLm6TH6V8Gg=", + "crlite_enrolled": false, + "id": "859fdf2e-06a7-46de-b240-87da7e946d0c", + "last_modified": 1663786625981 + }, + { + "schema": 1663786318657, + "derHash": "32UwhcjtX4RMuwkWG4rgvy82gi+Te/e7Gmicb8i9V5o=", + "subject": "CN=cPanel Extended Validation ECC Certification Authority,OU=Controlled by COMODO exclusively for cPanel\\, Inc.,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MIHAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVFgxEDAOBgNVBAcTB0hvdXN0b24xFTATBgNVBAoTDGNQYW5lbCwgSW5jLjE6MDgGA1UECxMxQ29udHJvbGxlZCBieSBDT01PRE8gZXhjbHVzaXZlbHkgZm9yIGNQYW5lbCwgSW5jLjE/MD0GA1UEAxM2Y1BhbmVsIEV4dGVuZGVkIFZhbGlkYXRpb24gRUNDIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "fe32c59eace740c2f5bc700fc9febd026a18c608db095cadcf8dc4381d72824c", + "size": 1427, + "filename": "M0KI4Ko0g5TclE_ltD5sgAvt8tWcj6K5Je7fmwLXcmg=.pem", + "location": "security-state-staging/intermediates/6279bd6f-1b86-4024-bc2d-1564bc86236f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M0KI4Ko0g5TclE/ltD5sgAvt8tWcj6K5Je7fmwLXcmg=", + "crlite_enrolled": false, + "id": "a556b29e-ca02-4b67-b969-851956917861", + "last_modified": 1663786625951 + }, + { + "schema": 1663786317813, + "derHash": "NhledtZVmIiLBeOJnyESqcW+tngVXrgVwzccy6fDeBk=", + "subject": "CN=ZeroSSL RSA Business Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "ME0xCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSwwKgYDVQQDEyNaZXJvU1NMIFJTQSBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "73f8a792dec2eb3e69543d2f0f0f7859f8cd0e20e1f8eba64c5d32c506f73f47", + "size": 2430, + "filename": "-xzCearoGbvh_Q0yMU_NCkm14rjMFnaa57u3iwTvlBk=.pem", + "location": "security-state-staging/intermediates/19f97698-ee5a-4a61-9c62-a2c8974f8137.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+xzCearoGbvh/Q0yMU/NCkm14rjMFnaa57u3iwTvlBk=", + "crlite_enrolled": false, + "id": "b2cf83bd-95d9-4714-93eb-800cb4a4fab5", + "last_modified": 1663786625937 + }, + { + "schema": 1663786312648, + "derHash": "cj4fieqqwPVrn5OF8TEvbNvXVFQGl7pGNA3yrjDqWec=", + "subject": "CN=SSL.com High Assurance CA,OU=www.ssl.com,O=SSL.com,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdTU0wuY29tMRQwEgYDVQQLEwt3d3cuc3NsLmNvbTEiMCAGA1UEAxMZU1NMLmNvbSBIaWdoIEFzc3VyYW5jZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "09954e1ba56d5de2350aa2be48974a6c3b30878a90b78ebe7f1294e29fcab376", + "size": 2121, + "filename": "Ck8cX6ydytkZDI6Az0ES3m8TykmMPRUEb5UVDjRtJ_w=.pem", + "location": "security-state-staging/intermediates/c7908f51-74f4-4f42-83ae-1aea4789362b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ck8cX6ydytkZDI6Az0ES3m8TykmMPRUEb5UVDjRtJ/w=", + "crlite_enrolled": false, + "id": "32f01173-0130-437b-bc2a-95324d09359b", + "last_modified": 1663786625922 + }, + { + "schema": 1663786310061, + "derHash": "9unOsfPDaJhR+IYFVTHacu0LxeVtFkkls65/3OhxfXA=", + "subject": "CN=KeyNet Systems RSA OV CA,O=KeyNet Systems LLC,L=Las Vegas,ST=Nevada,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEbMBkGA1UEChMSS2V5TmV0IFN5c3RlbXMgTExDMSEwHwYDVQQDExhLZXlOZXQgU3lzdGVtcyBSU0EgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "4cee0f57010b6cc4313065b886827b9ffed670c7c970eb66656a30b5c235ae1b", + "size": 2138, + "filename": "yZ5Axfrz6TT2LCftMDwKgTE4QQ41sZW0CP1oieeztpI=.pem", + "location": "security-state-staging/intermediates/81a3613f-022c-4d7c-bb22-2af1e0f90ae4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yZ5Axfrz6TT2LCftMDwKgTE4QQ41sZW0CP1oieeztpI=", + "crlite_enrolled": false, + "id": "f4611f45-f320-4e5a-8db4-4d955828f53e", + "last_modified": 1663786625901 + }, + { + "schema": 1663786309189, + "derHash": "vZZq1uj8wuFrVwk6n6K3k6SlxRfDxPiwPDPU2Pk4+j8=", + "subject": "CN=E-SAFER DOMAIN SSL ECC CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGFMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTE3MDUGA1UEAwwuRS1TQUZFUiBET01BSU4gU1NMIEVDQyBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "45f367ee7cb4a6a5178d456130f831bfeb3c3e4e2c3b63112052567011e91b0a", + "size": 1317, + "filename": "_fNfZ1t3T83OiGnI63N_nf9v5CGZhdrLS6hulZCq0Dw=.pem", + "location": "security-state-staging/intermediates/529eba8d-dd4d-4b53-8548-29ea01bf8597.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/fNfZ1t3T83OiGnI63N/nf9v5CGZhdrLS6hulZCq0Dw=", + "crlite_enrolled": false, + "id": "d4d9fa7d-3f75-4dfc-885e-66ac80a4f31a", + "last_modified": 1663786625894 + }, + { + "schema": 1663786313489, + "derHash": "VlyCcCtexjICdU1PS3bMO64ypMkUbtO+zXOkBP+tTN4=", + "subject": "CN=UTN-USERFirst-Client Authentication and Email,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US", + "subjectDN": "MIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls", + "whitelist": false, + "attachment": { + "hash": "ea4a8d488e32504fd104cbe43ae869fa05d6c9a40760f4ad44d6138279f5a48c", + "size": 1674, + "filename": "Laj56jRU0hFGRko_nQKNxMf7tXscUsc8KwVyovWZotM=.pem", + "location": "security-state-staging/intermediates/ff1f6147-d1b8-4857-90c1-1c69f32d66f2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Laj56jRU0hFGRko/nQKNxMf7tXscUsc8KwVyovWZotM=", + "crlite_enrolled": false, + "id": "f9921d3b-2c14-4cd8-ba89-946e88ff8a45", + "last_modified": 1663786625873 + }, + { + "schema": 1663786305752, + "derHash": "dnvCnbma9MKmJkkADRcvxswtCdQIxMq2qNmaHdXc99s=", + "subject": "CN=CertCenter Enterprise ECC DV CA,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MHIxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xEDAOBgNVBAcTB0dpZXNzZW4xFjAUBgNVBAoTDUNlcnRDZW50ZXIgQUcxKDAmBgNVBAMTH0NlcnRDZW50ZXIgRW50ZXJwcmlzZSBFQ0MgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "33b1d6db84e6012ac1be4df4cade7ba5dca27aacfe49eff645ce6c0e218f845d", + "size": 1297, + "filename": "zuRQp-iYZxMyj8bp4pFJtJ30vMQId6DXMagFWZ9jfmU=.pem", + "location": "security-state-staging/intermediates/c6cea523-b17e-4be0-854f-b260946a7e95.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zuRQp+iYZxMyj8bp4pFJtJ30vMQId6DXMagFWZ9jfmU=", + "crlite_enrolled": false, + "id": "4df6a740-5dba-40de-a39a-40a9bfbf65a2", + "last_modified": 1663786625839 + }, + { + "schema": 1663786301005, + "derHash": "6ZOtZU1FhUS7Dk2n7HxfumDEZFebGyLGWSPd0UpngKw=", + "subject": "CN=DNEncrypt ECC EV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE2MDQGA1UEAwwtRE5FbmNyeXB0IEVDQyBFViBTU0wvVExTICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "3240a57b77abf143765a3e536f853a3c02f44159103590752c8b1ad0334bc93d", + "size": 1341, + "filename": "m7sp44VsyZf6_AVSHx-N3M0tXcbynVKIOugeitl06ZM=.pem", + "location": "security-state-staging/intermediates/9cb5bf4b-9792-4a50-9fda-1a9ffd6336df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "m7sp44VsyZf6/AVSHx+N3M0tXcbynVKIOugeitl06ZM=", + "crlite_enrolled": false, + "id": "d9d01fba-b934-48cf-9906-8eacf630d825", + "last_modified": 1663786625832 + }, + { + "schema": 1663786300107, + "derHash": "n6n8tQDxqYNVCZ9wA0zw0u50Orpj9utn1VaF6pBlMos=", + "subject": "CN=GlobeSSL OV Certification Authority 2,O=Globe Hosting\\, Inc.,L=Wilmington,ST=DE,C=US", + "subjectDN": "MH0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJERTETMBEGA1UEBxMKV2lsbWluZ3RvbjEcMBoGA1UEChMTR2xvYmUgSG9zdGluZywgSW5jLjEuMCwGA1UEAxMlR2xvYmVTU0wgT1YgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMg==", + "whitelist": false, + "attachment": { + "hash": "fdefe5ef43df12d4bd1335d174b6f41e641b22c6567a16702be003ef652c472f", + "size": 2150, + "filename": "iaAN4YIvVl4idrHfqCHBL-dZTUISZ2ANiVmgZiYqSUY=.pem", + "location": "security-state-staging/intermediates/f1647464-5e0e-498c-bb82-47efb78d1a12.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iaAN4YIvVl4idrHfqCHBL+dZTUISZ2ANiVmgZiYqSUY=", + "crlite_enrolled": false, + "id": "e8ebfc10-dc4f-4b6f-bde6-b9aaa7e3ece4", + "last_modified": 1663786625825 + }, + { + "schema": 1663786297584, + "derHash": "e9C9d7T7i2+CUz5OzsPHVl61IKP1NUnBerElNX//TFo=", + "subject": "CN=CertAssure RSA OV Secure Server Certification Authority,O=CertAssure Inc.,L=San Jose,ST=CA,C=US", + "subjectDN": "MIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRgwFgYDVQQKEw9DZXJ0QXNzdXJlIEluYy4xQDA+BgNVBAMTN0NlcnRBc3N1cmUgUlNBIE9WIFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "a494f6ded1b3ee592546f651560380cf11ada45b7b1f09047c87bdd7035f95ae", + "size": 2166, + "filename": "AFV-kOki635A1nbhk6e6ZQs4lmPcP6IboL5EBPOsVYE=.pem", + "location": "security-state-staging/intermediates/9d96fd07-75d9-431b-8def-dffb7d6f2f6e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AFV+kOki635A1nbhk6e6ZQs4lmPcP6IboL5EBPOsVYE=", + "crlite_enrolled": false, + "id": "21789ee1-7d42-4706-88a7-ed5c6225e68f", + "last_modified": 1663786625811 + }, + { + "schema": 1663786296703, + "derHash": "BBFKVCPaXKegx4rci2PYiT4zQwhOEzZ8GvgyRybs+nM=", + "subject": "CN=WebNIC ECC Business Secure Site CA,O=WebNIC,L=Singapore,C=SG", + "subjectDN": "MF8xCzAJBgNVBAYTAlNHMRIwEAYDVQQHEwlTaW5nYXBvcmUxDzANBgNVBAoTBldlYk5JQzErMCkGA1UEAxMiV2ViTklDIEVDQyBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "14c9b9004285c929a1d1208098676c708bd3ea81af8d89616998f6e9f6c5e924", + "size": 1256, + "filename": "YUjFyuQK3Sa5iGXuitIqguBGtB0x_Lr2rwEwdRQVZWA=.pem", + "location": "security-state-staging/intermediates/e5d65851-f530-41f9-842f-bb15a94d6970.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YUjFyuQK3Sa5iGXuitIqguBGtB0x/Lr2rwEwdRQVZWA=", + "crlite_enrolled": false, + "id": "be09bf8b-4c8d-4a12-bd2c-cedb674fd4fe", + "last_modified": 1663786625804 + }, + { + "schema": 1663786298429, + "derHash": "KL3bAjnS1WB+jejyI7X3Bpy40aJ4iV1MTFcefQtpdmM=", + "subject": "CN=E-SAFER EXTENDED SSL ECC CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGHMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTE5MDcGA1UEAwwwRS1TQUZFUiBFWFRFTkRFRCBTU0wgRUNDIENBICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "7eb0ee10e7d371019652a16d317ccd09167dcfddda0c7a0c75a39170a87eb3c1", + "size": 1309, + "filename": "a-SCFBMdXh0W-FGGIMQo7sUur1zyBw7unZ6Sh2EyoDU=.pem", + "location": "security-state-staging/intermediates/c38e19aa-6b1e-4db2-8c50-4c80a3b81d9d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "a+SCFBMdXh0W+FGGIMQo7sUur1zyBw7unZ6Sh2EyoDU=", + "crlite_enrolled": false, + "id": "b9145148-2720-4d9a-a89b-a1336ff8d05c", + "last_modified": 1663786625797 + }, + { + "schema": 1663786301845, + "derHash": "CJKAkjZZMWGXWfK4tE7Sq8Aw2K9sPDFJ+8sHx4qWzjY=", + "subject": "CN=SSLs.com RSA EV Secure Server CA,OU=Controlled by Sectigo exclusively for SSLs.com,O=SSLs.com (Namecheap\\, Inc.),L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MIG6MQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTEQMA4GA1UEBxMHUGhvZW5peDEjMCEGA1UEChMaU1NMcy5jb20gKE5hbWVjaGVhcCwgSW5jLikxNzA1BgNVBAsTLkNvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgU1NMcy5jb20xKTAnBgNVBAMTIFNTTHMuY29tIFJTQSBFViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "700e0c3ba80a94dc3f004489c3bfd9f9e4069496d22fe5cdc05ad2110bfef9d6", + "size": 2263, + "filename": "e3CoG9n5TuSLIlY6MxPqo4fACcPQacL73N47EKEZh80=.pem", + "location": "security-state-staging/intermediates/03bffa5d-f6c2-4e19-9cc3-8703ad8b63a7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e3CoG9n5TuSLIlY6MxPqo4fACcPQacL73N47EKEZh80=", + "crlite_enrolled": false, + "id": "5cb1707f-a3c5-4bec-93f5-ea54cf32feb3", + "last_modified": 1663786625784 + }, + { + "schema": 1663786291500, + "derHash": "MoTgNczXH3odBjrpjimx5ZEjEa2fbZEYhMr513HTfE0=", + "subject": "CN=BlackCert\\, Inc. RSA OV Certification Authority,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRgwFgYDVQQKEw9CbGFja0NlcnQsIEluYy4xNzA1BgNVBAMTLkJsYWNrQ2VydCwgSW5jLiBSU0EgT1YgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "35bdce85f3230d2b21cf676bcf2eb1b6f77b6324e7f590ff39e91ee3d18226f7", + "size": 2150, + "filename": "B5IV9Zui5uDR_htFQKKJviedASQLhuhY2W547xasQvc=.pem", + "location": "security-state-staging/intermediates/b12990b4-e8de-4d6b-82d3-76eb85619484.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B5IV9Zui5uDR/htFQKKJviedASQLhuhY2W547xasQvc=", + "crlite_enrolled": false, + "id": "b5f8be2f-292a-4792-9a15-d1f31c2c9f23", + "last_modified": 1663786625756 + }, + { + "schema": 1663786294128, + "derHash": "XKBOk6WgfXTG+0r+Gz1NvGLCXOdNhhfH7mbLVDi6ZX8=", + "subject": "CN=KeyNet Systems ECC OV CA,O=KeyNet Systems LLC,L=Las Vegas,ST=Nevada,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEbMBkGA1UEChMSS2V5TmV0IFN5c3RlbXMgTExDMSEwHwYDVQQDExhLZXlOZXQgU3lzdGVtcyBFQ0MgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "93cc48e487dca26d68f0602a96fd621d41d5dd4c9fa214a443cb7714c8a68394", + "size": 1297, + "filename": "px8fWni3abLFLZV9MOsgswaWeefS4bTPmddWxtEXiag=.pem", + "location": "security-state-staging/intermediates/498727fd-028e-44bb-a78f-776bec4f5e05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "px8fWni3abLFLZV9MOsgswaWeefS4bTPmddWxtEXiag=", + "crlite_enrolled": false, + "id": "b9760777-9abd-4830-aefe-5da3bcc9ef81", + "last_modified": 1663786625749 + }, + { + "schema": 1663786289994, + "derHash": "k3CbW9sMdo/JaftJZYaCGNbehqaib4E7cm/0aXZgfGQ=", + "subject": "CN=ISSAuth RSA OV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIFJTQSBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "910dab04293874c9e0cc9405e153272c7c1276de07f2105afbd0fad66461365f", + "size": 2085, + "filename": "pedwg-t32CS6XTTc_JUBqMnUFDQfsrfFl-YRQXc_Jco=.pem", + "location": "security-state-staging/intermediates/0cd057e5-8ae6-471f-b361-2f360736ea13.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pedwg+t32CS6XTTc/JUBqMnUFDQfsrfFl+YRQXc/Jco=", + "crlite_enrolled": false, + "id": "8868db83-09a9-4441-bd6b-134b8da3bb7b", + "last_modified": 1663786625742 + }, + { + "schema": 1663786289009, + "derHash": "C/jSeHGiXvvMcPVsJoyAxC0oxfHRimnAGA2YmyuNZxo=", + "subject": "CN=CertAssure ECC EV Secure Server Certification Authority,OU=Controlled by COMODO exclusively for CertAssure Inc.,O=CertAssure Inc.,L=San Jose,ST=CA,C=US", + "subjectDN": "MIHIMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRgwFgYDVQQKEw9DZXJ0QXNzdXJlIEluYy4xPTA7BgNVBAsTNENvbnRyb2xsZWQgYnkgQ09NT0RPIGV4Y2x1c2l2ZWx5IGZvciBDZXJ0QXNzdXJlIEluYy4xQDA+BgNVBAMTN0NlcnRBc3N1cmUgRUNDIEVWIFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "00aca0ba7da5c96b589d5d53e756e112e52564c056fbef370cb56c3baae1f717", + "size": 1443, + "filename": "ok9f2xbTTMytgvbxoAC3WVM2u44IiamlaLqKpKO_Se0=.pem", + "location": "security-state-staging/intermediates/26632b9e-c780-4fe0-9693-f12acf56514b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ok9f2xbTTMytgvbxoAC3WVM2u44IiamlaLqKpKO/Se0=", + "crlite_enrolled": false, + "id": "92dc0bc9-33dc-42b3-be70-a4eb3834c6e0", + "last_modified": 1663786625735 + }, + { + "schema": 1663786288120, + "derHash": "IIOI6/mbEhiIUvEd15Rme145laJ2etslXpuPXUz1BXY=", + "subject": "CN=CertCloud RSA EV TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "ME0xCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMSAwHgYDVQQDExdDZXJ0Q2xvdWQgUlNBIEVWIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "64fb5ef3ea484815ce7291a578837ce507a0ebd1ebbc3699a8037b3fc57d68fc", + "size": 2109, + "filename": "qtoy2s5rEsyXk3LyvPdOnjlq0FouOkWa_MxbJugEHms=.pem", + "location": "security-state-staging/intermediates/934f2bdf-d096-47a8-bf21-709cd735ff02.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qtoy2s5rEsyXk3LyvPdOnjlq0FouOkWa/MxbJugEHms=", + "crlite_enrolled": false, + "id": "d821dbc7-4e63-4b10-8f39-f3eece989b57", + "last_modified": 1663786625722 + }, + { + "schema": 1663786287240, + "derHash": "8eEsDErH5WSh/1XssL9QvUKqDJHhfvJGlspyloB/XCc=", + "subject": "CN=Omit Security ECC Extended Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxMTAvBgNVBAMTKE9taXQgU2VjdXJpdHkgRUNDIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0E=", + "whitelist": false, + "attachment": { + "hash": "404468e323d5a5c0bb85a31da305f9499ea81d23b517563c385f7e509057caf1", + "size": 1252, + "filename": "JEa9bLzQ1IZeG2imjTLMA-kakRvgwcIMaaeKq70eCDU=.pem", + "location": "security-state-staging/intermediates/25b8b0f1-f4f7-4f78-bcd2-921358e4789a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JEa9bLzQ1IZeG2imjTLMA+kakRvgwcIMaaeKq70eCDU=", + "crlite_enrolled": false, + "id": "d14d0ec4-73bc-4c38-9c07-0c255bf6feb6", + "last_modified": 1663786625715 + }, + { + "schema": 1663786283824, + "derHash": "O7dLHmfyh2k2ivEdxJmzH04EWaeG87DYSbautZ0JJAI=", + "subject": "CN=SSLs.com ECC OV Secure Server CA,O=SSLs.com,L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRAwDgYDVQQHEwdQaG9lbml4MREwDwYDVQQKEwhTU0xzLmNvbTEpMCcGA1UEAxMgU1NMcy5jb20gRUNDIE9WIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "6ea5d175c8123f2a5a3db091bc2cd2548833fbba6f97f340a8d10d2ea9fc82aa", + "size": 1293, + "filename": "8OVFe7aea8Jz58DI1YaYq0kntK7F4fquqS_LHwOMg5o=.pem", + "location": "security-state-staging/intermediates/3c3093eb-8681-4091-99c2-cc9d0a9e870f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8OVFe7aea8Jz58DI1YaYq0kntK7F4fquqS/LHwOMg5o=", + "crlite_enrolled": false, + "id": "7a4aafe4-e9da-46b6-95f3-754181e2a9e7", + "last_modified": 1663786625708 + }, + { + "schema": 1663786282996, + "derHash": "qyyT4BzCFYB/BM2/w2KAjYiUCnss7nAjjTVtjZSVbfk=", + "subject": "CN=COMODO RSA Extended Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGUMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE6MDgGA1UEAxMxQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "8e70b65da05caf7f28d7bcfb3ec44605864986d6ec0bf12372711eda665619b8", + "size": 2207, + "filename": "_KV5PqHI-A-AHDl7BRjU7Yfi8xL4XtBw8FQAx1HWgH4=.pem", + "location": "security-state-staging/intermediates/26a042d6-fc2e-44df-a739-754b6e28914a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/KV5PqHI+A+AHDl7BRjU7Yfi8xL4XtBw8FQAx1HWgH4=", + "crlite_enrolled": false, + "id": "02e75ed1-98e8-41e9-9a28-8a2924ba9fb8", + "last_modified": 1663786625701 + }, + { + "schema": 1663786285532, + "derHash": "qK7gyk++CQh4qQHLVkElV07GS6KOnpApbtPkkrFgtNk=", + "subject": "CN=DNEncrypt SHA2 OV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE3MDUGA1UEAwwuRE5FbmNyeXB0IFNIQTIgT1YgU1NML1RMUyAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "613ee862f4be363eb2532e0e090efaa66ef732b43729e48062b1b3785df24b22", + "size": 2454, + "filename": "oTOjvcPyAYB-WsThLALbWlgbUUxVxj6xmmL3GnHrCkY=.pem", + "location": "security-state-staging/intermediates/d5882f14-7089-4b3b-b825-bfd6e8434776.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oTOjvcPyAYB+WsThLALbWlgbUUxVxj6xmmL3GnHrCkY=", + "crlite_enrolled": false, + "id": "edacd60b-2528-417c-9f6e-c72333549051", + "last_modified": 1663786625694 + }, + { + "schema": 1663786282117, + "derHash": "6UL/g/YzoI29ZeQ7VxmkNA3hPbfGG9aofNywuph1qWg=", + "subject": "CN=K Software Certificate Authority (OV) 2,O=K Software,L=Ashland,ST=KY,C=US", + "subjectDN": "MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJLWTEQMA4GA1UEBxMHQXNobGFuZDETMBEGA1UEChMKSyBTb2Z0d2FyZTEwMC4GA1UEAxMnSyBTb2Z0d2FyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgKE9WKSAy", + "whitelist": false, + "attachment": { + "hash": "6b5d6d13ec9593e3b5105f745119087884482867ef84044045a62c1495667b19", + "size": 2138, + "filename": "afXDX7F7Ynjqxjg6ewL0PMjH-QIuDGSiQQ1PAGTTkE4=.pem", + "location": "security-state-staging/intermediates/4aa0f861-18da-4875-b438-9abb6a514d4e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "afXDX7F7Ynjqxjg6ewL0PMjH+QIuDGSiQQ1PAGTTkE4=", + "crlite_enrolled": false, + "id": "4723b4a1-0b24-4bf8-8290-9b009135c88f", + "last_modified": 1663786625687 + }, + { + "schema": 1663786281224, + "derHash": "MkC4HG31Uq6hbXAinb/v8k1Q6qmiJyORiI9lvpW0UOM=", + "subject": "CN=CertCenter Enterprise RSA OV CA,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MHIxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xEDAOBgNVBAcTB0dpZXNzZW4xFjAUBgNVBAoTDUNlcnRDZW50ZXIgQUcxKDAmBgNVBAMTH0NlcnRDZW50ZXIgRW50ZXJwcmlzZSBSU0EgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "38e3592a50a0e4393644837ec75acbcd1e98238775437cb3df0c2098744a3613", + "size": 2138, + "filename": "ke_AQ9WF4UMSgAU_wMJhEjKM4oBlpoIH0lidCk0FTIM=.pem", + "location": "security-state-staging/intermediates/28ee494b-9984-49e9-a489-b7c163d4b1aa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ke/AQ9WF4UMSgAU/wMJhEjKM4oBlpoIH0lidCk0FTIM=", + "crlite_enrolled": false, + "id": "84f0f509-a064-4bd6-b70a-9b819a278a14", + "last_modified": 1663786625681 + }, + { + "schema": 1663786279437, + "derHash": "Xxg5DyFJApKglf/zgPLokn3TFZIjp2ET7eNXqisxHB8=", + "subject": "CN=Gehirn Managed Certification Authority - ECC EV,OU=Controlled by COMODO CA exclusively for Gehirn Inc.,O=Gehirn Inc.,L=Chiyoda-ku,ST=Tokyo,C=JP", + "subjectDN": "MIHAMQswCQYDVQQGEwJKUDEOMAwGA1UECBMFVG9reW8xEzARBgNVBAcTCkNoaXlvZGEta3UxFDASBgNVBAoTC0dlaGlybiBJbmMuMTwwOgYDVQQLEzNDb250cm9sbGVkIGJ5IENPTU9ETyBDQSBleGNsdXNpdmVseSBmb3IgR2VoaXJuIEluYy4xODA2BgNVBAMTL0dlaGlybiBNYW5hZ2VkIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUNDIEVW", + "whitelist": false, + "attachment": { + "hash": "74fff6280d31bf6c13681da4c04a0f76bf3fd9987e0033083a6849f84200b119", + "size": 1435, + "filename": "qSizb9i49llI7pUZp1-Y6gXcrABhDgM-8a1v9VWvTnY=.pem", + "location": "security-state-staging/intermediates/65cafff9-159b-44e4-9432-4de5f11f82bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qSizb9i49llI7pUZp1+Y6gXcrABhDgM+8a1v9VWvTnY=", + "crlite_enrolled": false, + "id": "1d1eff80-2db9-4a11-a9af-025d6de8db70", + "last_modified": 1663786625667 + }, + { + "schema": 1663786278548, + "derHash": "Z7vZFEbTre+bIcBXaZbc2OZmjWsMEz8Ha5H6cc7mtCg=", + "subject": "CN=eMudhra ECC Extended Validation Secure Server CA,OU=Controlled by Sectigo exclusively for eMudhra Technologies Ltd.,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIHhMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxSDBGBgNVBAsTP0NvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgZU11ZGhyYSBUZWNobm9sb2dpZXMgTHRkLjE5MDcGA1UEAxMwZU11ZGhyYSBFQ0MgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "fceb1146a096197adea5c3b2a0fcba6b0e4f7f5726e22df779e10c53b263bced", + "size": 1475, + "filename": "4ilprzeauYUkRwucZKCyuMuQcVH9en_FzR8kab23FUw=.pem", + "location": "security-state-staging/intermediates/a3194256-9f03-4a3b-ac55-7b3c3225d1a6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4ilprzeauYUkRwucZKCyuMuQcVH9en/FzR8kab23FUw=", + "crlite_enrolled": false, + "id": "94fd33f9-32a1-46d8-855d-f1247dd7f164", + "last_modified": 1663786625660 + }, + { + "schema": 1663786276803, + "derHash": "BMQG8UgFI5rZqnMOQEMJhCUGJjcjBPeIg8iy7sTh8Ow=", + "subject": "CN=CertAssure RSA EV Secure Server Certification Authority,OU=Controlled by COMODO exclusively for CertAssure Inc.,O=CertAssure Inc.,L=San Jose,ST=CA,C=US", + "subjectDN": "MIHIMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRgwFgYDVQQKEw9DZXJ0QXNzdXJlIEluYy4xPTA7BgNVBAsTNENvbnRyb2xsZWQgYnkgQ09NT0RPIGV4Y2x1c2l2ZWx5IGZvciBDZXJ0QXNzdXJlIEluYy4xQDA+BgNVBAMTN0NlcnRBc3N1cmUgUlNBIEVWIFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "7130d1714118b17d36b8220e5831a4569f90e94b4f1e26305262c55ca1a7fdfc", + "size": 2284, + "filename": "V-duof7YTC0BUw__nveYUxHnrma4qFXZJSOkbo7nmLA=.pem", + "location": "security-state-staging/intermediates/46aa14d1-b89b-42be-ac4c-543828a411d1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "V+duof7YTC0BUw//nveYUxHnrma4qFXZJSOkbo7nmLA=", + "crlite_enrolled": false, + "id": "1aa3aeb1-2c39-480e-9e8b-d12d49ecd345", + "last_modified": 1663786625639 + }, + { + "schema": 1663786271562, + "derHash": "ztQGeH82ZG2JfkaLe4tdyuEDDDp1rxybEFUUuV7G6nA=", + "subject": "CN=GENIOUS ECC Extended Validation Secure Server CA,OU=Controlled by Sectigo exclusively for Genious Communications,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIHYMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxRTBDBgNVBAsTPENvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgR2VuaW91cyBDb21tdW5pY2F0aW9uczE5MDcGA1UEAxMwR0VOSU9VUyBFQ0MgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "14d637973a280f20494200706d78da85fd44084a54b1c9dda11e24d6359bd7c7", + "size": 1463, + "filename": "lGB7vG_pOqUpHiqWKO7WQg8lfxBsfGVE0yulTTjMgJg=.pem", + "location": "security-state-staging/intermediates/f162042c-ed41-47ea-b249-c88c10579579.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lGB7vG/pOqUpHiqWKO7WQg8lfxBsfGVE0yulTTjMgJg=", + "crlite_enrolled": false, + "id": "2de2ef43-5065-45b6-b169-7005c0765f47", + "last_modified": 1663786625604 + }, + { + "schema": 1663786269755, + "derHash": "RJkQo/SqqB2u7RSgHPh/Mk/ogKh53feCjlXEd1iWIK4=", + "subject": "CN=PSW GROUP (RSA) EV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChSU0EpIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "0766809ec12dde473461195e73da6a1e3ba59cadadbc439c1353d287ab7355cd", + "size": 2113, + "filename": "NDJIxRHObvxpsBbETDEpHT9Zbz7P7Fm134T9ImWjIH4=.pem", + "location": "security-state-staging/intermediates/ef36effd-bf8d-4a3c-901d-ea1ed95a6ff0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NDJIxRHObvxpsBbETDEpHT9Zbz7P7Fm134T9ImWjIH4=", + "crlite_enrolled": false, + "id": "05ad4347-4482-4895-b1c9-86827b717c29", + "last_modified": 1663786625590 + }, + { + "schema": 1663786265836, + "derHash": "4cSwtb6SRx7sKI2yquyA++Bl+66MmppXaY172J8wcDE=", + "subject": "CN=CERTDATA SSL EV ECC CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MHIxCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExNDAyBgNVBAMMK0NFUlREQVRBIFNTTCBFViBFQ0MgQ0EgIFtSdW4gYnkgdGhlIElzc3Vlcl0=", + "whitelist": false, + "attachment": { + "hash": "0837942d30356413e1df1a2d21139f95edcc20068cf54b83038a725d55dd4a6d", + "size": 1325, + "filename": "9vCfWQvK73Fpm-vFHRSFb1GMEe-jrHgkRGYDgnPa5M0=.pem", + "location": "security-state-staging/intermediates/b8156f88-626f-4d05-8b5f-1da278a1d047.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9vCfWQvK73Fpm+vFHRSFb1GMEe+jrHgkRGYDgnPa5M0=", + "crlite_enrolled": false, + "id": "eb505e66-dc7b-41b8-b824-5014633006d4", + "last_modified": 1663786625556 + }, + { + "schema": 1663786264105, + "derHash": "7qFSzPdRfhZRMga3HBfiBPpAB8JsNgUexn30yUdGPPg=", + "subject": "CN=cPanel High Assurance ECC Certification Authority,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MH8xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMTowOAYDVQQDEzFjUGFuZWwgSGlnaCBBc3N1cmFuY2UgRUNDIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "56469124688b87ff25f088f51115f48b2ec40457df5dbbbc985e040dcb0aa76d", + "size": 1297, + "filename": "L4q9ILB-EyvjFKWbhrCSV_fq9CRcnS_EmX6BUSqw8Zo=.pem", + "location": "security-state-staging/intermediates/0adc1a25-d7a0-4ae0-b71f-693099a2dae5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "L4q9ILB+EyvjFKWbhrCSV/fq9CRcnS/EmX6BUSqw8Zo=", + "crlite_enrolled": false, + "id": "eb13cd3a-108a-48b8-9eaf-406e45792c89", + "last_modified": 1663786625549 + }, + { + "schema": 1663786263200, + "derHash": "dHz01wwMqeY99r9g7xzOtvlTn6ilNPtp8Fxh3flypBk=", + "subject": "CN=OneSignSSL ECC OV Secure Server CA,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MH8xCzAJBgNVBAYTAlNHMRIwEAYDVQQIEwlTaW5nYXBvcmUxEjAQBgNVBAcTCVNpbmdhcG9yZTEbMBkGA1UEChMST25lIFNpZ24gUHRlLiBMdGQuMSswKQYDVQQDEyJPbmVTaWduU1NMIEVDQyBPViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "48234d68810d1dad4af833b7d5a9a5c0155a65cd632f71eb0d41b0f2f662dca7", + "size": 1313, + "filename": "k5mlhqxcFc-cb0nNpLyym3e1yVLu33JKb1dKa-MuMjg=.pem", + "location": "security-state-staging/intermediates/82c04f91-6505-4a58-9aca-c23390aebc9c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "k5mlhqxcFc+cb0nNpLyym3e1yVLu33JKb1dKa+MuMjg=", + "crlite_enrolled": false, + "id": "a3437eaa-40bd-43a9-bea9-9846d44316fd", + "last_modified": 1663786625542 + }, + { + "schema": 1663786261486, + "derHash": "agt+UEBHElIXYKML9EJw7qyKYC/pesNurqk6cvs+o3k=", + "subject": "CN=XinChaCha Trust EV CA - ECC,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGsxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xJDAiBgNVBAMTG1hpbkNoYUNoYSBUcnVzdCBFViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "ea21bed9f236d2e9f1a685e8a1b8a0dbd39306cdf5ead1a81abe75e73834afa3", + "size": 1268, + "filename": "7ZXdfPfc8OcYB2GIKvO1mGzw8TPQeKMNhzcVSkgmEbs=.pem", + "location": "security-state-staging/intermediates/13659617-2e57-4fbe-8277-eafe18f171c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7ZXdfPfc8OcYB2GIKvO1mGzw8TPQeKMNhzcVSkgmEbs=", + "crlite_enrolled": false, + "id": "bec539ab-394c-4eb8-a46c-0673db9cfe6b", + "last_modified": 1663786625528 + }, + { + "schema": 1663786256282, + "derHash": "jNlTxQYii/HXTWBi6R5+CHPq67sZ+/IArK3CODAAa4M=", + "subject": "CN=TrustAsia RSA EV SSL Server CA,OU=Controlled by COMODO exclusively for TrustAsia Technologies,O=TrustAsia Technologies\\, Inc.,ST=Shanghai,C=CN", + "subjectDN": "MIG2MQswCQYDVQQGEwJDTjERMA8GA1UECBMIU2hhbmdoYWkxJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xRDBCBgNVBAsTO0NvbnRyb2xsZWQgYnkgQ09NT0RPIGV4Y2x1c2l2ZWx5IGZvciBUcnVzdEFzaWEgVGVjaG5vbG9naWVzMScwJQYDVQQDEx5UcnVzdEFzaWEgUlNBIEVWIFNTTCBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c2efa4fc4ae5ff682bde526938082f043757f4778d42af29365e4dfb24909daa", + "size": 2263, + "filename": "GxkkcEUvgSYWq0Iy2T9NnbqmBzYGl6Xday5FGhp66kM=.pem", + "location": "security-state-staging/intermediates/c3e6686e-c252-4c57-8d5a-6d3e19c03ece.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GxkkcEUvgSYWq0Iy2T9NnbqmBzYGl6Xday5FGhp66kM=", + "crlite_enrolled": false, + "id": "c378b647-3422-49bf-8425-2d7c89cad3af", + "last_modified": 1663786625494 + }, + { + "schema": 1663786255420, + "derHash": "dCZArGf41xssR3dQdK2MYLriwBI6sYNUwkwXGTLYYYM=", + "subject": "CN=eMudhra ECC Organization Validation Secure Server CA,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIGbMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxPTA7BgNVBAMTNGVNdWRocmEgRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "088bd1a2e7c9544c4dc555fbf9a888c314f577c8a74a9eb6273d5d3d4ee6437c", + "size": 1353, + "filename": "1Mpue8qB6gEzwG3fbKc1IdTydXsa-PS26DWSlP08NHA=.pem", + "location": "security-state-staging/intermediates/50a35242-d62b-4357-8c14-a2c7cd4430f1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1Mpue8qB6gEzwG3fbKc1IdTydXsa+PS26DWSlP08NHA=", + "crlite_enrolled": false, + "id": "fa5eec68-081b-41c9-84d7-455fdbc614af", + "last_modified": 1663786625487 + }, + { + "schema": 1663786246708, + "derHash": "SW91SZGMpkRI/eobpgWuOo7aN3rSHM7RfnmFhkaLGgE=", + "subject": "CN=Verokey Secure Site (ECC),O=Verokey,C=AU", + "subjectDN": "MEMxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSIwIAYDVQQDExlWZXJva2V5IFNlY3VyZSBTaXRlIChFQ0Mp", + "whitelist": false, + "attachment": { + "hash": "58d016f621a3e365554244cdd72503f3e68440cf4c3c61af1128bc279d494764", + "size": 1228, + "filename": "KEfbOZyGCFUBzJZiZQbU_I60GckBWF8w10VqJoSlCiM=.pem", + "location": "security-state-staging/intermediates/81185301-dc73-4e95-8ce9-10cad2163699.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KEfbOZyGCFUBzJZiZQbU/I60GckBWF8w10VqJoSlCiM=", + "crlite_enrolled": false, + "id": "ee35c056-5cb2-4175-887b-46af77e0feee", + "last_modified": 1663786625481 + }, + { + "schema": 1663786248472, + "derHash": "fwfBL/9GU9a7ZfnM/RoOnuRmiE6wdyd7ubtF12JDzCI=", + "subject": "CN=cPanel Extended Validation RSA Certification Authority,OU=Controlled by COMODO exclusively for cPanel\\, Inc.,O=cPanel\\, Inc.,L=Houston,ST=TX,C=US", + "subjectDN": "MIHAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVFgxEDAOBgNVBAcTB0hvdXN0b24xFTATBgNVBAoTDGNQYW5lbCwgSW5jLjE6MDgGA1UECxMxQ29udHJvbGxlZCBieSBDT01PRE8gZXhjbHVzaXZlbHkgZm9yIGNQYW5lbCwgSW5jLjE/MD0GA1UEAxM2Y1BhbmVsIEV4dGVuZGVkIFZhbGlkYXRpb24gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "3a0e29dccb6cd00fec5f4ac39e84417371b598a3214552e382f70b50bb852af0", + "size": 2263, + "filename": "KpHjtz2O9rMfP4azz-Q54sswiBTZMPXTpgqz8YbaLxY=.pem", + "location": "security-state-staging/intermediates/a1eb829c-52de-4c21-b46f-8d4dfe82073e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KpHjtz2O9rMfP4azz+Q54sswiBTZMPXTpgqz8YbaLxY=", + "crlite_enrolled": false, + "id": "937cf717-f122-4278-833c-a2aa0da62c8a", + "last_modified": 1663786625460 + }, + { + "schema": 1663786249318, + "derHash": "wWnWndPSbTbLgt8TXFf+M27P9+fqQB6Z484ZF6j7gYA=", + "subject": "CN=CSP SSL Service CA 5,O=CENTRAL SECURITY PATROLS CO.\\, LTD.,L=Shinjuku-ku,ST=Tokyo,C=JP", + "subjectDN": "MH8xCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEBxMLU2hpbmp1a3Uta3UxKzApBgNVBAoTIkNFTlRSQUwgU0VDVVJJVFkgUEFUUk9MUyBDTy4sIExURC4xHTAbBgNVBAMTFENTUCBTU0wgU2VydmljZSBDQSA1", + "whitelist": false, + "attachment": { + "hash": "b53bbabfdb6bc860b29381d741f151ba37e4505a6e59c7748435e5ad8b141f24", + "size": 2154, + "filename": "feELO37IL4l2CJfivsLI0VTQkKQc7sskntJ5vxPVVW8=.pem", + "location": "security-state-staging/intermediates/1c8e31b4-e999-4a9c-8ba3-128b9c701303.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "feELO37IL4l2CJfivsLI0VTQkKQc7sskntJ5vxPVVW8=", + "crlite_enrolled": false, + "id": "03b13f89-3801-4577-bc0e-a2997dfd8ae2", + "last_modified": 1663786625446 + }, + { + "schema": 1663786240629, + "derHash": "l6cYCCt47nSAl5shFtS6AhXGGqpSC0bKa5wqs3j0CCE=", + "subject": "CN=Sectigo SHA-256 OV Secure Server CA,O=Sectigo Limited,C=GB", + "subjectDN": "MFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gU0hBLTI1NiBPViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "a4653012fdc300a8c96716c3b0593ce16c431b8768ccbd5193c8d0aeaee652ca", + "size": 1613, + "filename": "318hNJIiS7rMUgV2mjh-LHcZUnFNjtl4to5OuT2nr4s=.pem", + "location": "security-state-staging/intermediates/4c2e1ba8-26f1-438a-8923-5ffddfe213db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "318hNJIiS7rMUgV2mjh+LHcZUnFNjtl4to5OuT2nr4s=", + "crlite_enrolled": false, + "id": "6b4cbf82-1c69-43e5-98c8-09565ead65b8", + "last_modified": 1663786625405 + }, + { + "schema": 1663786238894, + "derHash": "+4zIwu0bOE3kWfIX1U08zPCJDsWKqdl8Eo6jIYpCYyU=", + "subject": "CN=Omit Security RSA Extended Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxMTAvBgNVBAMTKE9taXQgU2VjdXJpdHkgUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0E=", + "whitelist": false, + "attachment": { + "hash": "72e48983e66c42ba624b446dda43981fe3df8edf6cb09bf92935d597f3f9de37", + "size": 2263, + "filename": "pRhSVuIRF4agMyw-eQL9d_wVikShwRLflEjYMNExMxQ=.pem", + "location": "security-state-staging/intermediates/f649dbf4-4653-4f68-a6f0-35abbed53d12.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "pRhSVuIRF4agMyw+eQL9d/wVikShwRLflEjYMNExMxQ=", + "crlite_enrolled": false, + "id": "1c70d86c-ef0b-46f4-98c3-c651d9d768ae", + "last_modified": 1663786625398 + }, + { + "schema": 1663786234596, + "derHash": "P2H4DHzQVrhJO+ROmXmaC7cPAbpi89UfZGvCdrG5WjE=", + "subject": "CN=PSW GROUP (ECC) OV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChFQ0MpIE9WIENB", + "whitelist": false, + "attachment": { + "hash": "210cfeeb08450c6e130a56b55a30b1d942e131dabe1271b953d7a7927c777a1d", + "size": 1240, + "filename": "VXaG8WOJDIeerycUKi_172jWyS0TrTMTpOCs-dvdVZM=.pem", + "location": "security-state-staging/intermediates/6ad56217-78b8-49fc-ace3-753e8928dbd9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VXaG8WOJDIeerycUKi/172jWyS0TrTMTpOCs+dvdVZM=", + "crlite_enrolled": false, + "id": "71062991-e3ea-4fef-ba1c-1583e1df60f2", + "last_modified": 1663786625371 + }, + { + "schema": 1663786232882, + "derHash": "wG7UFefe0G5aSgg6gCX2XkXU5GtjUema20feSLAvi+0=", + "subject": "CN=CertCenter Enterprise RSA DV CA,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MHIxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZIZXNzZW4xEDAOBgNVBAcTB0dpZXNzZW4xFjAUBgNVBAoTDUNlcnRDZW50ZXIgQUcxKDAmBgNVBAMTH0NlcnRDZW50ZXIgRW50ZXJwcmlzZSBSU0EgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "47e6d5451a30c283ca90ae38210ca9f8f849b0e4b510b3cfecc7b413f2c50f07", + "size": 2133, + "filename": "FfDDB8UNFaSYYtmGpdb9uuU8NXG50-ypsKYjpMCN2c0=.pem", + "location": "security-state-staging/intermediates/6ed5ac38-b157-41e9-8172-4efee7179bef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "FfDDB8UNFaSYYtmGpdb9uuU8NXG50+ypsKYjpMCN2c0=", + "crlite_enrolled": false, + "id": "9b1dd7a9-f2e4-44b2-873f-a3aecceb34e2", + "last_modified": 1663786625365 + }, + { + "schema": 1663786235444, + "derHash": "nGJKAXdk/Lo2NYZ5gEp1ys6V8ETwVd/fAlBfOwYBfzQ=", + "subject": "CN=Sectigo Partners - Server Authentication - ECC,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUGFydG5lcnMgLSBTZXJ2ZXIgQXV0aGVudGljYXRpb24gLSBFQ0M=", + "whitelist": false, + "attachment": { + "hash": "4301025cd95d434f3bf95ac473c040fa149c913c086d8df20cb78ff56ceb2c87", + "size": 1280, + "filename": "_WhZ_JfPDtfaL87jEAix_9CGklMDqSAXqw8H6I5WmaM=.pem", + "location": "security-state-staging/intermediates/78768961-a784-4914-826d-12a0730f7d2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/WhZ/JfPDtfaL87jEAix/9CGklMDqSAXqw8H6I5WmaM=", + "crlite_enrolled": false, + "id": "3377e410-48b3-4539-b704-678f14914f2d", + "last_modified": 1663786625358 + }, + { + "schema": 1663786230301, + "derHash": "OcedKkz/4Jxh3mnsFJkdjpXOcTTs9x7weZANCtVHjK0=", + "subject": "CN=BlackCert\\, Inc. ECC EV Certification Authority,OU=Controlled by COMODO exclusively for BlackCert\\, Inc.,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MIG9MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ08xDzANBgNVBAcTBkRlbnZlcjEYMBYGA1UEChMPQmxhY2tDZXJ0LCBJbmMuMT0wOwYDVQQLEzRDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgQmxhY2tDZXJ0LCBJbmMuMTcwNQYDVQQDEy5CbGFja0NlcnQsIEluYy4gRUNDIEVWIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "cbf77056b300bdaecc476ef372be42ee1bbb163d57247103d428fd65833bc732", + "size": 1431, + "filename": "YMJYGd5waNYmUtnRh_uwLFzS5lXUfdRoNphUMGKD7UY=.pem", + "location": "security-state-staging/intermediates/b5439e20-bf07-4d4a-8a0e-1427b2b6b0f1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YMJYGd5waNYmUtnRh/uwLFzS5lXUfdRoNphUMGKD7UY=", + "crlite_enrolled": false, + "id": "50d77a86-7f84-4da8-8e05-e89dfa9cd97b", + "last_modified": 1663786625337 + }, + { + "schema": 1663786220064, + "derHash": "qYQdLkfL5tcdj6/fODh/k/Q9dteSUE77F6IQIMWMC4k=", + "subject": "CN=Sectigo Qualified Website Authentication CA Natural E35,O=Sectigo (Europe) SL,C=ES", + "subjectDN": "MG0xCzAJBgNVBAYTAkVTMRwwGgYDVQQKExNTZWN0aWdvIChFdXJvcGUpIFNMMUAwPgYDVQQDEzdTZWN0aWdvIFF1YWxpZmllZCBXZWJzaXRlIEF1dGhlbnRpY2F0aW9uIENBIE5hdHVyYWwgRTM1", + "whitelist": false, + "attachment": { + "hash": "2d2aab66e8404758b5758de10db64fea402d592c6547b312637e9b3497df0e7a", + "size": 1256, + "filename": "_E4OWM1It5EGPdb8mrqad2xJL7i_-r8fvlJCYaFSPu8=.pem", + "location": "security-state-staging/intermediates/db474b30-202b-4208-929d-6879ca45f652.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/E4OWM1It5EGPdb8mrqad2xJL7i/+r8fvlJCYaFSPu8=", + "crlite_enrolled": false, + "id": "d897a056-2a85-4827-9ba0-d2782d50f2ea", + "last_modified": 1663786625256 + }, + { + "schema": 1663786218358, + "derHash": "Aldk5uGdkLxsxwZpRFWT1T/A2YNve5QoA2IIYQtTmUM=", + "subject": "CN=TrustSign ECC EV CA,OU=Controlled by Sectigo exclusively for Ziwit,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MIGZMQswCQYDVQQGEwJGUjEQMA4GA1UECBMHSGVyYXVsdDEUMBIGA1UEBxMLTW9udHBlbGxpZXIxDjAMBgNVBAoTBVppd2l0MTQwMgYDVQQLEytDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIFppd2l0MRwwGgYDVQQDExNUcnVzdFNpZ24gRUNDIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "8689745a20d37bbbc0be468bf0d8af75318c9fedbe27e9a4c549927dd8d402c3", + "size": 1382, + "filename": "JtXGnD_eYf2MNlncNj_WbCi9uD6VYCdIXyT735ieGbg=.pem", + "location": "security-state-staging/intermediates/072d3e2f-c6d6-4fad-b857-dd2ce6d10d88.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JtXGnD/eYf2MNlncNj/WbCi9uD6VYCdIXyT735ieGbg=", + "crlite_enrolled": false, + "id": "3e7d59e2-7b46-41ac-acfe-e9767c392d58", + "last_modified": 1663786625249 + }, + { + "schema": 1663786219237, + "derHash": "gqG7ouv+rq5snTToQaGoljd/4tA33Kip3v99OAcDmNY=", + "subject": "CN=Verokey High Assurance Business (ECC),O=Verokey,C=AU", + "subjectDN": "ME8xCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MS4wLAYDVQQDEyVWZXJva2V5IEhpZ2ggQXNzdXJhbmNlIEJ1c2luZXNzIChFQ0Mp", + "whitelist": false, + "attachment": { + "hash": "e40028283fefd7e84c8c1cef86c79abb705c812f9cc5b9354c2826e8de657360", + "size": 1232, + "filename": "rTRi0OMMsWcdMjzm7_ul5cmMkqVN7jui8CNXJREPeGM=.pem", + "location": "security-state-staging/intermediates/6b06d729-8be3-4df3-9838-35914a46ce79.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rTRi0OMMsWcdMjzm7/ul5cmMkqVN7jui8CNXJREPeGM=", + "crlite_enrolled": false, + "id": "257321c1-9e8e-4b9e-884d-ce79493f1cbf", + "last_modified": 1663786625242 + }, + { + "schema": 1663786216472, + "derHash": "uilZ+kSOmALIWfkMtdrHOO952/zM1NQHlDZR8LBFahM=", + "subject": "CN=OneSignSSL ECC EV Secure Server CA,OU=Controlled by Sectigo exclusively for One Sign Pte. Ltd.,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MIHCMQswCQYDVQQGEwJTRzESMBAGA1UECBMJU2luZ2Fwb3JlMRIwEAYDVQQHEwlTaW5nYXBvcmUxGzAZBgNVBAoTEk9uZSBTaWduIFB0ZS4gTHRkLjFBMD8GA1UECxM4Q29udHJvbGxlZCBieSBTZWN0aWdvIGV4Y2x1c2l2ZWx5IGZvciBPbmUgU2lnbiBQdGUuIEx0ZC4xKzApBgNVBAMTIk9uZVNpZ25TU0wgRUNDIEVWIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "407728b9c1868b1b41651d9f4ba73f6ec824e21e296ccb5d53a089be4802f2af", + "size": 1435, + "filename": "zWWzic6NajzDYmdUGim_cTkcA88HtIi7aWRbwgP9kKU=.pem", + "location": "security-state-staging/intermediates/fc77bcfe-20ac-48c8-8c85-07a5b0227c7e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zWWzic6NajzDYmdUGim/cTkcA88HtIi7aWRbwgP9kKU=", + "crlite_enrolled": false, + "id": "683a0a38-fb6e-407c-b548-88de90b4bcb4", + "last_modified": 1663786625228 + }, + { + "schema": 1663786221757, + "derHash": "b1h9G+4vFhxPwuZiBT35z/7+URRM6SfTFD5KTDSLhWc=", + "subject": "CN=GENIOUS ECC Organization Validation Secure Server CA,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIGVMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxPTA7BgNVBAMTNEdFTklPVVMgRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "d09d87738623b188bae447ddb1c66ef0566fe71daf7f41395de20c27ec082b52", + "size": 1345, + "filename": "9JEABMnN_SwMIz1lP1QSJXAr9u9DfOC_51G3sB_hgvg=.pem", + "location": "security-state-staging/intermediates/dd284600-85a4-404a-b6b8-8b721f7750bb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9JEABMnN/SwMIz1lP1QSJXAr9u9DfOC/51G3sB/hgvg=", + "crlite_enrolled": false, + "id": "d5635adf-8c37-4fc5-8c8c-9da7d2131f0e", + "last_modified": 1663786625221 + }, + { + "schema": 1663786215594, + "derHash": "Yt3PoFpjrPt8IHCkNSY/ImvyA5/BHRzPxzZ5LkRmmDs=", + "subject": "CN=BitCert RSA Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for BitCert,O=成都数证科技有限公司,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MIHLMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHU2ljaHVhbjEQMA4GA1UEBxMHQ2hlbmdkdTEnMCUGA1UECgwe5oiQ6YO95pWw6K+B56eR5oqA5pyJ6ZmQ5YWs5Y+4MTYwNAYDVQQLEy1Db250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIEJpdENlcnQxNzA1BgNVBAMTLkJpdENlcnQgUlNBIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "8aed570f7ad4ad44241cfa9f9eff5fd8ad9aadaf6ee803e458cff7b325da5649", + "size": 2288, + "filename": "zdcDW7Ob8cQk0OUfB1X4d7hxi0MhL_P6fBOYC60fvWs=.pem", + "location": "security-state-staging/intermediates/c36facb9-c315-46eb-8ec9-c4daa39b8944.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zdcDW7Ob8cQk0OUfB1X4d7hxi0MhL/P6fBOYC60fvWs=", + "crlite_enrolled": false, + "id": "8193aeac-378e-42e9-9700-e2f8a1f4f8ec", + "last_modified": 1663786625215 + }, + { + "schema": 1663786213464, + "derHash": "FlLGbbZC0KUkv4n/3z7tWo6UsUpusH56ReqAxU+ehZM=", + "subject": "CN=DigitalTrust High Assurance CA G3 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MG0xCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjE/MD0GA1UEAww2RGlnaXRhbFRydXN0IEhpZ2ggQXNzdXJhbmNlIENBIEczICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "e95b3109e389f155cf24e55a56fab76ca289e0468d557d586422616ef4b39d30", + "size": 1358, + "filename": "mLgcAaWaR7874mBWx4gTZEcxbuqvMnF41qXNwAW8A1E=.pem", + "location": "security-state-staging/intermediates/1859d7f8-d465-4dd6-ac20-ecac31696d6f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mLgcAaWaR7874mBWx4gTZEcxbuqvMnF41qXNwAW8A1E=", + "crlite_enrolled": false, + "id": "da3ebee7-4178-4c0a-a0cb-603601101b83", + "last_modified": 1663786625208 + }, + { + "schema": 1663786211817, + "derHash": "FfrEJaqEQEs2L6NInrZ9BV1donb4aFxNaY2hgw1CDw8=", + "subject": "CN=COMODO RSA Client Authentication and Secure Email CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGXMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE9MDsGA1UEAxM0Q09NT0RPIFJTQSBDbGllbnQgQXV0aGVudGljYXRpb24gYW5kIFNlY3VyZSBFbWFpbCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "680f7cb3b0c5dd3322fa9e91ca4b95160e8279cb28f7c7344065b69851e278ab", + "size": 2105, + "filename": "mHI0vDHAQ12vglM3cO2DlsvGPmhqena8_DpLQz3BaYs=.pem", + "location": "security-state-staging/intermediates/171d47aa-68b4-4b8e-be92-91f4d69e6bcd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mHI0vDHAQ12vglM3cO2DlsvGPmhqena8/DpLQz3BaYs=", + "crlite_enrolled": false, + "id": "03565241-38f4-41ae-894a-a4f2e660a0a9", + "last_modified": 1663786625187 + }, + { + "schema": 1663786210056, + "derHash": "SKL31+gNQucip1LYkjgcBu7EQUFVydZFKqUG7qTtQ/Y=", + "subject": "CN=Trustico ECC OV CA,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MG4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIEwZMb25kb24xEDAOBgNVBAcTB0Nyb3lkb24xHzAdBgNVBAoTFlRoZSBUcnVzdGljbyBHcm91cCBMdGQxGzAZBgNVBAMTElRydXN0aWNvIEVDQyBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "5c0dbb17419e274b842fc2940f0f389fe4e5dae3be8ecf7932b6c68b1455ecd9", + "size": 1272, + "filename": "WBn6yHQKpmzXFlZmp92hV73p71SuDLIwooOX99zzGBs=.pem", + "location": "security-state-staging/intermediates/acb254e8-2f39-4e7a-9ab6-a573d1334ccd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WBn6yHQKpmzXFlZmp92hV73p71SuDLIwooOX99zzGBs=", + "crlite_enrolled": false, + "id": "749c1ffb-e04b-43c6-889f-c4bde9c5ad7e", + "last_modified": 1663786625172 + }, + { + "schema": 1663786208254, + "derHash": "xcX8r25L0K2VvYvr0a6/Fl0dv0SagS8Swatuq2zHkV4=", + "subject": "CN=KeyNet Systems RSA DV CA,O=KeyNet Systems LLC,L=Las Vegas,ST=Nevada,C=US", + "subjectDN": "MHIxCzAJBgNVBAYTAlVTMQ8wDQYDVQQIEwZOZXZhZGExEjAQBgNVBAcTCUxhcyBWZWdhczEbMBkGA1UEChMSS2V5TmV0IFN5c3RlbXMgTExDMSEwHwYDVQQDExhLZXlOZXQgU3lzdGVtcyBSU0EgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "35fd4e19cb19a20ed2e5f925f36236d78743959baec9fcf7329291c0122fe6e1", + "size": 2138, + "filename": "VTJmz2gVhXT9nFPyCAVJ7HDpV4Rfyg-dNTgE3kbMShE=.pem", + "location": "security-state-staging/intermediates/3d3331b5-9658-4b69-ad99-28073ad83c6c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VTJmz2gVhXT9nFPyCAVJ7HDpV4Rfyg+dNTgE3kbMShE=", + "crlite_enrolled": false, + "id": "2bcf62e8-a5fa-4c24-9042-19e0f3a6785a", + "last_modified": 1663786625165 + }, + { + "schema": 1663786209152, + "derHash": "Elpf18ZA1eWfXOV2PNjJMvXll93cTq8dWWZ89LVWojc=", + "subject": "CN=USERTrust RSA Extended Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGVMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE7MDkGA1UEAxMyVVNFUlRydXN0IFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2e9d75f7d0d8aa06bfac1b13a625d3fbb6324f37551aa7ab61d162cf02b2b8b7", + "size": 2174, + "filename": "8E_u4IJLStWPqhVEn4td_2Ae6WYCii1AmJC2dgTCj1s=.pem", + "location": "security-state-staging/intermediates/00dd2de4-de11-4300-977b-1a5b2e1d3a11.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8E/u4IJLStWPqhVEn4td/2Ae6WYCii1AmJC2dgTCj1s=", + "crlite_enrolled": false, + "id": "cc51eda3-828e-45a1-b371-d7933fe0dd67", + "last_modified": 1663786625151 + }, + { + "schema": 1663786203928, + "derHash": "l59ghLeI9tECyXjZ7x2kzMkWXDaYGioaPOP0UO5IgOA=", + "subject": "CN=Omit Security RSA Organization Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxNTAzBgNVBAMTLE9taXQgU2VjdXJpdHkgUlNBIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "f0445304665ab2df900d9c622e9b705981ff4fea42f9b211c1b8277be4b90a7a", + "size": 2268, + "filename": "nUrKAUH1HHuQjUl8XeRRJaFGQa4OVx62g1P0aSaxdZ0=.pem", + "location": "security-state-staging/intermediates/4d6c291a-2150-46d4-a796-ac1d4efb3fa9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nUrKAUH1HHuQjUl8XeRRJaFGQa4OVx62g1P0aSaxdZ0=", + "crlite_enrolled": false, + "id": "eb896b8b-34f7-4b31-8ca9-429754a2d9ce", + "last_modified": 1663786625123 + }, + { + "schema": 1663786201235, + "derHash": "E/ezfMH4RCqM4T57RCIhfY6JlXz+qFDDy751y+Vh53Q=", + "subject": "CN=CertCloud ECC EV TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "ME0xCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMSAwHgYDVQQDExdDZXJ0Q2xvdWQgRUNDIEVWIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "6af196e65acca11a6b1ae085c4a089340bed7180338c071cfdc7487d70bbd379", + "size": 1272, + "filename": "S9iaDtfedpswiFg99kL_1fGIcuuU0gvf0uBHeNGHlmw=.pem", + "location": "security-state-staging/intermediates/d6df969e-f4d9-4948-89f9-268f158d3101.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S9iaDtfedpswiFg99kL/1fGIcuuU0gvf0uBHeNGHlmw=", + "crlite_enrolled": false, + "id": "c09e10cf-333a-4b4f-a02e-0843a8b26930", + "last_modified": 1663786625107 + }, + { + "schema": 1663786200366, + "derHash": "cZQ0Ojqs2mKaJMULZF+fDMTlGUJ3mqByAnapTyG/VfY=", + "subject": "CN=PSW GROUP (ECC) EV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChFQ0MpIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "34dac5a6d60fb1dd2d153d416d8cae8d3a701e91106c9684350a57fb2571e76e", + "size": 1272, + "filename": "scuMyj9NwSFH70ot_DBsYulkVhwaNFAbwDAK6FaE-io=.pem", + "location": "security-state-staging/intermediates/170fda9d-6d7b-48e8-b281-ebcf097a7cf9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "scuMyj9NwSFH70ot/DBsYulkVhwaNFAbwDAK6FaE+io=", + "crlite_enrolled": false, + "id": "c144e9df-5015-4265-bd43-190661a5a599", + "last_modified": 1663786625094 + }, + { + "schema": 1663786203062, + "derHash": "J84nIGUCAw46ELyW06IMPYiV+HH7ar4dgEdKtFbzGvU=", + "subject": "CN=BitCert ECC Business Secure Site CA,O=BitCert,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdTaWNodWFuMRAwDgYDVQQHEwdDaGVuZ2R1MRAwDgYDVQQKEwdCaXRDZXJ0MSwwKgYDVQQDEyNCaXRDZXJ0IEVDQyBCdXNpbmVzcyBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "00aecf4f1dec26b7f68f5e0d93ff1e485f5d840fd451d4e0266ba7a77e9412cd", + "size": 1293, + "filename": "WavFTPiAKmlGYUqrK6pToCjDxiEpoNAPMXa5dEGGoi0=.pem", + "location": "security-state-staging/intermediates/3d31722e-5a59-45aa-82f7-1d14ed749e3d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WavFTPiAKmlGYUqrK6pToCjDxiEpoNAPMXa5dEGGoi0=", + "crlite_enrolled": false, + "id": "0d0c8f8d-bb1c-443f-81cc-e55adee103a3", + "last_modified": 1663786625080 + }, + { + "schema": 1663786196929, + "derHash": "fsjnqkIZXpo2uFjilmnqo31XsTr6IzG9rqah6/fkbn4=", + "subject": "CN=CertCenter Enterprise ECC EV CA,OU=Controlled by COMODO CA exclusively for CertCenter AG,O=CertCenter AG,L=Giessen,ST=Hessen,C=DE", + "subjectDN": "MIGyMQswCQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRAwDgYDVQQHEwdHaWVzc2VuMRYwFAYDVQQKEw1DZXJ0Q2VudGVyIEFHMT4wPAYDVQQLEzVDb250cm9sbGVkIGJ5IENPTU9ETyBDQSBleGNsdXNpdmVseSBmb3IgQ2VydENlbnRlciBBRzEoMCYGA1UEAxMfQ2VydENlbnRlciBFbnRlcnByaXNlIEVDQyBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "1178434d55986dcbe450b8ae8f40e37d7f8ab9a1263af1edd377c1977031bc0e", + "size": 1414, + "filename": "9IEsy6v-cBI7ee5Y4xt277Tel65rfvCSsKBMLrGfjVI=.pem", + "location": "security-state-staging/intermediates/0a92f9ba-738a-4078-8687-7c5e715a8d58.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9IEsy6v+cBI7ee5Y4xt277Tel65rfvCSsKBMLrGfjVI=", + "crlite_enrolled": false, + "id": "bca06865-db6e-4a2f-8e37-99a8fa30dcc2", + "last_modified": 1663786625073 + }, + { + "schema": 1663786197767, + "derHash": "P7oKpJ+94cgUyoMIeO0Dx9JDI/Y0g+Ut7o/0LBU8I08=", + "subject": "CN=GlobeSSL EV Certification Authority 2,OU=Controlled by COMODO exclusively for Globe Hosting\\, Inc.,O=Globe Hosting\\, Inc.,L=Wilmington,ST=DE,C=US", + "subjectDN": "MIHAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCREUxEzARBgNVBAcTCldpbG1pbmd0b24xHDAaBgNVBAoTE0dsb2JlIEhvc3RpbmcsIEluYy4xQTA/BgNVBAsTOENvbnRyb2xsZWQgYnkgQ09NT0RPIGV4Y2x1c2l2ZWx5IGZvciBHbG9iZSBIb3N0aW5nLCBJbmMuMS4wLAYDVQQDEyVHbG9iZVNTTCBFViBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAy", + "whitelist": false, + "attachment": { + "hash": "f06b43560a33b7807368324ccf447e5bcb73d414540229cb7e2a299286b5dbe1", + "size": 2276, + "filename": "iQ6SmjjdklCphf7sBBL9m4lWWf44bngHmvy7B8dl6XQ=.pem", + "location": "security-state-staging/intermediates/a210dedc-b460-4b50-955f-29bffcbdf15a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iQ6SmjjdklCphf7sBBL9m4lWWf44bngHmvy7B8dl6XQ=", + "crlite_enrolled": false, + "id": "df620035-09a4-4024-9f85-7ef537405397", + "last_modified": 1663786625065 + }, + { + "schema": 1663786196048, + "derHash": "SSY9SthPOS1+ksTC8iQEBcyG+t8Strpgp3RRPJPBAYY=", + "subject": "CN=Verokey Verified Business,O=Verokey,C=AU", + "subjectDN": "MEMxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSIwIAYDVQQDExlWZXJva2V5IFZlcmlmaWVkIEJ1c2luZXNz", + "whitelist": false, + "attachment": { + "hash": "fc77741c5389e3c338c47843443a305bb54ea23b33ffed02a7cf4e061577ce07", + "size": 2239, + "filename": "An8uJvTqHfwY0_-ynaShDbvgWhyS1u6LiQpWVu048_M=.pem", + "location": "security-state-staging/intermediates/9eda3658-c720-4d2d-a0c6-c5c5a7e1647c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "An8uJvTqHfwY0/+ynaShDbvgWhyS1u6LiQpWVu048/M=", + "crlite_enrolled": false, + "id": "b1c97468-20ac-48c5-82ce-d9a156a3766f", + "last_modified": 1663786625050 + }, + { + "schema": 1663786195168, + "derHash": "3LiusAU7pcUw9Hor4dHk93taB24diij202yPHv3nYaM=", + "subject": "CN=上海锐成信息科技有限公司 DV CA,O=上海锐成信息科技有限公司,C=CN", + "subjectDN": "MHExCzAJBgNVBAYTAkNOMS0wKwYDVQQKDCTkuIrmtbfplJDmiJDkv6Hmga/np5HmioDmnInpmZDlhazlj7gxMzAxBgNVBAMMKuS4iua1t+mUkOaIkOS/oeaBr+enkeaKgOaciemZkOWFrOWPuCBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "5c432fe0a440afcd246f19683e406759c816fd01d81e8f431215e4302a449557", + "size": 2125, + "filename": "bVTZhSTxXd9oL6FqukK0jPVCRZZN4NKl8Nq2p5TLW1Y=.pem", + "location": "security-state-staging/intermediates/403eee64-4e15-4445-9125-880c50700edc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bVTZhSTxXd9oL6FqukK0jPVCRZZN4NKl8Nq2p5TLW1Y=", + "crlite_enrolled": false, + "id": "3571c852-2474-4bd5-a552-220e45a48138", + "last_modified": 1663786625037 + }, + { + "schema": 1663786194347, + "derHash": "PHLh9aCgG80zsYAetARJla5Mmxuot9tRNnXcv3+CKJo=", + "subject": "CN=E-SAFER ORGANIZATION SSL ECC CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGLMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTE9MDsGA1UEAww0RS1TQUZFUiBPUkdBTklaQVRJT04gU1NMIEVDQyBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "1803cc84ed2e28cc67a8e3ffc1e3661fb116014edf0688fb0c421e1ef2bec684", + "size": 1325, + "filename": "Npod1XsOwiAqsSReK1vco3WHhvi_2dcU_nupbYHmSmo=.pem", + "location": "security-state-staging/intermediates/48155cfa-c1a6-4ecd-9104-ade6ca2ca21b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Npod1XsOwiAqsSReK1vco3WHhvi/2dcU/nupbYHmSmo=", + "crlite_enrolled": false, + "id": "8fdffd79-5bb1-448b-90d3-11e3584a9160", + "last_modified": 1663786625029 + }, + { + "schema": 1663786189121, + "derHash": "MWImoQ0g2iJE0dqLwLKCg1ral92pT5AVPDy/n9UI8/s=", + "subject": "CN=TERENA eScience SSL CA 2,O=TERENA,L=Amsterdam,ST=Noord-Holland,C=NL", + "subjectDN": "MG0xCzAJBgNVBAYTAk5MMRYwFAYDVQQIEw1Ob29yZC1Ib2xsYW5kMRIwEAYDVQQHEwlBbXN0ZXJkYW0xDzANBgNVBAoTBlRFUkVOQTEhMB8GA1UEAxMYVEVSRU5BIGVTY2llbmNlIFNTTCBDQSAy", + "whitelist": false, + "attachment": { + "hash": "6086bae59471fac82182c394c42a30cd6147767167137c104869d063e26efd10", + "size": 2162, + "filename": "oc7AIKwh7i1ZIjRUXdABQp1IDxMtDZil5mONjuWhNrw=.pem", + "location": "security-state-staging/intermediates/7c02a248-d55d-4d08-9f14-62e6089e3af6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oc7AIKwh7i1ZIjRUXdABQp1IDxMtDZil5mONjuWhNrw=", + "crlite_enrolled": false, + "id": "0e12a901-b06f-4671-b20e-8f894026f0ac", + "last_modified": 1663786625004 + }, + { + "schema": 1663786188270, + "derHash": "TY7Uo7Xzz3T+yHXoIEISpyGX1x3aOgeuX6W+/rYeycY=", + "subject": "CN=Trustico ECC EV CA,OU=Controlled by COMODO exclusively for The Trustico Group Ltd,O=The Trustico Group Ltd,L=Croydon,ST=London,C=GB", + "subjectDN": "MIG0MQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMRAwDgYDVQQHEwdDcm95ZG9uMR8wHQYDVQQKExZUaGUgVHJ1c3RpY28gR3JvdXAgTHRkMUQwQgYDVQQLEztDb250cm9sbGVkIGJ5IENPTU9ETyBleGNsdXNpdmVseSBmb3IgVGhlIFRydXN0aWNvIEdyb3VwIEx0ZDEbMBkGA1UEAxMSVHJ1c3RpY28gRUNDIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "e854fbf2f96a0ba4c5a75bdf2602ff2a99e4bb2096822c4755c8148a4c98e2f2", + "size": 1406, + "filename": "oSp7zLM2UdmSs3L-sF5vVgDPO3AMbxo2i3f5JYu4BYI=.pem", + "location": "security-state-staging/intermediates/c8de19a4-056a-423c-a8a3-4fefbedca2ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oSp7zLM2UdmSs3L+sF5vVgDPO3AMbxo2i3f5JYu4BYI=", + "crlite_enrolled": false, + "id": "9b2f91f7-174b-43a3-8585-26d804165907", + "last_modified": 1663786624996 + }, + { + "schema": 1663786184777, + "derHash": "d1MwBWSsZl1W/vW9r9mciKcxwh6tjjmWyzc+ea39v9w=", + "subject": "CN=SignSec High-Assurance OV Authority,OU=SSL Department,O=FBS Inc,L=Irvine,ST=CA,C=US", + "subjectDN": "MIGEMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExDzANBgNVBAcTBklydmluZTEQMA4GA1UEChMHRkJTIEluYzEXMBUGA1UECxMOU1NMIERlcGFydG1lbnQxLDAqBgNVBAMTI1NpZ25TZWMgSGlnaC1Bc3N1cmFuY2UgT1YgQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "b3f6b2b0fcf51e58b433e5f7227ed488ff7643994e55098dff3724b805ca9a1b", + "size": 2162, + "filename": "-NJzGF6Gew8FPxWa8xRHx0tz0t0bJjNH38uDaSIgIqI=.pem", + "location": "security-state-staging/intermediates/f538856a-1986-4ea8-9af5-e9a9636d887e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+NJzGF6Gew8FPxWa8xRHx0tz0t0bJjNH38uDaSIgIqI=", + "crlite_enrolled": false, + "id": "30bd429d-11ce-4d9a-b5a8-5c55e73d232c", + "last_modified": 1663786624969 + }, + { + "schema": 1663786185682, + "derHash": "LNS3kUOdL0Zf/G/a+SXgB0q1C61Olxev1fXRvNnkh78=", + "subject": "CN=CertAssure ECC OV Secure Server Certification Authority,O=CertAssure Inc.,L=San Jose,ST=CA,C=US", + "subjectDN": "MIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRgwFgYDVQQKEw9DZXJ0QXNzdXJlIEluYy4xQDA+BgNVBAMTN0NlcnRBc3N1cmUgRUNDIE9WIFNlY3VyZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "e996aae6de450ab3337d594ba14ab3beb202d478dfa1c795685be8548d56868f", + "size": 1329, + "filename": "RHKZjmRhs4WSRd8tMaFGldq7b6_ydn4V_z1gGMLw964=.pem", + "location": "security-state-staging/intermediates/2055c982-8a8d-4529-a8af-e58ca1c6e51a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RHKZjmRhs4WSRd8tMaFGldq7b6/ydn4V/z1gGMLw964=", + "crlite_enrolled": false, + "id": "c1e4d8ec-8d80-4f4a-9c0a-7a81f83941bf", + "last_modified": 1663786624962 + }, + { + "schema": 1663786182228, + "derHash": "GY4zLxD6D1ivCDrpZLxkQfBs0PrTGbEjX+PtD3xur5M=", + "subject": "CN=WebNIC RSA Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for WebNIC,O=WebNIC (Web Commerce Communications (Singapore) Pte. Ltd.),L=Singapore,C=SG", + "subjectDN": "MIHVMQswCQYDVQQGEwJTRzESMBAGA1UEBxMJU2luZ2Fwb3JlMUMwQQYDVQQKEzpXZWJOSUMgKFdlYiBDb21tZXJjZSBDb21tdW5pY2F0aW9ucyAoU2luZ2Fwb3JlKSBQdGUuIEx0ZC4pMTUwMwYDVQQLEyxDb250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIFdlYk5JQzE2MDQGA1UEAxMtV2ViTklDIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTaXRlIENB", + "whitelist": false, + "attachment": { + "hash": "32f14c42def451582c84df3be94b853165a5e9ee3d4e82b8c5889aecaee12548", + "size": 2284, + "filename": "DkUXONpZG5H7NGx6X1k9T9v4PYppUd35pZ3oginTOG0=.pem", + "location": "security-state-staging/intermediates/29b2d5ce-e48e-429f-a418-f913b46cf131.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DkUXONpZG5H7NGx6X1k9T9v4PYppUd35pZ3oginTOG0=", + "crlite_enrolled": false, + "id": "1ff84181-0e64-4461-a632-b2d0775f4b82", + "last_modified": 1663786624948 + }, + { + "schema": 1663786180541, + "derHash": "9HHFZ8FAz4vNwKp2lSEyhx3QwH2qEeCYJDWnceMF7eU=", + "subject": "CN=OneSignSSL RSA OV Secure Server CA,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MH8xCzAJBgNVBAYTAlNHMRIwEAYDVQQIEwlTaW5nYXBvcmUxEjAQBgNVBAcTCVNpbmdhcG9yZTEbMBkGA1UEChMST25lIFNpZ24gUHRlLiBMdGQuMSswKQYDVQQDEyJPbmVTaWduU1NMIFJTQSBPViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "e66757e38a39a57c92c66efdf548d94e28744f5e4038075f8c07bdf2280e3cef", + "size": 2154, + "filename": "GcCWGhSfB0XZcAsL42Mnp5VnYlDpXAIzW27AN4z6kJs=.pem", + "location": "security-state-staging/intermediates/abf229aa-a29d-4d56-a0e0-00892adb194c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GcCWGhSfB0XZcAsL42Mnp5VnYlDpXAIzW27AN4z6kJs=", + "crlite_enrolled": false, + "id": "885812da-ab11-4336-bbb2-4aa36e13ba65", + "last_modified": 1663786624935 + }, + { + "schema": 1663786183925, + "derHash": "ek4dGkw5XOS6ItmAyagViiBOg+pH4qUf8hSSfbQlpfM=", + "subject": "CN=Omit Security ECC Organization Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxNTAzBgNVBAMTLE9taXQgU2VjdXJpdHkgRUNDIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "581e2ef4e785e2dbf50718a68487a5de833614b3f99df7e332c03e669aff920b", + "size": 1256, + "filename": "9BS1F_mETyHRyaqwtTWxoEH83ENQd6T_EvhFLxJQsyA=.pem", + "location": "security-state-staging/intermediates/6870e61d-2832-4276-9084-e2e18fc27bb4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9BS1F/mETyHRyaqwtTWxoEH83ENQd6T/EvhFLxJQsyA=", + "crlite_enrolled": false, + "id": "6d3fad92-366b-49c3-b663-d08037c93511", + "last_modified": 1663786624928 + }, + { + "schema": 1663786176213, + "derHash": "Nrpwuq7fY3tMFrVGUa7DYFna9k3PbK4NlMuIwstMJwI=", + "subject": "CN=sslTrus (ECC) EV CA,O=sslTrus (上海锐成信息科技有限公司),C=CN", + "subjectDN": "MGQxCzAJBgNVBAYTAkNOMTcwNQYDVQQKDC5zc2xUcnVzICjkuIrmtbfplJDmiJDkv6Hmga/np5HmioDmnInpmZDlhazlj7gpMRwwGgYDVQQDExNzc2xUcnVzIChFQ0MpIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "77d2c2227897d9d9945a23913e6d01f636faf00158fb0e33980b6c5c0e1b128c", + "size": 1301, + "filename": "D9cufgL1WPvVfln-nAZCjnwBzvm0PkPT02NbL_keD3w=.pem", + "location": "security-state-staging/intermediates/19468270-9b9d-4b3e-8e6d-4d6ed6cefb33.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D9cufgL1WPvVfln+nAZCjnwBzvm0PkPT02NbL/keD3w=", + "crlite_enrolled": false, + "id": "a210c3de-e93a-4f1a-9912-5049a3e655e4", + "last_modified": 1663786624914 + }, + { + "schema": 1663786177980, + "derHash": "yYqJmVEeL2CJUFOv9MU5vRtnZXVOu3s1OJaz8pdO2D8=", + "subject": "CN=CertCloud ECC OV TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "ME0xCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMSAwHgYDVQQDExdDZXJ0Q2xvdWQgRUNDIE9WIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d46e6897324670fb7f3f8deb68542223c29cdf47e3e804808fb4e1feb7800748", + "size": 1240, + "filename": "q6ASqDW7KvwqyTSX_m1w95QebvKZnwudV__ETDG8YTc=.pem", + "location": "security-state-staging/intermediates/89edccce-e255-48f4-a1b1-9b9cc8a60b8c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "q6ASqDW7KvwqyTSX/m1w95QebvKZnwudV//ETDG8YTc=", + "crlite_enrolled": false, + "id": "851d55d6-31d5-42f0-86d5-9af5a75f6947", + "last_modified": 1663786624907 + }, + { + "schema": 1663786179693, + "derHash": "Bf/GS4/FO3n1ucYpYevMrLoZMdUPNNAU6SnrACtb2DI=", + "subject": "CN=Sectigo Partners - Server Authentication - RSA,O=Sectigo Limited,C=GB", + "subjectDN": "MGAxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUGFydG5lcnMgLSBTZXJ2ZXIgQXV0aGVudGljYXRpb24gLSBSU0E=", + "whitelist": false, + "attachment": { + "hash": "e7bcfa319cc1d4f4c63c8e2f0490ee042545a7066f5e26ac0b2f03b556e9bc1b", + "size": 2255, + "filename": "KnIj02v94up0vjV7A3aidbRS7a3ZTae0ANppOclqdzs=.pem", + "location": "security-state-staging/intermediates/bf579e11-8bdb-4e03-8285-94b9025091f6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KnIj02v94up0vjV7A3aidbRS7a3ZTae0ANppOclqdzs=", + "crlite_enrolled": false, + "id": "a5d57b58-42af-4896-8d42-5978e9342d5b", + "last_modified": 1663786624892 + }, + { + "schema": 1663786174523, + "derHash": "LHPumlL/rQcWa4co12gdjL79makGYzRbJarPmx7x8zM=", + "subject": "CN=SignSec Certification Authority,OU=SSL Department,O=FBS Inc,L=Irvine,ST=CA,C=US", + "subjectDN": "MIGAMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExDzANBgNVBAcTBklydmluZTEQMA4GA1UEChMHRkJTIEluYzEXMBUGA1UECxMOU1NMIERlcGFydG1lbnQxKDAmBgNVBAMTH1NpZ25TZWMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "330eca8b3c6c71906174a6f376dd96a4be63f53fd0d56981b4dfeae2d6a7401d", + "size": 2158, + "filename": "41fzHe29HqEJ0_pGALXNvRuPsmNV1cP7w8L-2fi7OPc=.pem", + "location": "security-state-staging/intermediates/c7c190cf-5113-4753-9b26-d5f26b89127f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "41fzHe29HqEJ0/pGALXNvRuPsmNV1cP7w8L+2fi7OPc=", + "crlite_enrolled": false, + "id": "2506e9cb-3ad3-4e08-a82b-9b825f074989", + "last_modified": 1663786624885 + }, + { + "schema": 1663786173668, + "derHash": "428WX/dznNkAolYnZ7Pq++Z6NMg8OoL0wh1fUcPtGR4=", + "subject": "CN=WoTrus DV Server CA,OU=Controlled by Sectigo exclusively for WoTrus CA Limited,O=WoTrus CA Limited,L=Shenzhen,ST=Guangdong,C=CN", + "subjectDN": "MIGwMQswCQYDVQQGEwJDTjESMBAGA1UECBMJR3Vhbmdkb25nMREwDwYDVQQHEwhTaGVuemhlbjEaMBgGA1UEChMRV29UcnVzIENBIExpbWl0ZWQxQDA+BgNVBAsTN0NvbnRyb2xsZWQgYnkgU2VjdGlnbyBleGNsdXNpdmVseSBmb3IgV29UcnVzIENBIExpbWl0ZWQxHDAaBgNVBAMTE1dvVHJ1cyBEViBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "932232ec4789438ef2c8b4653c030177809d2eb1e5f57c57e16d07a278b94f1a", + "size": 2203, + "filename": "BhCtKhlg16YgpKtkvEyMb10W4rnVIagCz4ZLBicH1aQ=.pem", + "location": "security-state-staging/intermediates/6051e27b-bea1-437c-8eca-4a2b58ef49ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BhCtKhlg16YgpKtkvEyMb10W4rnVIagCz4ZLBicH1aQ=", + "crlite_enrolled": false, + "id": "b8072f6e-6587-4d1d-9f2a-ca768a9c82f1", + "last_modified": 1663786624858 + }, + { + "schema": 1663786169208, + "derHash": "Fq1RJaCVWvAmQhhUXnSCfiw5oDLV0L9mWxfDv5nSNJY=", + "subject": "CN=BitCert ECC Extended Validation Secure Site CA,OU=Controlled by Sectigo exclusively for BitCert,O=成都数证科技有限公司,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MIHLMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHU2ljaHVhbjEQMA4GA1UEBxMHQ2hlbmdkdTEnMCUGA1UECgwe5oiQ6YO95pWw6K+B56eR5oqA5pyJ6ZmQ5YWs5Y+4MTYwNAYDVQQLEy1Db250cm9sbGVkIGJ5IFNlY3RpZ28gZXhjbHVzaXZlbHkgZm9yIEJpdENlcnQxNzA1BgNVBAMTLkJpdENlcnQgRUNDIEV4dGVuZGVkIFZhbGlkYXRpb24gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "a918b5e74457c9481154b94bf276704329250fa0507722001ff5e8bb7d8d6b4b", + "size": 1451, + "filename": "7tz57vvcF4mLPHC5kUb-dwbT09hTvQJZuiNDyCG4Swc=.pem", + "location": "security-state-staging/intermediates/2c48b14e-9147-4386-87c6-cf8874ba6cfe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7tz57vvcF4mLPHC5kUb+dwbT09hTvQJZuiNDyCG4Swc=", + "crlite_enrolled": false, + "id": "aa4299b3-468c-40a5-b96b-1ed15544e16a", + "last_modified": 1663786624844 + }, + { + "schema": 1663786259688, + "derHash": "Roc0T0cTq9A9rZGZQoxq1qVhRGKsO9efsPDBpW9KIGU=", + "subject": "CN=ISSAuth ECC EV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIEVDQyBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "adb708ffd2a0bdf96d05d74f6df65518ec99b85dc232025c20058eb5096a0339", + "size": 1280, + "filename": "CtBkdSMYFcbDm93WqDl2w4aG4lRl0t4pSXZrBwxP4NQ=.pem", + "location": "security-state-staging/intermediates/171d841c-9bc2-497f-9620-15bde725247d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CtBkdSMYFcbDm93WqDl2w4aG4lRl0t4pSXZrBwxP4NQ=", + "crlite_enrolled": false, + "id": "9df71874-f7d5-4b9d-988f-3fc447e9ad82", + "last_modified": 1663786624796 + }, + { + "schema": 1663786254555, + "derHash": "0tfUvuj/G33rpCd0AIHAf3sYai8poSNg1T+MINRVU40=", + "subject": "CN=TrustAsia RSA OV SSL Server CA,O=TrustAsia Technologies\\, Inc.,ST=Shanghai,C=CN", + "subjectDN": "MHAxCzAJBgNVBAYTAkNOMREwDwYDVQQIEwhTaGFuZ2hhaTElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEnMCUGA1UEAxMeVHJ1c3RBc2lhIFJTQSBPViBTU0wgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "d2859475c5a274222e80c88a79ecbbd4b359956c1266cb22f4b9ebeac0090f22", + "size": 2133, + "filename": "ZyqPojkZzOKu3M2n4NkoNEh6f1YBTiJm2DtfHJroAWA=.pem", + "location": "security-state-staging/intermediates/ba87cd52-2a75-4061-97d5-1f128a3e849d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZyqPojkZzOKu3M2n4NkoNEh6f1YBTiJm2DtfHJroAWA=", + "crlite_enrolled": false, + "id": "9412c58c-b477-40c8-9dc1-d802d8e4d797", + "last_modified": 1663786624789 + }, + { + "schema": 1663786251995, + "derHash": "+C+LUJZ8ClLp2Bt5qQ/+uOUVt4FKXl3i8msPavb+dTE=", + "subject": "CN=AlpiroSSL ECC EV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgRUNDIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "741e34fecaf6ab5ce38e7902f038e568ccc21ac7339be27e1dbe4ccd30863880", + "size": 1293, + "filename": "GnXFLd5ceheMJdGnZvJlKLtfXp4UjCGVFGe713NdmTc=.pem", + "location": "security-state-staging/intermediates/d580378c-e6f3-45e0-8abb-7d9d2d6d19c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GnXFLd5ceheMJdGnZvJlKLtfXp4UjCGVFGe713NdmTc=", + "crlite_enrolled": false, + "id": "d5878154-045c-4da9-8650-3758a533a7bc", + "last_modified": 1663786624782 + }, + { + "schema": 1663786257180, + "derHash": "tpG0appRDb1muYnamtBpBKwGRz2NDXnyGV1rE/+0oYM=", + "subject": "CN=TrustSign ECC OV CA,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MGMxCzAJBgNVBAYTAkZSMRAwDgYDVQQIEwdIZXJhdWx0MRQwEgYDVQQHEwtNb250cGVsbGllcjEOMAwGA1UEChMFWml3aXQxHDAaBgNVBAMTE1RydXN0U2lnbiBFQ0MgT1YgQ0E=", + "whitelist": false, + "attachment": { + "hash": "831449b5812c470917682a1eab7c5e9b8c788d897182e5b29c1b951981ae08ac", + "size": 1276, + "filename": "zdcn7aFVDOpjk4ry7Di1KqCcZhqCF7892ueBmfuRY2g=.pem", + "location": "security-state-staging/intermediates/2ce65543-848c-483b-b95e-f06e56737bb4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zdcn7aFVDOpjk4ry7Di1KqCcZhqCF7892ueBmfuRY2g=", + "crlite_enrolled": false, + "id": "50b8ada5-881a-4861-8618-669919d34962", + "last_modified": 1663786624771 + }, + { + "schema": 1663786252838, + "derHash": "PKGOHtvmhg1wkU6aoNuOYeGT5+9UfNq4tN9UX0bexrs=", + "subject": "CN=BlackCert\\, Inc. ECC OV Certification Authority,O=BlackCert\\, Inc.,L=Denver,ST=CO,C=US", + "subjectDN": "MH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRgwFgYDVQQKEw9CbGFja0NlcnQsIEluYy4xNzA1BgNVBAMTLkJsYWNrQ2VydCwgSW5jLiBFQ0MgT1YgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "whitelist": false, + "attachment": { + "hash": "37a914d05ca64eee1c00792d05096a08d43a4139983cca5f78abd8e569677510", + "size": 1313, + "filename": "bQkT-mQD0009MLZyZZBcx4FuOI9_hOoj322Ms-tQWFo=.pem", + "location": "security-state-staging/intermediates/6162d339-3ba2-4905-a428-441a37839f24.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bQkT+mQD0009MLZyZZBcx4FuOI9/hOoj322Ms+tQWFo=", + "crlite_enrolled": false, + "id": "db21ebe3-6953-4f00-a637-b99d8c6ae432", + "last_modified": 1663786624754 + }, + { + "schema": 1663721326517, + "derHash": "DphDOXJKJnwqPcT8yNAgs7S6MpoK1+OQz7926Igj4Rs=", + "subject": "CN=SECOM TimeStamping CA3,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MFUxCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMR8wHQYDVQQDExZTRUNPTSBUaW1lU3RhbXBpbmcgQ0Ez", + "whitelist": false, + "attachment": { + "hash": "463cf2757c6416cb2cc917556944e96af398c0a00905ed5979f163df8747f2e4", + "size": 2312, + "filename": "IC2V_Sr9amKWi2B5ADjjgWQL0_t8lvVPSo8xpR6Kl48=.pem", + "location": "security-state-staging/intermediates/f4319f13-4e4e-4657-b0f9-510490c561d4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IC2V/Sr9amKWi2B5ADjjgWQL0/t8lvVPSo8xpR6Kl48=", + "crlite_enrolled": false, + "id": "1f70454b-9d50-45a0-beff-4f2288a42660", + "last_modified": 1663721823295 + }, + { + "schema": 1663699718906, + "derHash": "LBcQZNv6KAofKU9y4qH8JMhhEbI3I9uTddMASyfnszs=", + "subject": "CN=DigiCert G5 TLS EU ECC P-384 SHA384 2022 CA1,O=DigiCert Ireland Limited,C=IE", + "subjectDN": "MGcxCzAJBgNVBAYTAklFMSEwHwYDVQQKExhEaWdpQ2VydCBJcmVsYW5kIExpbWl0ZWQxNTAzBgNVBAMTLERpZ2lDZXJ0IEc1IFRMUyBFVSBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "c07dfb402d6c20d7e2b4a02926a9ceb61c80b760ed987a4976f0f029cf6b69a6", + "size": 1268, + "filename": "8HipfJuHnDWN9kxgoOmRAwD6ESeqEAhr0OO35oUFGBg=.pem", + "location": "security-state-staging/intermediates/5304eb87-803a-448b-9aed-80ff0075705a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8HipfJuHnDWN9kxgoOmRAwD6ESeqEAhr0OO35oUFGBg=", + "crlite_enrolled": false, + "id": "28465661-4967-47f4-b421-b98c74bf8196", + "last_modified": 1663700223141 + }, + { + "schema": 1663332647683, + "derHash": "4+KjkwljIkP6Cu+/wX0YCayZoIsIGiojhX6Pld8O8Lg=", + "subject": "CN=Certera DV SSL CA,O=Certera,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMRowGAYDVQQDExFDZXJ0ZXJhIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "cf66055ca34f4ef7593366a4f52b6fef7e4a7126d8af74c6e7ea14a1aedbc666", + "size": 2227, + "filename": "3NMxlWykygiSVC2sQB1tYdvjir1O78WwXvX7HPMjZvM=.pem", + "location": "security-state-staging/intermediates/24e6a22e-41b8-4e77-beed-a3b12797d80c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3NMxlWykygiSVC2sQB1tYdvjir1O78WwXvX7HPMjZvM=", + "crlite_enrolled": false, + "id": "77c4b146-2331-45cc-b1f3-3ee06f9e4429", + "last_modified": 1663333023167 + }, + { + "schema": 1663332646821, + "derHash": "OfuacPjXNQLBQNYc7i1j9cEgr5h/W9wHQx2G7/yme9E=", + "subject": "CN=Certera OV SSL CA,O=Certera,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMRowGAYDVQQDExFDZXJ0ZXJhIE9WIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "539b0565e14f732514cbc4509141312fef005dd5cf39a5bf5295ec9e6cdbdbbf", + "size": 2227, + "filename": "my6Y_J_g5ajU7AuDChRMBETRuyX5zroTaBtMAj125Pk=.pem", + "location": "security-state-staging/intermediates/27b809cb-6d2b-4521-a6d3-a1ffb02fb55f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "my6Y/J/g5ajU7AuDChRMBETRuyX5zroTaBtMAj125Pk=", + "crlite_enrolled": false, + "id": "e2dc7c78-efd3-47b6-b12a-d76d2fcdf8b1", + "last_modified": 1663333023160 + }, + { + "schema": 1663332645971, + "derHash": "mRjaNOSkCzPGll8Hcnmfg7yjafpCxriGLeqvxkq+PQk=", + "subject": "CN=Certera DV SSL CA - ECC,O=Certera,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMSAwHgYDVQQDExdDZXJ0ZXJhIERWIFNTTCBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "5605b655eda11261eed10c494bcd22be07aa0201fd4f2b11e854139a6b2f884d", + "size": 1223, + "filename": "B6ndYwXFNRsPz-FmIUAAWAxXb1X0co2J0LSpo9CSrwU=.pem", + "location": "security-state-staging/intermediates/91491062-2721-4f38-8d4c-cd231a5e16f3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B6ndYwXFNRsPz+FmIUAAWAxXb1X0co2J0LSpo9CSrwU=", + "crlite_enrolled": false, + "id": "4bc04b63-20cd-4e47-9e8c-2c24e08768de", + "last_modified": 1663333023154 + }, + { + "schema": 1663332645140, + "derHash": "xH7iRyN4lI8zXr2Au/2PiZ0VYmPuyCO4fdqUHGo0t3E=", + "subject": "CN=Certera OV SSL CA - ECC,O=Certera,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMSAwHgYDVQQDExdDZXJ0ZXJhIE9WIFNTTCBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "02df529df3c10017530613c48a055b318f012f0e2d1b0877354bac563bfa827f", + "size": 1223, + "filename": "1p4Bvlzzjcq59U7nxtoaOHhtY4GctTEIrKkdGg50hLY=.pem", + "location": "security-state-staging/intermediates/6d9d0c9b-927b-4892-92b3-a2cc6f5abd00.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1p4Bvlzzjcq59U7nxtoaOHhtY4GctTEIrKkdGg50hLY=", + "crlite_enrolled": false, + "id": "f82f41dc-acf6-4861-bac8-e83ff69f4405", + "last_modified": 1663333023147 + }, + { + "schema": 1663332644273, + "derHash": "a2U7zXOC/lj3RvL6OwUOjPavNVlo4agXU+miJERgywM=", + "subject": "CN=Certera EV SSL CA - ECC,O=Certera,C=US", + "subjectDN": "MEExCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMSAwHgYDVQQDExdDZXJ0ZXJhIEVWIFNTTCBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "6b4da154ea23b11b7703593a2476650bc8a9c7d1203d68c7d938f9916fde7776", + "size": 1211, + "filename": "_CWqdR86zoPPvghEwZkVUa7kIb6kOzKKCpYrOyR6fDI=.pem", + "location": "security-state-staging/intermediates/b7e9f57d-1c5e-4cf7-b788-d89308e0f6b7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/CWqdR86zoPPvghEwZkVUa7kIb6kOzKKCpYrOyR6fDI=", + "crlite_enrolled": false, + "id": "bb477346-a98a-4d9a-9519-50bf8b0af89e", + "last_modified": 1663333023140 + }, + { + "schema": 1663332643312, + "derHash": "yLBowrqthmKfXoYpAQYIDN/45vzaqMQeRjoqxK4cRDU=", + "subject": "CN=Certera EV SSL CA,O=Certera,C=US", + "subjectDN": "MDsxCzAJBgNVBAYTAlVTMRAwDgYDVQQKEwdDZXJ0ZXJhMRowGAYDVQQDExFDZXJ0ZXJhIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "f1bda6c63d6989632bf51771a73706154d20d3686092b4f1f866393debd5037b", + "size": 2215, + "filename": "cEo0zrXzUO072D2bzLOam_2Go3ZIOtyH-wv01n2T_1k=.pem", + "location": "security-state-staging/intermediates/fcb09875-0066-42a8-a350-465e8ae1585a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cEo0zrXzUO072D2bzLOam/2Go3ZIOtyH+wv01n2T/1k=", + "crlite_enrolled": false, + "id": "6831d020-be6b-40ae-bcf0-87582137009e", + "last_modified": 1663333023134 + }, + { + "schema": 1663292931545, + "derHash": "vTDA0eesuD78T19sYvjzpXm6snUnr65mbGlsOoZxdfE=", + "subject": "CN=vTrus ECC EV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEkxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRwwGgYDVQQDExN2VHJ1cyBFQ0MgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "5efe51a26c5280ef3910747587a1258ee032a5cd4592afff27a55faecd152eec", + "size": 1183, + "filename": "7k0rYa-qry4wDaWSSycBwDVQALkMXBvBWeWHCjNIjTA=.pem", + "location": "security-state-staging/intermediates/cd43148a-54af-4d80-ae67-169c20b9e44f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7k0rYa+qry4wDaWSSycBwDVQALkMXBvBWeWHCjNIjTA=", + "crlite_enrolled": false, + "id": "e538f9af-2862-49fd-bf86-757961d7fae8", + "last_modified": 1663293423124 + }, + { + "schema": 1663292929822, + "derHash": "86ptcSoV9j+DUIBJedtUJBmmGysdIudWxBer/o10o8o=", + "subject": "CN=vTrus EV SSL CA,O=iTrusChina Co.\\,Ltd.,C=CN", + "subjectDN": "MEUxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRgwFgYDVQQDEw92VHJ1cyBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "01cb1f5c741d941d11e0575e8235a3df364dc571bdd452841c23bd28e35016d3", + "size": 2003, + "filename": "tHoUHCrraSrjkHaUbZGcxRVrjSHn8i6s8_iI6HHC6Ok=.pem", + "location": "security-state-staging/intermediates/ebfa270b-ca4e-440a-992b-6b490b598528.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tHoUHCrraSrjkHaUbZGcxRVrjSHn8i6s8/iI6HHC6Ok=", + "crlite_enrolled": false, + "id": "abf9e632-aa0d-4b33-a74e-1afa980faa53", + "last_modified": 1663293423105 + }, + { + "schema": 1663225023673, + "derHash": "q97sUxSQmPigsH79lys0Wom+3o7eaXXmG+le4Cbafvo=", + "subject": "CN=Actalis Client Authentication CA G1,O=Actalis S.p.A./03358520967,L=Milano,ST=Milano,C=IT", + "subjectDN": "MIGCMQswCQYDVQQGEwJJVDEPMA0GA1UECAwGTWlsYW5vMQ8wDQYDVQQHDAZNaWxhbm8xIzAhBgNVBAoMGkFjdGFsaXMgUy5wLkEuLzAzMzU4NTIwOTY3MSwwKgYDVQQDDCNBY3RhbGlzIENsaWVudCBBdXRoZW50aWNhdGlvbiBDQSBHMQ==", + "whitelist": false, + "attachment": { + "hash": "737f0f0ce1590b65bb7efa52f0521e84ec1ac16313c8b4c6cd288064af2d127d", + "size": 2235, + "filename": "YsqG87ugr7iITRNFFaHeNX5oMmY80JlEVuqBCxAhZAM=.pem", + "location": "security-state-staging/intermediates/5df58a07-e383-4a1a-8af4-9471aa61c013.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YsqG87ugr7iITRNFFaHeNX5oMmY80JlEVuqBCxAhZAM=", + "crlite_enrolled": false, + "id": "621a2382-280e-48de-b859-5293ba5379b7", + "last_modified": 1663246623215 + }, + { + "schema": 1662515823574, + "derHash": "Yjq+xvhacCisoQ9b3F2BtWtjFN8odD/8yE7KMvNa6EY=", + "subject": "CN=MuaSSL.com TLS Issuing ECC CA R1,O=Hao Quang Viet Software Company Limited,C=VN", + "subjectDN": "MGoxCzAJBgNVBAYTAlZOMTAwLgYDVQQKDCdIYW8gUXVhbmcgVmlldCBTb2Z0d2FyZSBDb21wYW55IExpbWl0ZWQxKTAnBgNVBAMMIE11YVNTTC5jb20gVExTIElzc3VpbmcgRUNDIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "4bc297912beeadd2623b4a9a35eb5f3a3edd6b8122f64073f6c3feedbb3f3e56", + "size": 1894, + "filename": "aWadCp7kMcffucdctgN2uR7OBZm-XJI09tOLxjLbhv8=.pem", + "location": "security-state-staging/intermediates/cb2c8fd9-e390-4ea9-8d08-c689e5d173f2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aWadCp7kMcffucdctgN2uR7OBZm+XJI09tOLxjLbhv8=", + "crlite_enrolled": false, + "id": "9e0c4d64-984d-43a7-bce2-36a26101fe95", + "last_modified": 1662620223051 + }, + { + "schema": 1661950129116, + "derHash": "EiGqtgN56f55kJqhCNwcLT/DEvZxy0vN4BxOZ9fnwIw=", + "subject": "CN=XinNet ECC EV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBFQ0MgRVY=", + "whitelist": false, + "attachment": { + "hash": "ec1b5594e93dd3df5f47be180392a5df3799db70fa6f739a38b2dc1258d844f2", + "size": 1223, + "filename": "7l3LEq83uTOiu25Z-S2ntnn1tY2VhTob4m2EuUoejj4=.pem", + "location": "security-state-staging/intermediates/000d7a9e-dc19-4de7-819e-a3d1642169d7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7l3LEq83uTOiu25Z+S2ntnn1tY2VhTob4m2EuUoejj4=", + "crlite_enrolled": false, + "id": "e09c7cae-f477-4daa-a33a-f1e5ff8b4b3e", + "last_modified": 1661950623229 + }, + { + "schema": 1661950129980, + "derHash": "WA/yu8tE2HQCXO2sGHMy6f+f/lF1vfnUO/HFLsjWexM=", + "subject": "CN=XinNet ECC DV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBFQ0MgRFY=", + "whitelist": false, + "attachment": { + "hash": "c5d97c542f1f94ed8f61b6adff2b757dbfc935677082bc3a7b2ee23f09f4f1ca", + "size": 1232, + "filename": "WP9XnD6Mzq1Rfi6Wow1435AnbFh7wz9X46zcDZb0kVE=.pem", + "location": "security-state-staging/intermediates/25c1e6b8-ac9d-4a5d-a746-5a91a4226446.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WP9XnD6Mzq1Rfi6Wow1435AnbFh7wz9X46zcDZb0kVE=", + "crlite_enrolled": false, + "id": "50f44cf2-d9fa-456c-a653-e1b601d9579c", + "last_modified": 1661950623222 + }, + { + "schema": 1661950126528, + "derHash": "WgJok1zFELRL/f49TCrM3kh93AkTGaeFiUjpEateRMo=", + "subject": "CN=XinNet ECC OV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBFQ0MgT1Y=", + "whitelist": false, + "attachment": { + "hash": "927177d7d47caf03e7f4528c61e35fad1088d69d5519540d7739f1def2c2d1d9", + "size": 1232, + "filename": "4UHsE1oKRNnyRDb9r1JEoDJdB_2Z_iYsr5srbVeKXWE=.pem", + "location": "security-state-staging/intermediates/6d619157-2e56-4c0e-b34a-054ef0da5c2d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4UHsE1oKRNnyRDb9r1JEoDJdB/2Z/iYsr5srbVeKXWE=", + "crlite_enrolled": false, + "id": "fef2ab3a-953e-4073-b1f2-075943647fd8", + "last_modified": 1661950623209 + }, + { + "schema": 1661950125663, + "derHash": "lBbHH6N1XLbnzbugOfYwYE0BcZNPl4JOjz6tSyqKVyA=", + "subject": "CN=XinNet RSA DV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBSU0EgRFY=", + "whitelist": false, + "attachment": { + "hash": "b61e9fd17d0575a17d1e3a51c9d9964f2572b4aeb88cf2f7d4566e7420426f0e", + "size": 2243, + "filename": "b-c_9Frdh5caVlh6xbrUxlx8iw2QGiqGgHHInw7aQiU=.pem", + "location": "security-state-staging/intermediates/3cdf11cb-6978-4ba3-bc21-6d79993cac7c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b+c/9Frdh5caVlh6xbrUxlx8iw2QGiqGgHHInw7aQiU=", + "crlite_enrolled": false, + "id": "caffc552-3e32-412d-b52b-b3d2853e49f7", + "last_modified": 1661950623202 + }, + { + "schema": 1661950128246, + "derHash": "ua8z+kJvLIsZg4VqX5H8DSjskw0UGYiPww31+03nYV8=", + "subject": "CN=XinNet RSA EV,O=Xin Net Technology Corp.,C=CN", + "subjectDN": "MEgxCzAJBgNVBAYTAkNOMSEwHwYDVQQKExhYaW4gTmV0IFRlY2hub2xvZ3kgQ29ycC4xFjAUBgNVBAMTDVhpbk5ldCBSU0EgRVY=", + "whitelist": false, + "attachment": { + "hash": "3a41f53cdc05597979b2623ddc040f3fae0b066dc2ea78f533ef4a467cb6be89", + "size": 2235, + "filename": "-slmUS6OMhZJUXFu69pBpnaUXYm81RhXXFsdayDb5Q4=.pem", + "location": "security-state-staging/intermediates/0903cfac-baf5-4e5a-a04c-3d446acaee56.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+slmUS6OMhZJUXFu69pBpnaUXYm81RhXXFsdayDb5Q4=", + "crlite_enrolled": false, + "id": "29c1bc7e-013c-43ec-ac2d-6de40bdf28f0", + "last_modified": 1661950623195 + }, + { + "schema": 1661950130681, + "derHash": "hrEJIywZ7FFPjLJniOZQkCk/lt2NKUIBejMDWrIl1fs=", + "subject": "CN=CrowdStrike Global EV CA,O=CrowdStrike\\, Inc.,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRowGAYDVQQKExFDcm93ZFN0cmlrZSwgSW5jLjEhMB8GA1UEAxMYQ3Jvd2RTdHJpa2UgR2xvYmFsIEVWIENB", + "whitelist": false, + "attachment": { + "hash": "8f864179f48ab2665c707017cf4785a5f7c422122f7b13f019daf945d09be137", + "size": 1739, + "filename": "jbtEGKuN8b5BZ9dJqvvW9XPxqqghhmKxB6nuThNyJd8=.pem", + "location": "security-state-staging/intermediates/9313df52-3e03-45ef-9534-34735860f583.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jbtEGKuN8b5BZ9dJqvvW9XPxqqghhmKxB6nuThNyJd8=", + "crlite_enrolled": false, + "id": "cbf4b97a-4484-4add-84fe-5fa8e074c83e", + "last_modified": 1661950623184 + }, + { + "schema": 1661906933349, + "derHash": "Zo+7JXQ1z23AEzmvCycq0Vz0Z2d2ooYc2ve0tZi/8Wk=", + "subject": "CN=XinChaCha Trust EV CA,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xHjAcBgNVBAMTFVhpbkNoYUNoYSBUcnVzdCBFViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "f70e2c6d4c35d993bbd23ea704ba440aeb7e897051779c8b3991db73f197269d", + "size": 2272, + "filename": "z_CnQiGlE74EJzi1ZG4akzGd1nL6ajMCLhA5qyb8cU4=.pem", + "location": "security-state-staging/intermediates/6ec34579-3925-47b2-ba80-0c4fca54ff44.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z/CnQiGlE74EJzi1ZG4akzGd1nL6ajMCLhA5qyb8cU4=", + "crlite_enrolled": false, + "id": "1b981b05-f49c-4ca4-a586-26a3109ef7a5", + "last_modified": 1661907423176 + }, + { + "schema": 1661906935892, + "derHash": "FvZ5hlHV7bbuxvlSzgh5+PQsvoU3szQJp5yknNu8/5k=", + "subject": "CN=cnTrus OV SSL - ECC,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEcMBoGA1UEAxMTY25UcnVzIE9WIFNTTCAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "002a6acdc3c374f62f67fd30dd9536cf465dbc4627de7b84e65b927fa9d99a28", + "size": 1272, + "filename": "OL0bKOdUSLHUHrFUERbz016kPXNkYaN0TJUKfwHea20=.pem", + "location": "security-state-staging/intermediates/c5a9ddfb-bdbe-41fa-a272-1dc4a26c19ea.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OL0bKOdUSLHUHrFUERbz016kPXNkYaN0TJUKfwHea20=", + "crlite_enrolled": false, + "id": "2265ec70-253a-4676-918d-6682af991506", + "last_modified": 1661907423169 + }, + { + "schema": 1661906934190, + "derHash": "8HJ01/0PLMcb67qwrjjHijVeRApctxGANl443W1Ug8M=", + "subject": "CN=XinChaCha Trust OV CA,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xHjAcBgNVBAMTFVhpbkNoYUNoYSBUcnVzdCBPViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d57ef18ccc13871bffd1bdad41889ee99b3c115253fedd6680a313387dbef6f6", + "size": 2284, + "filename": "uLEgjMGP3fq8t2rIJ970JMr2rukdDUMNKl6PaiWvQVQ=.pem", + "location": "security-state-staging/intermediates/9d5d1d3b-bb29-41bd-958b-f7b81c81a6ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "uLEgjMGP3fq8t2rIJ970JMr2rukdDUMNKl6PaiWvQVQ=", + "crlite_enrolled": false, + "id": "cc250962-aec5-417e-8c3e-469ad27f7fa7", + "last_modified": 1661907423162 + }, + { + "schema": 1661906936754, + "derHash": "Y9vmdVbbAXpBoS5guRLOWdpURf9oIDypG5Sk4qRVQbE=", + "subject": "CN=XinChaCha Trust DV CA - ECC,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGsxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xJDAiBgNVBAMTG1hpbkNoYUNoYSBUcnVzdCBEViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "4ca1d8b93b380dff6393b77a14765975970188962575db0b94ba1723d0365cae", + "size": 1280, + "filename": "fmCenho_93PqREjCITEp8fFzy55lRWOGXJCfhad7-kE=.pem", + "location": "security-state-staging/intermediates/81daeee6-5b2c-447f-9056-b6d23b4a476a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fmCenho/93PqREjCITEp8fFzy55lRWOGXJCfhad7+kE=", + "crlite_enrolled": false, + "id": "1e509002-bf7b-4bb1-a264-7b4d11b10f0c", + "last_modified": 1661907423149 + }, + { + "schema": 1661906931570, + "derHash": "PZMWEUZxY12g6fitbx2PCxJTLJelrwcZ+En+/IK5FQI=", + "subject": "CN=XinChaCha Trust DV CA,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xHjAcBgNVBAMTFVhpbkNoYUNoYSBUcnVzdCBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "d6c77cb834d99148e2d80269af29e983025a66de533d334273c82fc23be8341e", + "size": 2284, + "filename": "TRR7aySIhDwHJnu0AR64QyqmI4l57gkFluuxAb09MlY=.pem", + "location": "security-state-staging/intermediates/da220e75-a68f-4527-85dd-1a47672425c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TRR7aySIhDwHJnu0AR64QyqmI4l57gkFluuxAb09MlY=", + "crlite_enrolled": false, + "id": "09be45e1-6f3d-4a53-90e4-14b4a25ad410", + "last_modified": 1661907423142 + }, + { + "schema": 1661906935049, + "derHash": "b3xt4CkZ3/b2iFnm+1egIVG7737PzP+aWYEwSWtadTk=", + "subject": "CN=cnTrus EV SSL - ECC,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEcMBoGA1UEAxMTY25UcnVzIEVWIFNTTCAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "4905349723070c7f45e8ac115a26d5d88916ebe60c5ddb4b5acf39f4f7a7aae6", + "size": 1260, + "filename": "AL_7FpWAouK0_07PMAyzjk9aKTezKRO9_ksLpB7QgpA=.pem", + "location": "security-state-staging/intermediates/b73656ba-3c09-4803-b936-51dbf559fed3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AL/7FpWAouK0/07PMAyzjk9aKTezKRO9/ksLpB7QgpA=", + "crlite_enrolled": false, + "id": "84894d2a-3eed-47ff-b1c7-8e1a52c09a3d", + "last_modified": 1661907423135 + }, + { + "schema": 1661906930734, + "derHash": "/DBCDWjBfIOfO5RuvbEBv3LGTJ6WZYwfPjV+atiqyuI=", + "subject": "CN=cnTrus DV SSL - ECC,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGUxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEcMBoGA1UEAxMTY25UcnVzIERWIFNTTCAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "b30469b78b032a6a7f37cece69e37d38c7d8651ef0c2d6fa3a7a38eb1c5f71a9", + "size": 1272, + "filename": "4d2pGHdP5aQB3aOa2R6b2UM0oy5Bxp2lK4kDKazLTEw=.pem", + "location": "security-state-staging/intermediates/d74f32a6-0439-48e9-a261-378ccd2c60ee.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4d2pGHdP5aQB3aOa2R6b2UM0oy5Bxp2lK4kDKazLTEw=", + "crlite_enrolled": false, + "id": "f56167e2-2e5b-4f9b-8cd0-6cf6480b1ee1", + "last_modified": 1661907423128 + }, + { + "schema": 1661906929849, + "derHash": "FDYT2+zGVCEAska/UZUjZM9wI8ZW9wZ7tm0tNH37F8Y=", + "subject": "CN=XinChaCha Trust OV CA - ECC,O=Beijing Xinchacha Credit Management Co.\\, Ltd.,C=CN", + "subjectDN": "MGsxCzAJBgNVBAYTAkNOMTYwNAYDVQQKEy1CZWlqaW5nIFhpbmNoYWNoYSBDcmVkaXQgTWFuYWdlbWVudCBDby4sIEx0ZC4xJDAiBgNVBAMTG1hpbkNoYUNoYSBUcnVzdCBPViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "fa3afdda42bafdb83d42ebfb1818f7002972d937dee47db3397ebaa90e628d96", + "size": 1280, + "filename": "oYo9hFLoMunri3WRdgEd07J7XcD-WrfKOTckVulvO3s=.pem", + "location": "security-state-staging/intermediates/383258ec-eed4-4c4d-917a-e2b89cd2cb4b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oYo9hFLoMunri3WRdgEd07J7XcD+WrfKOTckVulvO3s=", + "crlite_enrolled": false, + "id": "468cbfa3-97cb-4c68-95da-aaff3c92cc10", + "last_modified": 1661907423121 + }, + { + "schema": 1661885332436, + "derHash": "W32hFE0mDLEL8pVXHAk9UG/SN5/ehxBpK4wEu5gahtQ=", + "subject": "CN=cnTrus DV SSL CA,O=Zhejiang Huluwa Digital Certification Co.\\, Ltd.,C=CN", + "subjectDN": "MGIxCzAJBgNVBAYTAkNOMTgwNgYDVQQKEy9aaGVqaWFuZyBIdWx1d2EgRGlnaXRhbCBDZXJ0aWZpY2F0aW9uIENvLiwgTHRkLjEZMBcGA1UEAxMQY25UcnVzIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "24f352badaf2347cae30cfc9b2aed7261b2bde847b964ca6bded8f56f5c3c1b7", + "size": 2280, + "filename": "taASTswaPD95aPQ40zBUMUDMhrg_ilqpKUJp_BA4fZY=.pem", + "location": "security-state-staging/intermediates/2b620349-594f-4ba3-ab50-5100b887e28a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "taASTswaPD95aPQ40zBUMUDMhrg/ilqpKUJp/BA4fZY=", + "crlite_enrolled": false, + "id": "b79706c1-d86c-4666-b8de-81f9823acc9c", + "last_modified": 1661885823291 + }, + { + "schema": 1661561330852, + "derHash": "O2+rCrEcFO75Axlp4K2wN8j8wzZnKKZWdiPTwms2Mss=", + "subject": "CN=Amazon RSA 4096 M04,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgNDA5NiBNMDQ=", + "whitelist": false, + "attachment": { + "hash": "34bf53680a87649997ed01605801e9d3b8b70a68912f9c199f9c337b93a11e4f", + "size": 2268, + "filename": "iZEKooQ3Z2hRlfox5B5j5upQGSBxBNialNi2XBoFqSc=.pem", + "location": "security-state-staging/intermediates/6c0364a6-c99d-4709-837b-affccae1d280.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iZEKooQ3Z2hRlfox5B5j5upQGSBxBNialNi2XBoFqSc=", + "crlite_enrolled": false, + "id": "2f15de25-e0d0-4a35-8fd0-ebfb645c709f", + "last_modified": 1661561823149 + }, + { + "schema": 1661561329955, + "derHash": "9A2kVb4Tbg2zGhFv/TwwArHrvFkVWEk/5jCpyMmvrXA=", + "subject": "CN=Amazon ECDSA 384 M03,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAzODQgTTAz", + "whitelist": false, + "attachment": { + "hash": "86b10dab50bee3f45091a9ecfddc42ba6680d817ca24ea41be6aaf3aaa00c57d", + "size": 1122, + "filename": "usZw2qTFJ4qcr9M5rzEibaC45ka1MVh3UTG1JnYOLUU=.pem", + "location": "security-state-staging/intermediates/020c27d9-8f2b-4358-90ca-c7e1b7b079bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "usZw2qTFJ4qcr9M5rzEibaC45ka1MVh3UTG1JnYOLUU=", + "crlite_enrolled": false, + "id": "78f4fa53-96f6-49dc-82cf-f7ad7aa4f553", + "last_modified": 1661561823142 + }, + { + "schema": 1661561329138, + "derHash": "LMOfZ4m5Z7AoKurKRUj2AqGydOkNJgdz6eew98DxNDI=", + "subject": "CN=Amazon RSA 4096 M02,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgNDA5NiBNMDI=", + "whitelist": false, + "attachment": { + "hash": "36b459dadf1ae81b06a1a296babb5453455cc61d98c287ecae1516aab89cc7c9", + "size": 2268, + "filename": "Z2B_Qo9zcoKyitUg31ADZFus4Q75K2JIBBm9wSkjyIA=.pem", + "location": "security-state-staging/intermediates/46bab996-659a-463c-84b0-639116cabdd8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z2B/Qo9zcoKyitUg31ADZFus4Q75K2JIBBm9wSkjyIA=", + "crlite_enrolled": false, + "id": "13532219-6957-4705-ae1c-ad9544b02037", + "last_modified": 1661561823135 + }, + { + "schema": 1661561328272, + "derHash": "Qb3AdOuVMeyB/vCNn/oWU29h/Fl4kmXpXomkmCrvP5E=", + "subject": "CN=Amazon RSA 4096 M03,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgNDA5NiBNMDM=", + "whitelist": false, + "attachment": { + "hash": "6252c9f3228e9bc532d49c1987fcd448ebcf0bad5801030567028b12e9916f5c", + "size": 2268, + "filename": "g_Ugk7jmp2vbs_AT4CuAjxXteaWqr_ztjrmsNwrL3bE=.pem", + "location": "security-state-staging/intermediates/69bc118e-ba59-4734-a5c1-546158301582.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "g/Ugk7jmp2vbs/AT4CuAjxXteaWqr/ztjrmsNwrL3bE=", + "crlite_enrolled": false, + "id": "656b8121-d96d-4d65-ba29-0508adecf1e6", + "last_modified": 1661561823129 + }, + { + "schema": 1661561323950, + "derHash": "3+NcdAz0HAsFPiIC6lr8LwIfcL+QsmvIYf4dmgv8Tx4=", + "subject": "CN=Amazon ECDSA 384 M01,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAzODQgTTAx", + "whitelist": false, + "attachment": { + "hash": "42da450f52d50b3ee609748e63503b182b6b21818f0f0bf4e8f57e6edacb5658", + "size": 1122, + "filename": "RM3y5tcn1oEOdEd-IzeZYwxr-FiMF6DAbydz8US9IHA=.pem", + "location": "security-state-staging/intermediates/b5c9a2e1-5433-4a48-a84a-caff8d512f05.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RM3y5tcn1oEOdEd+IzeZYwxr+FiMF6DAbydz8US9IHA=", + "crlite_enrolled": false, + "id": "cdc04459-16a6-4084-bb8f-413baeee84e8", + "last_modified": 1661561823122 + }, + { + "schema": 1661561325712, + "derHash": "0kEZLM5X1DiYZyOXLdbxi1o6NFanCOjyc9FHIjq2+l0=", + "subject": "CN=Amazon ECDSA 256 M04,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAyNTYgTTA0", + "whitelist": false, + "attachment": { + "hash": "3ce0e57793930070dcb59f4baf0e716cef8295746f98dd9346833cc81d073b93", + "size": 1037, + "filename": "8dcSZIGOkkJAXV71MH2lsFJuG8T2GkkAq3oGwdC4sHo=.pem", + "location": "security-state-staging/intermediates/5ba01136-cb9d-42b7-9009-79d5e3b8cd87.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8dcSZIGOkkJAXV71MH2lsFJuG8T2GkkAq3oGwdC4sHo=", + "crlite_enrolled": false, + "id": "a9fa59e9-1551-4d4d-b912-0024e88cf96c", + "last_modified": 1661561823102 + }, + { + "schema": 1661561331720, + "derHash": "R/0RrVUqsmTX8nJ3DTtVkKrhRUEvStYIG9xIUpjY4AA=", + "subject": "CN=Amazon ECDSA 384 M04,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAzODQgTTA0", + "whitelist": false, + "attachment": { + "hash": "e633493b8c8cc9cb2eee8c019886d44339cdbba66970181e59375105dcd06a6f", + "size": 1122, + "filename": "o_dEE2RF7HEyYEYGA0EpZHFv7rjZnYSU6_574VsBiVY=.pem", + "location": "security-state-staging/intermediates/4a5b886c-8f4f-4861-b304-e3caf581db5a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "o/dEE2RF7HEyYEYGA0EpZHFv7rjZnYSU6/574VsBiVY=", + "crlite_enrolled": false, + "id": "26f055fb-77ea-4ea0-a8d5-4ecbc6a59202", + "last_modified": 1661561823095 + }, + { + "schema": 1661561326562, + "derHash": "p9FeYseIJZGftZveWO/PsCJbQQffxgAmiFQgGF3Gm2M=", + "subject": "CN=Amazon ECDSA 384 M02,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAzODQgTTAy", + "whitelist": false, + "attachment": { + "hash": "7694a8ef8c7aaf69abaecc3b05262945607bd60899528570d93ce85ea9859917", + "size": 1122, + "filename": "0Yzy98IG0oPAJ2UzD253NkswQJH7XFYXIgCWppwVPbQ=.pem", + "location": "security-state-staging/intermediates/c1258ec4-f8e9-4fad-ae81-7789d99c7f47.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0Yzy98IG0oPAJ2UzD253NkswQJH7XFYXIgCWppwVPbQ=", + "crlite_enrolled": false, + "id": "1ff6197c-5263-4e83-b354-360d0db926fb", + "last_modified": 1661561823088 + }, + { + "schema": 1661561324860, + "derHash": "+WkyVZM7aBWdFoqpokfaHcZuI8BiAzjvcUnkj4Oxrnk=", + "subject": "CN=Amazon ECDSA 256 M02,O=Amazon,C=US", + "subjectDN": "MD0xCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHTAbBgNVBAMTFEFtYXpvbiBFQ0RTQSAyNTYgTTAy", + "whitelist": false, + "attachment": { + "hash": "960f585c95350d0f3af0983bb0cdf9ec14ef932a553228568ec01d3931a4ba41", + "size": 1041, + "filename": "lyXQLG_81tHkNq6AspMb9zMj2vhe29x5k_iuxJHpsms=.pem", + "location": "security-state-staging/intermediates/ee24440a-cafa-4fbc-8101-8963db58c40b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lyXQLG/81tHkNq6AspMb9zMj2vhe29x5k/iuxJHpsms=", + "crlite_enrolled": false, + "id": "1d66c671-4640-4887-a303-da01cc17a608", + "last_modified": 1661561823067 + }, + { + "schema": 1661561323109, + "derHash": "QP4o3JJdGoprj4YYY+tXzTDGd2QWq4qZkgusfJJaQXQ=", + "subject": "CN=Amazon RSA 4096 M01,O=Amazon,C=US", + "subjectDN": "MDwxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xHDAaBgNVBAMTE0FtYXpvbiBSU0EgNDA5NiBNMDE=", + "whitelist": false, + "attachment": { + "hash": "dfa8552f1a25377b7875e689ee5febd25cea8c88bbc2ce0a14c3150ae8c9c81f", + "size": 2268, + "filename": "gPI4HNthHVSIbODF1ry6NYD4gjsOabolcpW-LEix0BY=.pem", + "location": "security-state-staging/intermediates/d4f5427c-1e67-49a4-a618-e8dc465c36f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gPI4HNthHVSIbODF1ry6NYD4gjsOabolcpW+LEix0BY=", + "crlite_enrolled": false, + "id": "cfee27d0-e82d-4360-a733-a0b8ee36f617", + "last_modified": 1661561823060 + }, + { + "schema": 1659747423666, + "derHash": "PzHbdYKNqpbk3luCoGeP4gI71bJGw/klDL9nGOEJWPU=", + "subject": "CN=Domain The Net Technologies Ltd CA for EV SSL R2,O=Domain The Net Technologies Ltd,C=IL", + "subjectDN": "MHIxCzAJBgNVBAYTAklMMSgwJgYDVQQKDB9Eb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkMTkwNwYDVQQDDDBEb21haW4gVGhlIE5ldCBUZWNobm9sb2dpZXMgTHRkIENBIGZvciBFViBTU0wgUjI=", + "whitelist": false, + "attachment": { + "hash": "8a4d28681845cdc8a071852db1380fdac3ad47d045153ee6fc939a47193b2bff", + "size": 2487, + "filename": "Lk19AkNIC7AwHNF5HsU_phCEnUBI-eiA0mbFhxxeQsQ=.pem", + "location": "security-state-staging/intermediates/88af22d0-cfec-40f3-bb5e-5e0f70ca65bf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Lk19AkNIC7AwHNF5HsU/phCEnUBI+eiA0mbFhxxeQsQ=", + "crlite_enrolled": false, + "id": "ebf62e8f-f268-416f-b7de-23e67b4313c9", + "last_modified": 1660093023424 + }, + { + "schema": 1659617319739, + "derHash": "r9ep9iQLEcBtD2ck1E9FTg1eMX1cz/a4GNVyRO7VoZk=", + "subject": "CN=Positiwise OV SSL CA,O=Positiwise Software LLC,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdQb3NpdGl3aXNlIFNvZnR3YXJlIExMQzEdMBsGA1UEAxMUUG9zaXRpd2lzZSBPViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c4ec2c1e05e468d6247f1f67567d5c1437ff5797ef9506b388ab557499e416a8", + "size": 2251, + "filename": "_HOHQG8BYGtjcdTsmB0eQrdSw2NwbRkfzyQF2M0b_co=.pem", + "location": "security-state-staging/intermediates/fdc0d0de-779e-4ed4-b480-e0927eac2eeb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/HOHQG8BYGtjcdTsmB0eQrdSw2NwbRkfzyQF2M0b/co=", + "crlite_enrolled": false, + "id": "3f9817fc-bac5-4bf3-a8af-6ad620556e43", + "last_modified": 1659617823015 + }, + { + "schema": 1659617320606, + "derHash": "/KxiTWGykzp25Lg+/b6Wo+pdrdA9DJp00TR+HSMN5N4=", + "subject": "CN=Positiwise EV SSL CA,O=Positiwise Software LLC,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdQb3NpdGl3aXNlIFNvZnR3YXJlIExMQzEdMBsGA1UEAxMUUG9zaXRpd2lzZSBFViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "f624afa22b06c6e84dcd89da70c4128623b5ae1b3a44a09b3ec39c1e56db733b", + "size": 2243, + "filename": "emmcOPyAVo6LIPla_I-MPpiNAvseJXgTSOudm_tynlM=.pem", + "location": "security-state-staging/intermediates/bcee9cca-570b-48b8-a0cb-105ebc1e26cd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "emmcOPyAVo6LIPla/I+MPpiNAvseJXgTSOudm/tynlM=", + "crlite_enrolled": false, + "id": "bf92d805-ec1a-498e-8a47-5f30fe40dfe6", + "last_modified": 1659617823008 + }, + { + "schema": 1659617321409, + "derHash": "7DlNop/WInbPhhTBCN6hJPJ9RZ0zL7yp3SyFtePNpc8=", + "subject": "CN=Positiwise DV SSL CA,O=Positiwise Software LLC,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdQb3NpdGl3aXNlIFNvZnR3YXJlIExMQzEdMBsGA1UEAxMUUG9zaXRpd2lzZSBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "3e0549b00df190225f3368aa91834ebe8ce10a9d5043f98e29ad33e50c769b9d", + "size": 2251, + "filename": "wxLKruYttl1r1VfuyraoK42ynOZh5C-0gVygSGGIipQ=.pem", + "location": "security-state-staging/intermediates/eec72944-f23d-4fd7-af54-2e8bef884ac3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wxLKruYttl1r1VfuyraoK42ynOZh5C+0gVygSGGIipQ=", + "crlite_enrolled": false, + "id": "37a41017-a262-4ab3-83bb-7c484c39226e", + "last_modified": 1659617823001 + }, + { + "schema": 1659487717052, + "derHash": "M4RrVFpJyb5JA8YOAXE8G9Tk7zHqZc2V1p5ieU8wuUE=", + "subject": "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNA==", + "whitelist": false, + "attachment": { + "hash": "d320aaa4e99e185779fe19dba121e5ee319db377b0c6a3aa6f3f3970db6f8b7e", + "size": 1983, + "filename": "Wd8xe_qfTwq3ylFNd3IpaqLHZbh2ZNCLluVzmeNkcpw=.pem", + "location": "security-state-staging/intermediates/66893dc7-1be7-433a-8144-0eae9b9d0e63.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wd8xe/qfTwq3ylFNd3IpaqLHZbh2ZNCLluVzmeNkcpw=", + "crlite_enrolled": false, + "id": "b2b75b02-f724-47d0-b1fb-6d20723bca10", + "last_modified": 1659488223102 + }, + { + "schema": 1659055773370, + "derHash": "AhXbfiLTbQ51NaEmkansDcf0PYOrWAwHCXEcHnqbVew=", + "subject": "CN=Thawte G5 TLS ECC P-384 SHA384 2022 CA2,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEwMC4GA1UEAxMnVGhhd3RlIEc1IFRMUyBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ey", + "whitelist": false, + "attachment": { + "hash": "df52b927575157f4e16e30ea1072080bee51464b0942385f3266ab0a1ea5346a", + "size": 1248, + "filename": "esCUnA-9UZsmRXwbYfSbArWPcFq6Z167GpRf7H-27ys=.pem", + "location": "security-state-staging/intermediates/02700163-492b-4d1c-a944-6202014c148b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "esCUnA+9UZsmRXwbYfSbArWPcFq6Z167GpRf7H+27ys=", + "crlite_enrolled": false, + "id": "c5722222-eae7-4478-a467-60b8c4e42a53", + "last_modified": 1659056262852 + }, + { + "schema": 1659055772487, + "derHash": "ZueVVQsWSX589FZuxjtWZg8o29VRw1fFJvuw12IKgRI=", + "subject": "CN=GeoTrust G5 TLS ECC P-384 SHA384 2022 CA2,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEyMDAGA1UEAxMpR2VvVHJ1c3QgRzUgVExTIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTI=", + "whitelist": false, + "attachment": { + "hash": "3d35fa8391134cc25a04cff91e43367c6c601b0865fa2aa0facc570f001e9162", + "size": 1248, + "filename": "JGfTNIFHMkgQHavA89Uv6HkQpInX2z7goI1CbZYyReI=.pem", + "location": "security-state-staging/intermediates/d68a9395-6cfa-4112-b32d-bf294911ad57.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JGfTNIFHMkgQHavA89Uv6HkQpInX2z7goI1CbZYyReI=", + "crlite_enrolled": false, + "id": "2f54bed6-0451-4315-9bd3-15464e287e25", + "last_modified": 1659056262846 + }, + { + "schema": 1658342922602, + "derHash": "cQ3k0iZCnhZXwVBCs/QE3lv23r/gD3ff1nGuxno3Tkg=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIyIFE0", + "whitelist": false, + "attachment": { + "hash": "0eff80957ef30dbc8d8aaa88f3093f1641c383fdeb9ae334bc689d0947a469ec", + "size": 1199, + "filename": "JOrKj-oZpSgjnQTqIbypZD_MJmkot5VjNSLG_YJvIVo=.pem", + "location": "security-state-staging/intermediates/203ed815-ab49-4ccd-a130-34fdfb4568d2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JOrKj+oZpSgjnQTqIbypZD/MJmkot5VjNSLG/YJvIVo=", + "crlite_enrolled": false, + "id": "73480ca0-dab2-44a8-9c86-792e06603d08", + "last_modified": 1658343423313 + }, + { + "schema": 1658342921705, + "derHash": "ZJv7C6oOJWVe20ELa24HPVZw4Ay3bzkL2X6yaswKpEo=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "7c51e89cf86406eb209ad056a22498ae1b26163af135b95d0033c6e0d4d17a4c", + "size": 1199, + "filename": "nU7EwPudCI5rXxufi3t7tpc70dp80O3ZotM1AT7ykWM=.pem", + "location": "security-state-staging/intermediates/006fccb2-4e50-4a04-9fb3-e494af645da7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nU7EwPudCI5rXxufi3t7tpc70dp80O3ZotM1AT7ykWM=", + "crlite_enrolled": false, + "id": "0f3afefb-650b-4cde-8c51-1df2f9c0f3d1", + "last_modified": 1658343423295 + }, + { + "schema": 1658342917102, + "derHash": "+mMxumKN9Zm+siQ73joVCYWHcE6Vb+UKeTOrwPz3nvc=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "8313929cdeaddcf8c97a3c6eddd7039590de333942574d510029006f20ef5067", + "size": 1642, + "filename": "NTYQd0G957Ta5R5kkO8Wfm5kSc5JLNHicY6ov35lm8w=.pem", + "location": "security-state-staging/intermediates/1c2ec99e-c535-4068-889b-88c1a83bded4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NTYQd0G957Ta5R5kkO8Wfm5kSc5JLNHicY6ov35lm8w=", + "crlite_enrolled": false, + "id": "4320d94e-b13f-4d43-8319-d412485e69ea", + "last_modified": 1658343423248 + }, + { + "schema": 1658342916203, + "derHash": "x6x8W4Fi4PCTHRsvd0DCKE/xWVwQ6C2x7joSWPCnQG8=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2022 Q4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMiBRNA==", + "whitelist": false, + "attachment": { + "hash": "8b689b865e6f89099e8215d8dfed3d904ea786c462ff8b1b9f7c6cb635056cb0", + "size": 1199, + "filename": "og4LaEAtW-sWqKhXY7RHwH0Efd29XJryEVlUaFL65Cc=.pem", + "location": "security-state-staging/intermediates/41a9091f-10cc-458f-9fe8-9314dca24e34.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "og4LaEAtW+sWqKhXY7RHwH0Efd29XJryEVlUaFL65Cc=", + "crlite_enrolled": false, + "id": "c8731483-52e6-4546-a499-04415c4c488d", + "last_modified": 1658343423239 + }, + { + "schema": 1657673315354, + "derHash": "1gTwFIol2TurV304cFy6rixEd95+tR2GoPayZYQBPXo=", + "subject": "CN=Thawte G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEzMDEGA1UEAxMqVGhhd3RlIEc1IFRMUyBDTiBFQ0MgUC0zODQgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "3129242e85cdf0f309e258648b1644d453f871ce619308f47cbf52fb63efabc7", + "size": 1244, + "filename": "OJlQ9mdtsB5vrqQ648dNZZGkd5CgwQhqLYav-0L7PzY=.pem", + "location": "security-state-staging/intermediates/590b4b4e-10b8-46d4-b352-5badc01a1fef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OJlQ9mdtsB5vrqQ648dNZZGkd5CgwQhqLYav+0L7PzY=", + "crlite_enrolled": false, + "id": "27e46e16-a6cd-4328-810e-bbfd6f6cda46", + "last_modified": 1657673823452 + }, + { + "schema": 1657673317285, + "derHash": "kV7I3MI3+pMssCRamW/zpxFVCkZCFP11EvIlqmCAYcg=", + "subject": "CN=DigiCert G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE1MDMGA1UEAxMsRGlnaUNlcnQgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "1fb03123d91ae87c93b92fe243f54a0a2bef16ebd20fd7929d508c587216e450", + "size": 1248, + "filename": "R3PZqaW3zCxxaQ5bSQKCTasFBA4J5Cnc9fLzxRf0_bs=.pem", + "location": "security-state-staging/intermediates/dc178a9d-41d4-43e0-b432-a4241b65e4dc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "R3PZqaW3zCxxaQ5bSQKCTasFBA4J5Cnc9fLzxRf0/bs=", + "crlite_enrolled": false, + "id": "ab257771-7ba0-4d9a-82f4-c57bc9689748", + "last_modified": 1657673823443 + }, + { + "schema": 1657673313115, + "derHash": "q373BbC42yKRwryNyfaa2DRaPMVD24zcSai0GSgt6iI=", + "subject": "CN=DigiCert G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEzMDEGA1UEAxMqRGlnaUNlcnQgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "75314f6a9a6c8cab0a538f33f85a9899fe32efa8ba2411045fd4e1490b813572", + "size": 2393, + "filename": "wbQtVyDhsJDZ7ZTgMHcNj5L1XL3nQo_qsShPSxHiGlg=.pem", + "location": "security-state-staging/intermediates/7d18b322-587b-4da8-b437-277da1ed32eb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wbQtVyDhsJDZ7ZTgMHcNj5L1XL3nQo/qsShPSxHiGlg=", + "crlite_enrolled": false, + "id": "a00a96bc-01aa-427d-a44a-46bb9ede86eb", + "last_modified": 1657673823434 + }, + { + "schema": 1657673312034, + "derHash": "5Duq14t4+e6mkD5W9FbewkvZZIwTrgLi3heh+E89CAc=", + "subject": "CN=GeoTrust G5 TLS CN ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE1MDMGA1UEAxMsR2VvVHJ1c3QgRzUgVExTIENOIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "d4c1f4983ff6e6192de0acc76728fb52393bf92a90670487d4f3b63f1852ee17", + "size": 1248, + "filename": "5zJ7jFc2gNMH1N9rX_DHOV3JCnG-ukw0EkslpYXr-fM=.pem", + "location": "security-state-staging/intermediates/a77313bc-afed-4e76-b175-842d33665225.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5zJ7jFc2gNMH1N9rX/DHOV3JCnG+ukw0EkslpYXr+fM=", + "crlite_enrolled": false, + "id": "0f02ee9b-1f97-4776-a939-78091f93ea63", + "last_modified": 1657673823425 + }, + { + "schema": 1657673316324, + "derHash": "K20Zn341yMZ+C7qCn5KZHPMUZiZeEfowvRn8W2w2FRw=", + "subject": "CN=GeoTrust G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEzMDEGA1UEAxMqR2VvVHJ1c3QgRzUgVExTIENOIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "ddb561a05fdd0b1dd429bc3c3c50e0a3805423d5b279916f9ffb3d4ff59bc5b4", + "size": 2393, + "filename": "HHoUjrAj1XP5vnU44w9Pu7YgFS_u0QD7hTHolzugvKQ=.pem", + "location": "security-state-staging/intermediates/cb70ae36-1854-4bda-922f-ee1120bc9050.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HHoUjrAj1XP5vnU44w9Pu7YgFS/u0QD7hTHolzugvKQ=", + "crlite_enrolled": false, + "id": "3f839c5b-ebd1-4e42-9cbe-54203b19af29", + "last_modified": 1657673823416 + }, + { + "schema": 1657673314428, + "derHash": "Honj0nAtPxc13OfamVYWAFNKBkVKlIdNB4DQRDyvQVE=", + "subject": "CN=Thawte G5 TLS CN RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjExMC8GA1UEAxMoVGhhd3RlIEc1IFRMUyBDTiBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "4e176dd6e8181e4dc2b1cbad5e566815a70ec2668675335f58b4f586eacfa6a1", + "size": 2389, + "filename": "8S2WQvlz04IQBZ9zLQRNo9VXRswtrQL75271G7UZlK4=.pem", + "location": "security-state-staging/intermediates/8eec4898-0567-46bf-aa86-2f2e3a4151f4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8S2WQvlz04IQBZ9zLQRNo9VXRswtrQL75271G7UZlK4=", + "crlite_enrolled": false, + "id": "2bcd4ea3-7fd9-4719-9830-8b7a610d018e", + "last_modified": 1657673823406 + }, + { + "schema": 1657155187491, + "derHash": "V1wJsHoAHKDvazscVzC/r+vHrZ9WoAnxlmwy8I5llxM=", + "subject": "CN=Certainly Intermediate R1,O=Certainly,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxIjAgBgNVBAMTGUNlcnRhaW5seSBJbnRlcm1lZGlhdGUgUjE=", + "whitelist": false, + "attachment": { + "hash": "e41f784cddd9ac3598e0258784831181fc8dccce987e7db71c6ce134e24e0fd6", + "size": 1849, + "filename": "oW7smChMJRcnzTObF7K-HzInReAPTxB_L1h6eZTmw9Q=.pem", + "location": "security-state-staging/intermediates/aed34aad-86d4-49be-a6b3-93dee7593195.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oW7smChMJRcnzTObF7K+HzInReAPTxB/L1h6eZTmw9Q=", + "crlite_enrolled": false, + "id": "b9823b97-5e12-4d86-abb6-9ac0496f63ec", + "last_modified": 1657155423567 + }, + { + "schema": 1657155185382, + "derHash": "uzIJhSZvYcXRgnJPFc7EKKaY7XEmK04JfLEa7YpBquY=", + "subject": "CN=RapidSSL G5 TLS RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEwMC4GA1UEAxMnUmFwaWRTU0wgRzUgVExTIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "a46da143e8828415ed2c19d4abe0277a73bdca9d15a7e56d823ef705e4e9dd9d", + "size": 2393, + "filename": "ZN29cWo4gqX1T1Jhiy4cANI2l-qFWm4CRPmNAjZHQ0M=.pem", + "location": "security-state-staging/intermediates/0b6da9cc-807f-4985-9313-92743a07df63.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZN29cWo4gqX1T1Jhiy4cANI2l+qFWm4CRPmNAjZHQ0M=", + "crlite_enrolled": false, + "id": "e41650b2-446a-4178-ae1f-1795e6289b42", + "last_modified": 1657155423558 + }, + { + "schema": 1657155181556, + "derHash": "SFacT6v/TjqiLg4ZYT6Q2omqLuJGmwcYaVQ39ThO3Ew=", + "subject": "CN=Thawte G5 TLS RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEuMCwGA1UEAxMlVGhhd3RlIEc1IFRMUyBSU0E0MDk2IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "9257dd0fa829ef23d417c2dafc5307d97f7d408119a7ea39bbb182e69714ad7a", + "size": 2389, + "filename": "jwygsm5ekiXhlM9Qo1Gukh9nqupPzCn5Q9hF-L5my7g=.pem", + "location": "security-state-staging/intermediates/8c95e577-1bbe-4c78-8379-8ed1b8e67b03.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jwygsm5ekiXhlM9Qo1Gukh9nqupPzCn5Q9hF+L5my7g=", + "crlite_enrolled": false, + "id": "20db9248-efc7-42b5-882a-4efd4be40a4f", + "last_modified": 1657155423521 + }, + { + "schema": 1657155180562, + "derHash": "f044uLIvkdr0btWFbW5/EYApb7FSwCZHPGMoUZ0qyZo=", + "subject": "CN=Thawte G5 TLS EC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFcxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEvMC0GA1UEAxMmVGhhd3RlIEc1IFRMUyBFQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "21950c52b103d1dedbe04a646addc9951618071a9d6f63996cc7ac190b8279b2", + "size": 1248, + "filename": "XIsZdr3joH5_p8qyCKQXsogaRvG4hpH8M4VBUjk1YPc=.pem", + "location": "security-state-staging/intermediates/042a667f-3d52-4905-b7ce-be55d86fec6e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XIsZdr3joH5/p8qyCKQXsogaRvG4hpH8M4VBUjk1YPc=", + "crlite_enrolled": false, + "id": "885b1280-4e8d-454d-8ace-a1afa9441a5d", + "last_modified": 1657155423511 + }, + { + "schema": 1657155184412, + "derHash": "cvEECE23kUvYr+bjR7klftTB1/xx0/HlHzz0e3ObOGo=", + "subject": "CN=GeoTrust G5 TLS EC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgRzUgVExTIEVDIFAtMzg0IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "09240b4c7dcd750c91356f4939b42eb301d12e093ea1842eefbc7b3120a2be3c", + "size": 1248, + "filename": "SxX48lclrQeNqlmN7mftdfAXq6mZ-YNTatoQ23u7wVw=.pem", + "location": "security-state-staging/intermediates/087a89be-4ff7-4814-95dd-5a32c4964f37.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "SxX48lclrQeNqlmN7mftdfAXq6mZ+YNTatoQ23u7wVw=", + "crlite_enrolled": false, + "id": "45fa4b14-2582-44d3-966a-c443d6a821bd", + "last_modified": 1657155423484 + }, + { + "schema": 1657155177680, + "derHash": "0qPU7ZwFlk9KCYQ4GTPFqx6T5KC6f83WCUCUbWi9YmI=", + "subject": "CN=RapidSSL G5 TLS ECC P-384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFoxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEyMDAGA1UEAxMpUmFwaWRTU0wgRzUgVExTIEVDQyBQLTM4NCBTSEEzODQgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "1b3dbfd915cc4a8ef194f75d79f0825274f4d7629227049a55d8eaa7fec5a797", + "size": 1252, + "filename": "0uoOEtLd79sSIvnrUVWhaOfwrdGuESG2lhR8UbLO3Zs=.pem", + "location": "security-state-staging/intermediates/521141ab-758a-4230-a712-f1a6da8db7a1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0uoOEtLd79sSIvnrUVWhaOfwrdGuESG2lhR8UbLO3Zs=", + "crlite_enrolled": false, + "id": "1737f3cc-3ca3-47a8-8ee9-c332f69156cb", + "last_modified": 1657155423474 + }, + { + "schema": 1657155174817, + "derHash": "ADDGjFP8BSN/zHq2AX2B/rugYxVSbFf35ykHyHuYK2E=", + "subject": "CN=GeoTrust G5 TLS RSA4096 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEwMC4GA1UEAxMnR2VvVHJ1c3QgRzUgVExTIFJTQTQwOTYgU0hBMzg0IDIwMjIgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "183211b8d3e915377a7cd28d02480b161afecaf6559d1f46cdf0860879dbfc3a", + "size": 2393, + "filename": "wGQDAGF3i3-kzCeCWjjReM5gy3jaEbG-RB-IpVmNIS8=.pem", + "location": "security-state-staging/intermediates/b3ac4b57-3de7-499a-b496-5f0d967cd4fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wGQDAGF3i3+kzCeCWjjReM5gy3jaEbG+RB+IpVmNIS8=", + "crlite_enrolled": false, + "id": "39c3717b-2f27-4997-a877-64c6b1d0c07a", + "last_modified": 1657155423465 + }, + { + "schema": 1657155176734, + "derHash": "mN0qdfZXSPJme8SweRd4E2R8F+3Mbe6S8fBmaL4PGj0=", + "subject": "CN=Certainly Intermediate E1,O=Certainly,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxIjAgBgNVBAMTGUNlcnRhaW5seSBJbnRlcm1lZGlhdGUgRTE=", + "whitelist": false, + "attachment": { + "hash": "76c6228150a0a8f381c06291729798de20be55cd5043dd4f523760e9d3b235f2", + "size": 1049, + "filename": "XkxTESlVMF59ZsGRFSBQJyCYjYJYNYkLwv_sqaDnMJA=.pem", + "location": "security-state-staging/intermediates/95aebc57-351d-416d-80ee-ba89b85b10b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XkxTESlVMF59ZsGRFSBQJyCYjYJYNYkLwv/sqaDnMJA=", + "crlite_enrolled": false, + "id": "db125289-bd3d-4494-a5af-8d553f2fb947", + "last_modified": 1657155423456 + }, + { + "schema": 1657025499984, + "derHash": "PcCAd/dgZGNNbt4ptDjRKHp5fZJ22htxR9HrgOI160c=", + "subject": "CN=HARICA Institutional TLS RSA 2,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MG8xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMScwJQYDVQQDDB5IQVJJQ0EgSW5zdGl0dXRpb25hbCBUTFMgUlNBIDI=", + "whitelist": false, + "attachment": { + "hash": "6199215d6d6e46bcc4cc8973cc1f299c4d54c1ce51053c54f517e1562ed3ce4d", + "size": 2410, + "filename": "WVVX2ZHgkd43h3xVW_h-NvoJNZbiDQTQMV5w5LAW0Q8=.pem", + "location": "security-state-staging/intermediates/da686243-6e58-421e-8d7c-2b806ee0a81f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WVVX2ZHgkd43h3xVW/h+NvoJNZbiDQTQMV5w5LAW0Q8=", + "crlite_enrolled": false, + "id": "c8fa87e5-592d-40b9-a3e4-89a6607912a0", + "last_modified": 1657025823257 + }, + { + "schema": 1657025499027, + "derHash": "lIAvGw2ydCYpOxLId1iA7/A0XmYgB59xSnUT4ZRlecM=", + "subject": "CN=HARICA Institutional TLS ECC 2,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MG8xCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMScwJQYDVQQDDB5IQVJJQ0EgSW5zdGl0dXRpb25hbCBUTFMgRUNDIDI=", + "whitelist": false, + "attachment": { + "hash": "0046a36b3f7ab1ff70620a8b32ba6f8a789d40ecd860bd003b3011fc436d4a1b", + "size": 1264, + "filename": "DIZUveJ7mhIqL0Ad_f-ZERAdfZL3-sHtErv5U5zzYXE=.pem", + "location": "security-state-staging/intermediates/928f16ab-8166-47c9-bba1-4e9b404461a8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DIZUveJ7mhIqL0Ad/f+ZERAdfZL3+sHtErv5U5zzYXE=", + "crlite_enrolled": false, + "id": "69bbac33-5ce5-4456-a193-bdc6d908fa4b", + "last_modified": 1657025823248 + }, + { + "schema": 1656701823763, + "derHash": "xJw1DlqCBeBj50xVSplDNbhDXJllJ9TvGisMe1FYSy0=", + "subject": "CN=Buypass Class 3 CA 3,O=Buypass AS-983163327,C=NO", + "subjectDN": "MEsxCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEdMBsGA1UEAwwUQnV5cGFzcyBDbGFzcyAzIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "954fa22283314bcbe40077bc8cee7655cbcfce4fb215854858c5599d8c8b498f", + "size": 1723, + "filename": "b2TqrhO3HsQ2hHTQB23iQnAAL127LlWqd3L_IgvgTtk=.pem", + "location": "security-state-staging/intermediates/379744b3-624d-49d1-b082-83d5b607dc72.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b2TqrhO3HsQ2hHTQB23iQnAAL127LlWqd3L/IgvgTtk=", + "crlite_enrolled": false, + "id": "636e348f-d4b7-425a-aabb-4eab295a6c6e", + "last_modified": 1656896223181 + }, + { + "schema": 1656701331513, + "derHash": "WoTJQFTTQNZQopmF75e7OWNS4hWu1sCzPKf/3TvV0qI=", + "subject": "CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFMxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxLTArBgNVBAMTJFN3aXNzU2lnbiBSU0EgU01JTUUgUm9vdCBDQSAyMDIyIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "f5c50b1eb05ddcd12e58d62bf14a7146c9ee6abac9c4e0a2212437d6fd8e8953", + "size": 2117, + "filename": "lscl7HdjRMUHcclcvnvcvSKZWwmFafcnrjiw69_1v34=.pem", + "location": "security-state-staging/intermediates/2eb2a167-41f4-4bde-bdaa-fdcf93c34129.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lscl7HdjRMUHcclcvnvcvSKZWwmFafcnrjiw69/1v34=", + "crlite_enrolled": false, + "id": "4cfb7454-e1fb-470f-9660-70576be6323f", + "last_modified": 1656701823304 + }, + { + "schema": 1656701333450, + "derHash": "GTFE9DHg/dt0BxfU3pJqVxEziEtDYNMOJykTy+ZgzkE=", + "subject": "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMiAtIDE=", + "whitelist": false, + "attachment": { + "hash": "6ede8f075d26a4efe3aaa11acec3ac0aa5fb6a50fe08fb36ec7d919a8ad0be8e", + "size": 1991, + "filename": "68l4rg3Z5YItaxllJZb2IMk9fK76lSGRywUKYyypAF8=.pem", + "location": "security-state-staging/intermediates/f6df929b-2bdd-462f-a3b4-18df32996b70.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "68l4rg3Z5YItaxllJZb2IMk9fK76lSGRywUKYyypAF8=", + "crlite_enrolled": false, + "id": "305ffb0c-8b3a-4927-a85c-8bc98600693e", + "last_modified": 1656701823286 + }, + { + "schema": 1656701328514, + "derHash": "mhLDkr/leJGgxUUwnU2f1WfkgMthPWNCJ4sZXHmnkx8=", + "subject": "CN=SwissSign RSA SMIME Root CA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFMxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxLTArBgNVBAMTJFN3aXNzU2lnbiBSU0EgU01JTUUgUm9vdCBDQSAyMDIyIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "68921ff34a6b13fef5893df8dcd7193fbf6c07d6756c8e72fc46604ce69dbcfa", + "size": 1999, + "filename": "lscl7HdjRMUHcclcvnvcvSKZWwmFafcnrjiw69_1v34=.pem", + "location": "security-state-staging/intermediates/219de1a1-ca20-4079-93af-cd73fe778a24.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lscl7HdjRMUHcclcvnvcvSKZWwmFafcnrjiw69/1v34=", + "crlite_enrolled": false, + "id": "a644bff9-3b45-42cb-a1e9-a33b7fc61bc4", + "last_modified": 1656701823267 + }, + { + "schema": 1656679991246, + "derHash": "KItKn2BbCbmZshWFCCXIH5tTfbryNmSsqYv2upjtw3k=", + "subject": "CN=SwissSign RSA TLS Root CA 2022 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMiAtIDE=", + "whitelist": false, + "attachment": { + "hash": "478084a4303c773a356f2f8589398cc13b07ecdb944e3ba05b5426b3994d121f", + "size": 2113, + "filename": "68l4rg3Z5YItaxllJZb2IMk9fK76lSGRywUKYyypAF8=.pem", + "location": "security-state-staging/intermediates/ba1fb05a-72e7-4589-9d39-525cd7813c6b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "68l4rg3Z5YItaxllJZb2IMk9fK76lSGRywUKYyypAF8=", + "crlite_enrolled": false, + "id": "74f5feed-48bf-4f12-a0bc-3ba6b4efa970", + "last_modified": 1656680223653 + }, + { + "schema": 1656593438911, + "derHash": "e6jwu0n1Aem7cuOhNwjGqTMUD9xlWSo3rN5mrQfeYH4=", + "subject": "CN=E-Tugra EV TLS ECC CA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHkxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEhMB8GA1UEAwwYRS1UdWdyYSBFViBUTFMgRUNDIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "44ede938c644c24e6bc4a9519c7d68e213dbaab15d040f4c11be5df30334cab1", + "size": 1301, + "filename": "4ZfJiEG0nvuWdrXyXFm7I_5f5kGPrz17N0dYeK9fTB8=.pem", + "location": "security-state-staging/intermediates/e42e0283-11fb-4d68-9fdc-e27de55696c0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4ZfJiEG0nvuWdrXyXFm7I/5f5kGPrz17N0dYeK9fTB8=", + "crlite_enrolled": false, + "id": "575f3809-4ce0-46d3-9943-7e5d0bdbf409", + "last_modified": 1656593824346 + }, + { + "schema": 1656593435988, + "derHash": "o2wcxiPs8+2JmprBT91WIJGYWOYh5od+Ae9Q2h22o6s=", + "subject": "CN=E-Tugra EV TLS ECC SubCA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHwxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEkMCIGA1UEAwwbRS1UdWdyYSBFViBUTFMgRUNDIFN1YkNBIFIx", + "whitelist": false, + "attachment": { + "hash": "ac69fb7040d5de96db8f50a57d6c27115912d6bc2118023b36597e5d37ef36c9", + "size": 1325, + "filename": "xI042fT9TrCjknH-POp5PoRYy2_NfN2GeXL65Ro9074=.pem", + "location": "security-state-staging/intermediates/89734696-672f-4546-8ced-8c9db56c1389.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xI042fT9TrCjknH+POp5PoRYy2/NfN2GeXL65Ro9074=", + "crlite_enrolled": false, + "id": "a975d8c0-8a24-44d2-b5e5-04a63110ee4f", + "last_modified": 1656593824319 + }, + { + "schema": 1656593433064, + "derHash": "7aycRZCfTex7tl8KT79qigN15Srt1m4GiI/tfj7exTc=", + "subject": "CN=SSL.com SSL Enterprise Intermediate CA RSA R1,O=SSL Corp,L=Houston,ST=Texas,C=US", + "subjectDN": "MHoxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxNjA0BgNVBAMMLVNTTC5jb20gU1NMIEVudGVycHJpc2UgSW50ZXJtZWRpYXRlIENBIFJTQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "e7ea8ef85389aa88ac596ccb897735ba7ddd66059d1fbe03da617c86f1076aef", + "size": 2438, + "filename": "oKApNpDL2hzvB6fqN6yfWxbdoxF2lvnVZLw24c9MNWU=.pem", + "location": "security-state-staging/intermediates/3a3891d7-fdc2-4adc-ba30-4906428c1433.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oKApNpDL2hzvB6fqN6yfWxbdoxF2lvnVZLw24c9MNWU=", + "crlite_enrolled": false, + "id": "127dc6af-f205-455a-95fc-0616594d5388", + "last_modified": 1656593824300 + }, + { + "schema": 1656593421243, + "derHash": "YZB58QCmv7ZcsumIkN0KxJK2RmXiVKZqUp2PWRfYKrE=", + "subject": "CN=SSL.com EV TLS Transit ECC CA R1,O=SSL Corp,C=US", + "subjectDN": "MEsxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhTU0wgQ29ycDEpMCcGA1UEAwwgU1NMLmNvbSBFViBUTFMgVHJhbnNpdCBFQ0MgQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "aba9b8b17d29de5bf429a3a146e9f035652511f71e8604b5e6d669660d481733", + "size": 1240, + "filename": "TJF_18-c6aze9ptYohDyVqW8qpXBnJDFv7VibJ8Mwrk=.pem", + "location": "security-state-staging/intermediates/26a3afe7-ca6e-46a5-8158-e5e83ce49bea.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TJF/18+c6aze9ptYohDyVqW8qpXBnJDFv7VibJ8Mwrk=", + "crlite_enrolled": false, + "id": "ebb798e5-5790-4ba9-8cf7-165335d2209c", + "last_modified": 1656593824210 + }, + { + "schema": 1656593424089, + "derHash": "eF/TI2TEV+KutDU6qVgI83GqiQbhHEDhPDOKaz7nO+E=", + "subject": "CN=MuaSSL.com EV TLS Issuing ECC CA R1,O=Hao Quang Viet Software Company Limited,C=VN", + "subjectDN": "MG0xCzAJBgNVBAYTAlZOMTAwLgYDVQQKDCdIYW8gUXVhbmcgVmlldCBTb2Z0d2FyZSBDb21wYW55IExpbWl0ZWQxLDAqBgNVBAMMI011YVNTTC5jb20gRVYgVExTIElzc3VpbmcgRUNDIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "12d9a498ee7bb4d247b3c09642f3b69721fe49d6185d4bde2cbb621512bc2fd5", + "size": 1955, + "filename": "KsymRI2FihoZ5c5m000bkzpYqk9OfXKMBPz7WEodqEg=.pem", + "location": "security-state-staging/intermediates/eab4cf67-c490-49d7-9a15-8b9697dd8773.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KsymRI2FihoZ5c5m000bkzpYqk9OfXKMBPz7WEodqEg=", + "crlite_enrolled": false, + "id": "83c84620-c1ca-4851-a226-92279eafb78e", + "last_modified": 1656593824174 + }, + { + "schema": 1656593418343, + "derHash": "EIFi8rNe1xxuqVAvlLk+l1R4nd6HGyrCbOBtRyB6lcw=", + "subject": "CN=E-Tugra TLS ECC CA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHYxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEeMBwGA1UEAwwVRS1UdWdyYSBUTFMgRUNDIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "008fdcffa730381608cc67c889da1e0ab12bc12a12ddf2208d4c2c687d6db002", + "size": 1284, + "filename": "77Kdi8Xr9UzIu5aCuPnN3svP6mCdcSnpHa2V6e8x1zA=.pem", + "location": "security-state-staging/intermediates/52b2ee0b-8206-410b-b7fe-cf2b915bb6cf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "77Kdi8Xr9UzIu5aCuPnN3svP6mCdcSnpHa2V6e8x1zA=", + "crlite_enrolled": false, + "id": "99b4dab8-e999-4921-8a6c-8090ff410950", + "last_modified": 1656593824146 + }, + { + "schema": 1656593414422, + "derHash": "wtT9+72C9KHNrEcT46sJmVYJGNehcN3gP33b+aKI3Nw=", + "subject": "CN=SafeToOpen EV TLS ICA RSA R1,O=SafeToOpen Ltd,C=NZ", + "subjectDN": "ME0xCzAJBgNVBAYTAk5aMRcwFQYDVQQKDA5TYWZlVG9PcGVuIEx0ZDElMCMGA1UEAwwcU2FmZVRvT3BlbiBFViBUTFMgSUNBIFJTQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "c8ea2c7378fce0abf39bad65390bcf5769cfc2cba17276d42c03ea8042f2583f", + "size": 2438, + "filename": "wceoTySaLCXY7FAABlMa1eB4Fc2Vodc8zSQij-gt6Ko=.pem", + "location": "security-state-staging/intermediates/f046eb18-fb56-4077-bc4c-18ac9c16947a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wceoTySaLCXY7FAABlMa1eB4Fc2Vodc8zSQij+gt6Ko=", + "crlite_enrolled": false, + "id": "ffdd3c38-1c78-4fe8-8e61-51d74d34cbdf", + "last_modified": 1656593824138 + }, + { + "schema": 1656593398913, + "derHash": "myWmp71RvF9cTwbg0SGP03DdMfr9XNO2J8gvIC8bRlc=", + "subject": "CN=SSL.com EV SSL Enterprise Intermediate CA RSA R2,O=SSL Corp,L=Houston,ST=Texas,C=US", + "subjectDN": "MH0xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxOTA3BgNVBAMMMFNTTC5jb20gRVYgU1NMIEVudGVycHJpc2UgSW50ZXJtZWRpYXRlIENBIFJTQSBSMg==", + "whitelist": false, + "attachment": { + "hash": "0f9bd08401f5f0614a6d92b2a24f7f9f3e37fc29bebb56b26277cf035d2febcd", + "size": 2454, + "filename": "vcL6kVP3jP8hbTn5A-tyKe1IWIWBFnhvUva3qmvEG2I=.pem", + "location": "security-state-staging/intermediates/7a6a9384-b314-47c5-b7f6-3282864f07ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vcL6kVP3jP8hbTn5A+tyKe1IWIWBFnhvUva3qmvEG2I=", + "crlite_enrolled": false, + "id": "10178d5e-c0db-4e9c-96a0-42d3461fccd0", + "last_modified": 1656593823993 + }, + { + "schema": 1656593393258, + "derHash": "JOegR1aXeTq/Pg3bBNVC/+fJPMcdGfYGcUSsqJpMpng=", + "subject": "CN=E-Tugra EV TLS RSA CA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHkxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEhMB8GA1UEAwwYRS1UdWdyYSBFViBUTFMgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "ebef98f07c68cf6ac52e810feef1f4bfe2c3bbd23d29ed1bc67b27cc0986a461", + "size": 2454, + "filename": "xybzNe-3QsP87NG3ziCnmdMQCSi_ImhJj-f6c7P2LVg=.pem", + "location": "security-state-staging/intermediates/4acf221b-66fc-4211-9056-7e3919e9081e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xybzNe+3QsP87NG3ziCnmdMQCSi/ImhJj+f6c7P2LVg=", + "crlite_enrolled": false, + "id": "8a51f832-3ea2-4ec9-8b7d-d8fcd23dc5cf", + "last_modified": 1656593823867 + }, + { + "schema": 1656593383759, + "derHash": "WGpcmDbZ+zvoY7p2Wnlm2UApf+x73UiAFseqHq4fHVE=", + "subject": "CN=E-Tugra TLS RSA CA R1,O=E-TUGRA EBG BILISIM TEKNOLOJILERI VE HIZMETLERI ANONIM SIRKETI,C=TR", + "subjectDN": "MHYxCzAJBgNVBAYTAlRSMUcwRQYDVQQKDD5FLVRVR1JBIEVCRyBCSUxJU0lNIFRFS05PTE9KSUxFUkkgVkUgSElaTUVUTEVSSSBBTk9OSU0gU0lSS0VUSTEeMBwGA1UEAwwVRS1UdWdyYSBUTFMgUlNBIENBIFIx", + "whitelist": false, + "attachment": { + "hash": "ad5db3ac540ab992c75f23d4cac4cd2eb9106ce956062e0d8fa5b0a82bebfd69", + "size": 2434, + "filename": "1vdxb07v0C-5fBq6GPagfHvdyZ31E78A2uKjjcHp3Z0=.pem", + "location": "security-state-staging/intermediates/c13e4075-4455-4d13-b078-eb10f272dd6f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1vdxb07v0C+5fBq6GPagfHvdyZ31E78A2uKjjcHp3Z0=", + "crlite_enrolled": false, + "id": "5f65e164-f0df-47e7-8cd7-7b61698c7405", + "last_modified": 1656593823848 + }, + { + "schema": 1656593439685, + "derHash": "hZlo+SeLTcgu4w/RGKXSl9KEtofLZM+XVauKnTjytw8=", + "subject": "CN=Trustwave Domain Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGvMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE3MDUGA1UEAxMuVHJ1c3R3YXZlIERvbWFpbiBWYWxpZGF0aW9uIFNIQTI1NiBDQSwgTGV2ZWwgMTEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbQ==", + "whitelist": false, + "attachment": { + "hash": "9eada92e3a97dd020d1e54af280aa60bf3400d8c05725aec56aa8357bd9c9bed", + "size": 1768, + "filename": "D2BA_wGfRVRt6vATeLdkNJ0B3ckV2GlyUIDdWUqCSDQ=.pem", + "location": "security-state-staging/intermediates/9e9ff0f0-f45d-41dc-8a17-414e7c15f484.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D2BA/wGfRVRt6vATeLdkNJ0B3ckV2GlyUIDdWUqCSDQ=", + "crlite_enrolled": false, + "id": "60bc13d6-1760-488c-b696-f2dd9d3df1a2", + "last_modified": 1656593823567 + }, + { + "schema": 1656593439858, + "derHash": "dPVuGrM52D6A8vbiZPiUd1tCbIm0RwPfT6ijIxFjor0=", + "subject": "CN=Trustwave Domain Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGvMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE3MDUGA1UEAxMuVHJ1c3R3YXZlIERvbWFpbiBWYWxpZGF0aW9uIFNIQTI1NiBDQSwgTGV2ZWwgMTEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbQ==", + "whitelist": false, + "attachment": { + "hash": "f3dfc731d826e0d0984812c4ea4c6e2485418da6eea5873875262b9282480123", + "size": 1792, + "filename": "D2BA_wGfRVRt6vATeLdkNJ0B3ckV2GlyUIDdWUqCSDQ=.pem", + "location": "security-state-staging/intermediates/ddbc7552-96a7-4b2e-91f9-ffb3835036ce.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "D2BA/wGfRVRt6vATeLdkNJ0B3ckV2GlyUIDdWUqCSDQ=", + "crlite_enrolled": false, + "id": "ef25e7fa-720b-412c-bf83-aba79dc2aa8e", + "last_modified": 1656593823398 + }, + { + "schema": 1656377500409, + "derHash": "M3kjPENGEMjqqTYay90kx9ZVQJxtaAqMJYX/2icBHuc=", + "subject": "CN=Certainly Intermediate E1,O=Certainly,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxIjAgBgNVBAMTGUNlcnRhaW5seSBJbnRlcm1lZGlhdGUgRTE=", + "whitelist": false, + "attachment": { + "hash": "739708f1858ccbfda885584c5cda37b99b3adb250bfc100597651a2181434123", + "size": 1431, + "filename": "XkxTESlVMF59ZsGRFSBQJyCYjYJYNYkLwv_sqaDnMJA=.pem", + "location": "security-state-staging/intermediates/aec42e84-5781-4550-abcc-32034d806ae9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XkxTESlVMF59ZsGRFSBQJyCYjYJYNYkLwv/sqaDnMJA=", + "crlite_enrolled": false, + "id": "290c07c5-00f6-499d-b963-14f9f99c0063", + "last_modified": 1656377823175 + }, + { + "schema": 1656377499425, + "derHash": "/sQeMsp1wpWmJA+mOdOr47+1yxMdZpDiMxoXa+0uW9I=", + "subject": "CN=Certainly Intermediate R1,O=Certainly,C=US", + "subjectDN": "MEUxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxIjAgBgNVBAMTGUNlcnRhaW5seSBJbnRlcm1lZGlhdGUgUjE=", + "whitelist": false, + "attachment": { + "hash": "afc653cd77db9c4d8fa80e206f6353a4bd9573ca975c47a0a2f9c1de8e118a79", + "size": 1662, + "filename": "oW7smChMJRcnzTObF7K-HzInReAPTxB_L1h6eZTmw9Q=.pem", + "location": "security-state-staging/intermediates/a0e64f06-95e9-4575-91b4-1ab0f57b85d1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oW7smChMJRcnzTObF7K+HzInReAPTxB/L1h6eZTmw9Q=", + "crlite_enrolled": false, + "id": "c5bdc84f-b484-4dbf-a07f-8203dc7b56e2", + "last_modified": 1656377823166 + }, + { + "schema": 1656074940515, + "derHash": "1dlEXuqlV2CB3fLmwJBAkbvHm6EJFeUhXIoqfYeRX/0=", + "subject": "CN=Telekom Security OV RSA CA 22,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MF4xCzAJBgNVBAYTAkRFMScwJQYDVQQKDB5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxJjAkBgNVBAMMHVRlbGVrb20gU2VjdXJpdHkgT1YgUlNBIENBIDIy", + "whitelist": false, + "attachment": { + "hash": "e09897d7a0cefffbe417704c74cb1c421a8074435dbbf081fb73d50c98b66bed", + "size": 2113, + "filename": "cXfBiiJQaSpqBKYGsSWkrU78rL4ZagLtMBlxJDoTues=.pem", + "location": "security-state-staging/intermediates/045aac79-cc35-49db-8b4d-0666190c0af9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cXfBiiJQaSpqBKYGsSWkrU78rL4ZagLtMBlxJDoTues=", + "crlite_enrolled": false, + "id": "5707ab1e-5263-4ff4-9640-082925acaabe", + "last_modified": 1656075423209 + }, + { + "schema": 1656031805961, + "derHash": "xetU60WOOBg8cL9L0QZNDPVX6gfqocswWWjoqKUHM+0=", + "subject": "CN=GoGetSSL Legacy TLS RSA2048 SHA256 2022 CA-1,O=EnVers Group SIA,C=LV", + "subjectDN": "MF8xCzAJBgNVBAYTAkxWMRkwFwYDVQQKExBFblZlcnMgR3JvdXAgU0lBMTUwMwYDVQQDEyxHb0dldFNTTCBMZWdhY3kgVExTIFJTQTIwNDggU0hBMjU2IDIwMjIgQ0EtMQ==", + "whitelist": false, + "attachment": { + "hash": "0621906b64527a9ffd48374d345c214f5c294b845139264be771b12e12ec89eb", + "size": 1727, + "filename": "_-7hi_M7WfeBQhcPsdYywwHKrxNuRchyIJJwekKe7xE=.pem", + "location": "security-state-staging/intermediates/924b0427-8b37-4c8d-b740-85dc711cd404.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/+7hi/M7WfeBQhcPsdYywwHKrxNuRchyIJJwekKe7xE=", + "crlite_enrolled": false, + "id": "4a62e5bc-31c3-4715-90f6-88e4396be8c2", + "last_modified": 1656032223542 + }, + { + "schema": 1656031804967, + "derHash": "iq3waKG3wEs+NG98l/2WGf/xTsxsgsLxVZS5cy8/PnI=", + "subject": "CN=GoGetSSL G2 TLS RSA4096 SHA256 2022 CA-1,O=EnVers Group SIA,C=LV", + "subjectDN": "MFsxCzAJBgNVBAYTAkxWMRkwFwYDVQQKExBFblZlcnMgR3JvdXAgU0lBMTEwLwYDVQQDEyhHb0dldFNTTCBHMiBUTFMgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQS0x", + "whitelist": false, + "attachment": { + "hash": "7f2a25db43334d8fed2fde428bbcb7e25d9183af1cc960187e10f961cf264d96", + "size": 2068, + "filename": "jqXG1ZIhT1CAG6gSh_w8OV1sD_1AJ2zg4zLiWHfnYyU=.pem", + "location": "security-state-staging/intermediates/3062e9f5-5339-48f3-9ccc-182e2a04e7f4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jqXG1ZIhT1CAG6gSh/w8OV1sD/1AJ2zg4zLiWHfnYyU=", + "crlite_enrolled": false, + "id": "797d2c5d-38d4-4806-b74b-e58bbc66b4b9", + "last_modified": 1656032223533 + }, + { + "schema": 1656031806939, + "derHash": "fmh/qGaAJTXDr8OTk5N21L8P11PZQhcZqKqRpP8wWtI=", + "subject": "CN=GoGetSSL G3 TLS ECC P-384 SHA384 2022 CA-1,O=EnVers Group SIA,C=LV", + "subjectDN": "MF0xCzAJBgNVBAYTAkxWMRkwFwYDVQQKExBFblZlcnMgR3JvdXAgU0lBMTMwMQYDVQQDEypHb0dldFNTTCBHMyBUTFMgRUNDIFAtMzg0IFNIQTM4NCAyMDIyIENBLTE=", + "whitelist": false, + "attachment": { + "hash": "91d14297224a52444ef2e2fa1d5453f0a692049817e1155d4ec69de35e2dcae7", + "size": 1268, + "filename": "7s9fuoVWEHaVP7ml2DtXZ-yh3fLBiY2abtu_D9VpzNo=.pem", + "location": "security-state-staging/intermediates/aac560b1-555b-4de5-8036-e24472f61a88.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7s9fuoVWEHaVP7ml2DtXZ+yh3fLBiY2abtu/D9VpzNo=", + "crlite_enrolled": false, + "id": "cc20f2ed-b23f-45b9-b4f9-1aaffc1a93b7", + "last_modified": 1656032223524 + }, + { + "schema": 1655988662460, + "derHash": "7qNsD/o/VP/bDPFLO9NKU+x7d19UBT3YsH39L4vskIM=", + "subject": "CN=Trustwave Extended Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGxMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE5MDcGA1UEAxMwVHJ1c3R3YXZlIEV4dGVuZGVkIFZhbGlkYXRpb24gU0hBMjU2IENBLCBMZXZlbCAxMR8wHQYJKoZIhvcNAQkBFhBjYUB0cnVzdHdhdmUuY29t", + "whitelist": false, + "attachment": { + "hash": "621b7f65d119f8cacb26cf5956b7a206e1d42c2d931a41cf4311fdbc0bf8501e", + "size": 1825, + "filename": "zE8XYBj3Yf6nXNVdTry0tXUx6LGqIbwnbyd7rIOKue0=.pem", + "location": "security-state-staging/intermediates/1cd64f4c-6774-4383-aea2-1be395858f0e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zE8XYBj3Yf6nXNVdTry0tXUx6LGqIbwnbyd7rIOKue0=", + "crlite_enrolled": false, + "id": "3ba57112-f78c-4fa3-94f7-1eb77f9f8f2f", + "last_modified": 1655989023253 + }, + { + "schema": 1655988662264, + "derHash": "ScWCcPcPG+C3wZ0bwsLrxB4v3g1IhdT6Ze8DcKzsegA=", + "subject": "CN=Trustwave Extended Validation SHA256 CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGxMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE5MDcGA1UEAxMwVHJ1c3R3YXZlIEV4dGVuZGVkIFZhbGlkYXRpb24gU0hBMjU2IENBLCBMZXZlbCAxMR8wHQYJKoZIhvcNAQkBFhBjYUB0cnVzdHdhdmUuY29t", + "whitelist": false, + "attachment": { + "hash": "8d10fdb281e7d06718cfb07684595820b2ba91fe916794f089027ecd08741ae9", + "size": 1808, + "filename": "zE8XYBj3Yf6nXNVdTry0tXUx6LGqIbwnbyd7rIOKue0=.pem", + "location": "security-state-staging/intermediates/ce6b6bbd-6319-4603-8b73-cca667b9433f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zE8XYBj3Yf6nXNVdTry0tXUx6LGqIbwnbyd7rIOKue0=", + "crlite_enrolled": false, + "id": "9ae59d4e-7e03-474f-941d-3981e7735030", + "last_modified": 1655989023218 + }, + { + "schema": 1655189537975, + "derHash": "Du8FvaeELqhdHZgSSV78VhKDuqkNFDHFed1h9+2SaaE=", + "subject": "CN=Viking Cloud Organization Validation CA\\, Level 1,O=Viking Cloud\\, Inc.,C=US", + "subjectDN": "MGUxCzAJBgNVBAYTAlVTMRswGQYDVQQKExJWaWtpbmcgQ2xvdWQsIEluYy4xOTA3BgNVBAMTMFZpa2luZyBDbG91ZCBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBDQSwgTGV2ZWwgMQ==", + "whitelist": false, + "attachment": { + "hash": "006d8c79464234dbd24dd5a3685784e0002a510529a17a45c586e2ffdbfabd36", + "size": 1605, + "filename": "unLMj_QBNe_Q7to-eIcm3DJAgrMoPWPkEwFZMsbRY7k=.pem", + "location": "security-state-staging/intermediates/6745493c-4ee2-43be-b7a9-f05026fe3760.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "unLMj/QBNe/Q7to+eIcm3DJAgrMoPWPkEwFZMsbRY7k=", + "crlite_enrolled": false, + "id": "e59c2c37-90bb-440b-8396-b716e868dd84", + "last_modified": 1655189822967 + }, + { + "schema": 1655189537137, + "derHash": "OGrZZdokgSAW7T8BHyES39WRaT2r6z0OYbEUX1+dEhc=", + "subject": "CN=Viking Cloud Domain Validation CA\\, Level 1,O=Viking Cloud\\, Inc.,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRswGQYDVQQKExJWaWtpbmcgQ2xvdWQsIEluYy4xMzAxBgNVBAMTKlZpa2luZyBDbG91ZCBEb21haW4gVmFsaWRhdGlvbiBDQSwgTGV2ZWwgMQ==", + "whitelist": false, + "attachment": { + "hash": "098935eae4896b90a68d29bc952ccfd67b8c3dd84203ffcb2ac77db9abae8adb", + "size": 1585, + "filename": "junHeKNTWfpbz93WVSLN_XeDp-DEZkJSqMzt00XxYBA=.pem", + "location": "security-state-staging/intermediates/482b11d7-dab4-4f06-8578-b1e78997ed3a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "junHeKNTWfpbz93WVSLN/XeDp+DEZkJSqMzt00XxYBA=", + "crlite_enrolled": false, + "id": "459a3e96-9edd-4b67-a702-416676d676b7", + "last_modified": 1655189822960 + }, + { + "schema": 1655189536211, + "derHash": "ELYI5T4PP0RDKjK5Bs11MAFzasi/6Q7ToIR98oXi2Xc=", + "subject": "CN=Viking Cloud Extended Validation CA\\, Level 1,O=Viking Cloud\\, Inc.,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRswGQYDVQQKExJWaWtpbmcgQ2xvdWQsIEluYy4xNTAzBgNVBAMTLFZpa2luZyBDbG91ZCBFeHRlbmRlZCBWYWxpZGF0aW9uIENBLCBMZXZlbCAx", + "whitelist": false, + "attachment": { + "hash": "6ec99683dce9696b64633a066f22f6b0706a0ecb071f4e7b9410380d74da608c", + "size": 1601, + "filename": "iaX3F_DBxteIvZKYoTUXBd_2bVXGONmIkaeGjVNPR2w=.pem", + "location": "security-state-staging/intermediates/df82cc9d-4f57-40e8-af0e-c60132ffa421.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iaX3F/DBxteIvZKYoTUXBd/2bVXGONmIkaeGjVNPR2w=", + "crlite_enrolled": false, + "id": "0f04579c-51bb-474a-b50c-f4fb19248d2b", + "last_modified": 1655189822953 + }, + { + "schema": 1654825724896, + "derHash": "WVPN/qO+2zfjDeiJw/bQbgAHUqMF4OD8b1RP7iSLMlo=", + "subject": "CN=ZwTrus ECC DV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxHTAbBgNVBAMTFFp3VHJ1cyBFQ0MgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "f34ca745ef44319856cba4a050307d3f871aa0d51f34fcd79c644accc2667ac2", + "size": 1264, + "filename": "Req3epNPjKJ-Rs6KENthIqbXt0QUQNEqKEs3d3aoHyk=.pem", + "location": "security-state-staging/intermediates/82b8a718-1f7d-4d67-8acb-6f56003e84bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Req3epNPjKJ+Rs6KENthIqbXt0QUQNEqKEs3d3aoHyk=", + "crlite_enrolled": false, + "id": "7baf8016-e5eb-47ca-932f-f89c343bab81", + "last_modified": 1654826223105 + }, + { + "schema": 1654825723984, + "derHash": "9r50i0OIRvP1IBkINvF1J+7HCdunHJsy+Blo7MAlR1c=", + "subject": "CN=ZwTrus ECC EV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxHTAbBgNVBAMTFFp3VHJ1cyBFQ0MgRVYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "cba793053502603a3ebe98509c020d3accd30219f3236ad6e29af1018c66d283", + "size": 1256, + "filename": "2foXbUUoEVKfGaTlEmvOmVuXlyfrBAVV328MkIxr1y4=.pem", + "location": "security-state-staging/intermediates/112e4c37-55c6-4b45-9a86-1a2108fba5ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2foXbUUoEVKfGaTlEmvOmVuXlyfrBAVV328MkIxr1y4=", + "crlite_enrolled": false, + "id": "ad672bb3-9416-4831-81af-1e669749e096", + "last_modified": 1654826223099 + }, + { + "schema": 1654825723094, + "derHash": "2UXfXUkXq6DGPCWrVEEPbfm9XR4aOEMEA2mYGSWSj4I=", + "subject": "CN=ZwTrus ECC OV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MGExCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxHTAbBgNVBAMTFFp3VHJ1cyBFQ0MgT1YgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "a96eb112a51dcd7aa9dfffa37cc94c1e71a4a94715e25963eccada2b975ad0a9", + "size": 1264, + "filename": "-SivwIIZZ5XMDBPpRysjqX4hUVcsx0NmbrShcxjja6k=.pem", + "location": "security-state-staging/intermediates/99ba2c3d-3343-4152-b286-cc8597f7e752.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+SivwIIZZ5XMDBPpRysjqX4hUVcsx0NmbrShcxjja6k=", + "crlite_enrolled": false, + "id": "d2f202a9-c4a9-4724-b071-36ca4a817509", + "last_modified": 1654826223090 + }, + { + "schema": 1654825721302, + "derHash": "EhLoLHa+d950OqkcVf2z9FVE2M65z2SWvQ6ses7K6R8=", + "subject": "CN=cnWebTrust DV CA - ECC,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEcxCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEfMB0GA1UEAxMWY25XZWJUcnVzdCBEViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "87018e6d7035c598f6d44dd6a23d43098f9af13871c58f0e1564fe173c2695b8", + "size": 1232, + "filename": "kT_gnxnm88DZcFflMXkoWCVHV2kU0m_AIAWWwR0E1a0=.pem", + "location": "security-state-staging/intermediates/3591a8aa-ef82-448a-9782-b9b4aecbab2c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kT/gnxnm88DZcFflMXkoWCVHV2kU0m/AIAWWwR0E1a0=", + "crlite_enrolled": false, + "id": "746f382b-1d3e-4a1c-a5a1-522154b06425", + "last_modified": 1654826223084 + }, + { + "schema": 1654825719491, + "derHash": "zeCfT4yLgzm/UFtaUiIhmkv0nlNz7QYKJ4te5U8fAX8=", + "subject": "CN=ZwTrus DV SSL CA,O=北京中万网络科技有限责任公司,C=CN", + "subjectDN": "MF0xCzAJBgNVBAYTAkNOMTMwMQYDVQQKDCrljJfkuqzkuK3kuIfnvZHnu5znp5HmioDmnInpmZDotKPku7vlhazlj7gxGTAXBgNVBAMTEFp3VHJ1cyBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "ab985c1e8b39ad8742fa123d7c0c04afbe868e46175aedcbc36c51f8d31f690c", + "size": 2272, + "filename": "W9sKv5zj48qfjviocv1xMHCxROdMlOWyOvVCKOYDehg=.pem", + "location": "security-state-staging/intermediates/66fe1f2b-f2db-4f71-b2a3-71ed411360c0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W9sKv5zj48qfjviocv1xMHCxROdMlOWyOvVCKOYDehg=", + "crlite_enrolled": false, + "id": "5a533e6f-a88b-4c5a-871c-2feee0089847", + "last_modified": 1654826223063 + }, + { + "schema": 1654825716050, + "derHash": "zhX0y2O4lF0S1Va9VmtAqaT2Kr2WzRllSrgmVBpHWEw=", + "subject": "CN=cnWebTrust EV CA - ECC,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEcxCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEfMB0GA1UEAxMWY25XZWJUcnVzdCBFViBDQSAtIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "874855ad95972dc01d239c1bfd8ce9380dadc81e946c380c5a37304271c109fe", + "size": 1219, + "filename": "6uCYMqp6OmT4VZ6V72MUCocVXJrKp_DNCWIy7YMe9_A=.pem", + "location": "security-state-staging/intermediates/9da5ee83-928e-402e-bb45-daec09143011.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6uCYMqp6OmT4VZ6V72MUCocVXJrKp/DNCWIy7YMe9/A=", + "crlite_enrolled": false, + "id": "430337bf-7b6e-4f30-8e57-7ade9ff65bbe", + "last_modified": 1654826223043 + }, + { + "schema": 1654804112375, + "derHash": "NeOhiRd6ZoYNQ0U96hfvdO5rckd7DlOdodI9JXe267o=", + "subject": "CN=cnWebTrust DV CA,O=cnWebTrust Inc,C=CN", + "subjectDN": "MEExCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5jbldlYlRydXN0IEluYzEZMBcGA1UEAxMQY25XZWJUcnVzdCBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b48adfa896bdb2d2e3ab00d9293aa6007a91074865174a1632b79b4d68ac25bc", + "size": 2235, + "filename": "g-4uy6SgXERcva4V3D4XVBmrZPEVCm5VrzAAAFk11wg=.pem", + "location": "security-state-staging/intermediates/1c87ed53-84ff-4135-832c-1fed54ca9089.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "g+4uy6SgXERcva4V3D4XVBmrZPEVCm5VrzAAAFk11wg=", + "crlite_enrolled": false, + "id": "90e8d1ea-a147-43dc-b502-164677314799", + "last_modified": 1654804623143 + }, + { + "schema": 1652389030686, + "derHash": "ayP62JAYDDN7hkFV2uDem62e8L2njREvLL3D0CrReVY=", + "subject": "CN=NetLock Üzleti (Class B) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIGpMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE3MDUGA1UEAwwuTmV0TG9jayDDnHpsZXRpIChDbGFzcyBCKSBUYW7DunPDrXR2w6FueWtpYWTDsw==", + "whitelist": false, + "attachment": { + "hash": "573f7ca4199a4ab7b67cf4b5b1c8e25a89da7c93d3296ece5e8301e5bf64c39b", + "size": 2182, + "filename": "1YROuDznuNvrat3sL9gwGR124PRLocGQQWd6IoM50Ls=.pem", + "location": "security-state-staging/intermediates/5ae11eb3-59e2-4f2f-a3ad-09d610869295.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1YROuDznuNvrat3sL9gwGR124PRLocGQQWd6IoM50Ls=", + "crlite_enrolled": false, + "id": "1a8145a6-66a3-4c40-914b-e027086ea091", + "last_modified": 1652540239008 + }, + { + "schema": 1652539714356, + "derHash": "NyuPTOc77fyIcYxAe7az5tj5p5vpVxkNDnEBx7DvmjI=", + "subject": "CN=NetLock Expressz (Class C) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIGqMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE4MDYGA1UEAwwvTmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFuw7pzw610dsOhbnlraWFkw7M=", + "whitelist": false, + "attachment": { + "hash": "96ba448b3b21c633bd5e293f4383a8b88b67e802476a48d48ba20ffa8359a5ce", + "size": 2182, + "filename": "KS2adY6eMaWNhbLW6JREO3Oo34Qy_UvmA5f-f4ROpYw=.pem", + "location": "security-state-staging/intermediates/6380c544-10af-435b-ae2d-adad842694a3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KS2adY6eMaWNhbLW6JREO3Oo34Qy/UvmA5f+f4ROpYw=", + "crlite_enrolled": false, + "id": "522256d0-e9e9-44e9-b35d-221ddf453cad", + "last_modified": 1652540238996 + }, + { + "schema": 1651243731947, + "derHash": "UscwCUvRr5jslRBbbVjwm78ICoZh/vA8Xn4xwccao54=", + "subject": "CN=TrustAsia ECC DV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIERWIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "478efe9c3c15d76a274b7b94340eb3b4a5aa70c20a0d5cbaac979c3993097d60", + "size": 1256, + "filename": "8U_3Q2XOjlBspygbt3HFvzxWcbklTeiwjAtB025Rr1I=.pem", + "location": "security-state-staging/intermediates/e18b71a8-f92d-4225-816e-66e332620f39.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8U/3Q2XOjlBspygbt3HFvzxWcbklTeiwjAtB025Rr1I=", + "crlite_enrolled": false, + "id": "1228f9f3-de33-42cc-9741-c4a0535147ed", + "last_modified": 1651244234008 + }, + { + "schema": 1651243728381, + "derHash": "Ju073hkw2MU87NGz1QsNe/0vRDnJ7cjXCHYe1v5doKs=", + "subject": "CN=DNSPod RSA DV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBSU0EgRFY=", + "whitelist": false, + "attachment": { + "hash": "7642f6824be11258ea3f0461090541db4e35b54673ba2828a4a659aeb6a708c4", + "size": 2231, + "filename": "v6XUDOez9KzX6ugG6Y6NOWiaa_DRl8DzRlXbI58Od9k=.pem", + "location": "security-state-staging/intermediates/5f0a95c5-9d8e-4c78-9d1f-196ef8cc6902.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v6XUDOez9KzX6ugG6Y6NOWiaa/DRl8DzRlXbI58Od9k=", + "crlite_enrolled": false, + "id": "6ffb2ec9-df2d-4929-b852-b0a2baf9dbfb", + "last_modified": 1651244233993 + }, + { + "schema": 1651243727502, + "derHash": "DlsAchtjwEpX4EE1pKkELElltPUMUF/ibTTg5xhV7kc=", + "subject": "CN=TrustAsia RSA DV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIERWIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "ba644b4e970b564e35c343bd85c1f55b3a02c97618ea03227095daab7a78cf6c", + "size": 2268, + "filename": "B_N1Dbk37d8gadFssO90DgDkAmm6ZJJMG8xYPOwEHLM=.pem", + "location": "security-state-staging/intermediates/1e17e390-a9ef-44c1-a97e-33f792ea06ef.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "B/N1Dbk37d8gadFssO90DgDkAmm6ZJJMG8xYPOwEHLM=", + "crlite_enrolled": false, + "id": "754fd284-0877-48e4-8334-dfea0d1ba2bf", + "last_modified": 1651244233979 + }, + { + "schema": 1651243729229, + "derHash": "u69UbyjDANkG+qdhQV2XEpsddy5Jixfq1n46rUQ54/0=", + "subject": "CN=DNSPod ECC OV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBFQ0MgT1Y=", + "whitelist": false, + "attachment": { + "hash": "f51de2adcd277d82212f7ba2b81c3469c008db61ce0f650977b8fb5d867e8855", + "size": 1215, + "filename": "7ds1KNk3gHiDZE-zHMhKH0wvOfldhHNVm9yiuC-lHLA=.pem", + "location": "security-state-staging/intermediates/d4cbe4a7-1afd-48a6-b8fe-14515748caf4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7ds1KNk3gHiDZE+zHMhKH0wvOfldhHNVm9yiuC+lHLA=", + "crlite_enrolled": false, + "id": "123a831f-1176-4d05-92b0-f531ca3e2ebe", + "last_modified": 1651244233972 + }, + { + "schema": 1651243725752, + "derHash": "FlqkMjFqqCbL6x56E+XpbrknevAQVYCnzuUhDwQl4FY=", + "subject": "CN=DNSPod ECC DV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBFQ0MgRFY=", + "whitelist": false, + "attachment": { + "hash": "917484046b08ddb16e5f4891f59d516b8db7d3d30f21cbf4cde560fd7c41912e", + "size": 1219, + "filename": "27O4Zhd_18oAKoYHojx2N40ZdxUC7hSU_D7DEzfjNJA=.pem", + "location": "security-state-staging/intermediates/e50dec77-38ef-4168-9577-b0585e2309c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "27O4Zhd/18oAKoYHojx2N40ZdxUC7hSU/D7DEzfjNJA=", + "crlite_enrolled": false, + "id": "b909a7ce-f134-4f57-b1b8-9e3782185240", + "last_modified": 1651244233965 + }, + { + "schema": 1651243726636, + "derHash": "Gh1Zs8/bhCOm4md+8V2fw9Dancz6Y39g6ubpt8iHyP0=", + "subject": "CN=DNSPod ECC EV,O=DNSPod\\, Inc.,C=CN", + "subjectDN": "MDwxCzAJBgNVBAYTAkNOMRUwEwYDVQQKEwxETlNQb2QsIEluYy4xFjAUBgNVBAMTDUROU1BvZCBFQ0MgRVY=", + "whitelist": false, + "attachment": { + "hash": "2692ec914eee66f4e17bda0de59e931b3877a19e3726280a0f501ee2295374bd", + "size": 1207, + "filename": "O0m5yObV4fYLRVw9HYDQAt0LFs94XlBp0zKzD8rjXHQ=.pem", + "location": "security-state-staging/intermediates/56484c21-741c-45b4-b5f5-fb671af7ce51.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "O0m5yObV4fYLRVw9HYDQAt0LFs94XlBp0zKzD8rjXHQ=", + "crlite_enrolled": false, + "id": "90b19d80-2f05-483d-8d8c-cbd9e378f8f6", + "last_modified": 1651244233958 + }, + { + "schema": 1651243722147, + "derHash": "OXgI2rB2Wy0iSDH800v+VqQJPxTEinAHJ7sxp61CDLQ=", + "subject": "CN=TrustAsia ECC OV TLS CA G3,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIE9WIFRMUyBDQSBHMw==", + "whitelist": false, + "attachment": { + "hash": "0628e7fb6abad65d30b851eea91243cff7f96f162f6e9e99a8f2e14319f87de0", + "size": 1252, + "filename": "kVjrK6V1JscLjPoduH7ECVQLKek-gB3MuQ9evhN9eNI=.pem", + "location": "security-state-staging/intermediates/851b1982-bad1-48c9-8ad3-2ebdf8db6940.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kVjrK6V1JscLjPoduH7ECVQLKek+gB3MuQ9evhN9eNI=", + "crlite_enrolled": false, + "id": "12dc1ddf-21cf-48f3-a951-66c500745442", + "last_modified": 1651244233936 + }, + { + "schema": 1651200582692, + "derHash": "Fed8WUBAFWWe4m+SVh8Aeq9TmGonkm8OcHWFwqfzAbg=", + "subject": "CN=DigiCert Global G3 TLS EE\\+ ECC384 SHA384 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGIxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE6MDgGA1UEAxMxRGlnaUNlcnQgR2xvYmFsIEczIFRMUyBFRSsgRUNDMzg0IFNIQTM4NCAyMDIyIENBMQ==", + "whitelist": false, + "attachment": { + "hash": "d698eb7c871e953a2d52f5a3e37a7c94099ce9cd6e5e351f175434f9b4647831", + "size": 1276, + "filename": "qX0VkaXNOTvb_iR2T3sjz2frmsxWXSBKxeCVlcqFpuM=.pem", + "location": "security-state-staging/intermediates/b22276db-4c8e-4141-a014-1fe8448280d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qX0VkaXNOTvb/iR2T3sjz2frmsxWXSBKxeCVlcqFpuM=", + "crlite_enrolled": false, + "id": "3504d097-0f0d-4eb1-bcab-f8c9cff23a8e", + "last_modified": 1651201056425 + }, + { + "schema": 1651200581750, + "derHash": "uRW4Bqi3vi44CfLpMplufBDMgmkYZKiWBr1QCA3C6rM=", + "subject": "CN=DigiCert Global G2 TLS EE\\+ RSA4096 SHA256 2022 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgR2xvYmFsIEcyIFRMUyBFRSsgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQTE=", + "whitelist": false, + "attachment": { + "hash": "2770a30970c5f0e28e817c1788eb2c06bff044a066ecca40a7a2b96211daf4fc", + "size": 2077, + "filename": "nfWaEttE0IGAYQsFsn8KzN13pSJzWY96Gu0Dlx4QYig=.pem", + "location": "security-state-staging/intermediates/4916e83a-41de-416d-910e-e4d6d8f4f953.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nfWaEttE0IGAYQsFsn8KzN13pSJzWY96Gu0Dlx4QYig=", + "crlite_enrolled": false, + "id": "168daeb9-d058-4d6b-946a-87f8af07577f", + "last_modified": 1651201056418 + }, + { + "schema": 1650919770377, + "derHash": "tvZVm7zgosyR5Qe11zGeMkh+2uKKBjvHO2QF5sRGZbY=", + "subject": "CN=QuoVadis Enterprise Trust CA 2 G3,O=QuoVadis Limited,C=BM", + "subjectDN": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "whitelist": false, + "attachment": { + "hash": "0e8105a00963a2942951dacf763351b5c4f4fcffe32ab691c090848452d6b285", + "size": 2377, + "filename": "U_l9o-Lg2NOgB-7MLJUzZzbVrqatI_likIE44Ciah6M=.pem", + "location": "security-state-staging/intermediates/97b8b4d9-8b6c-4fe0-b01e-f743bdb8a98b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "U/l9o+Lg2NOgB+7MLJUzZzbVrqatI/likIE44Ciah6M=", + "crlite_enrolled": false, + "id": "77b42da0-9139-44b4-aa57-0c540a0680c3", + "last_modified": 1650920239287 + }, + { + "schema": 1650919769437, + "derHash": "PyJb2814jOkkhwyvkvgUt8b/Ttq6utk/HTqRdyUs8dE=", + "subject": "CN=QuoVadis Qualified Web ICA G3,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MFcxCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdRdW9WYWRpcyBUcnVzdGxpbmsgQi5WLjEmMCQGA1UEAwwdUXVvVmFkaXMgUXVhbGlmaWVkIFdlYiBJQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "8eb4cfbfd4e31a971f563f4a974c582ecd679cc8c15604af9f0f24fb1daf720e", + "size": 2385, + "filename": "e7hbHQSuhSDkhjhlY1CGhoHNKcgJCpQxSU_inT417rc=.pem", + "location": "security-state-staging/intermediates/848eef45-4d07-4d85-b898-032a76b2a75b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e7hbHQSuhSDkhjhlY1CGhoHNKcgJCpQxSU/inT417rc=", + "crlite_enrolled": false, + "id": "7d41a0d3-fa4f-45f9-bec4-6c38cd62e11c", + "last_modified": 1650920239278 + }, + { + "schema": 1650919766509, + "derHash": "gmV1bdXNijfuYeQDUSiOSxaondJIwexOuiWq8WGr9Jg=", + "subject": "CN=AC Sector Público,OU=Ceres,O=FNMT-RCM,C=ES", + "subjectDN": "MGcxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEOMAwGA1UECwwFQ2VyZXMxGDAWBgNVBGEMD1ZBVEVTLVEyODI2MDA0SjEbMBkGA1UEAwwSQUMgU2VjdG9yIFDDumJsaWNv", + "whitelist": false, + "attachment": { + "hash": "cfe09ce65e4dcbc8449292b7e92e1cdaacc6c228de14a49ccc6300fda8e73598", + "size": 2816, + "filename": "aN4wUQfL0YidepsDn9ylA8GoitH0XRdS349Qxylp2RU=.pem", + "location": "security-state-staging/intermediates/6e8aaf5f-dcdb-4a22-852f-74fd18687428.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aN4wUQfL0YidepsDn9ylA8GoitH0XRdS349Qxylp2RU=", + "crlite_enrolled": false, + "id": "cfdbbcaf-8ad7-44df-988e-6b0c1d3a6ef0", + "last_modified": 1650920239252 + }, + { + "schema": 1650919752248, + "derHash": "GLtw79o/kUOBFcnrTzM+tOxoNUVx5pSRlaCpHJ5vvNg=", + "subject": "CN=QuoVadis RCA1G3 TLS CA,O=QuoVadis Limited,C=BM", + "subjectDN": "MEkxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMR8wHQYDVQQDDBZRdW9WYWRpcyBSQ0ExRzMgVExTIENB", + "whitelist": false, + "attachment": { + "hash": "b3bb67e5eae26a4c94edb219c24b0b5e4b61ccf76dfc1f6b45000e822a22ceb3", + "size": 2369, + "filename": "oVTzYxEWPWmbDlo2GZhwL-6my7pLP_DO_Q5zS0j4erM=.pem", + "location": "security-state-staging/intermediates/fc79241e-cca4-4146-ba3c-7d9f2433463e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oVTzYxEWPWmbDlo2GZhwL+6my7pLP/DO/Q5zS0j4erM=", + "crlite_enrolled": false, + "id": "82f7ad48-cda9-4a45-be46-d27e56212f18", + "last_modified": 1650920239191 + }, + { + "schema": 1650919756111, + "derHash": "uCIQzend6g4UvimvZH5LMvlu0qnvGqW6qcxks4tsAco=", + "subject": "OU=AC RAIZ FNMT-RCM,O=FNMT-RCM,C=ES", + "subjectDN": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "whitelist": false, + "attachment": { + "hash": "dda828b4a97c8a8f24d378bef318af5d144be4d873ff73ba3f07303c1c298e49", + "size": 1971, + "filename": "L8VmekuaJnjtasatJUZfy_YJS_zZUECXx6j6R63l6Ig=.pem", + "location": "security-state-staging/intermediates/c8065d4f-f7f0-4e26-aa22-7e982bf06bba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "L8VmekuaJnjtasatJUZfy/YJS/zZUECXx6j6R63l6Ig=", + "crlite_enrolled": false, + "id": "752044c7-55ec-4fd9-a0fa-6bd6b3db2f27", + "last_modified": 1650920239183 + }, + { + "schema": 1650919749373, + "derHash": "VD2bf8KmRxzYT8pSws9hWd+D6/zYjYsIta8/iHN/UuY=", + "subject": "CN=Amazon Root CA 4,O=Amazon,C=US", + "subjectDN": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDQ=", + "whitelist": false, + "attachment": { + "hash": "529e23adcf592c5f3ead786e4cade919e70e5ce97467aa24135ffedb958ca909", + "size": 1410, + "filename": "9-ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=.pem", + "location": "security-state-staging/intermediates/0220ce80-c8b1-4eff-a8a9-8b3fcf86b20f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9+ze1cZgR9KO1kZrVDxA4HQ6voHRCSVNz4RdTCx4U8U=", + "crlite_enrolled": false, + "id": "ebd35018-c6ca-4fb3-863c-5433228a3dd0", + "last_modified": 1650920239156 + }, + { + "schema": 1650919743592, + "derHash": "HjVoI/5AwOqsKfntVGO3stscCItj67BYdqLmMcEId5g=", + "subject": "CN=QuoVadis Enterprise Trust CA 2 G4,O=QuoVadis Trustlink B.V.,C=NL", + "subjectDN": "MFsxCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdRdW9WYWRpcyBUcnVzdGxpbmsgQi5WLjEqMCgGA1UEAwwhUXVvVmFkaXMgRW50ZXJwcmlzZSBUcnVzdCBDQSAyIEc0", + "whitelist": false, + "attachment": { + "hash": "756cc72bf46fca106a4ae94315fc04ea6bbe1b93e3229513d02875b33ca13f45", + "size": 2385, + "filename": "z-lEyVPfwY15LjMDGouWc4Jt0MFWaf2MpLOSN9mx9tM=.pem", + "location": "security-state-staging/intermediates/b498521b-e32b-43cc-aca3-07c4179af1b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z+lEyVPfwY15LjMDGouWc4Jt0MFWaf2MpLOSN9mx9tM=", + "crlite_enrolled": false, + "id": "4e7a229c-6a33-40fa-b4ca-1c8e08a0c8ff", + "last_modified": 1650920239138 + }, + { + "schema": 1650919736041, + "derHash": "7MDResMmOsahZNzbCPgtB+k/zqcj9muIsG7rqWeN8rs=", + "subject": "CN=QuoVadis RCA3G3 TLS CA,O=QuoVadis Limited,C=BM", + "subjectDN": "MEkxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMR8wHQYDVQQDDBZRdW9WYWRpcyBSQ0EzRzMgVExTIENB", + "whitelist": false, + "attachment": { + "hash": "fc4fc6584822834debd279e4a8481536c6bd26913df00dda003fbc4ed5facf42", + "size": 2369, + "filename": "HGG1eHmdWucI4rZV77-UwfI3jqj3-wm0NFUmWbyNZvs=.pem", + "location": "security-state-staging/intermediates/fbd31d9d-d3c2-4949-83c0-0356f6cf38df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HGG1eHmdWucI4rZV77+UwfI3jqj3+wm0NFUmWbyNZvs=", + "crlite_enrolled": false, + "id": "49a8f373-42db-4fd2-8cec-a33b0642a63b", + "last_modified": 1650920239085 + }, + { + "schema": 1650919735069, + "derHash": "QMgm/bIroyovnbT5R3D3K4sdqcj/2nsR5vJ68kXIm14=", + "subject": "CN=Amazon Root CA 3,O=Amazon,C=US", + "subjectDN": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDM=", + "whitelist": false, + "attachment": { + "hash": "527804e0cdb3a780b57c0f3f7429e1a5b42984b3bd67157b9fda6ba75cdc6d0a", + "size": 1370, + "filename": "NqvDJlas_GRcYbcWE8S_IceH9cq77kg0jVhZeAPXq8k=.pem", + "location": "security-state-staging/intermediates/6aba445e-a993-4da1-8b31-508ae20809f9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NqvDJlas/GRcYbcWE8S/IceH9cq77kg0jVhZeAPXq8k=", + "crlite_enrolled": false, + "id": "e4674db5-3ed3-49af-8910-24de0606b82c", + "last_modified": 1650920239059 + }, + { + "schema": 1650919729415, + "derHash": "izWEZtZhJjEhIGRaWHWmpX48gdmEdqlnYEJEJU6sAPA=", + "subject": "CN=Amazon Root CA 2,O=Amazon,C=US", + "subjectDN": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDI=", + "whitelist": false, + "attachment": { + "hash": "099a90f379b0ecca696c3cde0f60b64211dbaafa0ce4ae4b1b13e1dc145c4f27", + "size": 1991, + "filename": "f0KW_FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=.pem", + "location": "security-state-staging/intermediates/f7a184d3-d555-4bc5-bf51-b6cf5fd02d86.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "f0KW/FtqTjs108NpYj42SrGvOB2PpxIVM8nWxjPqJGE=", + "crlite_enrolled": false, + "id": "2d641b97-843d-4094-b881-81031cf6ed4b", + "last_modified": 1650920239016 + }, + { + "schema": 1650919759845, + "derHash": "ORIgcFt1vPPtPNSzYxIT9WnSz4ImEB4XB5mlNUqxKGE=", + "subject": "CN=Amazon Root CA 1,O=Amazon,C=US", + "subjectDN": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "whitelist": false, + "attachment": { + "hash": "076400302106a1d71c433340807aa21af7fdcdd85ff586d57757cf6d6718dbc4", + "size": 1646, + "filename": "--MBgDH5WGvL9Bcn5Be30cRcL0f5O-NyoXuWtQdX1aI=.pem", + "location": "security-state-staging/intermediates/ba24dc88-716f-4abe-93f4-e880c36d57b8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=", + "crlite_enrolled": false, + "id": "c3a8d84d-8e96-4168-87eb-c44eb83e7053", + "last_modified": 1650920238973 + }, + { + "schema": 1650919758903, + "derHash": "BTHIb3hZWJOf3FOZJNOV0e+kCTZOaCfTq5h2MR/7J7A=", + "subject": "CN=QuoVadis Enterprise Trust CA 1 G3,O=QuoVadis Limited,C=BM", + "subjectDN": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDEgRzM=", + "whitelist": false, + "attachment": { + "hash": "0838a224858164040768ee9fb8bab6de6efb11294b58f2c5db9fea5788b39bc7", + "size": 2333, + "filename": "8ZpHrAG1eQIQZnOeYn-p9E7tqq_yc1fiaSaeKR1BY2I=.pem", + "location": "security-state-staging/intermediates/ce362e8f-6a48-4236-a1d6-2a63778041fc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8ZpHrAG1eQIQZnOeYn+p9E7tqq/yc1fiaSaeKR1BY2I=", + "crlite_enrolled": false, + "id": "59a0226b-255a-4648-b647-5ae17b95e143", + "last_modified": 1650920238964 + }, + { + "schema": 1650919757998, + "derHash": "OmbmSCEuYyH5TZ6rzJLAUuZ52ZLJmtRZak57ha3O5d4=", + "subject": "CN=QuoVadis RCA3G1 TLS CA,O=QuoVadis Limited,C=BM", + "subjectDN": "MEkxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMR8wHQYDVQQDDBZRdW9WYWRpcyBSQ0EzRzEgVExTIENB", + "whitelist": false, + "attachment": { + "hash": "e5c77c38ef33f161625bc3592afbf547db64bc8507aff887efbd2902cd674003", + "size": 2361, + "filename": "cDYpcfBIVfSGVjIXkR2jSnkBmuagMeyfAzJa6dOp_y4=.pem", + "location": "security-state-staging/intermediates/0143e551-4755-4a70-a0b6-a96811277ee2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cDYpcfBIVfSGVjIXkR2jSnkBmuagMeyfAzJa6dOp/y4=", + "crlite_enrolled": false, + "id": "c496ec4e-b18d-4f40-a77d-7e2e0cc924e4", + "last_modified": 1650920238938 + }, + { + "schema": 1650919754133, + "derHash": "h9zU3HRkCjIs0gVVJQbRvmTxJZYlgJZUSYa0hQvHJwY=", + "subject": "CN=Amazon Root CA 1,O=Amazon,C=US", + "subjectDN": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "whitelist": false, + "attachment": { + "hash": "8f2766bb42744fe0fcf37f433c66191277a29d9bd4641cc4121ccf842ecf940c", + "size": 1646, + "filename": "--MBgDH5WGvL9Bcn5Be30cRcL0f5O-NyoXuWtQdX1aI=.pem", + "location": "security-state-staging/intermediates/d3d8d455-9864-48ea-8742-d535b58189aa.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=", + "crlite_enrolled": false, + "id": "49bace65-a7b6-40f8-b913-cddadbdba9c3", + "last_modified": 1650920238920 + }, + { + "schema": 1650790673425, + "derHash": "dYlNTJTCLXKW7hn7RHYjJYwlkbFwMSiLs4aiCqHw4KY=", + "subject": "CN=NetLock Közjegyzői (Class A) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIGuMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE8MDoGA1UEAwwzTmV0TG9jayBLw7Z6amVneXrFkWkgKENsYXNzIEEpIFRhbsO6c8OtdHbDoW55a2lhZMOz", + "whitelist": false, + "attachment": { + "hash": "4327de015aac627d028d4696e3b582048933e2f87d179c261b01897e32b67a1d", + "size": 2190, + "filename": "RWiIGgLo9cnu9JGiGhnz5uw6FBH1Y5qDY4qGsl5Q9EM=.pem", + "location": "security-state-staging/intermediates/27099d75-dfc0-441a-a1de-81e112a1d6e5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "RWiIGgLo9cnu9JGiGhnz5uw6FBH1Y5qDY4qGsl5Q9EM=", + "crlite_enrolled": false, + "id": "a76d3004-9787-484d-a4d9-2e5555cd8b56", + "last_modified": 1650812270014 + }, + { + "schema": 1650552522984, + "derHash": "qLKkUYqCoMwKYLL7iLlGVXrbT6WEWOx2gKd7sOUzTY0=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "b68666da50c073d1a8847151f9a236a66ca0857536769a5018002d926683f749", + "size": 1195, + "filename": "TevF5ohME1f-tSSfuVGlGBS73B9YmpAiEHCMSCfHeIw=.pem", + "location": "security-state-staging/intermediates/2a4369e4-03bd-4e4e-baaa-020ef0879241.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TevF5ohME1f+tSSfuVGlGBS73B9YmpAiEHCMSCfHeIw=", + "crlite_enrolled": false, + "id": "d2cb52cc-1cbb-46b2-9d66-bee7fa46e823", + "last_modified": 1650553057220 + }, + { + "schema": 1650552520053, + "derHash": "DtWqAGHQm3A+GzFQ+2Nnhwu/JFzO7U/gjy2+QgFyOo0=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "81b835c34d222b602141a4c129500f2b1835fab3796a3c9532d5dfb2cb487b46", + "size": 2324, + "filename": "ZZ_BfqFFbhO7SSCmu78v8N2CE5ykK9-zz37sWEIWEXI=.pem", + "location": "security-state-staging/intermediates/6916706c-a910-4b05-9dc3-be264f846783.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZZ/BfqFFbhO7SSCmu78v8N2CE5ykK9+zz37sWEIWEXI=", + "crlite_enrolled": false, + "id": "2b253bb2-317f-47bb-a793-79a8ca184ca3", + "last_modified": 1650553057201 + }, + { + "schema": 1650552519107, + "derHash": "8G4sPQWvYIx7rLY2b3pHoBet7CL3Er1BczU5bLi1Vb4=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "a722c9f25470083db4069d629e106f0ca8de6caeaeceffd3c3af14cde8bc7220", + "size": 1642, + "filename": "v-6TVbKbKyGXPLB9TKvsbakeEhe-pi-eaT9sF4mgGr0=.pem", + "location": "security-state-staging/intermediates/884d150d-14aa-4b66-86e5-b81eab371750.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v+6TVbKbKyGXPLB9TKvsbakeEhe+pi+eaT9sF4mgGr0=", + "crlite_enrolled": false, + "id": "9090bd44-30a3-4269-9ea1-3973dddf9033", + "last_modified": 1650553057192 + }, + { + "schema": 1650552523929, + "derHash": "5MLYxuVOzhyZajBZcrGuCwZPEFvgbP1ylf0trjO5fho=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "5cc2b94fc8e9c1b123bee963843ba94aff76a64649883767f1338c8d7f44f4e2", + "size": 1195, + "filename": "ipI_vvoPazVvEdDn9l8fFY4jeKtvuZSfWutFCu1kIkk=.pem", + "location": "security-state-staging/intermediates/74dbf254-96d5-401a-b9a0-4d408e850b5f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ipI/vvoPazVvEdDn9l8fFY4jeKtvuZSfWutFCu1kIkk=", + "crlite_enrolled": false, + "id": "ee7ae6b9-2371-4662-8209-79d4825ea154", + "last_modified": 1650553057183 + }, + { + "schema": 1650552515284, + "derHash": "DJJZS9OUtIh6HWbsXmMf+tO6Swf+LrbRXS9f7kyQxFQ=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIyIFEz", + "whitelist": false, + "attachment": { + "hash": "8586ac0ab17dd90efb094612944d800faae022383e152b1aaeba033a284bc974", + "size": 2333, + "filename": "r0fgmmfQcPt1Okq9xY0UwyyuDUtKFs2nVQY9k68RyzY=.pem", + "location": "security-state-staging/intermediates/e10cbedd-0720-4272-9958-97e576da4e17.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "r0fgmmfQcPt1Okq9xY0UwyyuDUtKFs2nVQY9k68RyzY=", + "crlite_enrolled": false, + "id": "0f272ddb-4d19-4947-ba8f-5f5bddcf5046", + "last_modified": 1650553057156 + }, + { + "schema": 1650552514303, + "derHash": "5sGNbQCMcSswF94a52WUGJgyVLAd5Ila6R87Kf9IJBc=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIyIFEz", + "whitelist": false, + "attachment": { + "hash": "2a9c7dc537e9412fdafb13b4b2896b77ed6d2a7346610bce882f1ab2df5ac664", + "size": 1195, + "filename": "1OU4v4R9chWAHoW7_Lvqm0WW_68bV_okQ8VnVf6zQWY=.pem", + "location": "security-state-staging/intermediates/87b90dc7-c168-45d1-86f9-3a1dab597af5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1OU4v4R9chWAHoW7/Lvqm0WW/68bV/okQ8VnVf6zQWY=", + "crlite_enrolled": false, + "id": "005ef0fb-4b58-42d2-97cd-606c8ef01974", + "last_modified": 1650553057146 + }, + { + "schema": 1650552516241, + "derHash": "QAbN/22NUtwJD/U4K5wwzqjMUAXfLV6CA81nkheXdQ0=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2022 Q3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMiBRMw==", + "whitelist": false, + "attachment": { + "hash": "5b93c9596b472129b6e4d110929b25bdb6fc53e792138971181cc78dd845f257", + "size": 1179, + "filename": "s9qNeP1oAz3ML21RIr9Nd_nR1L1bcapzMKpZ_2MqKe0=.pem", + "location": "security-state-staging/intermediates/c23322e4-6c40-459f-aa50-1c2e9702b06b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "s9qNeP1oAz3ML21RIr9Nd/nR1L1bcapzMKpZ/2MqKe0=", + "crlite_enrolled": false, + "id": "961ef95e-bbf4-47de-89fb-52ef4fe4cdf6", + "last_modified": 1650553057118 + }, + { + "schema": 1649796517769, + "derHash": "P94NNuAmtujr4sKIg2B8hlHeEL1sH8rTZeVg9OovOwM=", + "subject": "CN=Entrust Root Certification Authority - EC1,OU=See www.entrust.net/legal-terms+OU=(c) 2012 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG/MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTMwMQYDVQQDEypFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBFQzE=", + "whitelist": false, + "attachment": { + "hash": "fa84991015956f299a21164f4a77c8da26802bb7bac4207eb696cc086ce715b0", + "size": 1577, + "filename": "_qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI=.pem", + "location": "security-state-staging/intermediates/df5a27df-b846-4677-a6cf-a3cff5a7c5fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI=", + "crlite_enrolled": false, + "id": "d033c52e-0329-43dc-8844-1181a6da4c4c", + "last_modified": 1649973509957 + }, + { + "schema": 1649796515890, + "derHash": "axQ8IAXVU5zCLqtfdy2yqf6HRn/v+gf88Kn30oJ0yno=", + "subject": "CN=Entrust Root Certification Authority - G2,OU=See www.entrust.net/legal-terms+OU=(c) 2009 Entrust\\, Inc. - for authorized use only,O=Entrust\\, Inc.,C=US", + "subjectDN": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "whitelist": false, + "attachment": { + "hash": "d1674b1f7b1aef60543ae1256c7298ed99b5016d5a49c8ba8b9745c679003a0f", + "size": 1792, + "filename": "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at_U=.pem", + "location": "security-state-staging/intermediates/4edf9367-ee46-443f-859e-c6bca2b52092.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U=", + "crlite_enrolled": false, + "id": "d1b5e055-c4f8-4f16-96cf-f0ef4ee6496e", + "last_modified": 1649973509938 + }, + { + "schema": 1649861371015, + "derHash": "E0XC05rktlzKceiOmispxx3JE/lS6TWoErAE26eueVc=", + "subject": "CN=ACCVCA-110,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMTAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "e6da6a137d62a7acc06ba43bbecbca249317995ef2a5821be7c4c3e4904d11cd", + "size": 2649, + "filename": "qX1E174XIirdC_7IZnHG6ACm0iCltwbZSr_U507C-Ck=.pem", + "location": "security-state-staging/intermediates/27528fc3-b963-415a-a99a-4f327d7d7c83.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qX1E174XIirdC/7IZnHG6ACm0iCltwbZSr/U507C+Ck=", + "crlite_enrolled": false, + "id": "4dff9826-c4f5-4bad-9c0b-ef5d5c0bc30d", + "last_modified": 1649973509688 + }, + { + "schema": 1649947717604, + "derHash": "6TJ6NHy+HLlM3JqlTLMbbkPWiWjRfQnOMmoJG/wvCxE=", + "subject": "CN=ACCVCA-110,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMTAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "88d175177fc311fc5ad5e439d61dfabfbe6a0cc3dbf7be58654a104156ba2dae", + "size": 2686, + "filename": "qX1E174XIirdC_7IZnHG6ACm0iCltwbZSr_U507C-Ck=.pem", + "location": "security-state-staging/intermediates/7b9e7264-b0e3-4c9e-bda1-908a922c5b3f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qX1E174XIirdC/7IZnHG6ACm0iCltwbZSr/U507C+Ck=", + "crlite_enrolled": false, + "id": "772a5e91-e4ab-4bc1-b2a7-9d88c0c7e9bf", + "last_modified": 1649973509629 + }, + { + "schema": 1648667430650, + "derHash": "XScMtO/1h61s0Xyytg2QhKFT130qp5oss9sd25BGhPU=", + "subject": "CN=DigiCert High Assurance TLS Hybrid ECC SHA256 2020 CA1,O=DigiCert\\, Inc.,C=US", + "subjectDN": "MGcxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE/MD0GA1UEAxM2RGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgVExTIEh5YnJpZCBFQ0MgU0hBMjU2IDIwMjAgQ0Ex", + "whitelist": false, + "attachment": { + "hash": "3ea69b8dafeb8e68666332244529451eccd6e970a91f738b5ddac122f8ad34d2", + "size": 1500, + "filename": "vnCogm4QYze_Bc9r88xdA6NTQY74p4BAz2w5gxkLG2M=.pem", + "location": "security-state-staging/intermediates/80e952ea-02dd-40b2-9b1d-7cc71c4315a3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vnCogm4QYze/Bc9r88xdA6NTQY74p4BAz2w5gxkLG2M=", + "crlite_enrolled": false, + "id": "5db1751f-eb67-4a74-b38e-d17a823eed64", + "last_modified": 1648695444528 + }, + { + "schema": 1648025321155, + "derHash": "B0hA46Z9zSYAtrAE4Rh6yAvf6JbK9JPflMw9mjymiBQ=", + "subject": "CN=TWCA InfoSec User CA,OU=User CA,O=TAIWAN-CA Inc.,C=TW", + "subjectDN": "MFcxCzAJBgNVBAYTAlRXMRcwFQYDVQQKEw5UQUlXQU4tQ0EgSW5jLjEQMA4GA1UECxMHVXNlciBDQTEdMBsGA1UEAxMUVFdDQSBJbmZvU2VjIFVzZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "45844bd66bf712d05a01885a5ed8fe191938dc90ceb1c6bb05a732c72ec77002", + "size": 1703, + "filename": "ffKwXiiyZfl8ltFLZ9k5urWtoCKNBNklL8cqiHHZUUQ=.pem", + "location": "security-state-staging/intermediates/54742694-a874-4df8-9c81-e88d2f2221f4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ffKwXiiyZfl8ltFLZ9k5urWtoCKNBNklL8cqiHHZUUQ=", + "crlite_enrolled": false, + "id": "73d165e8-c4b1-4300-a6eb-607ee522dabe", + "last_modified": 1648025848795 + }, + { + "schema": 1647654826542, + "derHash": "NCY+lCTYGblLz6h+ad2e9DIF1OLehKU0lzFAY9gilps=", + "subject": "CN=Verokey High Assurance Verified Business ECC,O=Verokey,C=AU", + "subjectDN": "MFYxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MTUwMwYDVQQDEyxWZXJva2V5IEhpZ2ggQXNzdXJhbmNlIFZlcmlmaWVkIEJ1c2luZXNzIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "b6f22240546a8b483896de6c2db0fccfb73386b23b7ad01978cf5ba9a6082950", + "size": 1260, + "filename": "bwcsf3KkBp7k-_kn61hwGrohcM2771HiHahosrGrHfc=.pem", + "location": "security-state-staging/intermediates/c9e91312-d78c-4dcb-ac34-fa8d5551fd6a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bwcsf3KkBp7k+/kn61hwGrohcM2771HiHahosrGrHfc=", + "crlite_enrolled": false, + "id": "d6958d94-103f-4a2b-92b9-2b27cabf0600", + "last_modified": 1647658654006 + }, + { + "schema": 1647654824627, + "derHash": "vP0mFOQtY8WWkcPOi+x5LeL86JztT5xGDQqEhXMHT8s=", + "subject": "CN=Verokey Secure Web ECC,O=Verokey,C=AU", + "subjectDN": "MEAxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MR8wHQYDVQQDExZWZXJva2V5IFNlY3VyZSBXZWIgRUND", + "whitelist": false, + "attachment": { + "hash": "29224ac45981f9cd3a7eb1dce987dac6168937b2ad8eea795026a1f3d0a67941", + "size": 1191, + "filename": "XmuaPGGLJTHOHi-UMbaLYNSGsmun3VCxmY6w4RyyJuM=.pem", + "location": "security-state-staging/intermediates/e3df8599-af15-414f-9c42-298124ca648c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XmuaPGGLJTHOHi+UMbaLYNSGsmun3VCxmY6w4RyyJuM=", + "crlite_enrolled": false, + "id": "ac31c3ff-db37-4e38-80a3-b0c09a6753df", + "last_modified": 1647658653989 + }, + { + "schema": 1647654821815, + "derHash": "vokmSlgxMBK3SWA8m+vDzXkU23FqSeXg6pfkOjiDpd8=", + "subject": "CN=Verokey High Assurance Verified Business G2,O=Verokey,C=AU", + "subjectDN": "MFUxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MTQwMgYDVQQDEytWZXJva2V5IEhpZ2ggQXNzdXJhbmNlIFZlcmlmaWVkIEJ1c2luZXNzIEcy", + "whitelist": false, + "attachment": { + "hash": "81e4f6e788d4f4a8e65ebc2209113e276031df40b369a1322d53a330142cfa5e", + "size": 2410, + "filename": "tlfNnL_wJhxfoQr77OWpEGwaZB50PiKeJS1xv1JczIc=.pem", + "location": "security-state-staging/intermediates/f3309154-7af6-449b-a26d-1fc2a0bf5a1f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tlfNnL/wJhxfoQr77OWpEGwaZB50PiKeJS1xv1JczIc=", + "crlite_enrolled": false, + "id": "fc71f24e-260a-458c-b6fc-49efd9879a92", + "last_modified": 1647658653971 + }, + { + "schema": 1647654820829, + "derHash": "ftvm+MSaHjmIyq3UOLcvB7avnOdRz1g1FjFh1jyuTd8=", + "subject": "CN=Verokey Verified Business G2,O=Verokey,C=AU", + "subjectDN": "MEYxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSUwIwYDVQQDExxWZXJva2V5IFZlcmlmaWVkIEJ1c2luZXNzIEcy", + "whitelist": false, + "attachment": { + "hash": "bebdb836f3821f22a2cf8a9d37d5cf2f9b6170f67a088076c6a0a61f478c9e99", + "size": 1865, + "filename": "F0x5cftsPCB48ynEovjelRoxjTrQOKe0TVZJV_E5uNY=.pem", + "location": "security-state-staging/intermediates/7bc559dd-136e-4b22-bba0-46275c42e46a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "F0x5cftsPCB48ynEovjelRoxjTrQOKe0TVZJV/E5uNY=", + "crlite_enrolled": false, + "id": "fb5813da-8a74-4f21-8165-3fe6274ab2c0", + "last_modified": 1647658653962 + }, + { + "schema": 1647654819872, + "derHash": "8e07Nckvpdqqo91F+Z66O4I6L7HCSonO0WmdleAlZKI=", + "subject": "CN=Verokey Verified Business ECC,O=Verokey,C=AU", + "subjectDN": "MEcxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MSYwJAYDVQQDEx1WZXJva2V5IFZlcmlmaWVkIEJ1c2luZXNzIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "3f188e11517ee73f64bbd36e9ee158a73be3ad9815dbfc0fd11f743283d12684", + "size": 1199, + "filename": "OIPaHQX_1ijRXDAogCIp0fJvOFmAwNZfokh0jDupQ_s=.pem", + "location": "security-state-staging/intermediates/873835df-c179-4e1d-b67d-65d580190ce4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "OIPaHQX/1ijRXDAogCIp0fJvOFmAwNZfokh0jDupQ/s=", + "crlite_enrolled": false, + "id": "42dc8291-ec41-4fad-8526-b3a45b3daaa0", + "last_modified": 1647658653954 + }, + { + "schema": 1647654823652, + "derHash": "Lw44WGTS3KiseuSBZCh7/kUSbGvNkukbs98SUMey2ro=", + "subject": "CN=Verokey High Assurance Verified Business,O=Verokey,C=AU", + "subjectDN": "MFIxCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MTEwLwYDVQQDEyhWZXJva2V5IEhpZ2ggQXNzdXJhbmNlIFZlcmlmaWVkIEJ1c2luZXNz", + "whitelist": false, + "attachment": { + "hash": "046bc1ca834973eaf6354dd03041477d215c49ce451e42a47d536a0ed8881434", + "size": 1707, + "filename": "ta5zRgM3pICBxt5qegtMsZETN77Zk1tN6gApXWT5Oz0=.pem", + "location": "security-state-staging/intermediates/e513b328-655a-441e-86ca-5d563a8439fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ta5zRgM3pICBxt5qegtMsZETN77Zk1tN6gApXWT5Oz0=", + "crlite_enrolled": false, + "id": "5163308a-7837-468c-9467-d3cb0e6db613", + "last_modified": 1647658653936 + }, + { + "schema": 1647442638278, + "derHash": "slTzzerx0pq1PLQ56A+5lvHpCig+m1CFuHSdGOT1mHs=", + "subject": "CN=GlobalSign Atlas R3 DV ACME CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIERWIEFDTUUgQ0EgSDIgMjAyMQ==", + "whitelist": false, + "attachment": { + "hash": "9b7764d2dd9d44f3454ff2c69278ccacf175873333cb4bc22bb78001354c66f5", + "size": 1715, + "filename": "Drirt5siv6auOz2CsdX1ih0Gq_A2hFkwS82cYSyOOSc=.pem", + "location": "security-state-staging/intermediates/be7d54ce-f543-44f1-8741-28214b2ee3cd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Drirt5siv6auOz2CsdX1ih0Gq/A2hFkwS82cYSyOOSc=", + "crlite_enrolled": false, + "id": "1ea36836-d34e-48ad-a1c3-679ebae83102", + "last_modified": 1647464240149 + }, + { + "schema": 1647442123610, + "derHash": "gCRH7lIcxmbNt7uuk6OF5V8gDXaj0TVqhURaxMvb7RI=", + "subject": "CN=Cybertrust Global Root,O=Cybertrust\\, Inc", + "subjectDN": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "whitelist": false, + "attachment": { + "hash": "0311ba514ea52c161164a44190d3ff8d3f21d7e178936261c9f0f3b873c30bc0", + "size": 1317, + "filename": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=.pem", + "location": "security-state-staging/intermediates/a7042ba0-a0c3-40c6-a970-0ee5cdb75e39.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=", + "crlite_enrolled": false, + "id": "75d60e87-9f16-4a0c-bd57-5fc185f8e40b", + "last_modified": 1647442637728 + }, + { + "schema": 1646449055023, + "derHash": "YO9BLqvnw/xjme7RtjO3d3R1FbKdchuWPdJYvEmKspI=", + "subject": "CN=SSL.com EV Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US", + "subjectDN": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "whitelist": false, + "attachment": { + "hash": "ab3dfad3dda55fb2c174fabf92fc81f2757e57a915b1f78f5483d12dfaf50f76", + "size": 1821, + "filename": "NIdnza073SiyuN1TUa7DDGjOxc1p0nbfOCfbxPWAZGQ=.pem", + "location": "security-state-staging/intermediates/734df71e-5087-4844-aa75-47d2809181f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NIdnza073SiyuN1TUa7DDGjOxc1p0nbfOCfbxPWAZGQ=", + "crlite_enrolled": false, + "id": "b512360d-9f2f-4206-97ac-09aa8d521233", + "last_modified": 1646513861708 + }, + { + "schema": 1646513358832, + "derHash": "BrlyKmmcV9/xhp9DC0ebtutJquEYTqycUyXBM0o06kw=", + "subject": "CN=SSL.com Root Certification Authority ECC,O=SSL Corporation,L=Houston,ST=Texas,C=US", + "subjectDN": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "whitelist": false, + "attachment": { + "hash": "3575f65e014fd8fa943566de2f3d7a42adc6b4e947cc86fb2028d533adc24ad9", + "size": 1804, + "filename": "oyD01TTXvpfBro3QSZc1vIlcMjrdLTiL_M9mLCPX-Zo=.pem", + "location": "security-state-staging/intermediates/34d6fd53-d5dd-4289-aa37-161e72f0a7e9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oyD01TTXvpfBro3QSZc1vIlcMjrdLTiL/M9mLCPX+Zo=", + "crlite_enrolled": false, + "id": "3ecf65b2-97c8-4b50-a416-2032e8bdbdf7", + "last_modified": 1646513861683 + }, + { + "schema": 1646513358478, + "derHash": "ie/rJQlrpMzUCvT7h1bwpIQ5lZdIIgIJFMToWdkyt/M=", + "subject": "CN=SSL.com EV Root Certification Authority RSA R2,O=SSL Corporation,L=Houston,ST=Texas,C=US", + "subjectDN": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "whitelist": false, + "attachment": { + "hash": "c7680e4e47bb04f8515b20fc6fe36741515835bace47ef132f02922b0ea612ea", + "size": 2113, + "filename": "fNZ8JI9p2D_C-bsB3LH3rWejY9BGBDeW0JhMOiMfa7A=.pem", + "location": "security-state-staging/intermediates/a586bb32-1766-40e6-bdd8-ba5e8bf95066.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fNZ8JI9p2D/C+bsB3LH3rWejY9BGBDeW0JhMOiMfa7A=", + "crlite_enrolled": false, + "id": "2dea51d4-7fea-4eb2-a507-7fa4ef9b5e26", + "last_modified": 1646513861656 + }, + { + "schema": 1645577291579, + "derHash": "GKvW6cqQe7NnVNDARuu1Ddm5XVJ4JNlYGjG3UPz9uu8=", + "subject": "CN=Trustwave Global ECDSA P-256 Organization Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGgMUkwRwYDVQQDE0BUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMjU2IE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "f16bdfde574887cdf9b20fdd1a1ad5e789c2d8b5094a68dc63225d64c51f2d50", + "size": 1329, + "filename": "lZMwN7CwdUNqVdEULuLxoCtwEIC9qGjWWMelIDRtDNU=.pem", + "location": "security-state-staging/intermediates/f98637f3-11ed-494f-aad4-f51ad1b477b3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lZMwN7CwdUNqVdEULuLxoCtwEIC9qGjWWMelIDRtDNU=", + "crlite_enrolled": false, + "id": "016e2b15-da30-4cb5-bab8-4f10f76d1605", + "last_modified": 1645578269083 + }, + { + "schema": 1645577292935, + "derHash": "fixTc5fnuNmyTYfzBMluoFekB84R0LZtmbpLiiK7EWc=", + "subject": "CN=University of the Aegean TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHoxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTIwMAYDVQQDDClVbml2ZXJzaXR5IG9mIHRoZSBBZWdlYW4gVExTIFJTQSBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "e8cc2f1ab38b3bbfb7f198b2db8aabe50976b53b0ae14ba764f7f5ba0bcf6b3e", + "size": 2816, + "filename": "-2hOVMwfAeVZbcXurvYs9YQXog6eDjr_2LpDSHWN4k8=.pem", + "location": "security-state-staging/intermediates/8f6533a7-68b0-4b38-a037-d9f617571f14.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+2hOVMwfAeVZbcXurvYs9YQXog6eDjr/2LpDSHWN4k8=", + "crlite_enrolled": false, + "id": "e12a234c-f8d9-45ff-ac55-29a269a1f1d2", + "last_modified": 1645578269073 + }, + { + "schema": 1645577298288, + "derHash": "+dSmwBHpxqj1pM9+0EFoaccjjvk6VhYepso9i0FxYJc=", + "subject": "CN=HydrantID EV SSL CA D1,O=Avalanche Cloud Corporation,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtBdmFsYW5jaGUgQ2xvdWQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFkh5ZHJhbnRJRCBFViBTU0wgQ0EgRDE=", + "whitelist": false, + "attachment": { + "hash": "6f1edc0e086f5591b54495a4bdb22c85010569ad80fc95e2451d850fb5258c2d", + "size": 1752, + "filename": "2Y6wMMYpuZSX8MzksrYWB1EKmU_UzVKarvAw_4VqjJc=.pem", + "location": "security-state-staging/intermediates/dbe5c0d2-63bb-4af1-9c90-4d6c60a970c8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2Y6wMMYpuZSX8MzksrYWB1EKmU/UzVKarvAw/4VqjJc=", + "crlite_enrolled": false, + "id": "83e46e54-0c13-4858-814f-2640b229bd01", + "last_modified": 1645578269031 + }, + { + "schema": 1645577307592, + "derHash": "cnqQaaPGDSE1hOYW1b9V39GccaOXZkXqanOxj4N2DSQ=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIyIFEx", + "whitelist": false, + "attachment": { + "hash": "a2b90105268ee9dda0958e2474167ec50f9728091ad8167d84d5dbcd73dc4eeb", + "size": 1268, + "filename": "qYuc8soPP5srLa_gV5q-OE9DtidJ5ntVNioJYOgqRGI=.pem", + "location": "security-state-staging/intermediates/d3e97d30-085c-4a0d-aa3c-6eb9aff86e9b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qYuc8soPP5srLa/gV5q+OE9DtidJ5ntVNioJYOgqRGI=", + "crlite_enrolled": false, + "id": "ecdc9609-c91d-4745-b86c-2d2f8ed319a2", + "last_modified": 1645578268957 + }, + { + "schema": 1645577313138, + "derHash": "h9QhabJpNVHKIFzPF/BvbmZR9yGM1tJmB79bpN0vJvI=", + "subject": "CN=Trustwave Global ECDSA P-384 Organization Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGgMUkwRwYDVQQDE0BUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMzg0IE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "8cd0dbed89841d6b6854301f7eecbbdcd40ac73d8c8347c1ea8a577a05c10603", + "size": 1410, + "filename": "91i1mhKwhcz0SKoJ9CzM_9ohVagG6cKG9Z_i4Ts1FFk=.pem", + "location": "security-state-staging/intermediates/cff3b717-e049-4d5a-b487-ceec8ee7788d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "91i1mhKwhcz0SKoJ9CzM/9ohVagG6cKG9Z/i4Ts1FFk=", + "crlite_enrolled": false, + "id": "d8f2f8d9-847b-449d-bb14-d59326e690d7", + "last_modified": 1645578268916 + }, + { + "schema": 1645577327799, + "derHash": "kfGc5QPJ/n/5WH2O++9zFarud9wtFFJhJkk7Stb+gB8=", + "subject": "CN=Trusted Root TLS CA SHA256 G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFAxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSYwJAYDVQQDEx1UcnVzdGVkIFJvb3QgVExTIENBIFNIQTI1NiBHMw==", + "whitelist": false, + "attachment": { + "hash": "9e03d57a2b38ff0564d3f320d536aae856aa3db18618f722e03e7da8e2a0d9eb", + "size": 1674, + "filename": "hMdIk_Qh87wkhjuY3vWYC85yuCKrEfbi9b3j7xJNPec=.pem", + "location": "security-state-staging/intermediates/e953b38b-8f78-45ce-a629-abedac23b9e6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hMdIk/Qh87wkhjuY3vWYC85yuCKrEfbi9b3j7xJNPec=", + "crlite_enrolled": false, + "id": "3a455bca-8194-4d00-928c-1fc33a8869f5", + "last_modified": 1645578268786 + }, + { + "schema": 1645577335880, + "derHash": "frj2Ma0chAjpcWrpILzWd5c7BZ6ZCu0B3aXhxZcLQCw=", + "subject": "CN=SwissSign RSA TLS Root CA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMSAtIDE=", + "whitelist": false, + "attachment": { + "hash": "58bbfd7b0e504fb6397ee10ee6822ccf19f8e05c9937946aa0d7aa6fb11aa944", + "size": 1987, + "filename": "nUaOHm7QsD6Xagrjxl1hPu2HFDpXiamEvjDZ1IoWx24=.pem", + "location": "security-state-staging/intermediates/a15cc313-776e-4ddc-8dee-23a7bc052cd2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nUaOHm7QsD6Xagrjxl1hPu2HFDpXiamEvjDZ1IoWx24=", + "crlite_enrolled": false, + "id": "3239899d-702b-4895-8978-8ec968b5f8c7", + "last_modified": 1645578268720 + }, + { + "schema": 1645577339845, + "derHash": "ttVvPdJqyETlfIv+kFT1cGE1CpCJS5nNmBHppUX8hMU=", + "subject": "CN=SwissSign RSA SMIME Root CA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFMxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxLTArBgNVBAMTJFN3aXNzU2lnbiBSU0EgU01JTUUgUm9vdCBDQSAyMDIxIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "7644498aff67a69e26a420c7e76e4649ead3a8bb2f20c592db5c9cd2b4412601", + "size": 1991, + "filename": "HKpRHDLoh8f2rEFEWyjA0ZeopKTbs4zV87Xess-HD4U=.pem", + "location": "security-state-staging/intermediates/d4bebbe8-5c5d-4a42-9a8c-6b64a9a76314.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HKpRHDLoh8f2rEFEWyjA0ZeopKTbs4zV87Xess+HD4U=", + "crlite_enrolled": false, + "id": "303a3d11-64e5-4b50-83d5-660aa76829be", + "last_modified": 1645578268688 + }, + { + "schema": 1645577343844, + "derHash": "2+LWPb33b3G6YxNU4W7JfBbgFnkqo7nMnSjFgsrUick=", + "subject": "CN=AlpiroSSL ECC DV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgRUNDIERWIENB", + "whitelist": false, + "attachment": { + "hash": "eb3d288ac2dd6b3d0b135d27bd25dbf9f91a95b47239a5aa2addc4b4da9205d3", + "size": 1264, + "filename": "wDnmANcSbDEP0jOnIAaMy-5juQ0SP1bWXgumTDhj_UI=.pem", + "location": "security-state-staging/intermediates/5bd158ec-9b1a-4f28-a72f-d1df9f315007.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wDnmANcSbDEP0jOnIAaMy+5juQ0SP1bWXgumTDhj/UI=", + "crlite_enrolled": false, + "id": "372cf26f-3773-45f0-ad5b-8136dfb20cb2", + "last_modified": 1645578268654 + }, + { + "schema": 1645577354679, + "derHash": "us3gRjBTzh1i+L50Nwu6551PyvGfwHZDrvGV5qWb1Xg=", + "subject": "CN=E2,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFMg==", + "whitelist": false, + "attachment": { + "hash": "051882dab9b07102f7aba08eef5eb943e8b8096631074d7f98f9c7dcef77e86d", + "size": 1020, + "filename": "vZNucrIS7293MQLGt304-UKXMi78JTlrwyeUIuDIknA=.pem", + "location": "security-state-staging/intermediates/0636e71b-fceb-48eb-b0f6-37d5ce14cea6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vZNucrIS7293MQLGt304+UKXMi78JTlrwyeUIuDIknA=", + "crlite_enrolled": false, + "id": "02bf75eb-e74f-44f4-8318-55d1097a8849", + "last_modified": 1645578268568 + }, + { + "schema": 1645577377719, + "derHash": "vOcJE1XSQXo3+M/irI8XwnSIiLoO6pAAD8mA7PwtBB8=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFUxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSswKQYDVQQDEyJHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIw", + "whitelist": false, + "attachment": { + "hash": "7a27c45b53bfd5690bb01873eeb250e11a9b84a706cbbd33be1cafcbad5ed9a5", + "size": 2402, + "filename": "P7xnWSLQVg6f_MMaRe-f9TBAwDOlwdoyxweVGx8v2hY=.pem", + "location": "security-state-staging/intermediates/a350f8ab-6eeb-43db-a3d4-37d09ca42bc3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "P7xnWSLQVg6f/MMaRe+f9TBAwDOlwdoyxweVGx8v2hY=", + "crlite_enrolled": false, + "id": "33b42f4a-4bff-4abf-b03a-d77a07dfc454", + "last_modified": 1645578268393 + }, + { + "schema": 1645577379076, + "derHash": "LmPcG+z0J/mQK0neYzD9Po7r+IsU5lOzui431OSICWU=", + "subject": "CN=Trustwave Global Organization Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGUMT0wOwYDVQQDEzRUcnVzdHdhdmUgR2xvYmFsIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "9d9bb0ca6a8a59110e9ecd8714e94894e0be5886e46f9c4e5265ffa83dcece40", + "size": 2519, + "filename": "z4qrcPL95VQXuiBgseJjj0x2uKXj6NQcTxtcnoGWFyk=.pem", + "location": "security-state-staging/intermediates/8771cde1-4865-436b-bb56-6604a726f4bc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z4qrcPL95VQXuiBgseJjj0x2uKXj6NQcTxtcnoGWFyk=", + "crlite_enrolled": false, + "id": "42a123e8-d13a-40c4-8e1c-70f3ac954774", + "last_modified": 1645578268382 + }, + { + "schema": 1645577384467, + "derHash": "KfjhBN845hK/T1BTZbXQsLIN1OickIMGSE64N0weZtg=", + "subject": "CN=Trustwave Global ECDSA P-384 Extended Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGcMUUwQwYDVQQDEzxUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMzg0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EsIExldmVsIDExITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjEQMA4GA1UEBxMHQ2hpY2FnbzERMA8GA1UECBMISWxsaW5vaXMxCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "53f8122b5a0fa5136d24ba312f1f8c41061bd774427e22f6d2a5e4fababcd5ea", + "size": 1406, + "filename": "dzssfumpi3rcJekwfVNUyE16SoxSUQ6SRbv3ubi21iE=.pem", + "location": "security-state-staging/intermediates/4704d8d9-40ac-48ab-a268-592fab67683c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dzssfumpi3rcJekwfVNUyE16SoxSUQ6SRbv3ubi21iE=", + "crlite_enrolled": false, + "id": "be725aa6-a968-4291-bcd7-93ee080e586d", + "last_modified": 1645578268342 + }, + { + "schema": 1645577387026, + "derHash": "Z4LG6a2/khbiaDBtRjM/vGdL3t+GtnvndweeLA42iI4=", + "subject": "CN=Trustwave Global Domain Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGOMTcwNQYDVQQDEy5UcnVzdHdhdmUgR2xvYmFsIERvbWFpbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "a1aae690e4bc653bc8dd049d0014c8bdf294c1327ae51c6f40d11e8b728cdee3", + "size": 2499, + "filename": "N8k0xQNnFdJBZpSXQ6W9OldaDSJ59X-kFQNRrNSN8Cw=.pem", + "location": "security-state-staging/intermediates/18b0f7cb-0da9-422a-b8b0-edf3ba36821b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N8k0xQNnFdJBZpSXQ6W9OldaDSJ59X+kFQNRrNSN8Cw=", + "crlite_enrolled": false, + "id": "d477a580-6e24-46a9-a2c7-4c09077650a5", + "last_modified": 1645578268322 + }, + { + "schema": 1645577388356, + "derHash": "Nk3yXMiPb7AgLALNm46gLf6KwCNB7/Wp3/xb4VntWZE=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSwwKgYDVQQDEyNHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "ed46410fc0225451ee5e4d30089f684656cdc7fcb4fee406212faae69b33ec90", + "size": 1248, + "filename": "BgxBr7_q7A0c_PXQW0DAZfIha764OD-yYH__95_SM_A=.pem", + "location": "security-state-staging/intermediates/a9e337b6-2e48-4b9d-8614-90c4860ea6d3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BgxBr7/q7A0c/PXQW0DAZfIha764OD+yYH//95/SM/A=", + "crlite_enrolled": false, + "id": "0eef680a-320d-4964-8f4c-7db3b7cef45c", + "last_modified": 1645578268312 + }, + { + "schema": 1645577399047, + "derHash": "G4NI2aBj2IEPdzWz0gXEXb2EuzqHxnQ/4yKGMNvC1zs=", + "subject": "CN=PSW GROUP (ECC) DV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChFQ0MpIERWIENB", + "whitelist": false, + "attachment": { + "hash": "626cee21526231cf35b1c70dbeba777d80f68d2e3d5cf4fd81d3ef7fd3e88f2c", + "size": 1240, + "filename": "PmqkRAF1ZpNBGgE1fKwSFH2jXK5foG8fHryhWbon5ec=.pem", + "location": "security-state-staging/intermediates/b5342138-3e7d-45ae-bcca-acf80c3dec08.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PmqkRAF1ZpNBGgE1fKwSFH2jXK5foG8fHryhWbon5ec=", + "crlite_enrolled": false, + "id": "c84acf1d-eee2-4a4b-9797-e68042bc1ed6", + "last_modified": 1645578268229 + }, + { + "schema": 1645577404449, + "derHash": "yadACdrhlKQL0SKr0BXUTpPLeHmwlJBUoxBC70yZ5dY=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIyIFEx", + "whitelist": false, + "attachment": { + "hash": "d654a6ce826d389336ce330646e5b80ca4bb56d02aa9b727acdf28b1351d111b", + "size": 2406, + "filename": "xnf9i2v7Syri1B3CTwp1xFENpad-RBcMO7FeEYw99N4=.pem", + "location": "security-state-staging/intermediates/a360a91c-d2e5-4d13-8496-29a389b5882e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xnf9i2v7Syri1B3CTwp1xFENpad+RBcMO7FeEYw99N4=", + "crlite_enrolled": false, + "id": "a806764f-b6e1-4ecc-af1c-a4abab801cca", + "last_modified": 1645578268188 + }, + { + "schema": 1645577412454, + "derHash": "GGJqO3IREo1WNGnGVFV9E8k2bX/OyLlNtPCqfgWUQEE=", + "subject": "CN=Trustwave Global Extended Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGQMTkwNwYDVQQDEzBUcnVzdHdhdmUgR2xvYmFsIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EsIExldmVsIDExITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjEQMA4GA1UEBxMHQ2hpY2FnbzERMA8GA1UECBMISWxsaW5vaXMxCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "4e61dd89fb06d5da14f5a4b0f7a2328cd7ab2dea6544a50ea59b3e7d59167e32", + "size": 2515, + "filename": "V3f1xGezL0zYaRJqAhBzSKsvj3vEMlp9KGBwnV4be38=.pem", + "location": "security-state-staging/intermediates/01a8f648-ea7e-430b-a212-50aaa75a5e54.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "V3f1xGezL0zYaRJqAhBzSKsvj3vEMlp9KGBwnV4be38=", + "crlite_enrolled": false, + "id": "f5515d37-9357-413c-a858-036df41b70fd", + "last_modified": 1645578268128 + }, + { + "schema": 1645577420440, + "derHash": "gpj8QnBMTtd0tNsxBSkmC1JKkHUlKh0Ks44G8bmoNJk=", + "subject": "CN=CertCloud RSA TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "MEoxCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMR0wGwYDVQQDExRDZXJ0Q2xvdWQgUlNBIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "59bb133020146d20f0d963a09c0520755e8a49c60982dbc377b4072ee287be19", + "size": 2073, + "filename": "Wz3-4Wr1VbAmx0vpewGzhrRMM8dT5kZ8_PHc1S6WqYs=.pem", + "location": "security-state-staging/intermediates/696927f8-d451-461d-9f12-0b837513fe0b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wz3+4Wr1VbAmx0vpewGzhrRMM8dT5kZ8/PHc1S6WqYs=", + "crlite_enrolled": false, + "id": "66fb44fd-6306-424a-b116-6e1330c7ef05", + "last_modified": 1645578268068 + }, + { + "schema": 1645577430023, + "derHash": "mRe/2FNziYXkbJIEGUEOlmwxaYJ2nnGBfifQOEu+Nnk=", + "subject": "CN=certSIGN Public CA,O=CERTSIGN SA,C=RO", + "subjectDN": "MFkxCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEbMBkGA1UEAxMSY2VydFNJR04gUHVibGljIENBMRcwFQYDVQRhEw5WQVRSTy0xODI4ODI1MA==", + "whitelist": false, + "attachment": { + "hash": "d61a1b35a91c710f44d6b989faabe0cbe91a89dbc701983bdbf2602b85bf3038", + "size": 2312, + "filename": "IGxtWhsxuC58R149YREUyaqPK7jXR9no_MMC227zRVo=.pem", + "location": "security-state-staging/intermediates/cd9c43c7-945a-402c-a5f2-06b30de10929.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IGxtWhsxuC58R149YREUyaqPK7jXR9no/MMC227zRVo=", + "crlite_enrolled": false, + "id": "470f3934-90e9-4eab-b69a-ee8a5812fa3f", + "last_modified": 1645578267998 + }, + { + "schema": 1645577434085, + "derHash": "iwW2jMZZ5e0PyzjyyUL7/SAOby/5+F1jxplO9eCwJwE=", + "subject": "CN=ISRG Root X2,O=Internet Security Research Group,C=US", + "subjectDN": "ME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgy", + "whitelist": false, + "attachment": { + "hash": "0a551cc852c7b0f054ae4c9e72acb7a6b4b1a8bab2d598b21063ab92fc36ecb6", + "size": 1577, + "filename": "diGVwiVYbubAI3RW4hB9xU8e_CH2GnkuvVFZE8zmgzI=.pem", + "location": "security-state-staging/intermediates/a9c4efcb-3d99-44aa-ba70-b708ebb61779.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=", + "crlite_enrolled": false, + "id": "f0173e43-16e0-4e9f-8374-fd40addf5a22", + "last_modified": 1645578267968 + }, + { + "schema": 1645577440775, + "derHash": "HjNJzTZqk+We9BUqHkg9rGB2GExdRtTNoA5gk+/J5qM=", + "subject": "CN=University of West Attica TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MHsxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMTMwMQYDVQQDDCpVbml2ZXJzaXR5IG9mIFdlc3QgQXR0aWNhIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "d8fb052081f2da972e89d0875b3e755d8dc1b1498a0a5f5cbceaccbae22779a7", + "size": 2820, + "filename": "CyajdIEcy4Hr_cxQQqnu0qrnumFkHRMCTTcJDgIK41E=.pem", + "location": "security-state-staging/intermediates/9ec09530-9d87-4bf2-a12b-3cf191b6a402.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "CyajdIEcy4Hr/cxQQqnu0qrnumFkHRMCTTcJDgIK41E=", + "crlite_enrolled": false, + "id": "07100a67-e0bc-4170-ad65-14e652c630ab", + "last_modified": 1645578267916 + }, + { + "schema": 1645577448994, + "derHash": "IYxHC3iwLTm4T8aJL1of6cmda5pri3Rx4Dntz8qRAEw=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "ccc18ae4433e6adabdc6786dea95c19022d2fc58b5d53697664ebbe709b8d444", + "size": 1272, + "filename": "tknQlS8xhdJy4vVuy2O8_4Qbd1dyLFObjTdmW8c4SkE=.pem", + "location": "security-state-staging/intermediates/4d6d5cfd-e2e1-4865-85bb-e0b9cfdaec63.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tknQlS8xhdJy4vVuy2O8/4Qbd1dyLFObjTdmW8c4SkE=", + "crlite_enrolled": false, + "id": "8e045253-b803-4d74-b99c-8e5da4c8a8e6", + "last_modified": 1645578267856 + }, + { + "schema": 1645577462286, + "derHash": "Wr5YGPbQLwUQbGw1VUDhviF8I1S1Nc8lB7+FFeGmBEo=", + "subject": "CN=e-Szigno Qualified CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHYxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxIzAhBgNVBAMMGmUtU3ppZ25vIFF1YWxpZmllZCBDQSAyMDE3", + "whitelist": false, + "attachment": { + "hash": "b1a685a82713fdb970ba0d46956ad6f758e20974ec4e06085ed70d31639c065a", + "size": 1666, + "filename": "S3q9Rterro1Xg3DAezkzEPMCdEp1MvG6n-mSxrdMylY=.pem", + "location": "security-state-staging/intermediates/78fe209e-4220-4121-a2f2-ab682476bdf4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S3q9Rterro1Xg3DAezkzEPMCdEp1MvG6n+mSxrdMylY=", + "crlite_enrolled": false, + "id": "d148b9a1-76f1-42bc-b0fc-fdc39255001b", + "last_modified": 1645578267753 + }, + { + "schema": 1645577472905, + "derHash": "Ss2NxgIKVFqFiUOlU7Xg8/xbhZrqF0ZlDWnPEhD5Vtg=", + "subject": "CN=HARICA TLS RSA Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGwxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSQwIgYDVQQDDBtIQVJJQ0EgVExTIFJTQSBSb290IENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "c609cf2b187fc49131dd24d1a8554ddf29c2e44c4a1c8026ef0bf83d94d69fa0", + "size": 2402, + "filename": "aTyaprJFs7AmFjd1CGPq22wkihblLW9LyQyGu_MtcEI=.pem", + "location": "security-state-staging/intermediates/7b6c364c-acf6-41d9-98d8-2e0f620ef645.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aTyaprJFs7AmFjd1CGPq22wkihblLW9LyQyGu/MtcEI=", + "crlite_enrolled": false, + "id": "d3910f00-b899-4e1d-b619-9b3cf3bfe4cb", + "last_modified": 1645578267671 + }, + { + "schema": 1645577475604, + "derHash": "PuAnjfcfo8ElxM1IfwHXdGlOb8V+DNlMJO/XaRM5GOU=", + "subject": "CN=GTS Root R1,O=Google Trust Services LLC,C=US", + "subjectDN": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMQ==", + "whitelist": false, + "attachment": { + "hash": "a7e1e9af45661eb891874dbac46389428139246fe7050143e9b0fd233f74e236", + "size": 1926, + "filename": "hxqRlPTu1bMS_0DITB1SSu0vd4u_8l8TjPgfaAp63Gc=.pem", + "location": "security-state-staging/intermediates/e9d2e4d0-8a98-44d8-b18f-0175f532672b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=", + "crlite_enrolled": false, + "id": "bc69c52c-a98e-4317-b071-af6fbb649250", + "last_modified": 1645578267651 + }, + { + "schema": 1645577504932, + "derHash": "T4OELx8Eqx4E1NjnUWZvyoLlGRyvwkBiv9H+d8AspLQ=", + "subject": "CN=e-Szigno Class3 CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHMxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxIDAeBgNVBAMMF2UtU3ppZ25vIENsYXNzMyBDQSAyMDE3", + "whitelist": false, + "attachment": { + "hash": "71132024dc6f9eb4be8dbf1a0131b491fae4efd3d711ad58c399ed1ebdd355ef", + "size": 1662, + "filename": "X4MOqhXQM4obftW5dgADxiIIIaZwoNHvuYhRJq7eVe0=.pem", + "location": "security-state-staging/intermediates/d8cbd3ca-ceef-4577-9bcf-ae3d02aa3294.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "X4MOqhXQM4obftW5dgADxiIIIaZwoNHvuYhRJq7eVe0=", + "crlite_enrolled": false, + "id": "e7defe75-c83c-406c-b139-f8446ba57e33", + "last_modified": 1645578267425 + }, + { + "schema": 1645577506295, + "derHash": "b/IVlwTU6IaFmhQP/k4zRx5RQhdDCwPEz2yFxjf37LM=", + "subject": "CN=AlpiroSSL RSA DV CA,O=Alpiro s.r.o.,C=CZ", + "subjectDN": "MEMxCzAJBgNVBAYTAkNaMRYwFAYDVQQKEw1BbHBpcm8gcy5yLm8uMRwwGgYDVQQDExNBbHBpcm9TU0wgUlNBIERWIENB", + "whitelist": false, + "attachment": { + "hash": "4d11311616c66072dc5bacbb6b155d283f17a79abd5b2fbb3a65e1b3c239ed39", + "size": 2414, + "filename": "DMHCW-wSFt63_5zZBrK0lQDZcM_mOSQpQa6ibF64R6U=.pem", + "location": "security-state-staging/intermediates/afab5d5a-cfac-46ad-84b9-90bb2d7e2326.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DMHCW+wSFt63/5zZBrK0lQDZcM/mOSQpQa6ibF64R6U=", + "crlite_enrolled": false, + "id": "a88e9060-e108-4138-a755-0134b1b235d9", + "last_modified": 1645578267414 + }, + { + "schema": 1645577513017, + "derHash": "4eFdKJaXrzKFvzQRKj5gvBHAyBIt0rmzQgqy5eV4g8o=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2020,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSwwKgYDVQQDEyNHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMA==", + "whitelist": false, + "attachment": { + "hash": "6c181f86b88cce371dc0b130111d3125f0ce1aa337a7d15752fd289f0481498b", + "size": 2393, + "filename": "MM5KYfD_P8xntjQu_wLg8lCufO2t49iWBsJodtwxC1E=.pem", + "location": "security-state-staging/intermediates/0aa04ea5-b733-461f-9792-f3d31ba9333e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MM5KYfD/P8xntjQu/wLg8lCufO2t49iWBsJodtwxC1E=", + "crlite_enrolled": false, + "id": "b96e710a-a202-4163-a6e7-d7ed71a53f61", + "last_modified": 1645578267362 + }, + { + "schema": 1645577515699, + "derHash": "ExbtQqhJIX1Rz7TeGhQYqNOneQL7MF0wJPctRja4L/A=", + "subject": "CN=Plex Devices High Assurance CA3,O=Plex\\, Inc.,C=US", + "subjectDN": "MEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpQbGV4LCBJbmMuMSgwJgYDVQQDEx9QbGV4IERldmljZXMgSGlnaCBBc3N1cmFuY2UgQ0Ez", + "whitelist": false, + "attachment": { + "hash": "e9cd49fd61bad66c4ff0edce99bee5694563718e5f3bf588c6893ae9da66bd4f", + "size": 1739, + "filename": "7vFogPWQjRRrBULu9CTSZG05Zp5wI5psu-SVeew9nqo=.pem", + "location": "security-state-staging/intermediates/cfae80ee-4477-4d1f-b7ad-5fd2bb30c24f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7vFogPWQjRRrBULu9CTSZG05Zp5wI5psu+SVeew9nqo=", + "crlite_enrolled": false, + "id": "e1650d9b-9a18-4002-af70-d3ba0ea2c74c", + "last_modified": 1645578267342 + }, + { + "schema": 1645577517042, + "derHash": "QtyCf0b7XoXf+uR9PGkPUB7OJdV11ZelDY+Hj6Qq/Oo=", + "subject": "CN=e-Szigno Class2 CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHMxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxIDAeBgNVBAMMF2UtU3ppZ25vIENsYXNzMiBDQSAyMDE3", + "whitelist": false, + "attachment": { + "hash": "24e4936e9492f5245f32e7f9b60258d25f2a4c8e36002f26691cdf9f19bb94f3", + "size": 1662, + "filename": "3TGdI2jR-PvtWXO9N84WdfCRvr5nND75u1oGBVvPEl4=.pem", + "location": "security-state-staging/intermediates/fb522fa8-154c-45a4-8386-828fc0b3b9c6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3TGdI2jR+PvtWXO9N84WdfCRvr5nND75u1oGBVvPEl4=", + "crlite_enrolled": false, + "id": "b0e8fd04-14bc-48eb-b953-1557ec7c8f22", + "last_modified": 1645578267331 + }, + { + "schema": 1645577544239, + "derHash": "nJ1emGtb6noeyEV+vKpgDSQ1gDuR/sl8TlBDvmdC1LE=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "dd328d8151311218aa922749509f8a8c6db7fadf4ef9eff750e0738bc8371ec6", + "size": 1252, + "filename": "DQGZqEoQOGPk3TGeSgw0Mv3YehFPmaDtde4hT_ZpQVY=.pem", + "location": "security-state-staging/intermediates/f0902f4c-c1c1-46a4-84ce-861f1fe14ae6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DQGZqEoQOGPk3TGeSgw0Mv3YehFPmaDtde4hT/ZpQVY=", + "crlite_enrolled": false, + "id": "0af2fa62-ff3e-4f7d-8a7d-8a81ddef262b", + "last_modified": 1645578267127 + }, + { + "schema": 1645577552443, + "derHash": "kxqqHsmyug+lmoIwL0+DBijIbZstKlCk0bLOiVxMxkg=", + "subject": "CN=Actalis Organization Validated Server CA G3,O=Actalis S.p.A.,L=Ponte San Pietro,ST=Bergamo,C=IT", + "subjectDN": "MIGJMQswCQYDVQQGEwJJVDEQMA4GA1UECAwHQmVyZ2FtbzEZMBcGA1UEBwwQUG9udGUgU2FuIFBpZXRybzEXMBUGA1UECgwOQWN0YWxpcyBTLnAuQS4xNDAyBgNVBAMMK0FjdGFsaXMgT3JnYW5pemF0aW9uIFZhbGlkYXRlZCBTZXJ2ZXIgQ0EgRzM=", + "whitelist": false, + "attachment": { + "hash": "71fafcd9a9832cc1330204b9331c40ce9c1fbcedeb9bd11114cd6cc2e1618b06", + "size": 2645, + "filename": "bX2Ri6lz4UqCdYdntmbFTrpmv0zVxEqhgCHCu9QV278=.pem", + "location": "security-state-staging/intermediates/1d6e3f25-34fc-40f2-a649-733598da5705.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bX2Ri6lz4UqCdYdntmbFTrpmv0zVxEqhgCHCu9QV278=", + "crlite_enrolled": false, + "id": "188137fb-bf8f-4c7e-ade5-b6d58a130896", + "last_modified": 1645578267065 + }, + { + "schema": 1645577557948, + "derHash": "Z63RFmsCCuYbj1/JaBPATCqliZYHloZVcqPH5zdhPf0=", + "subject": "CN=R3,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSMw==", + "whitelist": false, + "attachment": { + "hash": "c298ec17b9257dbfb7f36280caade22c6317186c146b5fdd5792bcb77afced29", + "size": 1825, + "filename": "jQJTbIh0grw0_1TkHSumWb-Fs0Ggogr621gT3PvPKG0=.pem", + "location": "security-state-staging/intermediates/d7c06dc8-e1a0-4fd1-accb-cb12a54a8760.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=", + "crlite_enrolled": false, + "id": "ba68b537-2a39-44eb-b070-be4b1ad5e0ef", + "last_modified": 1645578267024 + }, + { + "schema": 1645577560630, + "derHash": "FkjOSrG7ZcSFyyI2x2j6u4ZRR9QmkVuSr7yoHpsu47w=", + "subject": "CN=e-Szigno Qualified Pseudonymous CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGDMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MTAwLgYDVQQDDCdlLVN6aWdubyBRdWFsaWZpZWQgUHNldWRvbnltb3VzIENBIDIwMTc=", + "whitelist": false, + "attachment": { + "hash": "babeb483e7bc13a161427464ff4aa32b126458a9b7bd684609420f8d1a7cb6ab", + "size": 1687, + "filename": "9Mm86u4WwGKlrCcut3imyBIja3zt37WI7jdpaAucZOs=.pem", + "location": "security-state-staging/intermediates/e7001a08-57a5-4a1e-90f1-5c7b5aa6524c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9Mm86u4WwGKlrCcut3imyBIja3zt37WI7jdpaAucZOs=", + "crlite_enrolled": false, + "id": "64afda88-59b5-42e9-8202-171b65c35cdd", + "last_modified": 1645578267003 + }, + { + "schema": 1645577562423, + "derHash": "am8voTstnbu0CYAgAtM3BnJ2CiF42bjVaU1mBHQjH6Q=", + "subject": "CN=e-Szigno Pseudonymous CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHkxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJjAkBgNVBAMMHWUtU3ppZ25vIFBzZXVkb255bW91cyBDQSAyMDE3", + "whitelist": false, + "attachment": { + "hash": "4a22ba90a733131c2581c1d0dd2fbb9a8df38f518f2e5ba6ee0d10f4b3169c24", + "size": 1670, + "filename": "vqerAqvFBz-pP0lY3LKc96jOLcWu__imbw0SS3qBch8=.pem", + "location": "security-state-staging/intermediates/ad60497c-1544-4bff-98ec-e53c67af444a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vqerAqvFBz+pP0lY3LKc96jOLcWu//imbw0SS3qBch8=", + "crlite_enrolled": false, + "id": "58f498fd-580c-4bf5-badb-18c81958ad8d", + "last_modified": 1645578266993 + }, + { + "schema": 1645577563800, + "derHash": "l0uCB2FUzv9W7U21Yhhvc5SgL/OHqiBdY2eosI/3+qA=", + "subject": "CN=e-Szigno Online SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIE9ubGluZSBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "0abdd69f4b3ccf85097142e285b47d52e0f91c59aa3e684dfa3c25bb3c8d6039", + "size": 1666, + "filename": "G_JwHP_ydSe7pufWcUyNckBrxBbnQ6Kmqw_OFPPiQI0=.pem", + "location": "security-state-staging/intermediates/4ca5741b-3046-4651-a402-c819aca2f70d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "G/JwHP/ydSe7pufWcUyNckBrxBbnQ6Kmqw/OFPPiQI0=", + "crlite_enrolled": false, + "id": "ec1245af-ec52-405d-8a7f-67e52e819d5b", + "last_modified": 1645578266982 + }, + { + "schema": 1645577566450, + "derHash": "TPoUvH6PSNRnewYEd3r3arDFWzJvi5e6lfas4RXAXxY=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "df80822c69bdeecdf5fb5d1b30c3f046a0e6db7a73cb50ab774e35a2f8469f06", + "size": 2398, + "filename": "7o7YnXjBCwSnI2SqRPrDh62sfg55ErcgZ_bXovoqhw0=.pem", + "location": "security-state-staging/intermediates/637be407-0c8a-4e55-96f8-0556e5bf5471.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7o7YnXjBCwSnI2SqRPrDh62sfg55ErcgZ/bXovoqhw0=", + "crlite_enrolled": false, + "id": "f34a58bc-87fc-4426-9ae2-9d88ac41278a", + "last_modified": 1645578266962 + }, + { + "schema": 1645577572043, + "derHash": "J1vwBG4CcBeLE7Z+TbFQoQGXy3H5oGH602s9dmOf4YY=", + "subject": "CN=Trustwave Global ECDSA P-256 Extended Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGcMUUwQwYDVQQDEzxUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMjU2IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EsIExldmVsIDExITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjEQMA4GA1UEBxMHQ2hpY2FnbzERMA8GA1UECBMISWxsaW5vaXMxCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "b39b1eb52f8d651482f3d14bd9a033de3982f3cc3fa825ba761d2fefa3a11981", + "size": 1325, + "filename": "YzpgcYxSKDnEVZ3T3h2obh9WsI9ICItWbAqh4SyCprA=.pem", + "location": "security-state-staging/intermediates/e8727b1e-db90-43a1-896d-acfa3849475f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YzpgcYxSKDnEVZ3T3h2obh9WsI9ICItWbAqh4SyCprA=", + "crlite_enrolled": false, + "id": "327d0c33-3441-43da-a81a-797c08caae1b", + "last_modified": 1645578266923 + }, + { + "schema": 1645577573413, + "derHash": "UOJ/kOtq9JWw5u62VcyJREwn08lbaCP6AqvclfFjauE=", + "subject": "CN=HARICA TLS ECC Root CA 2021,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGwxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMSQwIgYDVQQDDBtIQVJJQ0EgVExTIEVDQyBSb290IENBIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "cd927097bb0f3ccd26c5b2e38f4bfa499465d2999a58b4bed7d295068d23630e", + "size": 1268, + "filename": "_HhDAOyN9NPRutdjg1GCkY1Sqf8COL32laHNm9uYMhw=.pem", + "location": "security-state-staging/intermediates/30823a44-60dd-4453-96f9-1e4bfc87c807.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/HhDAOyN9NPRutdjg1GCkY1Sqf8COL32laHNm9uYMhw=", + "crlite_enrolled": false, + "id": "fcef12c5-b871-4941-ab0c-8348d333a4e4", + "last_modified": 1645578266913 + }, + { + "schema": 1645577577654, + "derHash": "9i41qtNF+yU4Y8/A5lgjrNPn8IRtGbgybXCceRmiLX4=", + "subject": "CN=Trustwave Global ECDSA P-256 Domain Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGaMUMwQQYDVQQDEzpUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMjU2IERvbWFpbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "ce8879da72592af4bbeefb13d2c925ca1eed8e33ca17d960a29ad9ae0835124d", + "size": 1305, + "filename": "a-5FBlZcydYwIUikW5fVH6MjJzVRhdPbb586imNLagE=.pem", + "location": "security-state-staging/intermediates/62d82876-f314-4cd8-b5af-54a36e786129.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "a+5FBlZcydYwIUikW5fVH6MjJzVRhdPbb586imNLagE=", + "crlite_enrolled": false, + "id": "d5bf85e2-a4bf-45b9-9b11-ab887c49e96d", + "last_modified": 1645578266884 + }, + { + "schema": 1645577581818, + "derHash": "BrtWd3HEJpFMGaY47cGACPWQG5KBSpd0v+iBW6t+puk=", + "subject": "CN=PSW GROUP (RSA) DV CA,O=PSW GROUP GmbH & Co. KG,C=DE", + "subjectDN": "ME8xCzAJBgNVBAYTAkRFMSAwHgYDVQQKDBdQU1cgR1JPVVAgR21iSCAmIENvLiBLRzEeMBwGA1UEAxMVUFNXIEdST1VQIChSU0EpIERWIENB", + "whitelist": false, + "attachment": { + "hash": "2309ba2120931c2bd1e257481f59a22880eb3ccb912c468a99cbe78779e2bab1", + "size": 2081, + "filename": "Yv80yzPUjOuufrMJGuItTe5sk6C7c0w4o5O_8F6HdQw=.pem", + "location": "security-state-staging/intermediates/573c0997-0875-4d7e-8c49-c60e1dec138e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Yv80yzPUjOuufrMJGuItTe5sk6C7c0w4o5O/8F6HdQw=", + "crlite_enrolled": false, + "id": "e4c35ef9-ffde-447a-999d-1910ed7cec36", + "last_modified": 1645578266854 + }, + { + "schema": 1645577590130, + "derHash": "BYd+5cz+fPGrs8nt85BbX2FCx7eTnWfjJBHPiWMn+Yc=", + "subject": "CN=CertCloud ECC TLS CA,O=CertCloud Pte. Ltd.,C=SG", + "subjectDN": "MEoxCzAJBgNVBAYTAlNHMRwwGgYDVQQKExNDZXJ0Q2xvdWQgUHRlLiBMdGQuMR0wGwYDVQQDExRDZXJ0Q2xvdWQgRUNDIFRMUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "f8337f06d70a3f7f0f2ef60247a23ef7a2527a55578bd6069dab297153907ecc", + "size": 1232, + "filename": "-iSs_1O_biy_0c0nXxdAJJKe4jDCWam1eQs-GklMUgI=.pem", + "location": "security-state-staging/intermediates/99d8c47c-427a-4e41-bd9b-2a37827be6e1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+iSs/1O/biy/0c0nXxdAJJKe4jDCWam1eQs+GklMUgI=", + "crlite_enrolled": false, + "id": "b8e6fa01-6846-4ab5-9bb0-aa53693bf0b2", + "last_modified": 1645578266793 + }, + { + "schema": 1645577591483, + "derHash": "YIG+5bDfGRrE4mWsD294mfB4uMifBgVa4Wavkd9w1uA=", + "subject": "CN=e-Szigno Qualified QCP CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHmUtU3ppZ25vIFF1YWxpZmllZCBRQ1AgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "280a786ca35892c86124fb11c5e02f70f1bf6cd40daa1e6538e054942241cdaf", + "size": 1674, + "filename": "zohU4uap0ZYaGxznYnSZfimXMiL0fPpOwF3CBDSo608=.pem", + "location": "security-state-staging/intermediates/5db4f7dc-eb4f-42a0-b4b8-b8ca00102dbc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "zohU4uap0ZYaGxznYnSZfimXMiL0fPpOwF3CBDSo608=", + "crlite_enrolled": false, + "id": "10b077a9-4edf-4131-8777-4f2210b0e566", + "last_modified": 1645578266783 + }, + { + "schema": 1645577592853, + "derHash": "GgdSmos/AdIx360qvfcYmSALtlzX4DxZ+oInJTM1W3Q=", + "subject": "CN=R4,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJSNA==", + "whitelist": false, + "attachment": { + "hash": "e7c443b15be49a4083c31378f2a1749ea59e73cd564b4d4e0465ea2d9d839146", + "size": 1825, + "filename": "5VReIRNHJBiRxVSgOTTN6bdJZkpZ0m1hX-WPd5kPLQM=.pem", + "location": "security-state-staging/intermediates/4eb48f8a-eff3-4ca5-abab-9dfe2ab3a50e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5VReIRNHJBiRxVSgOTTN6bdJZkpZ0m1hX+WPd5kPLQM=", + "crlite_enrolled": false, + "id": "e35b8ec9-20fd-46e7-851c-4d7d2566d1c0", + "last_modified": 1645578266773 + }, + { + "schema": 1645577594200, + "derHash": "6lMubXj/AP1PVkR/fgpH0jnZRUJLd88NFaeVy07mAr0=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "ad472ef5a46ea68bbb4a1a3f17c53c17fdea08a495931ba70ce99284d3b5111f", + "size": 1715, + "filename": "h_7tTSynG1Dd6RmqneQ-zxMZtzQrsbf-70yJDma14Nc=.pem", + "location": "security-state-staging/intermediates/ab17d88b-ef26-4d65-8355-434a27c5f68a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "h/7tTSynG1Dd6RmqneQ+zxMZtzQrsbf+70yJDma14Nc=", + "crlite_enrolled": false, + "id": "68ddf421-2e8c-4807-bf06-c6cf66ff7798", + "last_modified": 1645578266763 + }, + { + "schema": 1645577614499, + "derHash": "ugMS97cva2S0zO40tfYoz2Wh87nxa43+etqQxU5HWhw=", + "subject": "CN=HARICA QWAC ECC SubCA R1,OU=Hellenic Academic and Research Institutions CA,O=Greek Universities Network (GUnet),L=Athens,C=GR", + "subjectDN": "MIHBMQswCQYDVQQGEwJHUjEPMA0GA1UEBwwGQXRoZW5zMSswKQYDVQQKDCJHcmVlayBVbml2ZXJzaXRpZXMgTmV0d29yayAoR1VuZXQpMRgwFgYDVQRhDA9WQVRHUi0wOTkwMjgyMjAxNzA1BgNVBAsMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExITAfBgNVBAMMGEhBUklDQSBRV0FDIEVDQyBTdWJDQSBSMQ==", + "whitelist": false, + "attachment": { + "hash": "f620f189d98376db941c6c7ddcf88debb8fa7eefe9a08b53fb9a7c7f2be0d70a", + "size": 1435, + "filename": "My5yT8M-8RZqS9wIe_1GKdv6BmFJetmYV8QXy2UQNVQ=.pem", + "location": "security-state-staging/intermediates/22cbb23e-cab4-423b-9a10-de48481aa920.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "My5yT8M+8RZqS9wIe/1GKdv6BmFJetmYV8QXy2UQNVQ=", + "crlite_enrolled": false, + "id": "d4e5f3f5-705e-4bbd-a60d-297d4ab5c2cd", + "last_modified": 1645578266606 + }, + { + "schema": 1645577615835, + "derHash": "xj35YY+69IZiuhKr6HshLPd3wnpEag+lVdep0ZEUjtc=", + "subject": "CN=Trustwave Global ECDSA P-384 Domain Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGaMUMwQQYDVQQDEzpUcnVzdHdhdmUgR2xvYmFsIEVDRFNBIFAtMzg0IERvbWFpbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "e81cc0a883e6e41a009a5d876bc62528cd6b26b1edd7421071b6ff434dca6265", + "size": 1390, + "filename": "NIbNiw79lRjCX7hAKe6oYePjjaaAQ9l_zvmwptYjCgg=.pem", + "location": "security-state-staging/intermediates/805d4e01-06d3-4cd4-9e90-5d413167243c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "NIbNiw79lRjCX7hAKe6oYePjjaaAQ9l/zvmwptYjCgg=", + "crlite_enrolled": false, + "id": "15c61ec9-5b89-4812-8240-d6bf15fa38dd", + "last_modified": 1645578266596 + }, + { + "schema": 1645577621120, + "derHash": "s0oztER0/bAHjxE1J7vbdv5b+ByiS7htYoE2xQFgQ+g=", + "subject": "CN=Certum Class I CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MHYxCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGjAYBgNVBAMTEUNlcnR1bSBDbGFzcyBJIENB", + "whitelist": false, + "attachment": { + "hash": "30af05c15084780c6d3ae3b8241e1c92fd060afc921724be7e63675470aeac33", + "size": 1711, + "filename": "MbF_6zzENJgNMBtdiV6R_9-GZ7l6Nl4QjwF-GW39Yms=.pem", + "location": "security-state-staging/intermediates/c2f18743-c18b-4fe1-b235-ecce5a4917d4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MbF/6zzENJgNMBtdiV6R/9+GZ7l6Nl4QjwF+GW39Yms=", + "crlite_enrolled": false, + "id": "db7340be-ba36-4e17-be92-a5e77a41f0ee", + "last_modified": 1645578266555 + }, + { + "schema": 1645577625212, + "derHash": "Euom9u7v7HarhZJUVAOriFFbAOJ12YiHE0B6hvxcf9c=", + "subject": "CN=e-Szigno Qualified Organization CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGDMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MTAwLgYDVQQDDCdlLVN6aWdubyBRdWFsaWZpZWQgT3JnYW5pemF0aW9uIENBIDIwMTc=", + "whitelist": false, + "attachment": { + "hash": "2dcc501b643fe8ac38e3b36c6e87ffbb458e7d031117857c3d81511ee2eadfd8", + "size": 1687, + "filename": "60e2cMobrxpGA71ke0o4H5luzpBPZvklmqnePPA8jxs=.pem", + "location": "security-state-staging/intermediates/f8233776-0e86-40da-8d8e-68aa8d6418ab.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "60e2cMobrxpGA71ke0o4H5luzpBPZvklmqnePPA8jxs=", + "crlite_enrolled": false, + "id": "77af9f91-e12c-482f-917b-e449a52918cc", + "last_modified": 1645578266525 + }, + { + "schema": 1645577626559, + "derHash": "RklOMDeQWd8YvlISQwXmBvxZBw5bIQds4ROVS2BRfNo=", + "subject": "CN=E1,O=Let's Encrypt,C=US", + "subjectDN": "MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJFMQ==", + "whitelist": false, + "attachment": { + "hash": "14cf69c79f1a688b326247f724ad50a3b22de5e34daf12c66670e5a38b1273d0", + "size": 1020, + "filename": "J2_oqMTsdhFWW_n85tys6b4yDBtb6idZayIEBx7QTxA=.pem", + "location": "security-state-staging/intermediates/6f24bdbe-5332-48da-8591-994a182c5271.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "J2/oqMTsdhFWW/n85tys6b4yDBtb6idZayIEBx7QTxA=", + "crlite_enrolled": false, + "id": "5f7b8a2f-c2df-4f79-b8e7-89710f155789", + "last_modified": 1645578266515 + }, + { + "schema": 1645577633162, + "derHash": "Kg4/KneoDcvlzVLVDWUHbr03+tUx2xDWoThaVX97cl0=", + "subject": "CN=e-Szigno Class2 SSL CA 2017,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG2UtU3ppZ25vIENsYXNzMiBTU0wgQ0EgMjAxNw==", + "whitelist": false, + "attachment": { + "hash": "bcb11e505804477ce4429c8ef9cc22d043e0f59d222d5e4524ec9158cc8e972b", + "size": 1666, + "filename": "HGXB7TIfcoLqLINF3LJD2A9t3V4VdHjcBv6LboViQMo=.pem", + "location": "security-state-staging/intermediates/895b5e1e-55e6-41d5-9237-87897bb298da.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HGXB7TIfcoLqLINF3LJD2A9t3V4VdHjcBv6LboViQMo=", + "crlite_enrolled": false, + "id": "74868831-ea0c-4662-8ac9-49306168fcf7", + "last_modified": 1645578266465 + }, + { + "schema": 1645577643866, + "derHash": "omL31lPmx0Xuq+v2Zyp1a1AagvBnCPK153kWIXA3u8o=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2022 Q1,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMiBRMQ==", + "whitelist": false, + "attachment": { + "hash": "9907027a14a417d41ca144866e5b162ce5b83cfec221eb1748d533d670a60401", + "size": 1272, + "filename": "L4y0ygHUFwuHByot9xpn_RDuWzf0QN0ZdIEbPkYlCnQ=.pem", + "location": "security-state-staging/intermediates/4b66b8d3-aa86-427c-add7-a2a478b8df44.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "L4y0ygHUFwuHByot9xpn/RDuWzf0QN0ZdIEbPkYlCnQ=", + "crlite_enrolled": false, + "id": "49a76008-6d74-4c56-b7cc-3c1f5df190a7", + "last_modified": 1645578266385 + }, + { + "schema": 1645577656054, + "derHash": "B0rdfx5z6xEOyOK3ipLFHPWkURNbb33vwBnunXS/pNY=", + "subject": "CN=TrustAsia RSA DV TLS CA - S1,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFsxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQDDBxUcnVzdEFzaWEgUlNBIERWIFRMUyBDQSAtIFMx", + "whitelist": false, + "attachment": { + "hash": "e3e6be32df35be180b103881b25f39b0efcaad3bc88d3ff492084a8e2c77b85a", + "size": 2044, + "filename": "UnhoHanj6MYk29pqvTEkZnAW1vQGrFENyVWfwU-5cE8=.pem", + "location": "security-state-staging/intermediates/edf7a0ab-f105-4045-ad80-91b14cc59cd4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UnhoHanj6MYk29pqvTEkZnAW1vQGrFENyVWfwU+5cE8=", + "crlite_enrolled": false, + "id": "41e38596-47f9-47d1-8818-44bcca75c54a", + "last_modified": 1645578266294 + }, + { + "schema": 1645577657404, + "derHash": "Ho8FHyIm+ZLRe3zxfw3s4Kp2mQLp/o0R81sZsYpaFPE=", + "subject": "CN=National Technical University of Athens TLS RSA SubCA R1,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MIGJMQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDQTFBMD8GA1UEAww4TmF0aW9uYWwgVGVjaG5pY2FsIFVuaXZlcnNpdHkgb2YgQXRoZW5zIFRMUyBSU0EgU3ViQ0EgUjE=", + "whitelist": false, + "attachment": { + "hash": "87d9d842f4187dc56baa4bdd139ca93eaf19d3da49d1e6bc36b01d122c3115fc", + "size": 2889, + "filename": "YiljC0xS2lPq3_MnjzPbKhYhVKBYFBS37Ng6sk1TQZA=.pem", + "location": "security-state-staging/intermediates/da93c16e-d081-43d0-adc7-0e75261c233a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YiljC0xS2lPq3/MnjzPbKhYhVKBYFBS37Ng6sk1TQZA=", + "crlite_enrolled": false, + "id": "e9029e2d-ef55-4f66-8731-cd2fd887c56d", + "last_modified": 1645578266284 + }, + { + "schema": 1645577666924, + "derHash": "urvKmGlGNSz5vzguiAZS9OlNvE/t0PHMIfqZc8ltZas=", + "subject": "CN=TunTrust Root CA,O=Agence Nationale de Certification Electronique,C=TN", + "subjectDN": "MGExCzAJBgNVBAYTAlROMTcwNQYDVQQKDC5BZ2VuY2UgTmF0aW9uYWxlIGRlIENlcnRpZmljYXRpb24gRWxlY3Ryb25pcXVlMRkwFwYDVQQDDBBUdW5UcnVzdCBSb290IENB", + "whitelist": false, + "attachment": { + "hash": "dc3faa0f8bfa8fb2ed668232aeb26788d58cd1b215adf55e781608b729eb9490", + "size": 2036, + "filename": "yUImLAx8CpW7FStxxCVW3b6aBPqDeDc1UNK3zifZUqM=.pem", + "location": "security-state-staging/intermediates/1cc74919-9700-4d96-baf0-420e4d3b1150.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yUImLAx8CpW7FStxxCVW3b6aBPqDeDc1UNK3zifZUqM=", + "crlite_enrolled": false, + "id": "e583ba69-1524-4ce5-926a-dc9a3aac8fed", + "last_modified": 1645578266213 + }, + { + "schema": 1645577670903, + "derHash": "3JRVykf1/Zvzu6u+rPiPPes7WL+oWvQE39IWF86QoN0=", + "subject": "CN=Academy of Athens SSL SubCA R2,O=Academy of Athens,L=Athens,C=GR", + "subjectDN": "MGMxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHDAZBdGhlbnMxGjAYBgNVBAoMEUFjYWRlbXkgb2YgQXRoZW5zMScwJQYDVQQDDB5BY2FkZW15IG9mIEF0aGVucyBTU0wgU3ViQ0EgUjI=", + "whitelist": false, + "attachment": { + "hash": "6dd5ea53e5e8b2afa37b209831937165a092a1863aa75c1a2c6afeb8d67e2e0b", + "size": 2771, + "filename": "JSBgh4-dzeoIV9S02ySUrHvvy_rDBARUsVV1tnTxX34=.pem", + "location": "security-state-staging/intermediates/4e158c73-39a1-48a7-b2b7-dda2b71c3cc8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JSBgh4+dzeoIV9S02ySUrHvvy/rDBARUsVV1tnTxX34=", + "crlite_enrolled": false, + "id": "08fe1d3e-243d-4e62-b920-e9e6a69fbd8b", + "last_modified": 1645578266184 + }, + { + "schema": 1645577672229, + "derHash": "zf8nr6Pa+fcGqnrFMCmDN+UgyLEKIvZRTgDiH9KHO3k=", + "subject": "CN=Ecclesiastical Academy of Vella SSL RSA SubCA R2,O=University Ecclesiastical Academy of Vella of Ioannina,L=Ioannina,C=GR", + "subjectDN": "MIGcMQswCQYDVQQGEwJHUjERMA8GA1UEBwwISW9hbm5pbmExPzA9BgNVBAoMNlVuaXZlcnNpdHkgRWNjbGVzaWFzdGljYWwgQWNhZGVteSBvZiBWZWxsYSBvZiBJb2FubmluYTE5MDcGA1UEAwwwRWNjbGVzaWFzdGljYWwgQWNhZGVteSBvZiBWZWxsYSBTU0wgUlNBIFN1YkNBIFIy", + "whitelist": false, + "attachment": { + "hash": "fb1958951d7c6940a1307c575a5b06f9cf76e6baf0059f80d1de3d4d4a596ebc", + "size": 2995, + "filename": "W-LP2L6GtiR5yYoztYP5ABaxz_faz3yBSub4W5M0wa4=.pem", + "location": "security-state-staging/intermediates/f27e30c0-417e-4232-8019-4cec824c80c2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W+LP2L6GtiR5yYoztYP5ABaxz/faz3yBSub4W5M0wa4=", + "crlite_enrolled": false, + "id": "e23e350c-f4ed-46ee-a1f2-560e15138f5e", + "last_modified": 1645578266173 + }, + { + "schema": 1643316508186, + "derHash": "S9FvSVXz88nI6kjvmZUyTaUSFyT4mRXV8skesLrvIzc=", + "subject": "OU=Public Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MFsxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEnMCUGA1UECwweUHVibGljIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "689c7ab13bb497838c62b566e5da7f914f4cf9354f08643500263bcfb46a32a3", + "size": 2105, + "filename": "qMjf0br8Ay71_wiJ0t25y4v3IWwirr3SF1bWHJzkbnE=.pem", + "location": "security-state-staging/intermediates/c2017a82-2237-4c28-8656-5764f263e185.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qMjf0br8Ay71/wiJ0t25y4v3IWwirr3SF1bWHJzkbnE=", + "crlite_enrolled": false, + "id": "a869aa70-b82b-40fc-b8b7-123af99bb58e", + "last_modified": 1643317054972 + }, + { + "schema": 1643295453377, + "derHash": "RksOwKYC8Bk9tfM5EYhaOmGSGtFtJmTiW++rEM+m7SU=", + "subject": "OU=Public Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MFsxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEnMCUGA1UECwweUHVibGljIENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "49845caec147a9f80e7f49b5e4b2b8629653bccf72d7aa8dfa0b7029f5d75514", + "size": 1934, + "filename": "qMjf0br8Ay71_wiJ0t25y4v3IWwirr3SF1bWHJzkbnE=.pem", + "location": "security-state-staging/intermediates/cabaf189-98ae-41ee-8da3-966fb99dbea2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qMjf0br8Ay71/wiJ0t25y4v3IWwirr3SF1bWHJzkbnE=", + "crlite_enrolled": false, + "id": "201bbcbe-98a9-4e1d-9ba8-1d91ccaef4d5", + "last_modified": 1643317054962 + }, + { + "schema": 1643294904826, + "derHash": "FbY83XcqjULcXkgXTB9+PUD8gsR43zpkQ8e9kvvG1d4=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSAyMDIyIFEy", + "whitelist": false, + "attachment": { + "hash": "36ec8a7f8b2cea9b43f6b7f86e3b8eb180de0b620da43a78d80676efd6b84cb5", + "size": 1195, + "filename": "Xj58HhZCZpimZtUExFKgYs2Sj1s9lkzi0nvcbhfxG8M=.pem", + "location": "security-state-staging/intermediates/8f304617-9f6c-410b-a3b5-d424f6063e72.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Xj58HhZCZpimZtUExFKgYs2Sj1s9lkzi0nvcbhfxG8M=", + "crlite_enrolled": false, + "id": "2ddabf35-3324-41a7-a8a7-caf9dd370fa3", + "last_modified": 1643295452283 + }, + { + "schema": 1643294906018, + "derHash": "ewY6nNSHZ8R1qmvO1GZ83FYGj4t7iVElvE6yRoLutgY=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "ca553b733f72e06ab70f4c99342af4062e00c5a060d0a2895380ae7c3bf92cde", + "size": 1199, + "filename": "QgFLk4fU5WIpsAIhnXQ09cN1jceKqmeNwV1QFjU-hHs=.pem", + "location": "security-state-staging/intermediates/2f74d7af-2322-481e-bf37-db31a0ed200e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "QgFLk4fU5WIpsAIhnXQ09cN1jceKqmeNwV1QFjU+hHs=", + "crlite_enrolled": false, + "id": "3f232eae-d8e3-4198-905a-cbcf2f06c798", + "last_modified": 1643295452276 + }, + { + "schema": 1643294907167, + "derHash": "OY3FYtwJjPdfOhUSFuWDOAwnlESw1j52bZ/fb0fQTGA=", + "subject": "CN=GlobalSign Atlas R46 EV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFI0NiBFViBUTFMgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "89cd87e6191d77c3eb3c7674be35ee4995b5c31932ad984526615de8c045ebc1", + "size": 2324, + "filename": "sZtlASgtwt8tmSKdqMgNBLUvLuai-nkfWA3xE3EyZvk=.pem", + "location": "security-state-staging/intermediates/43d5ad1d-fcfc-4e37-96db-b887fdc42750.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sZtlASgtwt8tmSKdqMgNBLUvLuai+nkfWA3xE3EyZvk=", + "crlite_enrolled": false, + "id": "c54d90b0-aaf9-4c0b-9ff5-1948b12df012", + "last_modified": 1643295452269 + }, + { + "schema": 1643294908352, + "derHash": "Xipz7mBp3mUyLc8EMBdrOF9SOMKW7kOS5+19tL6QikA=", + "subject": "CN=GlobalSign Atlas E46 EV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIEU0NiBFViBUTFMgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "30baad3f6e54af47cbe63bd65dad094713aa8f5bd4031321bc034781e0e975d0", + "size": 1179, + "filename": "4f83J9jBn9bDXsDfwj57CU5Uvv8c003ZVDiKZgoLl-o=.pem", + "location": "security-state-staging/intermediates/808a46ca-ac61-4ee4-82f9-3a9ebe3e7d50.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4f83J9jBn9bDXsDfwj57CU5Uvv8c003ZVDiKZgoLl+o=", + "crlite_enrolled": false, + "id": "de6506a7-2e51-4f8a-9030-a74ab368ad3f", + "last_modified": 1643295452263 + }, + { + "schema": 1643294910716, + "derHash": "JWYC3IqAQbLtwMrmEaapjZs5rU3j4Z9i3AoKGVi3InE=", + "subject": "CN=GlobalSign Atlas R6 EV TLS CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFI2IEVWIFRMUyBDQSAyMDIyIFEy", + "whitelist": false, + "attachment": { + "hash": "9e54420f95d8b0d4b05a893351b8e6f95b49a2c2612de88f06c664a4ecbcc9f6", + "size": 2333, + "filename": "ON2DwFIlFOjfxxypUz67Q5zAjbgrWKZXardbOwMvZO0=.pem", + "location": "security-state-staging/intermediates/1b578794-2e86-466b-ab76-1363531a895f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ON2DwFIlFOjfxxypUz67Q5zAjbgrWKZXardbOwMvZO0=", + "crlite_enrolled": false, + "id": "cb9ed798-b5dc-4b00-a04a-35e7aa09dbaf", + "last_modified": 1643295452249 + }, + { + "schema": 1643294913157, + "derHash": "1Qsgrf+Vm/vug7b9H6s2/eeUG5YM/Gg5qsY7n/20ZBM=", + "subject": "CN=GlobalSign Atlas R3 OV ACME CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEF0bGFzIFIzIE9WIEFDTUUgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "ca2cf072716f4c79ae3916330d287ad6fea3d8984d918f95052fe8c6d0aaa6ee", + "size": 1642, + "filename": "Fk25d1IaTcCVLP9zGx1VE9HFf50FhmOmciXIemB6lHQ=.pem", + "location": "security-state-staging/intermediates/a6c94dd1-537a-4106-9e35-fb9765ebe6df.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Fk25d1IaTcCVLP9zGx1VE9HFf50FhmOmciXIemB6lHQ=", + "crlite_enrolled": false, + "id": "ee6d035c-95ae-42a3-b398-340dd0c12120", + "last_modified": 1643295452235 + }, + { + "schema": 1643294916739, + "derHash": "yyxoXLIU3NLyzabiwVWq8hp+E0vAvOgIsRMMRZElsKQ=", + "subject": "CN=GlobalSign Atlas ECCR5 OV ACME CA 2022 Q2,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IE9WIEFDTUUgQ0EgMjAyMiBRMg==", + "whitelist": false, + "attachment": { + "hash": "938c89df07ebbcff14c56a18af20e3435a15ee5e41ae3abd34d078ee509d86f5", + "size": 1199, + "filename": "YTUzOLdwtiCZXE8AOPs1b6YbDa1p_ErT0xlZU3752XE=.pem", + "location": "security-state-staging/intermediates/68f7dd98-a21e-4de8-abb3-25a74c264184.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YTUzOLdwtiCZXE8AOPs1b6YbDa1p/ErT0xlZU3752XE=", + "crlite_enrolled": false, + "id": "e78f8c36-89b2-4ab8-9fb9-ed1cae795020", + "last_modified": 1643295452213 + }, + { + "schema": 1642150113750, + "derHash": "fPY09fr+ndzIizbWsefr+3B4A01kQZ58Z4M15W2CNCE=", + "subject": "CN=ZoTrus ECC DV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MFAxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMR0wGwYDVQQDExRab1RydXMgRUNDIERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "2478c889eb6fd2954ab5bb268bc6639e67f315ac0621c0695583cc0c6caa344c", + "size": 1244, + "filename": "ty8G1Vg0YP5ko08pK-aWhIrXQdPC95ObPQr5ckgmEno=.pem", + "location": "security-state-staging/intermediates/3a2aa500-087d-4576-a6e9-da5e391c759b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ty8G1Vg0YP5ko08pK+aWhIrXQdPC95ObPQr5ckgmEno=", + "crlite_enrolled": false, + "id": "8bc65e46-2eae-43bc-b059-c677433625a0", + "last_modified": 1642150669352 + }, + { + "schema": 1642150116124, + "derHash": "UlHipbOkICFmeZSwThlbaTubcbdSJnxE3DEKd5eb3AA=", + "subject": "CN=TrustAsia ECC DV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgRUNDIERWIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "ef83575c41120b60f74f583dfeb3da8a58c633b2f7fd2609edc7ca2403e253f9", + "size": 1353, + "filename": "VrZMiMWiKevkPo7Pv59P656TyE_PSgpqiVVIB-7pg9I=.pem", + "location": "security-state-staging/intermediates/a62357f0-6fdd-4028-9bf2-edb9b6c79244.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VrZMiMWiKevkPo7Pv59P656TyE/PSgpqiVVIB+7pg9I=", + "crlite_enrolled": false, + "id": "2570b685-e374-4db8-836b-fdbf67f0af9c", + "last_modified": 1642150669338 + }, + { + "schema": 1642150117328, + "derHash": "OnOeGQJiY1LDM36U2yB1mYxFbPZclfWpYysS0LfIncs=", + "subject": "CN=TrustAsia RSA DV TLS CA G2,O=TrustAsia Technologies\\, Inc.,C=CN", + "subjectDN": "MFkxCzAJBgNVBAYTAkNOMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSMwIQYDVQQDExpUcnVzdEFzaWEgUlNBIERWIFRMUyBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "ae6e2416ecaeea3d18fa9688879ae11935097c97642e8a4ed93678d5d6f51ec1", + "size": 1804, + "filename": "vPjNqvoQM9P5euex2cOxX4mZokDnoELI4_2mE8DY0iU=.pem", + "location": "security-state-staging/intermediates/e09dd7dc-4cc9-4aaf-a0d1-589630552f3a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vPjNqvoQM9P5euex2cOxX4mZokDnoELI4/2mE8DY0iU=", + "crlite_enrolled": false, + "id": "20a5012f-1aaf-4f01-9c9a-30eba2f9bd37", + "last_modified": 1642150669331 + }, + { + "schema": 1642104536736, + "derHash": "v5fbdcE2Ffr8bR517cP+MxxJgGlppag9eFltGhAGr/k=", + "subject": "CN=JoySSL Domain Secure Server CA,O=JoySSL Limited,C=CN", + "subjectDN": "ME8xCzAJBgNVBAYTAkNOMRcwFQYDVQQKEw5Kb3lTU0wgTGltaXRlZDEnMCUGA1UEAxMeSm95U1NMIERvbWFpbiBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "f1b1199040361930a784dd1efd2681ebd2512cc6f3b159510434831d22a91b45", + "size": 2255, + "filename": "rckLDvTLjlhstKutXzLaJdPwzZLuajgRn624tYevvEA=.pem", + "location": "security-state-staging/intermediates/dbd50095-5444-4d96-82dc-7f55c00e9bb8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rckLDvTLjlhstKutXzLaJdPwzZLuajgRn624tYevvEA=", + "crlite_enrolled": false, + "id": "ce70ab41-709c-445f-b102-7cc44cac2d22", + "last_modified": 1642107514816 + }, + { + "schema": 1640873410856, + "derHash": "fsrKSjWFo7QOJVdEFVEtVrV5mbdTAXhW8qsV+h8h9tA=", + "subject": "CN=NETLOCK Trust Qualified EV CA 3,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MGExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMSgwJgYDVQQDDB9ORVRMT0NLIFRydXN0IFF1YWxpZmllZCBFViBDQSAz", + "whitelist": false, + "attachment": { + "hash": "5c9cd98f81f0d60b8f3c40b8be94209d25226eb7ebf1c126947da32ed3d21dbc", + "size": 2215, + "filename": "ksEtUEbADR0V5fQig7YPrrgGcr2PovgbGzQx045WU_0=.pem", + "location": "security-state-staging/intermediates/be90d242-60d9-4a6b-aefe-8e8fb342b23e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ksEtUEbADR0V5fQig7YPrrgGcr2PovgbGzQx045WU/0=", + "crlite_enrolled": false, + "id": "bf7893da-0baf-4aae-8bfc-282641a4dcff", + "last_modified": 1640876333340 + }, + { + "schema": 1640269085632, + "derHash": "+RYG0bxSxhATbKqFarUAxIw7mTusSAjNgrxLeKvyQVY=", + "subject": "CN=NETLOCK DVSSL CA,OU=Tanúsítványkiadók (Certification Services),O=NETLOCK Kft.,L=Budapest,C=HU", + "subjectDN": "MIGLMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5FVExPQ0sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTEZMBcGA1UEAwwQTkVUTE9DSyBEVlNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "28fe966e32eb004f5c288e09eb400e8b6dd866ad8cee74b7e0ad399c7033fc15", + "size": 2446, + "filename": "Ss_LpeQiFGYRawte4JwIYIKszuA0KoeTtkpsRkiLCaw=.pem", + "location": "security-state-staging/intermediates/90489a16-17cf-4e28-83e7-e422bb5f8b09.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ss/LpeQiFGYRawte4JwIYIKszuA0KoeTtkpsRkiLCaw=", + "crlite_enrolled": false, + "id": "e7bd6d4d-f645-4abb-b632-af28f5ac9c61", + "last_modified": 1640293101518 + }, + { + "schema": 1639404982010, + "derHash": "Ce1umR/DJz2P6jF9M5wCBBhhlzVJz6bhVY9BHxEhGqM=", + "subject": "SERIALNUMBER=07969287,CN=Go Daddy Secure Certification Authority,OU=http://certificates.godaddy.com/repository,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4Nw==", + "whitelist": false, + "attachment": { + "hash": "3336e31997f16a6b0c8e3b2ca5641682721662f5ee9f648956959daaf8b8ab8d", + "size": 1748, + "filename": "MrZLZnJ6IGPkBm87lYywqu5Xal7O_ZUzmbuIdHMdlYc=.pem", + "location": "security-state-staging/intermediates/9d9449dc-fc0a-41b3-8e29-e1185a4d4526.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "MrZLZnJ6IGPkBm87lYywqu5Xal7O/ZUzmbuIdHMdlYc=", + "crlite_enrolled": false, + "id": "dbfd9482-8c71-4e1a-b80a-37fbb3beee31", + "last_modified": 1639407469076 + }, + { + "schema": 1637593069231, + "derHash": "sKbvA1DnxMYFa+6nr50thgue0QITe5cp08IyFtGVVGo=", + "subject": "CN=Advanced Class 3 e-Szigno CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGFMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xKjAoBgNVBAMMIUFkdmFuY2VkIENsYXNzIDMgZS1Temlnbm8gQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "0ed62164b86f37c69eb2018a4a79db046f2eae11bfb1c748569dff5da6e94178", + "size": 1768, + "filename": "VGt-HUDy6qey1Up5ZYyfXaLC_1Yy9l4AvJsHFIQXC5g=.pem", + "location": "security-state-staging/intermediates/912a52c5-97ea-4353-806f-dbf6b9c95e81.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VGt+HUDy6qey1Up5ZYyfXaLC/1Yy9l4AvJsHFIQXC5g=", + "crlite_enrolled": false, + "id": "1d065829-106f-4cf5-a7ce-33874e651514", + "last_modified": 1637614674594 + }, + { + "schema": 1637331030153, + "derHash": "8A5ha1ntBubMlxfQOfehpwyz0I4LatdGU2cMzkSMYfM=", + "subject": "CN=TeleSec Business TLS-CA 21,O=Deutsche Telekom Security GmbH,C=DE", + "subjectDN": "MFsxCzAJBgNVBAYTAkRFMScwJQYDVQQKEx5EZXV0c2NoZSBUZWxla29tIFNlY3VyaXR5IEdtYkgxIzAhBgNVBAMTGlRlbGVTZWMgQnVzaW5lc3MgVExTLUNBIDIx", + "whitelist": false, + "attachment": { + "hash": "5af51fb639abc7ecb3215b9fc5e15ae466f544ef15726b5ceaa0ab6da9b6d935", + "size": 2093, + "filename": "HwN-XWTswr50M_-6ArVXfcQin_00zYvWNeebDkP2YVI=.pem", + "location": "security-state-staging/intermediates/bf2aacbd-643a-4dc2-8720-b5f2241d2ad8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HwN+XWTswr50M/+6ArVXfcQin/00zYvWNeebDkP2YVI=", + "crlite_enrolled": false, + "id": "087d2225-53d6-4103-b228-7a7c44753024", + "last_modified": 1637333968854 + }, + { + "schema": 1635365174803, + "derHash": "GQmO/5wMtG4fBq363R89pXEjcPcXMzRZgTfUpVX/8Ec=", + "subject": "CN=E-SAFER DOMAIN SSL CA [Run by the Issuer],O=E-SAFER CONSULTORIA EM TECNOLOGIA DA INFORMACAO LTDA,C=BR", + "subjectDN": "MIGBMQswCQYDVQQGEwJCUjE9MDsGA1UEChM0RS1TQUZFUiBDT05TVUxUT1JJQSBFTSBURUNOT0xPR0lBIERBIElORk9STUFDQU8gTFREQTEzMDEGA1UEAwwqRS1TQUZFUiBET01BSU4gU1NMIENBICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "f72dd7cfe6c5d1436f5ded83580233c4748b1211156a5ee1acaaaa3bf4364949", + "size": 2324, + "filename": "1F-ircopaMOchYXl140IHR8UWAyuzQ40XNQ5j6A1Elo=.pem", + "location": "security-state-staging/intermediates/c0f1518f-d641-458f-8832-3dbac6f1eee7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1F+ircopaMOchYXl140IHR8UWAyuzQ40XNQ5j6A1Elo=", + "crlite_enrolled": false, + "id": "6d3efd26-5824-4230-8ccb-a238dca1217c", + "last_modified": 1635368388860 + }, + { + "schema": 1632967620638, + "derHash": "LZI/Z5gNpTqtXl5qYbqtn8ZF3SCNdsUcjtpcc4cKj5U=", + "subject": "CN=HARICA IV TLS ECC,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgSVYgVExTIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "00d2252c9529174634eed8cb60beef208d20814fca28520fba0eb05ddd227f70", + "size": 1256, + "filename": "tL8Y2fwIAhj0R31-s_hki_z_ctYJHccMgLZfqIQM0CI=.pem", + "location": "security-state-staging/intermediates/3ecf4f89-c083-40b0-955a-2966b7461b33.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tL8Y2fwIAhj0R31+s/hki/z/ctYJHccMgLZfqIQM0CI=", + "crlite_enrolled": false, + "id": "c88e7fe3-0379-4ebd-b050-1fa97b739596", + "last_modified": 1632970665996 + }, + { + "schema": 1632967622478, + "derHash": "MpNQDKpQex6SCkQfJ3uty7dQAsrsYoLSOjV494F9I4A=", + "subject": "CN=HARICA OV TLS ECC,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgT1YgVExTIEVDQw==", + "whitelist": false, + "attachment": { + "hash": "349e3f3e2135841b41e416f2f4af5854cd9b7ccce850ff07805ac43781e64616", + "size": 1256, + "filename": "5oO62_OtbnQ73vXVjYuf4N5xy-kgb1av8L5LWnAiefA=.pem", + "location": "security-state-staging/intermediates/4707fc72-bf42-4ca1-b92f-0e6c478b196a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5oO62/OtbnQ73vXVjYuf4N5xy+kgb1av8L5LWnAiefA=", + "crlite_enrolled": false, + "id": "587d38e2-58b3-4aad-befd-718833edd424", + "last_modified": 1632970665982 + }, + { + "schema": 1632967633283, + "derHash": "Hfkp2Qs61O+U02QC9INGLoy2eElcaHAiQjVFsZ4vBhU=", + "subject": "CN=HARICA IV TLS RSA,O=Hellenic Academic and Research Institutions CA,C=GR", + "subjectDN": "MGIxCzAJBgNVBAYTAkdSMTcwNQYDVQQKDC5IZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENBMRowGAYDVQQDDBFIQVJJQ0EgSVYgVExTIFJTQQ==", + "whitelist": false, + "attachment": { + "hash": "36bf996abce0e066fdedcbe8635e42555398206a9619333f9ef1b1e2c5478612", + "size": 2402, + "filename": "b8iqQGTynaLqy8XYkFisZ8z92o1_wHbyYf--7549HBE=.pem", + "location": "security-state-staging/intermediates/150ca073-76cf-4779-912a-29aab7ad3258.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "b8iqQGTynaLqy8XYkFisZ8z92o1/wHbyYf++7549HBE=", + "crlite_enrolled": false, + "id": "d7e4e53c-b5ce-4085-a212-1963e0caadab", + "last_modified": 1632970665899 + }, + { + "schema": 1631843450151, + "derHash": "Csn+hDix/i/GdkFtyXauj6qXe8xIXXymn60sfr/c6ls=", + "subject": "CN=ZoTrus DV SSL CA,O=ZoTrus Technology Limited,C=CN", + "subjectDN": "MEwxCzAJBgNVBAYTAkNOMSIwIAYDVQQKExlab1RydXMgVGVjaG5vbG9neSBMaW1pdGVkMRkwFwYDVQQDExBab1RydXMgRFYgU1NMIENB", + "whitelist": false, + "attachment": { + "hash": "d76abc5296eb06c65a415a434addf2de0cb065a21dbd0bd058900169863ac73e", + "size": 2251, + "filename": "7Ch7fDEL8Gdzq9P5PwDnOhxhxPHVj8AhlI7w1ICxteY=.pem", + "location": "security-state-staging/intermediates/480f0b48-c49a-4b19-93eb-7f0933ddb244.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7Ch7fDEL8Gdzq9P5PwDnOhxhxPHVj8AhlI7w1ICxteY=", + "crlite_enrolled": false, + "id": "ff96a466-fcd2-4551-a6ae-272651431f27", + "last_modified": 1631843845716 + }, + { + "schema": 1631843455899, + "derHash": "riEQ8jVKnbDFOqbpE5+vDdLrAYZaZnETXG/2LZ84SKA=", + "subject": "CN=CerSign DV SSL CA,O=CerSign Technology Limited,C=CN", + "subjectDN": "ME4xCzAJBgNVBAYTAkNOMSMwIQYDVQQKExpDZXJTaWduIFRlY2hub2xvZ3kgTGltaXRlZDEaMBgGA1UEAxMRQ2VyU2lnbiBEViBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "791c714efab84a0bcbf79655c9b9ff8ddd4cf4af29e8512a4f42f9f66f2a0610", + "size": 2255, + "filename": "Np0gVjrbABQaLmtqjQpVANm_x30TnOSHNVe2-OUxsLE=.pem", + "location": "security-state-staging/intermediates/1f78a7ed-7472-4028-9a40-787b5dece0de.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Np0gVjrbABQaLmtqjQpVANm/x30TnOSHNVe2+OUxsLE=", + "crlite_enrolled": false, + "id": "7903c85c-2387-43c5-b211-0b3bcf9c38e8", + "last_modified": 1631843845654 + }, + { + "schema": 1628258299195, + "derHash": "TlZm2sV5FhzwC42HBG0HTWycDA45lMZTvleZhzbFXZM=", + "subject": "CN=SwissSign RSA TLS Root CA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0EgMjAyMSAtIDE=", + "whitelist": false, + "attachment": { + "hash": "042c8b8edc66e748609524d1216e11e829cb1ea83b1e8436f4780f77b2f587c8", + "size": 2324, + "filename": "nUaOHm7QsD6Xagrjxl1hPu2HFDpXiamEvjDZ1IoWx24=.pem", + "location": "security-state-staging/intermediates/d9318494-fb6c-4bb5-b608-ffc24fc3cf5c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nUaOHm7QsD6Xagrjxl1hPu2HFDpXiamEvjDZ1IoWx24=", + "crlite_enrolled": false, + "id": "4dbd71fd-d75e-46bf-98be-d2a76863a40a", + "last_modified": 1629359842438 + }, + { + "schema": 1628258300820, + "derHash": "vIu9fSedLl8HC872+vOqsb7zDaPrKHVCQpWtFH8q7wc=", + "subject": "CN=SwissSign RSA SMIME Root CA 2021 - 1,O=SwissSign AG,C=CH", + "subjectDN": "MFMxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxLTArBgNVBAMTJFN3aXNzU2lnbiBSU0EgU01JTUUgUm9vdCBDQSAyMDIxIC0gMQ==", + "whitelist": false, + "attachment": { + "hash": "1f319976c1c0fd2ff4a52e8fac41cf199e02731a70087681607fe7b3133160c8", + "size": 2328, + "filename": "HKpRHDLoh8f2rEFEWyjA0ZeopKTbs4zV87Xess-HD4U=.pem", + "location": "security-state-staging/intermediates/22d80059-836c-4a22-9631-c2432c02e90c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HKpRHDLoh8f2rEFEWyjA0ZeopKTbs4zV87Xess+HD4U=", + "crlite_enrolled": false, + "id": "6bdf31ea-f0ff-4951-b310-0d3b28fd83d7", + "last_modified": 1629359842425 + }, + { + "schema": 1625125794637, + "derHash": "TVW8Sr6303+rV+VzrM6DEz42ISyGTgA/vLMLX8JIsBE=", + "subject": "CN=Microsoft RSA TLS Issuing EOC CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBSU0EgVExTIElzc3VpbmcgRU9DIENBIDAy", + "whitelist": false, + "attachment": { + "hash": "d57baf154afa36ed4b0f4fb14618c80d947893cbd9d458210424c8c00f5ebb0c", + "size": 2694, + "filename": "ieHvm9iTiA_KTC0Lpx7JPa9sCsFNWpmW8-Fvl2OZUHY=.pem", + "location": "security-state-staging/intermediates/7e97c268-63b0-4c44-9d28-da62cd929110.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ieHvm9iTiA/KTC0Lpx7JPa9sCsFNWpmW8+Fvl2OZUHY=", + "crlite_enrolled": false, + "id": "6b217478-b4de-451e-93a7-43d8d82c6c85", + "last_modified": 1625126256108 + }, + { + "schema": 1625125798020, + "derHash": "J2k4FTLZYYPtOb3E4yPzxSD75qzzvaMCIiOd38RMg4A=", + "subject": "CN=Microsoft ECC TLS Issuing EOC CA 01,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBFQ0MgVExTIElzc3VpbmcgRU9DIENBIDAx", + "whitelist": false, + "attachment": { + "hash": "a97f40932d6603d11dbedf200e53c32a7cfedaf59bbaf89917f9087a501ad759", + "size": 1544, + "filename": "7eFS2gwZ1sO7MWjRHkCQGRlka7aZjNXn0PvmU3__Qu8=.pem", + "location": "security-state-staging/intermediates/d4ca2b02-1cae-4163-8ebd-abb85f3e6a98.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7eFS2gwZ1sO7MWjRHkCQGRlka7aZjNXn0PvmU3//Qu8=", + "crlite_enrolled": false, + "id": "51f69875-c2c0-46d8-960d-8d496d28a655", + "last_modified": 1625126256083 + }, + { + "schema": 1625125801402, + "derHash": "13xFwVh3McRjLBnW88n+gyYmYVyHnqBTZkpLJusik+w=", + "subject": "CN=Microsoft RSA TLS Issuing AOC CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBSU0EgVExTIElzc3VpbmcgQU9DIENBIDAy", + "whitelist": false, + "attachment": { + "hash": "35e057717bd4761ffdeff57a4043dd5ed4e8f416db839dc0d81069f2b708e825", + "size": 2694, + "filename": "8ZoXa8d5x3YJQn9EG5lfFMq5oZ2Hzd2Scl5Jl8sIeLw=.pem", + "location": "security-state-staging/intermediates/bd898e6a-b3a3-4df7-834b-91aad8bfe687.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8ZoXa8d5x3YJQn9EG5lfFMq5oZ2Hzd2Scl5Jl8sIeLw=", + "crlite_enrolled": false, + "id": "9f93d3c1-efd6-462d-8664-618112f7aebf", + "last_modified": 1625126256057 + }, + { + "schema": 1625125803064, + "derHash": "ZZwPkC1gWfvR/KUog58gYEuAx0Nk5Y+dSKIpH4E+2C0=", + "subject": "CN=Microsoft ECC TLS Issuing EOC CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBFQ0MgVExTIElzc3VpbmcgRU9DIENBIDAy", + "whitelist": false, + "attachment": { + "hash": "eb29e40059012bccd1c1ef7c1449e3d635ce8dc6735cf792f6fea49cc563ec40", + "size": 1544, + "filename": "Ic1H4yALLX8TVn355pjsIaysd6j0oqEXP06rI7IEiWc=.pem", + "location": "security-state-staging/intermediates/5f6a6527-07c8-4227-92d9-929add05a883.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ic1H4yALLX8TVn355pjsIaysd6j0oqEXP06rI7IEiWc=", + "crlite_enrolled": false, + "id": "9681ce48-0dab-45a2-a78b-96fb8c2fe419", + "last_modified": 1625126256045 + }, + { + "schema": 1625125804722, + "derHash": "gIyhq7/i/xqaxxiH3acf9vymw7UiSCf1R1FaTZ968gk=", + "subject": "CN=Microsoft ECC TLS Issuing AOC CA 02,O=Microsoft Corporation,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLDAqBgNVBAMTI01pY3Jvc29mdCBFQ0MgVExTIElzc3VpbmcgQU9DIENBIDAy", + "whitelist": false, + "attachment": { + "hash": "cd261718c0ebaf93763c6cda8544a7e22801f419ed94181a8748e2dfa491b07f", + "size": 1544, + "filename": "hfop63QNkM537dvjq2xmpd4_yLqU1rKVX7fTOwQjFgE=.pem", + "location": "security-state-staging/intermediates/ae714381-141e-48ae-9257-6a2c7eee8243.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hfop63QNkM537dvjq2xmpd4/yLqU1rKVX7fTOwQjFgE=", + "crlite_enrolled": false, + "id": "cf6c1713-2260-42d1-b0f5-620ff1a5b92f", + "last_modified": 1625126256032 + }, + { + "schema": 1624913397315, + "derHash": "ZeuXLOtp6EowRGoqrYs/efDoBEJwvfoum2rl0q1VGJ4=", + "subject": "CN=Verokey Secure Site,O=Verokey,C=AU", + "subjectDN": "MD0xCzAJBgNVBAYTAkFVMRAwDgYDVQQKEwdWZXJva2V5MRwwGgYDVQQDExNWZXJva2V5IFNlY3VyZSBTaXRl", + "whitelist": false, + "attachment": { + "hash": "f7fa8bdd729bb1ee03edcf9a3f96ed1ebd3e23f6eb6aa1cad595e80503ff42fa", + "size": 2231, + "filename": "IRk62dBIyX2WPASLM4Hdh70WaoQeIh7ci7tKmRnWsmQ=.pem", + "location": "security-state-staging/intermediates/f53df78f-fd8d-4bae-bae6-46265fb79b23.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IRk62dBIyX2WPASLM4Hdh70WaoQeIh7ci7tKmRnWsmQ=", + "crlite_enrolled": false, + "id": "f10e152b-912b-41b0-8b22-172bab5325a5", + "last_modified": 1624913855021 + }, + { + "schema": 1623916924551, + "derHash": "aRbakW7hsemMsx7pRzDuzX8sB3llvbjU4Wo7gcWKKbY=", + "subject": "CN=GlobalSign Atlas ECCR5 DV TLS CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIFRMUyBDQSBIMiAyMDIx", + "whitelist": false, + "attachment": { + "hash": "abdf25b3a11dd96af2217032fe2da76890e22dc7b40e8981301a8971d97f32a9", + "size": 1268, + "filename": "6-g1TUJXD0WhY01dlTAcFTi6R1z-L4XP4pPgJVUTHfA=.pem", + "location": "security-state-staging/intermediates/2ff02552-6c42-4558-bb97-ca531ec69f7d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6+g1TUJXD0WhY01dlTAcFTi6R1z+L4XP4pPgJVUTHfA=", + "crlite_enrolled": false, + "id": "7760e5f2-11cf-4582-8639-e223f057cafa", + "last_modified": 1623920278097 + }, + { + "schema": 1623916935947, + "derHash": "YbMM6uoMjGOXw1mYKYCctzyo+Be7nN8ADXRT5EJDgCg=", + "subject": "CN=GlobalSign Atlas ECCR5 DV ACME CA H2 2021,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYDVQQDEylHbG9iYWxTaWduIEF0bGFzIEVDQ1I1IERWIEFDTUUgQ0EgSDIgMjAyMQ==", + "whitelist": false, + "attachment": { + "hash": "edb2ab3c9beabe368a1a743918ee6b2a8507da3e116b951154f6dcbac67de73c", + "size": 1272, + "filename": "N3T01UrP_szopZ--ULwPC9MuquTStnfM28bhLKuZ1rY=.pem", + "location": "security-state-staging/intermediates/8859905c-00e5-4d2a-b3f0-e00fa03ce62c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "N3T01UrP/szopZ++ULwPC9MuquTStnfM28bhLKuZ1rY=", + "crlite_enrolled": false, + "id": "bbc79d7d-1abd-4027-8cae-092d0491fd43", + "last_modified": 1623920278014 + }, + { + "schema": 1622555926542, + "derHash": "COfqyZimLEFVzEy8Xtoy9bQaEsAS8pqzQzvTZjSBSfA=", + "subject": "CN=Certum Trusted Network CA 2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "e0785e431e6c3051033a56f1edb8202c50396095a309bb8a6576ac654828f02a", + "size": 2064, + "filename": "aztX6eyI0bs9AWN_8zx2mLPJdYJV6fAeqRePPn87K1I=.pem", + "location": "security-state-staging/intermediates/2314351a-e3d4-490a-9af7-00dedbc5441d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aztX6eyI0bs9AWN/8zx2mLPJdYJV6fAeqRePPn87K1I=", + "crlite_enrolled": false, + "id": "44261107-8d3f-48ac-8a0d-8b9ef00b4189", + "last_modified": 1622559454515 + }, + { + "schema": 1618073380961, + "derHash": "VoAP2g1FLbopchlP9t5yFH0DeIh8YCSDMLuAF5XGh8Y=", + "subject": "CN=Omit Security ECC Domain Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxLzAtBgNVBAMTJk9taXQgU2VjdXJpdHkgRUNDIERvbWFpbiBWYWxpZGF0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "fcc8e935f2a14b2ba1fb3d39cb5e4a7c67dd965149305c2f06cc53978f61da33", + "size": 1252, + "filename": "feNlXJvY8VyoEBoaAWw5dogzI8DXuIN8E9xWmKCPLKc=.pem", + "location": "security-state-staging/intermediates/e9b3e5ee-f4ff-49fd-b9c6-01ec78d0904e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "feNlXJvY8VyoEBoaAWw5dogzI8DXuIN8E9xWmKCPLKc=", + "crlite_enrolled": false, + "id": "bd195bca-e43c-4f14-a906-3c1670c0c941", + "last_modified": 1618102648589 + }, + { + "schema": 1618073382598, + "derHash": "Wqv/7NSqrZ+siqXrUwG1vrFxDIZUFqJHxz21La1fskw=", + "subject": "CN=Omit Security RSA Domain Validation CA,O=Omit Security\\, Inc,C=US", + "subjectDN": "MFsxCzAJBgNVBAYTAlVTMRswGQYDVQQKExJPbWl0IFNlY3VyaXR5LCBJbmMxLzAtBgNVBAMTJk9taXQgU2VjdXJpdHkgUlNBIERvbWFpbiBWYWxpZGF0aW9uIENB", + "whitelist": false, + "attachment": { + "hash": "1f3556a6d93934a376d4070ca15ceb8e808ec1b85c97a0f1d1a15d90d7b38dce", + "size": 2259, + "filename": "XNJJXG_EeRn6g5ZLs9jqKelLBQyfGqytz-2dNmGP2VE=.pem", + "location": "security-state-staging/intermediates/bfd5cec4-42a7-4b3b-b908-8fee3edce79d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XNJJXG/EeRn6g5ZLs9jqKelLBQyfGqytz+2dNmGP2VE=", + "crlite_enrolled": false, + "id": "5cb35bb9-7702-4174-8419-37382a502bc9", + "last_modified": 1618102648566 + }, + { + "schema": 1616744987712, + "derHash": "Grxa1bw5EaW0qR5Cu6MhLiqj3IAUfv4dSVcuS+BTJjM=", + "subject": "CN=GlobalSign Atlas R3 AlphaSSL CA H1 2021,O=Globalsign nv-sa,C=BE", + "subjectDN": "MFoxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxzaWduIG52LXNhMTAwLgYDVQQDEydHbG9iYWxTaWduIEF0bGFzIFIzIEFscGhhU1NMIENBIEgxIDIwMjE=", + "whitelist": false, + "attachment": { + "hash": "0ac4293f335c1f7922726fccc78a0a749002116e0d6d354411046b3681c14fe0", + "size": 1715, + "filename": "HAdiOrIGPG14XkymnmeQ184Cm1Z8E9-jnOoBut3PORw=.pem", + "location": "security-state-staging/intermediates/a79140b4-b597-4021-b12a-e83c7b03a7e0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HAdiOrIGPG14XkymnmeQ184Cm1Z8E9+jnOoBut3PORw=", + "crlite_enrolled": false, + "id": "afbdb701-e59b-4398-967c-b0db2394d003", + "last_modified": 1616745549953 + }, + { + "schema": 1615384231447, + "derHash": "sQhhc3s+ybEfphVNI5cP+y2r/Ciuamv1bD8yBCZiOa0=", + "subject": "CN=SECOM Passport for Member PUB CA4,OU=SECOM Passport for Member 2.0 PUB,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MIGMMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU0VDT00gUGFzc3BvcnQgZm9yIE1lbWJlciAyLjAgUFVCMSowKAYDVQQDEyFTRUNPTSBQYXNzcG9ydCBmb3IgTWVtYmVyIFBVQiBDQTQ=", + "whitelist": false, + "attachment": { + "hash": "100ce60a3bd64526f68ff84f5c51cf0fabb69da5d8bf4709a02d9b8381cf053e", + "size": 1695, + "filename": "e0UlwaDksSng0NsuhQLDMdL3HdRLRk89Csk7QJK7Xm4=.pem", + "location": "security-state-staging/intermediates/5fe7d18d-a5f7-47cc-b17d-78176befba8d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e0UlwaDksSng0NsuhQLDMdL3HdRLRk89Csk7QJK7Xm4=", + "crlite_enrolled": false, + "id": "43cd6bcc-8fa9-4afc-8ef7-d30357eb5641", + "last_modified": 1615384713896 + }, + { + "schema": 1614908979131, + "derHash": "hjRy7HnbVAdxFNrecgzh97QaIzdI/Gdz+oZSDwuaqiE=", + "subject": "CN=HydrantID EV SSL CA D1,O=Avalanche Cloud Corporation,C=US", + "subjectDN": "MFQxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtBdmFsYW5jaGUgQ2xvdWQgQ29ycG9yYXRpb24xHzAdBgNVBAMTFkh5ZHJhbnRJRCBFViBTU0wgQ0EgRDE=", + "whitelist": false, + "attachment": { + "hash": "85a1c4db9d6bb83a0f69e06665699f762150e6f99f95c205c71a9903ebea6183", + "size": 1731, + "filename": "2Y6wMMYpuZSX8MzksrYWB1EKmU_UzVKarvAw_4VqjJc=.pem", + "location": "security-state-staging/intermediates/1d482bb3-92dc-4ec1-82a7-486a44d193d0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2Y6wMMYpuZSX8MzksrYWB1EKmU/UzVKarvAw/4VqjJc=", + "crlite_enrolled": false, + "id": "8cef2110-60e3-4274-b66f-73184cf70e37", + "last_modified": 1614909446623 + }, + { + "schema": 1614390578814, + "derHash": "U2dpLs5it1jQTZt+bfsNswf4WevGpstfd/8kVh18wAQ=", + "subject": "CN=USERTrust RSA Extended Validation Secure Server CA,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGVMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazE7MDkGA1UEAxMyVVNFUlRydXN0IFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "e824527655464dd31a8c05c4d0472f598ceba1ccfa4c8b6f1f82aacae141213f", + "size": 2174, + "filename": "8E_u4IJLStWPqhVEn4td_2Ae6WYCii1AmJC2dgTCj1s=.pem", + "location": "security-state-staging/intermediates/81cfbb36-b77a-44c7-b9d5-e2c1a3bdf3fb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8E/u4IJLStWPqhVEn4td/2Ae6WYCii1AmJC2dgTCj1s=", + "crlite_enrolled": false, + "id": "e80e13e0-9ae0-4852-86c7-1c266fe0906a", + "last_modified": 1614391116607 + }, + { + "schema": 1611280168224, + "derHash": "JpesuWpJviv8XAbDk5SfDY51EGrW1Mc+LaFyqQgxqFo=", + "subject": "CN=Abbott Laboratories Secure Authentication CA G2,O=Abbott Laboratories Inc.,C=US", + "subjectDN": "MGoxCzAJBgNVBAYTAlVTMSEwHwYDVQQKExhBYmJvdHQgTGFib3JhdG9yaWVzIEluYy4xODA2BgNVBAMTL0FiYm90dCBMYWJvcmF0b3JpZXMgU2VjdXJlIEF1dGhlbnRpY2F0aW9uIENBIEcy", + "whitelist": false, + "attachment": { + "hash": "2dd82d3bd0accbba61c96af89b612f8a1d398765d793f42607d3afc8b8dad683", + "size": 1723, + "filename": "bzDvo6S9sfCilsvuF3s9e-vz0NDErb57SOX9rMVGnlE=.pem", + "location": "security-state-staging/intermediates/7ab12434-0d0c-4347-822a-1a018c7369ba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bzDvo6S9sfCilsvuF3s9e+vz0NDErb57SOX9rMVGnlE=", + "crlite_enrolled": false, + "id": "88d5e263-ca11-45bf-82b8-fcdbe867c000", + "last_modified": 1611280673595 + }, + { + "schema": 1610999377196, + "derHash": "VKdoR9KTrLj6HYhd5LtEmVa3UJL5Yj+kNEGrRF3Vcoo=", + "subject": "CN=sslTrus (ECC) DV CA,O=sslTrus,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMRAwDgYDVQQKEwdzc2xUcnVzMRwwGgYDVQQDExNzc2xUcnVzIChFQ0MpIERWIENB", + "whitelist": false, + "attachment": { + "hash": "d23a03f951a5dfe7bf99283e17aa1a0e3634eb69ae9022b26bb71e6aa6efe532", + "size": 1219, + "filename": "UMr9k5clLq6HrdNc5SEiok4r2rONjYgWHW_eisDP7Gg=.pem", + "location": "security-state-staging/intermediates/1d61d630-620f-4687-a644-af28a0f2ddba.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "UMr9k5clLq6HrdNc5SEiok4r2rONjYgWHW/eisDP7Gg=", + "crlite_enrolled": false, + "id": "dde93a7d-dca5-41a4-be12-90191a32e81b", + "last_modified": 1610999846095 + }, + { + "schema": 1610999374114, + "derHash": "VGBJatScuCHWJ9rIqAUSmCK6skK5KaDjGV1wV2HR0Ww=", + "subject": "CN=sslTrus (RSA) DV CA,O=sslTrus,C=CN", + "subjectDN": "MD0xCzAJBgNVBAYTAkNOMRAwDgYDVQQKEwdzc2xUcnVzMRwwGgYDVQQDExNzc2xUcnVzIChSU0EpIERWIENB", + "whitelist": false, + "attachment": { + "hash": "641b2f28494debbb041a369cddea9252e11f7786dec7357bcdd5598f11cc6a1f", + "size": 2056, + "filename": "sz42Xvj0IDwSUHcevD4ns_3KOoE01yS63MA_HmP7uMM=.pem", + "location": "security-state-staging/intermediates/755b0ece-1ba5-476e-a318-239851989683.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sz42Xvj0IDwSUHcevD4ns/3KOoE01yS63MA/HmP7uMM=", + "crlite_enrolled": false, + "id": "2780ce74-15ac-4292-b126-97ed74d0137a", + "last_modified": 1610999846060 + }, + { + "schema": 1606678892869, + "derHash": "ROvwEj4n/x2wSXvS2uGBVbKkFOa82cbI+49IOYRJuek=", + "subject": "CN=TeleSec Business CA 1,OU=T-Systems Trust Center,O=T-Systems International GmbH,C=DE", + "subjectDN": "MHUxCzAJBgNVBAYTAkRFMSUwIwYDVQQKExxULVN5c3RlbXMgSW50ZXJuYXRpb25hbCBHbWJIMR8wHQYDVQQLExZULVN5c3RlbXMgVHJ1c3QgQ2VudGVyMR4wHAYDVQQDExVUZWxlU2VjIEJ1c2luZXNzIENBIDE=", + "whitelist": false, + "attachment": { + "hash": "5f456791aae0975819a267a4b01e2b4e0fcb12b375c5334b5a72562eefa58948", + "size": 1947, + "filename": "XmXOT9NXQoum_vwr876xEH0qCXmA3ZncBDyibtkXpQU=.pem", + "location": "security-state-staging/intermediates/085cace9-264b-4a9a-9330-f554e6585359.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "XmXOT9NXQoum/vwr876xEH0qCXmA3ZncBDyibtkXpQU=", + "crlite_enrolled": false, + "id": "eda28108-1b54-44e6-b8c5-1b6afcf10b67", + "last_modified": 1606744671982 + }, + { + "schema": 1605123695624, + "derHash": "yTGg+FoFKQEjSlT9Rg1BfxqLY8OMSBH3m5jzrhwv6fs=", + "subject": "CN=SECOM Passport for Member PUB CA5,OU=SECOM Passport for Member 2.0 PUB,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MIGMMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU0VDT00gUGFzc3BvcnQgZm9yIE1lbWJlciAyLjAgUFVCMSowKAYDVQQDEyFTRUNPTSBQYXNzcG9ydCBmb3IgTWVtYmVyIFBVQiBDQTU=", + "whitelist": false, + "attachment": { + "hash": "c65635be2bd0d20a2b0fd6d70bd227a546782c46ab1e3c6b2394239fc8cfd6cb", + "size": 1695, + "filename": "v3h5R_3OzMC-NBXKj05v4LQG5laFzE9fB4KsGpwAMtk=.pem", + "location": "security-state-staging/intermediates/de481d0f-d78a-4134-95c5-8978b12cffcd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v3h5R/3OzMC+NBXKj05v4LQG5laFzE9fB4KsGpwAMtk=", + "crlite_enrolled": false, + "id": "8cc1c1a9-82f5-425d-bc26-7033cc45e093", + "last_modified": 1605189460438 + }, + { + "schema": 1601473889131, + "derHash": "fOuUWEQnVvQKSFww8rrwAQfB6e6F2wL6APQbpYRDZOU=", + "subject": "CN=ISSAuth ECC DV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIEVDQyBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "43ee1efa5f0a207b149a67aaf0323e49197e5c769577b389eb3471c5539f8ad2", + "size": 1248, + "filename": "82GvRpB_uAZ2gBlFqbv5pwS1b52FWaLFTHSLLlPtkS4=.pem", + "location": "security-state-staging/intermediates/104f20f8-b19b-47ab-900b-8d1023c6ef8e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "82GvRpB/uAZ2gBlFqbv5pwS1b52FWaLFTHSLLlPtkS4=", + "crlite_enrolled": false, + "id": "5a3ba8e4-7aa8-42fe-b7d7-6a51a2cb824f", + "last_modified": 1601517444445 + }, + { + "schema": 1601473890650, + "derHash": "YkukJyyJbPvlUSj+vm5NbSSMI6HI2zLReJyBGHL2/Dw=", + "subject": "CN=ISSAuth RSA DV CA,O=INTEGRITY Security Services LLC,C=US", + "subjectDN": "MFMxCzAJBgNVBAYTAlVTMSgwJgYDVQQKEx9JTlRFR1JJVFkgU2VjdXJpdHkgU2VydmljZXMgTExDMRowGAYDVQQDExFJU1NBdXRoIFJTQSBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "c006295568921093e5148e77673f798172fdb1b6b28d3ac7737f3b5b35f7c661", + "size": 2085, + "filename": "3EfOHBtoOcqfHn9qHOGL-BkHEhOKUfa0ttPxLO6OaUY=.pem", + "location": "security-state-staging/intermediates/9a72162b-3c00-4079-9ce1-0b08c6e23202.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3EfOHBtoOcqfHn9qHOGL+BkHEhOKUfa0ttPxLO6OaUY=", + "crlite_enrolled": false, + "id": "db10a85d-fae6-4776-b901-d09000033eca", + "last_modified": 1601517444423 + }, + { + "schema": 1601376733088, + "derHash": "ZHFyUK+LAo3Y5cC65MkULIsQNTJhK8SHCF/Twxn5wGc=", + "subject": "CN=ePKI Root Certification Authority - G2,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MGMxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEvMC0GA1UEAwwmZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "73789188b1ff6dcd70442594b8ff119cb7b30d858c0e7f072cf8a014cd8f296a", + "size": 2649, + "filename": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=.pem", + "location": "security-state-staging/intermediates/3785b85c-da3b-4b7b-bd12-2796cce60c04.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=", + "crlite_enrolled": false, + "id": "ee033ab1-7bfc-4f08-b26a-b3b2bce86767", + "last_modified": 1601517444135 + }, + { + "schema": 1601376755836, + "derHash": "joxuv3fcc9s+OOk/SAPmK2tZM761HuQVL2jXqhRCazE=", + "subject": "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "3d68f4af65db5b779ba073b81f3674d2dd3df3323be8808d825116f4a0a129c3", + "size": 1557, + "filename": "YWFnIBQzrqbI5eMHCvyvZ0kYj4FL0auxea6NrTq_Juw=.pem", + "location": "security-state-staging/intermediates/ad34e608-081b-4ce8-83f8-80bf15e28c0e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YWFnIBQzrqbI5eMHCvyvZ0kYj4FL0auxea6NrTq/Juw=", + "crlite_enrolled": false, + "id": "35957622-8171-40c9-b5c6-f6630c77d128", + "last_modified": 1601517444025 + }, + { + "schema": 1601376747224, + "derHash": "uQ7q6THl4rfTNfFJ2mwiEJhgANIU/9tipy9zMtY3Ma8=", + "subject": "CN=Verizon Global Root CA,OU=OmniRoot,O=Verizon Business,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLEwhPbW5pUm9vdDEfMB0GA1UEAxMWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "b2ff06177ee90557f82e4d2b65f87cbe3e37ef185fd9f5b2f7d8c654bf7eefbc", + "size": 1760, + "filename": "v-gpCYcuRDTxFcUaVhaAGVlNDgPco2PZ87SDnQurzeU=.pem", + "location": "security-state-staging/intermediates/96d8b2a4-5c83-408a-aba1-f8c0818c74a1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v+gpCYcuRDTxFcUaVhaAGVlNDgPco2PZ87SDnQurzeU=", + "crlite_enrolled": false, + "id": "90eff404-0514-42a4-b6bc-375d060840d8", + "last_modified": 1601517443892 + }, + { + "schema": 1601376768762, + "derHash": "bay7iUUTex2tQhGwQ2774G8SrONpBJc7Ra4ldAgj02k=", + "subject": "CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENB", + "whitelist": false, + "attachment": { + "hash": "1a8c56af27223d777da24bba93b743c15e633c42f9ab430fc69c48fe2ac69bbb", + "size": 1577, + "filename": "r_mIkG3eEpVdm-u_ko_cwxzOMo1bk4TyHIlByibiA5E=.pem", + "location": "security-state-staging/intermediates/038b81ab-4eff-4d41-90a4-8f4f86b72f6b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=", + "crlite_enrolled": false, + "id": "39be0c8a-1350-49d8-95ce-b5095d2d226d", + "last_modified": 1601517443537 + }, + { + "schema": 1601376725349, + "derHash": "KGibMOTDBqq1OwJ7KeNq1t0dz0uVOZRILKhL3B7KyZY=", + "subject": "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIGYMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZpZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "f86af69b434af8ea5af1e027d56a7a38f6f22b387403ecb5009164accf5a6e79", + "size": 1605, + "filename": "KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6-oP5I=.pem", + "location": "security-state-staging/intermediates/901b5264-4d8d-44b2-9d34-a3510247f9db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=", + "crlite_enrolled": false, + "id": "c0aa9228-9471-4d9a-a0df-1f91fd16b767", + "last_modified": 1601517443467 + }, + { + "schema": 1601376781071, + "derHash": "RF7seLxhIVBEoDeWVqotXbXkL3bLcLjRTCB3qpQ9Trs=", + "subject": "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign", + "subjectDN": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "whitelist": false, + "attachment": { + "hash": "03136ae4f1cc15165e71fa5ef33f14d451ac52630098f060194f365a61a44a74", + "size": 1553, + "filename": "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP-4A=.pem", + "location": "security-state-staging/intermediates/de87ee5e-fc90-47ae-a551-27f45867b68c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=", + "crlite_enrolled": false, + "id": "61380e8f-ba33-418f-b251-e9adeb0d421b", + "last_modified": 1601517443361 + }, + { + "schema": 1601376723250, + "derHash": "3ajac2GH129PDtWl9me1TZmpiuBgkdDjoBcU6SIWla0=", + "subject": "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign", + "subjectDN": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "whitelist": false, + "attachment": { + "hash": "b41c9b2e15c3c244c322fbf94a4a850c7938223b476b1126737bdf2e5eec446a", + "size": 1890, + "filename": "aCdH-LpiG4fN07wpXtXKvOciocDANj0daLOJKNJ4fx4=.pem", + "location": "security-state-staging/intermediates/4d8959af-58c7-4dbf-b787-864dac9fdf64.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aCdH+LpiG4fN07wpXtXKvOciocDANj0daLOJKNJ4fx4=", + "crlite_enrolled": false, + "id": "ff319db3-4ac0-4406-9c29-03f632e42c98", + "last_modified": 1601517443342 + }, + { + "schema": 1601376755045, + "derHash": "5x2MO69D9rM1LfV0qfDUogZb8D2heVFLH8xdm+yMj80=", + "subject": "CN=Cybertrust Global Root,O=Cybertrust\\, Inc", + "subjectDN": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "whitelist": false, + "attachment": { + "hash": "1f262ade87c1ad2a96c3e27c26a783f6daabfe4db8d38fe377fa18eb20f05c8a", + "size": 1674, + "filename": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=.pem", + "location": "security-state-staging/intermediates/fbb7342f-2e0d-4f3b-8dc3-9f69e60e0638.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=", + "crlite_enrolled": false, + "id": "5a0248e5-fd5a-426c-99c1-331ad9cd50b2", + "last_modified": 1601517443236 + }, + { + "schema": 1601376758715, + "derHash": "ZLNULRvJcvWKHRefPQuWUr5DTzrjhC4MRHiA1NYjpN4=", + "subject": "CN=Cybertrust Global Root,O=Cybertrust\\, Inc", + "subjectDN": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "whitelist": false, + "attachment": { + "hash": "47de0a95895055ce31d99d828b0db0f2ec2817ccee0b1699eac0fc8b4c0d7a15", + "size": 1715, + "filename": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=.pem", + "location": "security-state-staging/intermediates/b0359886-f578-4ff8-b6f6-68c9c49ac966.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=", + "crlite_enrolled": false, + "id": "565f7b1a-68fa-497d-9c56-e9cd5a86b74e", + "last_modified": 1601517443097 + }, + { + "schema": 1601376718312, + "derHash": "y/j7d2YBZ+a6rNDfd82jl9ARfuK+6iO5NTF/i7W147A=", + "subject": "CN=DigiCert High Assurance EV Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US", + "subjectDN": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0E=", + "whitelist": false, + "attachment": { + "hash": "382e8d24a379c01750353f084bffee84b45ad51b937a3bc8fdb300cd978bbeac", + "size": 1593, + "filename": "WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=.pem", + "location": "security-state-staging/intermediates/007ae41e-e2a4-4671-8841-b6c69a70cb76.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=", + "crlite_enrolled": false, + "id": "c21f9364-fa90-4173-bcc3-ae3c385c1bb5", + "last_modified": 1601517443080 + }, + { + "schema": 1601376778540, + "derHash": "yU/t2k6GCJCFgLx/h7Q04DuyYuQvZMY4IKj1D7F8HOw=", + "subject": "CN=GlobalSign,OU=GlobalSign Root CA - R3,O=GlobalSign", + "subjectDN": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "whitelist": false, + "attachment": { + "hash": "46ad75ae4d4b3ea866008e00969c44fcf11c5283b34097a140d1ce9577c0080a", + "size": 1548, + "filename": "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP-4A=.pem", + "location": "security-state-staging/intermediates/ee510c0b-d1d8-40ba-bcca-1982d73cd1cb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=", + "crlite_enrolled": false, + "id": "fa4a1c02-26b3-4bf5-8034-bb4cabca2a73", + "last_modified": 1601517443010 + }, + { + "schema": 1601376781920, + "derHash": "JJBRRb2bm/6ZxgNUtJlRvg5wnxY0z70ONw/rnwaO1sM=", + "subject": "CN=Cybertrust Global Root,O=Cybertrust\\, Inc", + "subjectDN": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "whitelist": false, + "attachment": { + "hash": "14cdeb20754749b93fa91fc1e30b4e9dfedfb7f4d7022c381688a63321e757cd", + "size": 1674, + "filename": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=.pem", + "location": "security-state-staging/intermediates/d469a3f0-cf37-44b9-a3bf-95ac5492cd92.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "foeCwVDOOVL4AuY2AjpdPpW7XWjjPoWtsroXgSXOvxU=", + "crlite_enrolled": false, + "id": "fc237743-155a-442f-b754-e8fba2257bfe", + "last_modified": 1601517442922 + }, + { + "schema": 1601376762852, + "derHash": "aLnHYSGaWx8BMXhEdGZdthu9sQngDwXKn3QkTuX19Ss=", + "subject": "CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "9e2ebbe76bfd34d023064438681d83355a539b5267b279ebe8789bff5deae232", + "size": 1967, + "filename": "x4QzPSC810K5_cMjb05Qm4k3Bw5zBn4lTdO_nEW_Td4=.pem", + "location": "security-state-staging/intermediates/4357dc68-fa1b-4e2f-83fd-69a438b26167.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "x4QzPSC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4=", + "crlite_enrolled": false, + "id": "637da9fa-bc0c-4f4b-becc-9710f0181efd", + "last_modified": 1601517442888 + }, + { + "schema": 1601376724524, + "derHash": "yE4TeLl0qZGs3N1zNCHjBh5vohoEkciQK6/eOFXgBj4=", + "subject": "CN=GlobalSign,OU=GlobalSign Root CA - R6,O=GlobalSign", + "subjectDN": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "whitelist": false, + "attachment": { + "hash": "bc273edde0aa009fa424378923565ab23426f40d786f1f743e2a80d94c8514a4", + "size": 1902, + "filename": "aCdH-LpiG4fN07wpXtXKvOciocDANj0daLOJKNJ4fx4=.pem", + "location": "security-state-staging/intermediates/cd783c76-a2d3-49bb-8aaf-dd09ffcf625c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "aCdH+LpiG4fN07wpXtXKvOciocDANj0daLOJKNJ4fx4=", + "crlite_enrolled": false, + "id": "8feb1032-85fe-4d8c-b444-3c69d47c6f6d", + "last_modified": 1601517442835 + }, + { + "schema": 1601376765799, + "derHash": "2Wy8A7UjzTMVkYZRz0hiFiiH3VY6+yNS0/NLuUV2+T0=", + "subject": "CN=Verizon Global Root CA,OU=OmniRoot,O=Verizon Business,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "522a9129b746a60c4aa7d040da0f639b8349b1fee088f3f0ecf8b8101c7e452a", + "size": 1760, + "filename": "v-gpCYcuRDTxFcUaVhaAGVlNDgPco2PZ87SDnQurzeU=.pem", + "location": "security-state-staging/intermediates/98a3c55c-3f5d-4937-9389-3ca882ba85cf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "v+gpCYcuRDTxFcUaVhaAGVlNDgPco2PZ87SDnQurzeU=", + "crlite_enrolled": false, + "id": "62b74836-05bd-4881-9d60-b255969ce226", + "last_modified": 1601517442759 + }, + { + "schema": 1601376738922, + "derHash": "LRK2GaZgzvsBMnGDHYkSE/xDTpgqIVaCVs9OLoYyS+o=", + "subject": "CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIGYMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZpZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "4c84e52a6f32fdac5b7a09a6c9cd30af89415d99ee94c615e420b34e67cffcb0", + "size": 1605, + "filename": "KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6-oP5I=.pem", + "location": "security-state-staging/intermediates/70be3983-3e8b-4df2-b609-e35f19408bbc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=", + "crlite_enrolled": false, + "id": "f9243f28-e9ff-47ba-ae4a-e2657cce028c", + "last_modified": 1601517442703 + }, + { + "schema": 1601376763286, + "derHash": "0QjDSljA5KYWRJ+MSDGAI6IpyGzT3dXV/mBBpAHBahQ=", + "subject": "OU=ePKI Root Certification Authority,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "whitelist": false, + "attachment": { + "hash": "15e5931fc9e514bb8bf23b5f5c3c40bb3131ccf6ed8b0857416342b002b8db15", + "size": 2645, + "filename": "YlVMFwBVQ7I3IV8EJo3NL9HEcCQK08hmDiWuLFljD1U=.pem", + "location": "security-state-staging/intermediates/a894f5a2-8e74-4828-97c6-41052369ca59.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YlVMFwBVQ7I3IV8EJo3NL9HEcCQK08hmDiWuLFljD1U=", + "crlite_enrolled": false, + "id": "056b1bcf-53e9-405c-96eb-eea77dcff395", + "last_modified": 1601517442680 + }, + { + "schema": 1601376780263, + "derHash": "GEZ8TmTVhshEpERm3lunptWWnHqShZpRHF/a11sDzc4=", + "subject": "CN=ePKI Root Certification Authority - G2,O=Chunghwa Telecom Co.\\, Ltd.,C=TW", + "subjectDN": "MGMxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEvMC0GA1UEAwwmZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "6bf1025a0f2a333a3a9f9cf1fa26d326f099cad7c0d6658f30ea16733092bd7a", + "size": 2621, + "filename": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=.pem", + "location": "security-state-staging/intermediates/c7b2feb5-da29-4802-ba1d-e34f9184099c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tInMsiS5prgd0nTOr1IJwlKZjJp2r0jk9MUKByhGGCU=", + "crlite_enrolled": false, + "id": "ca26521c-22ff-48b3-bd4d-3c06d4ffa75a", + "last_modified": 1601517442632 + }, + { + "schema": 1601376767919, + "derHash": "ps9k27TI1f0ZzkiJYGjbA7UzqNEzbGJWqH0Ay7Pe8+o=", + "subject": "CN=USERTrust ECC Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US", + "subjectDN": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "3a7503777199e5b45661530654f7276cd82134eaa1ba8f391bb243e5a7a8bf47", + "size": 1386, + "filename": "ICGRfpgmOUXIWcQ_HXPLQTkFPEFPoDyjvH7ohhQpjzs=.pem", + "location": "security-state-staging/intermediates/8b43a741-845c-4f0d-9cb8-04371ae9c724.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ICGRfpgmOUXIWcQ/HXPLQTkFPEFPoDyjvH7ohhQpjzs=", + "crlite_enrolled": false, + "id": "256c0067-d90e-4625-aa2a-4223c14c4995", + "last_modified": 1601517442614 + }, + { + "schema": 1601376748460, + "derHash": "cvmvIVgYG68W1gybTm9L18qNI0GtSK/bZ8tMgzLVRvY=", + "subject": "CN=Microsec e-Szigno Root CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "b90c76e81f578b8bc26b991192e309fc1f7c20f49cb20bf33c55c1455c1f615e", + "size": 1557, + "filename": "YWFnIBQzrqbI5eMHCvyvZ0kYj4FL0auxea6NrTq_Juw=.pem", + "location": "security-state-staging/intermediates/1700e695-ab2e-4343-b33f-89b8ee9187a7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "YWFnIBQzrqbI5eMHCvyvZ0kYj4FL0auxea6NrTq/Juw=", + "crlite_enrolled": false, + "id": "4ac33a0c-1052-4702-874c-dc40ae073f75", + "last_modified": 1601517441658 + }, + { + "schema": 1592519327887, + "derHash": "HQzX7zwZJlUI4g5Ys1OJZPShGt7Ipx2KuKiv8VaDxq4=", + "subject": "CN=Trustwave XRamp Global Extended Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIG3MQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE/MD0GA1UEAxM2VHJ1c3R3YXZlIFhSYW1wIEdsb2JhbCBFeHRlbmRlZCBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMR8wHQYJKoZIhvcNAQkBFhBjYUB0cnVzdHdhdmUuY29t", + "whitelist": false, + "attachment": { + "hash": "8a9ab9f7d0fa1dce10d8a47b00a029a25fdd07597d57eaa58121c3285bd1534f", + "size": 1857, + "filename": "bNKluS4Pu3ijZ1polhtHJPfB2HD5sR_s46h0NYeir6U=.pem", + "location": "security-state-staging/intermediates/5016e431-0390-45dc-8e65-2642b4718233.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bNKluS4Pu3ijZ1polhtHJPfB2HD5sR/s46h0NYeir6U=", + "crlite_enrolled": false, + "id": "d9c54bdb-939c-4a86-a826-cc0f2f68da43", + "last_modified": 1600461980813 + }, + { + "schema": 1592519327482, + "derHash": "RbKwjhtYlIoCi+E6Z8DiDUskZq4rbsYlDrsQ/Wt/gjk=", + "subject": "CN=Trustwave Secure Global Extended Validation CA\\, Level 1,O=Trustwave Holdings\\, Inc.,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIG4MQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjFAMD4GA1UEAxM3VHJ1c3R3YXZlIFNlY3VyZSBHbG9iYWwgRXh0ZW5kZWQgVmFsaWRhdGlvbiBDQSwgTGV2ZWwgMTEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbQ==", + "whitelist": false, + "attachment": { + "hash": "296ea0b36874faa2487773b3da5792b5b41278241a226affe7e50a6ba22c0e72", + "size": 1784, + "filename": "jyPoX5LnCMukfvFxrxuJWUrZycp69MmdphXiTrhpMU4=.pem", + "location": "security-state-staging/intermediates/3c07694f-03e7-4fdb-b353-876f81151fda.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jyPoX5LnCMukfvFxrxuJWUrZycp69MmdphXiTrhpMU4=", + "crlite_enrolled": false, + "id": "170a74a6-1485-4c35-aecb-8b37550de004", + "last_modified": 1600461980794 + }, + { + "schema": 1591167038909, + "derHash": "7tCpXn3jylPkpscqhrPxZMDCPjgPj2t5o0nvF4Q0LB8=", + "subject": "CN=Sectigo SHA-256 DV Secure Server CA 2,O=Sectigo Limited,C=GB", + "subjectDN": "MFcxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLjAsBgNVBAMTJVNlY3RpZ28gU0hBLTI1NiBEViBTZWN1cmUgU2VydmVyIENBIDI=", + "whitelist": false, + "attachment": { + "hash": "8ee56d8ef938ba4af2c0e70e0468c434181b9e82ca18f599691bb7386c8440de", + "size": 1634, + "filename": "HPMSsaUyVhFGoL9nU4W2uLB0hGaRXQ5DmhLQh_ivAOI=.pem", + "location": "security-state-staging/intermediates/2b7ba355-fdd1-40c5-931a-ab2ab80990db.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "HPMSsaUyVhFGoL9nU4W2uLB0hGaRXQ5DmhLQh/ivAOI=", + "crlite_enrolled": false, + "id": "892ea545-f970-4099-82a0-cb0796cff322", + "last_modified": 1591199862421 + }, + { + "schema": 1591167008071, + "derHash": "U2ElE5cLnyZMpLzDv9hNvF/ndOPGKVs+u5nrnXQGnio=", + "subject": "CN=COMODO ECC Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "whitelist": false, + "attachment": { + "hash": "dc36e4c286a9069d729c789a17603fc5652298f8bf2def86e601936c8873ae90", + "size": 1382, + "filename": "58qRu_uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=.pem", + "location": "security-state-staging/intermediates/6652ce0d-3c54-446e-ace4-ab3bf3abd2a6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=", + "crlite_enrolled": false, + "id": "8b11a693-e60e-4d6d-80ee-c38a1d65c423", + "last_modified": 1591199862367 + }, + { + "schema": 1591166981675, + "derHash": "9gbY3rJ8BAHzTntqN8jipR242gJr5YoH6FS2ht50xpo=", + "subject": "CN=KICA RSA DV CA,O=KICA,C=KR", + "subjectDN": "MDUxCzAJBgNVBAYTAktSMQ0wCwYDVQQKEwRLSUNBMRcwFQYDVQQDEw5LSUNBIFJTQSBEViBDQQ==", + "whitelist": false, + "attachment": { + "hash": "318379d0564ebe89aa14dff5f62506a9553f6d9d680f10e82a54fa192e67f398", + "size": 1569, + "filename": "cK05Ok8jo8SXlxRdWcmPHjPUXO0-KqZKDWAQhEkCcCs=.pem", + "location": "security-state-staging/intermediates/d52fd294-49a6-40bb-a05a-5fdd485b772d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cK05Ok8jo8SXlxRdWcmPHjPUXO0+KqZKDWAQhEkCcCs=", + "crlite_enrolled": false, + "id": "d1256819-e772-4787-a82e-3e8aaeb2120a", + "last_modified": 1591199862320 + }, + { + "schema": 1591166946335, + "derHash": "t+EkLZaU+1AlsJaWT6dq997Z9VrkpnpCCtxfnRGzrTM=", + "subject": "CN=CERTDATA SSL DV ECC CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MHIxCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExNDAyBgNVBAMMK0NFUlREQVRBIFNTTCBEViBFQ0MgQ0EgIFtSdW4gYnkgdGhlIElzc3Vlcl0=", + "whitelist": false, + "attachment": { + "hash": "790d8b9e3ba565304aee9733ce4b0e0bfd2096f72e2e0601b8df408f07910a53", + "size": 1293, + "filename": "KCVGX9PZojp3nb42QFg8CInxwnSWCYZ4kG3HC87bf4o=.pem", + "location": "security-state-staging/intermediates/24bcf52a-aa3c-4d05-8236-9a1e72833516.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KCVGX9PZojp3nb42QFg8CInxwnSWCYZ4kG3HC87bf4o=", + "crlite_enrolled": false, + "id": "404783d0-cde7-4f2f-a512-dc32c5183168", + "last_modified": 1591199862248 + }, + { + "schema": 1591166922501, + "derHash": "BHeVeFzc/55uCuEiSS5be/CKnlxJdi4ry1J0fGkDFWE=", + "subject": "CN=NETLOCK Trust EV CA 3,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MFcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMR4wHAYDVQQDDBVORVRMT0NLIFRydXN0IEVWIENBIDM=", + "whitelist": false, + "attachment": { + "hash": "5baaa1ca8ca90156561df8c1bc2cd10801794f20c1984d55bbcedeb1417c7dd2", + "size": 2203, + "filename": "vRSkgbqFoy29lSNM6Ih557Nl-K07Rwh3GriW7t3fMJw=.pem", + "location": "security-state-staging/intermediates/7c1161bd-10bb-4642-b809-92c87a367bf2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vRSkgbqFoy29lSNM6Ih557Nl+K07Rwh3GriW7t3fMJw=", + "crlite_enrolled": false, + "id": "75389d14-b6d9-4b99-8f96-a61e30aaa117", + "last_modified": 1591199862208 + }, + { + "schema": 1591166909159, + "derHash": "f4FM0UVJAKwTMTZLa23ga4fMjLmTbsWDEAgichBX06M=", + "subject": "CN=Sectigo SHA-256 DV Secure Server CA,O=Sectigo Limited,C=GB", + "subjectDN": "MFUxCzAJBgNVBAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3RpZ28gU0hBLTI1NiBEViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "96985e5b8fc9114fc1bf6fb46c70668299157f698f5a03a51844df4b86d1c687", + "size": 1613, + "filename": "VrhAF5b9APFDNaFQfbVHB1xrJSjOWlQAmlz3WdQFkqE=.pem", + "location": "security-state-staging/intermediates/95b7263b-b946-4b1b-97fc-588b469275fc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "VrhAF5b9APFDNaFQfbVHB1xrJSjOWlQAmlz3WdQFkqE=", + "crlite_enrolled": false, + "id": "75e0caaf-9db0-4002-8fc9-28e0a8675dd8", + "last_modified": 1591199862181 + }, + { + "schema": 1591166884523, + "derHash": "XdZh08sztQBcvtBFoiPdxERaqkHRrLXfcAiEytm6QZU=", + "subject": "CN=ZeroSSL ECC Domain Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "MEsxCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSowKAYDVQQDEyFaZXJvU1NMIEVDQyBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "de8d5b5ee9e74901d1bbcc4ad4e1c43a96d0797608f7d04820cdabfa0de24f33", + "size": 1280, + "filename": "3fLLVjRIWnCqDqIETU2OcnMP7EzmN_Z3Q_jQ8cIaAoc=.pem", + "location": "security-state-staging/intermediates/cc8a3501-4e66-4b5b-9d98-d401566c67e8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3fLLVjRIWnCqDqIETU2OcnMP7EzmN/Z3Q/jQ8cIaAoc=", + "crlite_enrolled": false, + "id": "5f4e1d4e-6a23-4bbc-a9b9-366ff6016a5b", + "last_modified": 1591199862121 + }, + { + "schema": 1591166867546, + "derHash": "cNK5bnwEC2Jw/yC7a6mEITEq035GK+y4O+lQK4PfVpc=", + "subject": "CN=CERTDATA SSL DV CA [Run by the Issuer],O=CERTDATA SERVICOS DE INFORMACAO LTDA,C=BR", + "subjectDN": "MG4xCzAJBgNVBAYTAkJSMS0wKwYDVQQKEyRDRVJUREFUQSBTRVJWSUNPUyBERSBJTkZPUk1BQ0FPIExUREExMDAuBgNVBAMMJ0NFUlREQVRBIFNTTCBEViBDQSAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "10d55d2c2cadcf08f96b96c50475b9f79aeea102d66ab44733cfbd5b499b113c", + "size": 2129, + "filename": "96wDgmEz6BSfBEFFYy6VQtjNjdFAvz3d60x4NuYW52A=.pem", + "location": "security-state-staging/intermediates/de9b3e8a-9359-4fdf-803d-cc3302061a0d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "96wDgmEz6BSfBEFFYy6VQtjNjdFAvz3d60x4NuYW52A=", + "crlite_enrolled": false, + "id": "3ce95fd6-d1f8-4199-9fa8-c95bac94f60f", + "last_modified": 1591199862091 + }, + { + "schema": 1591166832366, + "derHash": "HhMjmuBLlxvd5lOv38cyRDi44UbFxIqBcDEibJV2RTk=", + "subject": "CN=GlobeSSL DV CA,O=CentralNic Luxembourg Sàrl,C=LU", + "subjectDN": "MEwxCzAJBgNVBAYTAkxVMSQwIgYDVQQKDBtDZW50cmFsTmljIEx1eGVtYm91cmcgU8OgcmwxFzAVBgNVBAMTDkdsb2JlU1NMIERWIENB", + "whitelist": false, + "attachment": { + "hash": "46ff926f845cbf0b70cc18fe28ae6ecc15392b7bf6d30f53d5e62c20fc4a177b", + "size": 2085, + "filename": "z9mSm0UWyXCmP7GyKCI8befW1Y69hzCdVgD4FqXpCJY=.pem", + "location": "security-state-staging/intermediates/e7f0cdf9-77c2-4123-9a86-5c429ea6c98a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "z9mSm0UWyXCmP7GyKCI8befW1Y69hzCdVgD4FqXpCJY=", + "crlite_enrolled": false, + "id": "64111cc5-6679-4853-a02f-ad3d4544e923", + "last_modified": 1591199862014 + }, + { + "schema": 1591166825015, + "derHash": "IazB29aUT5rBjHgstcMo1sKCHGtjcx+juJh/ViXeig0=", + "subject": "CN=ZeroSSL RSA Domain Secure Site CA,O=ZeroSSL,C=AT", + "subjectDN": "MEsxCzAJBgNVBAYTAkFUMRAwDgYDVQQKEwdaZXJvU1NMMSowKAYDVQQDEyFaZXJvU1NMIFJTQSBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "2c3ace9b9a1c08d3905fc990df49b6936f78e356141ef0cd8969bdb44ec78bff", + "size": 2430, + "filename": "R3hcMOAGw0WFztuG2skTodoHp8IGid3Qg63Cn7YUYoM=.pem", + "location": "security-state-staging/intermediates/5681e683-bd54-4255-89fe-dce439358be4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "R3hcMOAGw0WFztuG2skTodoHp8IGid3Qg63Cn7YUYoM=", + "crlite_enrolled": false, + "id": "a8da5928-6b84-408c-9441-1ac7d1244157", + "last_modified": 1591199861999 + }, + { + "schema": 1591167128660, + "derHash": "KrTf9p11u/lUEGC0NM5a0MTay3rw2/IdNhavy0c3lt4=", + "subject": "CN=Certum Class I CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MHsxCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBDbGFzcyBJIENBIFNIQTI=", + "whitelist": false, + "attachment": { + "hash": "42c093531894319de5b6736e8bb7302536bb599cd0c23045983bc8cdb0398cbf", + "size": 1711, + "filename": "DlML3IIZYmKbkc5sg_Vlwu7GmLOhTHxlwiDnuM8uXco=.pem", + "location": "security-state-staging/intermediates/537f9e86-0aa2-4487-ac82-d4e6d63a5030.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DlML3IIZYmKbkc5sg/Vlwu7GmLOhTHxlwiDnuM8uXco=", + "crlite_enrolled": false, + "id": "f0bd44f2-5281-42f1-ae71-9815dbe807b3", + "last_modified": 1591199861864 + }, + { + "schema": 1591167103436, + "derHash": "rv/kM17lZCLpJ/Rela4UK56zWXmnQAVprpvepsqrwdw=", + "subject": "CN=SHECA Global G3 SSL,O=UniTrust,ST=Shanghai,C=CN", + "subjectDN": "MFExCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UECgwIVW5pVHJ1c3QxHDAaBgNVBAMME1NIRUNBIEdsb2JhbCBHMyBTU0w=", + "whitelist": false, + "attachment": { + "hash": "966814ad4c7925d39057eb115ca90947f532fe888d00abdd9884f26cad189f7d", + "size": 1999, + "filename": "-3DYv-zWynH_VKDDzSsG9R5GfeXFt--F4B8P0tAoRsU=.pem", + "location": "security-state-staging/intermediates/72f5db49-9653-406b-b129-7db8f562a09a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+3DYv+zWynH/VKDDzSsG9R5GfeXFt++F4B8P0tAoRsU=", + "crlite_enrolled": false, + "id": "9a5f7622-15ab-419e-9203-7b79dd0f844e", + "last_modified": 1591199861813 + }, + { + "schema": 1591167133769, + "derHash": "wKsH2QcaTMHTRAkXj4vKBYMQqLER3c+mVWWHYCJvUPk=", + "subject": "CN=WoSign EV SSL CA,O=WoSign CA Limited,C=CN", + "subjectDN": "MEQxCzAJBgNVBAYTAkNOMRowGAYDVQQKDBFXb1NpZ24gQ0EgTGltaXRlZDEZMBcGA1UEAwwQV29TaWduIEVWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "70b0ebbc523a8c85dfbcba297ad8752536887543b61fafc141de629f096c6ef6", + "size": 1691, + "filename": "kUsaqEFcRNYeLMDaKST82jCK2fP2QzSDRUyN92iicvw=.pem", + "location": "security-state-staging/intermediates/77469872-4038-4fec-bc01-bcfacaf96e61.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kUsaqEFcRNYeLMDaKST82jCK2fP2QzSDRUyN92iicvw=", + "crlite_enrolled": false, + "id": "c63369ab-e91c-444e-ab6e-6400efdbc7ea", + "last_modified": 1591199861686 + }, + { + "schema": 1579028930299, + "derHash": "9NaVrak2Ni3frtpkTHzO8k63TcQ404GiivqqEUd65n8=", + "subject": "CN=Global Trust CA - DV (ECC),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBEViAoRUNDKQ==", + "whitelist": false, + "attachment": { + "hash": "c7355ba281b86440b42869da1995f8827d941dd7f536e3597c74d5f9bf8bf1ec", + "size": 1252, + "filename": "LUbiRYLu1pSHTj3Z-ypCjtIr7U83tgAbsXPL2H-meVE=.pem", + "location": "security-state-staging/intermediates/39747309-5dcc-499d-b5fe-d02d1627de6a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "LUbiRYLu1pSHTj3Z+ypCjtIr7U83tgAbsXPL2H+meVE=", + "crlite_enrolled": false, + "id": "5d5a8603-5a22-40cc-bcf3-f79feed83467", + "last_modified": 1579029043776 + }, + { + "schema": 1579028931778, + "derHash": "DaILPAU4a9l1sdvTfw63aJ5qMjZDluBlP/qe7mBnlPU=", + "subject": "CN=Global Trust CA - DV (RSA),O=Global Digital Inc.,C=TW", + "subjectDN": "MFAxCzAJBgNVBAYTAlRXMRwwGgYDVQQKExNHbG9iYWwgRGlnaXRhbCBJbmMuMSMwIQYDVQQDExpHbG9iYWwgVHJ1c3QgQ0EgLSBEViAoUlNBKQ==", + "whitelist": false, + "attachment": { + "hash": "5cdd0b9b0275f33beab775b37771a02bf084cef5c96a9cf78baff9e34cab1c37", + "size": 2089, + "filename": "hwddnilH_pWtWcAnCBvcpQEiJA8MsPuSY74GtASteIo=.pem", + "location": "security-state-staging/intermediates/8b16acf6-9f74-495d-a9f0-b039aa0c54b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hwddnilH/pWtWcAnCBvcpQEiJA8MsPuSY74GtASteIo=", + "crlite_enrolled": false, + "id": "6224b875-5c34-475b-8cdf-aecad54173d7", + "last_modified": 1579029043764 + }, + { + "schema": 1579028915292, + "derHash": "A3GlU+Ss9AJFkQfo95M3nMVpEw8u4DxBL+2HtcFyQkQ=", + "subject": "CN=DNEncrypt ECC DV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF4xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE2MDQGA1UEAwwtRE5FbmNyeXB0IEVDQyBEViBTU0wvVExTICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "f33c1660f86553d61d7d2c9223b3d32b7720ca57b4c5c5a3542c544085b3ba13", + "size": 1309, + "filename": "cMgUafsF3Pj6CXkE48F1T9jPwW-ZXrmmryJ99sHNyYA=.pem", + "location": "security-state-staging/intermediates/73b1704d-b140-489e-a73d-4b23fc14264f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cMgUafsF3Pj6CXkE48F1T9jPwW+ZXrmmryJ99sHNyYA=", + "crlite_enrolled": false, + "id": "626f9b82-c857-46cc-b90a-e7bcf3ac92cc", + "last_modified": 1579029043744 + }, + { + "schema": 1579028904846, + "derHash": "qXQcP6RM+Xd6Sbb1Du1dDrh+lIHzpFCWw5VqXuG7iEQ=", + "subject": "CN=DigitalTrust CA G4 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MF4xCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjEwMC4GA1UEAwwnRGlnaXRhbFRydXN0IENBIEc0ICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "84f190959472ef5c60f53b166df0e32ccc218bd9a02ad1184c432a5c1187fd7c", + "size": 2454, + "filename": "3MPa3WIVqOW4yU6-w-ZSt3hKIEBpElbYDOX4tDWu1pI=.pem", + "location": "security-state-staging/intermediates/b67dbf30-2e5f-4572-9a51-963a2e41fcd9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3MPa3WIVqOW4yU6+w+ZSt3hKIEBpElbYDOX4tDWu1pI=", + "crlite_enrolled": false, + "id": "b8b2f836-8e81-4a67-8afc-877abf1c8895", + "last_modified": 1579029043724 + }, + { + "schema": 1579028910703, + "derHash": "nRkILiQzvlUB9GkSlXD1c4c7Lf6DhrH4qd+pjC+q3Ns=", + "subject": "CN=DNEncrypt SHA2 DV SSL/TLS [Run by the Issuer],O=DNEncrypt\\, Inc,C=US", + "subjectDN": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5ETkVuY3J5cHQsIEluYzE3MDUGA1UEAwwuRE5FbmNyeXB0IFNIQTIgRFYgU1NML1RMUyAgW1J1biBieSB0aGUgSXNzdWVyXQ==", + "whitelist": false, + "attachment": { + "hash": "a65bbd2c04e427655a0c45d1f82f7685ada08bc874a74ab4e09037ec98ac9d5a", + "size": 2458, + "filename": "3E1xVsnJnhe9i57XMuug-LOT83YzrJjC1N60Oln6OaU=.pem", + "location": "security-state-staging/intermediates/c1c68b60-29de-4cdf-8ba2-7adf17023d5a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3E1xVsnJnhe9i57XMuug+LOT83YzrJjC1N60Oln6OaU=", + "crlite_enrolled": false, + "id": "66492022-3f9c-4abc-b14f-9bce674885fb", + "last_modified": 1579029043721 + }, + { + "schema": 1579028903393, + "derHash": "vg1slHe5ggklSa3T1cC9GejxPE9OhelHEpfhxeGax2g=", + "subject": "CN=DigitalTrust CA G3 [Run by the Issuer],O=Digital Trust L.L.C.,C=AE", + "subjectDN": "MF4xCzAJBgNVBAYTAkFFMR0wGwYDVQQKExREaWdpdGFsIFRydXN0IEwuTC5DLjEwMC4GA1UEAwwnRGlnaXRhbFRydXN0IENBIEczICBbUnVuIGJ5IHRoZSBJc3N1ZXJd", + "whitelist": false, + "attachment": { + "hash": "eb78b6848ba5286d4224160e64392e5ad1c80330a158642dd0a826b312889cf6", + "size": 1309, + "filename": "0r9lDiNtrXm0xXuKfgPxSSDXNDGuK2HF29dk8BUZsCA=.pem", + "location": "security-state-staging/intermediates/5ba9312c-58e2-408d-ab65-a5b513dd2ccf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "0r9lDiNtrXm0xXuKfgPxSSDXNDGuK2HF29dk8BUZsCA=", + "crlite_enrolled": false, + "id": "f4a41986-65e8-483c-bdb5-1b551dcf0317", + "last_modified": 1579029043719 + }, + { + "schema": 1579028942315, + "derHash": "VJiWPf+mUWBPRn4QjmWhg0cPqbVXwSnd+MnYErTwv5Y=", + "subject": "CN=NETLOCK Trust Qualified CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MFwxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMSMwIQYDVQQDDBpORVRMT0NLIFRydXN0IFF1YWxpZmllZCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "eca87866bec2d4554f1b58f8091c7337c82c9e65d743cf2eba31c9d2757e5244", + "size": 2073, + "filename": "fuUqgkkwOoU6AwXExGnKFu7G3knCONHh2ksz-wBRRbY=.pem", + "location": "security-state-staging/intermediates/5c604286-d66a-44d4-b654-de83a937b1a3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fuUqgkkwOoU6AwXExGnKFu7G3knCONHh2ksz+wBRRbY=", + "crlite_enrolled": false, + "id": "ce93fbf8-83be-4a31-acf0-fd669baf4f55", + "last_modified": 1579029043697 + }, + { + "schema": 1576536064064, + "derHash": "6nb58huSvtaYEIVm9fCSEbIExQNcnQGUM8Y1jfoKc8o=", + "subject": "CN=TI Trust Technologies DV CA,O=TI Trust Technologies S.R.L.,L=Pomezia,ST=Roma,C=IT", + "subjectDN": "MHsxCzAJBgNVBAYTAklUMQ0wCwYDVQQIEwRSb21hMRAwDgYDVQQHEwdQb21lemlhMSUwIwYDVQQKExxUSSBUcnVzdCBUZWNobm9sb2dpZXMgUy5SLkwuMSQwIgYDVQQDExtUSSBUcnVzdCBUZWNobm9sb2dpZXMgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c00194c16ee92968419bef2890e0a9f99c7ba4d5e671c1c2d6d8aa388428c9ea", + "size": 2150, + "filename": "_TQ1SdM_c-9Y7ggqiTm5AZCppmeqhMts_1_7qE6c-xM=.pem", + "location": "security-state-staging/intermediates/1b2efaae-5ec5-45f0-ad37-15376c4ec56f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/TQ1SdM/c+9Y7ggqiTm5AZCppmeqhMts/1/7qE6c+xM=", + "crlite_enrolled": false, + "id": "25f9b537-5a44-42db-a613-3918314a6ff8", + "last_modified": 1576536534251 + }, + { + "schema": 1576536058303, + "derHash": "DxVUwv1ZGwJWpgjhwTaoN+em4EFWHuCKkRsq/c08bBs=", + "subject": "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "whitelist": false, + "attachment": { + "hash": "619239fa36d141159ca184a5846f04250cf658d2d441555c880bc314397cd6f9", + "size": 1089, + "filename": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR_g_0scfb9gXM=.pem", + "location": "security-state-staging/intermediates/053b9ee7-c4d5-4804-9f95-f705688c379e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR/g/0scfb9gXM=", + "crlite_enrolled": false, + "id": "08445463-0e37-4bbe-a1ed-f079c2145e45", + "last_modified": 1576536534240 + }, + { + "schema": 1576536034560, + "derHash": "KHMACROLTh1tM0qPKAWmCqO+mWnVc7uy8FFjErt/NU4=", + "subject": "CN=OneSignSSL RSA DV Secure Server CA,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MH8xCzAJBgNVBAYTAlNHMRIwEAYDVQQIEwlTaW5nYXBvcmUxEjAQBgNVBAcTCVNpbmdhcG9yZTEbMBkGA1UEChMST25lIFNpZ24gUHRlLiBMdGQuMSswKQYDVQQDEyJPbmVTaWduU1NMIFJTQSBEViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "4fb6ba778562a884883cc8f1c7ddf3cc76924b6c481f1238cd9d0d09eef988cd", + "size": 2154, + "filename": "9IAV2sm8fSQPPgrFDnJNElfd6K4uJrxTx15o5jJy9nA=.pem", + "location": "security-state-staging/intermediates/fd1c6268-0059-46b1-8058-e8765d252ba4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9IAV2sm8fSQPPgrFDnJNElfd6K4uJrxTx15o5jJy9nA=", + "crlite_enrolled": false, + "id": "ec48a0f4-07fe-4af5-898f-dd29d59f3ea3", + "last_modified": 1576536534175 + }, + { + "schema": 1576536016807, + "derHash": "hV3KaPgZTW1byUpRzzZM3P/IOCWhItimLkehGEhNN0s=", + "subject": "CN=ITSO Ltd RSA DV,O=ITSO LTD,L=Milton Keynes,ST=Buckinghamshire,C=GB", + "subjectDN": "MGwxCzAJBgNVBAYTAkdCMRgwFgYDVQQIEw9CdWNraW5naGFtc2hpcmUxFjAUBgNVBAcTDU1pbHRvbiBLZXluZXMxETAPBgNVBAoTCElUU08gTFREMRgwFgYDVQQDEw9JVFNPIEx0ZCBSU0EgRFY=", + "whitelist": false, + "attachment": { + "hash": "f7f8ae5a4d6c669308077f3d66de5bced3fc6ce838c89515e89a27eb62208b17", + "size": 2129, + "filename": "hoWbyI9IWnkDxapxLWOIsrvM58HKk5XQdT0Wk5A2ovs=.pem", + "location": "security-state-staging/intermediates/8855bb62-810b-4e29-aa2c-072a825e881a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hoWbyI9IWnkDxapxLWOIsrvM58HKk5XQdT0Wk5A2ovs=", + "crlite_enrolled": false, + "id": "7e6c7559-1afe-4e48-aa16-564c45d842e6", + "last_modified": 1576536534134 + }, + { + "schema": 1576535984479, + "derHash": "Ptv//DTroC4NupzRGdvPmNdJclc3evi8YhDYgQASCng=", + "subject": "CN=WebNIC ECC Domain Secure Site CA,O=WebNIC,L=Singapore,C=SG", + "subjectDN": "MF0xCzAJBgNVBAYTAlNHMRIwEAYDVQQHEwlTaW5nYXBvcmUxDzANBgNVBAoTBldlYk5JQzEpMCcGA1UEAxMgV2ViTklDIEVDQyBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "dba8424009d862179d68d2288ea0622dd0951d98a15c8c2596e223b755ce68ec", + "size": 1252, + "filename": "df4BU-36qEmkjB2K0NlnnriyUqYwdm9JiwyAOrs_dqk=.pem", + "location": "security-state-staging/intermediates/03cca880-f44b-4cdf-9623-58ce69906e26.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "df4BU+36qEmkjB2K0NlnnriyUqYwdm9JiwyAOrs/dqk=", + "crlite_enrolled": false, + "id": "9ffe1aa1-fafe-45e2-825b-4897086c8615", + "last_modified": 1576536534045 + }, + { + "schema": 1576535973202, + "derHash": "ZokB5XZNfZCZq8ITSFmdn/NrkWyoObNR5odbGLFhML4=", + "subject": "CN=WebNIC RSA Domain Secure Site CA,O=WebNIC,L=Singapore,C=SG", + "subjectDN": "MF0xCzAJBgNVBAYTAlNHMRIwEAYDVQQHEwlTaW5nYXBvcmUxDzANBgNVBAoTBldlYk5JQzEpMCcGA1UEAxMgV2ViTklDIFJTQSBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "16d026d7e222b83b21f30fffd6c86d922e1bc13c0f20bb7a6760efc390c2a1be", + "size": 2093, + "filename": "-6H0JyfPfrRNWedIzjtNbJYDUqMxAM9vGdy4yFQ5yFI=.pem", + "location": "security-state-staging/intermediates/568e3b62-fb8c-462e-87f0-420b7b3c9dd9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+6H0JyfPfrRNWedIzjtNbJYDUqMxAM9vGdy4yFQ5yFI=", + "crlite_enrolled": false, + "id": "c67fab8d-6da0-439a-8323-77c7d8bf7a59", + "last_modified": 1576536534019 + }, + { + "schema": 1576535945982, + "derHash": "pjwTmLX43S1DL75MLBkUK+ptXQIh+ueUcYrnWXrMqW0=", + "subject": "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "whitelist": false, + "attachment": { + "hash": "378f762af5809d2286eb016c78a51681c834fff84abc2ac05a6cb350bb81bbeb", + "size": 1886, + "filename": "rn-WLLnmp9v3uDP7GPqbcaiRdd-UnCMrap73yz3yu_w=.pem", + "location": "security-state-staging/intermediates/d658e22b-e2c2-4eaf-a261-dda71e485545.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rn+WLLnmp9v3uDP7GPqbcaiRdd+UnCMrap73yz3yu/w=", + "crlite_enrolled": false, + "id": "de84677c-3935-4ff1-ad12-b7f06ddaa458", + "last_modified": 1576536533904 + }, + { + "schema": 1576535908358, + "derHash": "peM8KOMBOnH192CuOxZZUJAEPS7FIJ7FKQPE+60lja0=", + "subject": "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "whitelist": false, + "attachment": { + "hash": "fa8a56ce9427949481f533247ba9c60a2399344aa8e931f631f8a47245217c4d", + "size": 1894, + "filename": "rn-WLLnmp9v3uDP7GPqbcaiRdd-UnCMrap73yz3yu_w=.pem", + "location": "security-state-staging/intermediates/ef48945b-24c4-41bf-afdf-5d30c4725e72.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rn+WLLnmp9v3uDP7GPqbcaiRdd+UnCMrap73yz3yu/w=", + "crlite_enrolled": false, + "id": "0bac52a2-0949-48f2-abb2-f09ac245fd7f", + "last_modified": 1576536533799 + }, + { + "schema": 1576535854905, + "derHash": "xFG++6hwFOzVeFHR5oJAPjymCWN3Ouf6oA/9b/rIsqM=", + "subject": "CN=Apple Public Server ECC CA 11 - G1,O=Apple Inc.,ST=California,C=US", + "subjectDN": "MGQxKzApBgNVBAMTIkFwcGxlIFB1YmxpYyBTZXJ2ZXIgRUNDIENBIDExIC0gRzExEzARBgNVBAoTCkFwcGxlIEluYy4xEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "159650c695404e21baee3c84c9de3b14321fc786ae0d79ab3c53f0265e742c3e", + "size": 1179, + "filename": "AW191Or9_rr7bm1xPdsvaWeUUt-lfxY77hstOrTylbc=.pem", + "location": "security-state-staging/intermediates/28078966-bfa4-4d0f-b3af-2e868d081f40.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "AW191Or9/rr7bm1xPdsvaWeUUt+lfxY77hstOrTylbc=", + "crlite_enrolled": false, + "id": "b0ae8271-61f1-4d4d-89b4-39a4305bde48", + "last_modified": 1576536533666 + }, + { + "schema": 1576535841997, + "derHash": "bGZXjclq0T63toi9wJ20ctX78Ds70hMJZlBSqIbX6bQ=", + "subject": "CN=Apple Public Server RSA CA 11 - G1,O=Apple Inc.,ST=California,C=US", + "subjectDN": "MGQxKzApBgNVBAMTIkFwcGxlIFB1YmxpYyBTZXJ2ZXIgUlNBIENBIDExIC0gRzExEzARBgNVBAoTCkFwcGxlIEluYy4xEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "961515735a466b16075e55c7da361418ee043d9647f04c36b42e00fdd5304fa3", + "size": 2028, + "filename": "xXDXs35ozcjwk0JgHOmIO-JXQxta1bb9rQ14FVEqdWE=.pem", + "location": "security-state-staging/intermediates/f51e2efe-1586-43cb-8513-42d7587139fd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xXDXs35ozcjwk0JgHOmIO+JXQxta1bb9rQ14FVEqdWE=", + "crlite_enrolled": false, + "id": "b906d89b-573b-4733-920c-a0796309ee22", + "last_modified": 1576536533636 + }, + { + "schema": 1576535820935, + "derHash": "M+8VHvsI0cRPuFzD8j7GhzAU6fiBaRvUk4t/JRWAtpQ=", + "subject": "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "whitelist": false, + "attachment": { + "hash": "41d424d3da88a84293ad6feed475c1a513b2a30cd740e6d86c7ec641141bd50e", + "size": 1313, + "filename": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR_g_0scfb9gXM=.pem", + "location": "security-state-staging/intermediates/e4bdfc10-cf3c-4f1d-8566-19d3353552f5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR/g/0scfb9gXM=", + "crlite_enrolled": false, + "id": "59fa6d74-626e-410e-a605-3508e34fb522", + "last_modified": 1576536533576 + }, + { + "schema": 1576535812375, + "derHash": "Qx9jChtd5vD/G3MxjQGqqf5KEl8wJsHMMOJc9KXLuj8=", + "subject": "CN=BitCert RSA Domain Secure Site CA,O=BitCert,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MG8xCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdTaWNodWFuMRAwDgYDVQQHEwdDaGVuZ2R1MRAwDgYDVQQKEwdCaXRDZXJ0MSowKAYDVQQDEyFCaXRDZXJ0IFJTQSBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "6ffbd17583806cfd9f9461e1213cba2e87fa5d6150d467d60f1bea3a6057e527", + "size": 2129, + "filename": "cNE8mPePscWLEKBJVbelkXANOrn4fk_NgWvF39Zm9FU=.pem", + "location": "security-state-staging/intermediates/082754db-89db-4a21-b19c-beb0cea8ad0e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cNE8mPePscWLEKBJVbelkXANOrn4fk/NgWvF39Zm9FU=", + "crlite_enrolled": false, + "id": "67e2adbf-676e-4de8-b707-4616d1daef65", + "last_modified": 1576536533550 + }, + { + "schema": 1576535764596, + "derHash": "xa2jSGvdtUWzqn7JnGnBrtlCdNX+ynkz4BMuIS8CZnE=", + "subject": "CN=BitCert ECC Domain Secure Site CA,O=BitCert,L=Chengdu,ST=Sichuan,C=CN", + "subjectDN": "MG8xCzAJBgNVBAYTAkNOMRAwDgYDVQQIEwdTaWNodWFuMRAwDgYDVQQHEwdDaGVuZ2R1MRAwDgYDVQQKEwdCaXRDZXJ0MSowKAYDVQQDEyFCaXRDZXJ0IEVDQyBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "cf45883f3cb3aaa3f86e6fd95964673ab8be0ec688db38395556914cc50c457e", + "size": 1288, + "filename": "l03AMxIZai8wvW2nrYEfG_5pj3_oGL2PS1_B5kv47yg=.pem", + "location": "security-state-staging/intermediates/1dc1c5ba-377f-4791-bea6-1421a0da10d3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "l03AMxIZai8wvW2nrYEfG/5pj3/oGL2PS1/B5kv47yg=", + "crlite_enrolled": false, + "id": "a06f0bc4-ae00-450f-9b23-5180ec001c0e", + "last_modified": 1576536533432 + }, + { + "schema": 1576535718042, + "derHash": "HfYFTWZBQEYzZBu1+jdC/afQdeJRSECrYeAMy7t9NB0=", + "subject": "CN=GlobalSign Root E46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "whitelist": false, + "attachment": { + "hash": "7e5ce727726952ca1f6cb21c02d6ca82c82baaaf53c5c69de963c2ddc3344595", + "size": 1305, + "filename": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR_g_0scfb9gXM=.pem", + "location": "security-state-staging/intermediates/06349813-d93c-441c-a351-17e576175b0e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4EoCLOMvTM8sf2BGKHuCijKpCfXnUUR/g/0scfb9gXM=", + "crlite_enrolled": false, + "id": "60de9866-3e99-4374-b830-17ad74f56f75", + "last_modified": 1576536533289 + }, + { + "schema": 1576535711746, + "derHash": "k1BhvlLI6ojANLOa39UiuzFMv1ME5acGRzXdvaMkKq8=", + "subject": "CN=Certigna Entity Code Signing CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGIMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQRhDBROVFJGUi00ODE0NjMwODEwMDAzNjEoMCYGA1UEAwwfQ2VydGlnbmEgRW50aXR5IENvZGUgU2lnbmluZyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "dfa52fac8680622c2c208aa337be1f107052398f893cb8cdaa1bc8ee0c3c1f93", + "size": 2186, + "filename": "u3ZvFIlkZqOQDQbj9Abh3WXDOESr4pASOdoob9Oo2YI=.pem", + "location": "security-state-staging/intermediates/a34d09dc-c1e9-4b35-bb0f-0c7ee3e64ad8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "u3ZvFIlkZqOQDQbj9Abh3WXDOESr4pASOdoob9Oo2YI=", + "crlite_enrolled": false, + "id": "90283279-5bef-4526-8705-37883c1dc2bc", + "last_modified": 1576536533281 + }, + { + "schema": 1576535713205, + "derHash": "RUBA5JaQcEAcw1vffypOvOV5e7dpSucx2TqBFf1Zkpw=", + "subject": "CN=Starfield Root Certificate Authority - G2,OU=https://certs.starfieldtech.com/repository/,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIHFMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE0MDIGA1UECxMraHR0cHM6Ly9jZXJ0cy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5LzEyMDAGA1UEAxMpU3RhcmZpZWxkIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "9d2202937f8e54392d669c92cd8a75a53b1784157b44c73b0ce0c2614d485051", + "size": 1735, + "filename": "gI1os_q0iEpflxrOfRBVDXqVoWN3Tz7Dav_7IT--THQ=.pem", + "location": "security-state-staging/intermediates/25785222-2756-4269-a695-0a60c3918f3a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gI1os/q0iEpflxrOfRBVDXqVoWN3Tz7Dav/7IT++THQ=", + "crlite_enrolled": false, + "id": "1515b293-9629-40ac-b90c-843783835be0", + "last_modified": 1576536533268 + }, + { + "schema": 1576535685544, + "derHash": "Rcsdh0ywO9XFtuB5yPwp5RUh7lYoSGMBlkpB+Uuln4g=", + "subject": "CN=GlobalSign Root R46,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "whitelist": false, + "attachment": { + "hash": "6aeca6429f492ce0a1e976110a582cd7b8c66033c209b47b36f98a2ec75750f1", + "size": 2231, + "filename": "rn-WLLnmp9v3uDP7GPqbcaiRdd-UnCMrap73yz3yu_w=.pem", + "location": "security-state-staging/intermediates/7e4b802f-b595-45c5-b8c9-7a395c2592f2.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rn+WLLnmp9v3uDP7GPqbcaiRdd+UnCMrap73yz3yu/w=", + "crlite_enrolled": false, + "id": "db71c7f0-2fac-43c3-ad72-dd258c250fa5", + "last_modified": 1576536533196 + }, + { + "schema": 1576535658276, + "derHash": "Vyv4mf13Q2LcGSGWJezBV7tVQ06lFm1XWNxLT4kNZlM=", + "subject": "CN=ACCVCA-130,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMzAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "3bcfe77948ebaa1fe7f7bfb31339eeaba3e27a506d01e1f6704b54ecb78cc54a", + "size": 2686, + "filename": "Y4wPdQMFev6SAz6Zy7McH45VYp5uADyuOVtFnivRtu4=.pem", + "location": "security-state-staging/intermediates/ebb99ee5-c251-4021-80eb-3e74471c91bf.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y4wPdQMFev6SAz6Zy7McH45VYp5uADyuOVtFnivRtu4=", + "crlite_enrolled": false, + "id": "b94aff1d-4802-4d47-9d29-833321cc8644", + "last_modified": 1576536533095 + }, + { + "schema": 1576535621506, + "derHash": "dD4yjzKeGU2iUnEb9r/wDPY7akwKpmsuGWdxaRBniXE=", + "subject": "CN=TrustID Server CA E1,OU=TrustID Server,O=IdenTrust,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxFzAVBgNVBAsTDlRydXN0SUQgU2VydmVyMR0wGwYDVQQDExRUcnVzdElEIFNlcnZlciBDQSBFMQ==", + "whitelist": false, + "attachment": { + "hash": "0bdc1e653dd27bde4924c78842f2ce8a676e17e94570bcb4d9a68dbb15842d8a", + "size": 2398, + "filename": "ddXHjYup7j8-aweO9eKo5dPHP_ulMVLc4zqjLbj0f6s=.pem", + "location": "security-state-staging/intermediates/a2c07257-f449-4845-8a46-396088d1f54d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ddXHjYup7j8+aweO9eKo5dPHP/ulMVLc4zqjLbj0f6s=", + "crlite_enrolled": false, + "id": "3238adbc-8e16-4f0c-8de1-94a33d6b7601", + "last_modified": 1576536532946 + }, + { + "schema": 1576535577287, + "derHash": "80mVTo+21EARvLeJ2X2aLLIDK9XwtZjR+4oJn1hI1SM=", + "subject": "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign", + "subjectDN": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "whitelist": false, + "attachment": { + "hash": "8e31af76b6ffba628ef77f3869f8354882f946eb5ff2352cf52785a9787b247b", + "size": 1325, + "filename": "fg6tdrtoGdwvVFEahDVPboswe53YIFjqbABPAdndpd8=.pem", + "location": "security-state-staging/intermediates/afbb8634-f114-41c2-98ab-86ef89d3a7f8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fg6tdrtoGdwvVFEahDVPboswe53YIFjqbABPAdndpd8=", + "crlite_enrolled": false, + "id": "77a173f6-46fd-4be8-ba6f-c64b6308db3f", + "last_modified": 1576536532829 + }, + { + "schema": 1576535550809, + "derHash": "U5wT1vJzElL5ndK69D1AVZxGxYV1jE/EQQx+xILb7Wk=", + "subject": "CN=OneSignSSL ECC DV Secure Server CA,O=One Sign Pte. Ltd.,L=Singapore,ST=Singapore,C=SG", + "subjectDN": "MH8xCzAJBgNVBAYTAlNHMRIwEAYDVQQIEwlTaW5nYXBvcmUxEjAQBgNVBAcTCVNpbmdhcG9yZTEbMBkGA1UEChMST25lIFNpZ24gUHRlLiBMdGQuMSswKQYDVQQDEyJPbmVTaWduU1NMIEVDQyBEViBTZWN1cmUgU2VydmVyIENB", + "whitelist": false, + "attachment": { + "hash": "0e21e684e88796f0f844928dd52cc107b1280c4c7eae78e1934ca07301495bb1", + "size": 1313, + "filename": "8vM1dUqGdSXi2UQvVgGUGNw0L1p0-y-yK8MC0MVE9Zs=.pem", + "location": "security-state-staging/intermediates/86301532-3955-416c-9e34-7bcb144c4837.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "8vM1dUqGdSXi2UQvVgGUGNw0L1p0+y+yK8MC0MVE9Zs=", + "crlite_enrolled": false, + "id": "e0e26920-5001-4bb2-b934-8e6f0697161c", + "last_modified": 1576536532763 + }, + { + "schema": 1576535542013, + "derHash": "Oi++kokeV/4F1XCH9I5zDxflpfU+9APWGOW3TXp+bss=", + "subject": "CN=Go Daddy Root Certificate Authority - G2,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIGDMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xMTAvBgNVBAMTKEdvIERhZGR5IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "28840eaf068daaa9dc2d390f16a3685d787b1538e5d8cb9b2977d2063d3719ba", + "size": 1618, + "filename": "Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA=.pem", + "location": "security-state-staging/intermediates/9a9d5981-04f6-43af-9a85-cf7f8a3cfaf9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA=", + "crlite_enrolled": false, + "id": "db9d1a57-7d79-44f0-a9c2-f880bce304dc", + "last_modified": 1576536532745 + }, + { + "schema": 1576535535906, + "derHash": "hsZwe74nzeEhXiXT+BRqUiKB4YxF3yy4xvt6A8FzNRA=", + "subject": "CN=GDCA TrustAUTH R4 Generic CA,O=GUANG DONG CERTIFICATE AUTHORITY CO.\\,LTD.,C=CN", + "subjectDN": "MGgxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjElMCMGA1UEAwwcR0RDQSBUcnVzdEFVVEggUjQgR2VuZXJpYyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "3b6dea215a5d301b1acaa2f77d6d9182513b6bf335cd1cfaa9683bc4efe3d224", + "size": 2060, + "filename": "mXaq2dsX_TtlLffdUghF79fyMALXeMrXTTUzEgq6gv8=.pem", + "location": "security-state-staging/intermediates/74e3a779-4648-4fc7-8e96-17622071ed2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mXaq2dsX/TtlLffdUghF79fyMALXeMrXTTUzEgq6gv8=", + "crlite_enrolled": false, + "id": "6ed2cbdd-26bb-461a-81d7-712abefd1ddd", + "last_modified": 1576536532726 + }, + { + "schema": 1576535458597, + "derHash": "AsSjAKCcG4k7EflWdlmvlbu5u+eVOJPjbFuvF7VVzuM=", + "subject": "CN=Certigna Identity Plus CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGCMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQRhDBROVFJGUi00ODE0NjMwODEwMDAzNjEiMCAGA1UEAwwZQ2VydGlnbmEgSWRlbnRpdHkgUGx1cyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "4f9b8c5d9910a9c5057de2bdaa15c263ccc9194055f0f6d438f7dd65e0000884", + "size": 2178, + "filename": "ywhSp1oF34bfFheBzKO3KqbsXi-qQbyfXu94SMYBxwg=.pem", + "location": "security-state-staging/intermediates/a9861ffb-3a18-416a-b622-b2317ad102ed.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ywhSp1oF34bfFheBzKO3KqbsXi+qQbyfXu94SMYBxwg=", + "crlite_enrolled": false, + "id": "cd3a316e-5ab3-425f-9bd1-f6ef6ab8e077", + "last_modified": 1576536532530 + }, + { + "schema": 1576536109129, + "derHash": "ehQrGl4WIVGDoT6ECoYqQ34pPZNmkh2wfttU8TisDXg=", + "subject": "CN=TrustAsia OV SSL CA - C3,O=TrustAsia Technologies Inc.,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMSQwIgYDVQQKDBtUcnVzdEFzaWEgVGVjaG5vbG9naWVzIEluYy4xITAfBgNVBAMMGFRydXN0QXNpYSBPViBTU0wgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "bfb83a4526e35f2dc8e07512d6f16dfffedccf8e3263aebf657edd6423d54673", + "size": 1691, + "filename": "hEZRb0aBfyh_1yoi7II2m0TQ_JCkJgWwfLZni7lZUHY=.pem", + "location": "security-state-staging/intermediates/200bc2a0-44c0-40f1-b810-696bea5c140a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "hEZRb0aBfyh/1yoi7II2m0TQ/JCkJgWwfLZni7lZUHY=", + "crlite_enrolled": false, + "id": "c6d61ecf-df03-455e-b621-1a69e4c44ab9", + "last_modified": 1576536532463 + }, + { + "schema": 1576536101946, + "derHash": "q6amXc6JVbrwaFq4iAm3aZwXRJbvnumRUzJRSU9DzhA=", + "subject": "CN=emSign ECC Class 1 CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MG4xCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSMwIQYDVQQDExplbVNpZ24gRUNDIENsYXNzIDEgQ0EgLSBHMw==", + "whitelist": false, + "attachment": { + "hash": "08dbb6b7acb073d4854e540ae8a5b481d4a53af8dfa6bbd5167547bab1bbfd6c", + "size": 1146, + "filename": "Vi90DKA5oebj_na5AG-8ED2AOxp_ezxWTtviS14jpQ4=.pem", + "location": "security-state-staging/intermediates/6af4e353-16ba-477f-8e14-bcd1749fbc26.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Vi90DKA5oebj/na5AG+8ED2AOxp/ezxWTtviS14jpQ4=", + "crlite_enrolled": false, + "id": "2f009cfb-439c-4999-9f3b-77b9f7f84299", + "last_modified": 1576536532313 + }, + { + "schema": 1576536121896, + "derHash": "TptzFWcXfhd2qW1m2RILPesouACTfqRmJWWz717IAAs=", + "subject": "CN=emSign ECC Class 2 CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MG4xCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSMwIQYDVQQDExplbVNpZ24gRUNDIENsYXNzIDIgQ0EgLSBHMw==", + "whitelist": false, + "attachment": { + "hash": "b2c9c934bd28c6cdde9ab4d84c6c8da067598d50410f83afa8df464ff780c749", + "size": 1142, + "filename": "e6icTYnqPAeqRyiV6T1Z-dePhAfjZW9RbzFi-_h840Q=.pem", + "location": "security-state-staging/intermediates/2a9db6d2-ba72-49aa-a083-f23d88661c4b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e6icTYnqPAeqRyiV6T1Z+dePhAfjZW9RbzFi+/h840Q=", + "crlite_enrolled": false, + "id": "6058b519-9e61-482a-8f27-01c5ffd80762", + "last_modified": 1576536532275 + }, + { + "schema": 1576536100444, + "derHash": "noUsWd/G/Wq9ThfqgLX05W/AQZLRByWNVNqKklKGcNY=", + "subject": "CN=Certum Global Services CA SHA2,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGDMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MScwJQYDVQQDEx5DZXJ0dW0gR2xvYmFsIFNlcnZpY2VzIENBIFNIQTI=", + "whitelist": false, + "attachment": { + "hash": "8aa13e80c83c232b2f8047b3b1df764885c9c822e03078672b01899a2acd4a16", + "size": 1723, + "filename": "M7aD_Hmgy7CF8sTddr5so1MZWEBuNfLIdGe1jvy0X6E=.pem", + "location": "security-state-staging/intermediates/f51cd94a-f3bc-4f18-bbba-d7bc1b52c422.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M7aD/Hmgy7CF8sTddr5so1MZWEBuNfLIdGe1jvy0X6E=", + "crlite_enrolled": false, + "id": "b69c3618-7b62-4d9b-ac74-c8f39cf75c4c", + "last_modified": 1576536532214 + }, + { + "schema": 1576536127256, + "derHash": "PzGbKv7UoPdRJ75ZklVQ0EKOaHY6CeJz62qf+NGNu1s=", + "subject": "CN=GlobalSign,OU=GlobalSign ECC Root CA - R5,O=GlobalSign", + "subjectDN": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "whitelist": false, + "attachment": { + "hash": "b003225e25a61e89168f5f77baef1eabcd91e2a9c9117c1f5602208a802290e3", + "size": 1313, + "filename": "fg6tdrtoGdwvVFEahDVPboswe53YIFjqbABPAdndpd8=.pem", + "location": "security-state-staging/intermediates/8d1d0d1d-6a84-49b1-8a6d-74b898777b5d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fg6tdrtoGdwvVFEahDVPboswe53YIFjqbABPAdndpd8=", + "crlite_enrolled": false, + "id": "e97393c4-72ac-459c-8c42-ee0e57bd8a90", + "last_modified": 1576536532144 + }, + { + "schema": 1576536080221, + "derHash": "m/WJZ1RZlhlFEtthdxUa/plwauo9o2/u562fizwFB8s=", + "subject": "CN=Go Daddy Root Certificate Authority - G2,OU=https://certs.godaddy.com/repository/,O=GoDaddy.com\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIGzMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLjAsBgNVBAsTJWh0dHBzOi8vY2VydHMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS8xMTAvBgNVBAMTKEdvIERhZGR5IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "f01b7da33411fa8ad18c3acbb3b660ca139a5179f86a05a17e7ed8579b7c64a1", + "size": 1678, + "filename": "Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA=.pem", + "location": "security-state-staging/intermediates/32481f93-70cb-40f2-b608-39d1e40518fe.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ko8tivDrEjiY90yGasP6ZpBU4jwXvHqVvQI0GS3GNdA=", + "crlite_enrolled": false, + "id": "e7caf81b-a272-4ba4-ab9e-23e7d0270c4e", + "last_modified": 1576536532002 + }, + { + "schema": 1576536087107, + "derHash": "Dve4Y/qrw4SmlP9jLar5vTHO0j6SRlWaWezXRydUzOY=", + "subject": "CN=emSign Class 1 CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEfMB0GA1UEAxMWZW1TaWduIENsYXNzIDEgQ0EgLSBDMQ==", + "whitelist": false, + "attachment": { + "hash": "7794b1b190e82b18d7b5fa922d7bfc2eac719e69dd3bdb72cc135eddd69bc51c", + "size": 1540, + "filename": "tGZeupl-1fZilJwugiteFK3_QBrwzCRpRSZvX6uA41s=.pem", + "location": "security-state-staging/intermediates/1c8b5b0a-6ce6-460c-b68c-5b9e6dcd4acd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tGZeupl+1fZilJwugiteFK3/QBrwzCRpRSZvX6uA41s=", + "crlite_enrolled": false, + "id": "3d7a069a-f29d-4118-9ac6-49aa45da46c3", + "last_modified": 1576536531856 + }, + { + "schema": 1576536080984, + "derHash": "QtocVi+A5G2noyEkTvwj0Pqp/ru3qgN32WtC2eiKsgA=", + "subject": "CN=emSign Class 3 CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGoxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDExZlbVNpZ24gQ2xhc3MgMyBDQSAtIEcx", + "whitelist": false, + "attachment": { + "hash": "d7df1cc3c3cc232a8f51e22a5dd4ea1766aeacac640f8a976a7c227626346ed2", + "size": 1589, + "filename": "4d2pu0FN5HWVpPbYu-0cH1AeCZYz3dgz9eFeY0xuTh8=.pem", + "location": "security-state-staging/intermediates/dbf450a1-135d-4dc7-8677-00049ab4ecf1.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4d2pu0FN5HWVpPbYu+0cH1AeCZYz3dgz9eFeY0xuTh8=", + "crlite_enrolled": false, + "id": "6f7f445a-9db3-41b6-8e6e-c97091de4b0d", + "last_modified": 1576536531660 + }, + { + "schema": 1576536084043, + "derHash": "XbYMLWtr7PMUR3WJo6T7TM+EZJ1psLIbPWsquni9Nfs=", + "subject": "CN=GDCA TrustAUTH R4 Generic CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG8xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJTAjBgNVBAMMHEdEQ0EgVHJ1c3RBVVRIIFI0IEdlbmVyaWMgQ0E=", + "whitelist": false, + "attachment": { + "hash": "22066078c8b548595b3543e7f4efbc0819675c6d5799aab9f7eb51ee3085e95e", + "size": 2056, + "filename": "mXaq2dsX_TtlLffdUghF79fyMALXeMrXTTUzEgq6gv8=.pem", + "location": "security-state-staging/intermediates/874fedb4-3946-494c-8f65-a56d07cb81a3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mXaq2dsX/TtlLffdUghF79fyMALXeMrXTTUzEgq6gv8=", + "crlite_enrolled": false, + "id": "67740d97-16e0-48d6-9078-18385eaf1f0c", + "last_modified": 1576536531581 + }, + { + "schema": 1576536104206, + "derHash": "WpoD8tP+WJvmPNoRggqfJfB0ySA09RwEfTQibSUuwCU=", + "subject": "CN=emSign ECC Class 3 CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEjMCEGA1UEAxMaZW1TaWduIEVDQyBDbGFzcyAzIENBIC0gQzM=", + "whitelist": false, + "attachment": { + "hash": "6a976c7c76045fe8433c1fea57e7ae1de19f27a18f1c7bac51939e901257ea89", + "size": 1098, + "filename": "7yfF5AdGH75Zx9ksuCGXlGlIQcXlTbPFVOh5ccaTF0o=.pem", + "location": "security-state-staging/intermediates/f7dc2c14-5d52-4c59-97e7-78062c62da07.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7yfF5AdGH75Zx9ksuCGXlGlIQcXlTbPFVOh5ccaTF0o=", + "crlite_enrolled": false, + "id": "0e7af0a7-8aa0-4cbb-960e-7f7436ef41ed", + "last_modified": 1576536531553 + }, + { + "schema": 1576536104947, + "derHash": "dEaBgM5WS61+gSIQr3Q+hcqWy6RM9YUfoACCNBslNfU=", + "subject": "CN=GDCA TrustAUTH R4 DV SSL CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG4xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJDAiBgNVBAMMG0dEQ0EgVHJ1c3RBVVRIIFI0IERWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "7cfea5fa9465373b3b94affa9d9aab2c113c1118cc633be2b62f93987ea69fa5", + "size": 2052, + "filename": "iBHlL3zuDBffc94FLYVkQWUl8ghEY26YFGj-wm1psHY=.pem", + "location": "security-state-staging/intermediates/99cb19eb-1375-4867-90aa-b0b2d298c16c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "iBHlL3zuDBffc94FLYVkQWUl8ghEY26YFGj+wm1psHY=", + "crlite_enrolled": false, + "id": "66719d09-7132-4513-86df-72ef54bba5b6", + "last_modified": 1576536531539 + }, + { + "schema": 1576536079076, + "derHash": "20WR+Hj2Zy9bcHM6Zq18lTe5fm8K9cpJqrjsss4C+Gs=", + "subject": "CN=emSign ECC Class 2 CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEjMCEGA1UEAxMaZW1TaWduIEVDQyBDbGFzcyAyIENBIC0gQzM=", + "whitelist": false, + "attachment": { + "hash": "6726d4cd8b74a9dc90a4a784c54671d69630f8fb6349c445e838be1cfccdce15", + "size": 1098, + "filename": "6nBouKNAQSAKbMyn22fCnI3dwxK0fiT4UvzcTjEMQKo=.pem", + "location": "security-state-staging/intermediates/e7df3325-60f1-4ee6-9ef7-fad0411247c9.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6nBouKNAQSAKbMyn22fCnI3dwxK0fiT4UvzcTjEMQKo=", + "crlite_enrolled": false, + "id": "95e7d143-1acd-49b1-a78d-ad87dbf9fa68", + "last_modified": 1576536531522 + }, + { + "schema": 1576536129511, + "derHash": "9mCwwlZIHLK/xnZhweqP7uOVtxQbysNsNuBNCM2eFYI=", + "subject": "CN=DFN-Verein Certification Authority 2,OU=DFN-PKI,O=Verein zur Foerderung eines Deutschen Forschungsnetzes e. V.,C=DE", + "subjectDN": "MIGVMQswCQYDVQQGEwJERTFFMEMGA1UEChM8VmVyZWluIHp1ciBGb2VyZGVydW5nIGVpbmVzIERldXRzY2hlbiBGb3JzY2h1bmdzbmV0emVzIGUuIFYuMRAwDgYDVQQLEwdERk4tUEtJMS0wKwYDVQQDEyRERk4tVmVyZWluIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IDI=", + "whitelist": false, + "attachment": { + "hash": "6a4a495a8e0a5d05743b1038d796e892b2588f6e493314ad4c9bd9091e0e5590", + "size": 1817, + "filename": "nKACHrGIK7Y2Rip247vWnOEfapF_v8H8rewd_Mzla8U=.pem", + "location": "security-state-staging/intermediates/97909fb6-ddc3-470e-be2b-383bf9c3f177.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "nKACHrGIK7Y2Rip247vWnOEfapF/v8H8rewd/Mzla8U=", + "crlite_enrolled": false, + "id": "3cfbb558-f1ba-418a-9513-9ebf4f15523d", + "last_modified": 1576536531457 + }, + { + "schema": 1576536076791, + "derHash": "BabbOJOR35LgvpP9+k2x489TkDkYuNnYWpw5bLVd8DA=", + "subject": "SERIALNUMBER=10688435,CN=Starfield Secure Certification Authority,OU=http://certificates.starfieldtech.com/repository,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIHcMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE5MDcGA1UECxMwaHR0cDovL2NlcnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5MTEwLwYDVQQDEyhTdGFyZmllbGQgU2VjdXJlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgxMDY4ODQzNQ==", + "whitelist": false, + "attachment": { + "hash": "37d7bba2be8f3a98a69a003482d6d53844e60252e23b914f5352656847af97ff", + "size": 1804, + "filename": "lpQNmRQZFRRQ0edfZiGPbyWU4d9K8xpa1nPJqHRoF84=.pem", + "location": "security-state-staging/intermediates/6300e821-542e-4fd8-affd-d5e6a763540c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lpQNmRQZFRRQ0edfZiGPbyWU4d9K8xpa1nPJqHRoF84=", + "crlite_enrolled": false, + "id": "cad79643-368d-41c4-961a-f100b194b581", + "last_modified": 1576536531299 + }, + { + "schema": 1576536121479, + "derHash": "abDdCbmPNqnMe9f/6KANzTGaX8lHycivcskolNjoEJI=", + "subject": "CN=emSign Class 3 CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEfMB0GA1UEAxMWZW1TaWduIENsYXNzIDMgQ0EgLSBDMQ==", + "whitelist": false, + "attachment": { + "hash": "3aa155eb8f98e023c61de4d46433bc4046b93234f688f14f6806cdbe52688ea5", + "size": 1540, + "filename": "196cBc4ZpZ-dgc2f9eiIzsTDKH1UcMUQklpeqjt81U8=.pem", + "location": "security-state-staging/intermediates/99544220-2852-47f5-be07-65330e576225.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "196cBc4ZpZ+dgc2f9eiIzsTDKH1UcMUQklpeqjt81U8=", + "crlite_enrolled": false, + "id": "b1870974-fb66-409f-a707-cfab57324858", + "last_modified": 1576536531194 + }, + { + "schema": 1576536070287, + "derHash": "BbMLP8RPhXUzS9gS75+opSp1dD4ZvDWlvjkS7KYsRmk=", + "subject": "CN=emSign Class 2 CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFkxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEfMB0GA1UEAxMWZW1TaWduIENsYXNzIDIgQ0EgLSBDMQ==", + "whitelist": false, + "attachment": { + "hash": "9e607bd9110b39c805c7004ad3bfc08fe04b920a7b93635f51bf863efcf5e402", + "size": 1540, + "filename": "wS3fnwn6I-0aX-URrvRiO7hPWPjTnhX4ui6vAkztOls=.pem", + "location": "security-state-staging/intermediates/090d486e-d557-4442-bccd-17e9440798f4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "wS3fnwn6I+0aX+URrvRiO7hPWPjTnhX4ui6vAkztOls=", + "crlite_enrolled": false, + "id": "2938bfef-51d8-4a22-8411-49848fd8bfc8", + "last_modified": 1576536531087 + }, + { + "schema": 1576536095441, + "derHash": "+tLphknxxgYVD1UmnrwDWuoi/6wTHeZLppAMddhEe34=", + "subject": "CN=emSign ECC Class 1 CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MF0xCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEjMCEGA1UEAxMaZW1TaWduIEVDQyBDbGFzcyAxIENBIC0gQzM=", + "whitelist": false, + "attachment": { + "hash": "c516b0ad97b1477bff6c92128e355bb9684dfe454d1ed7edc1e2a015ab934271", + "size": 1098, + "filename": "EqlTjvAcH3DgHiMTTQZXlA45Cyry_ZcvmeEoRmnEl5k=.pem", + "location": "security-state-staging/intermediates/e0fbcccb-8a1c-4bef-bbd6-b6e85d8aa2bd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EqlTjvAcH3DgHiMTTQZXlA45Cyry/ZcvmeEoRmnEl5k=", + "crlite_enrolled": false, + "id": "aa27b627-8f3f-4f46-a72d-a321873e033e", + "last_modified": 1576536530924 + }, + { + "schema": 1576536101197, + "derHash": "n0PVLoCMIK/2ngL6rCBarGhOaXUhPWYg+sZL3l/KtLw=", + "subject": "CN=Starfield Root Certificate Authority - G2,O=Starfield Technologies\\, Inc.,L=Scottsdale,ST=Arizona,C=US", + "subjectDN": "MIGPMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UEAxMpU3RhcmZpZWxkIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "whitelist": false, + "attachment": { + "hash": "789b9056f79d6141a6214c6b990151b3e4959ece572fb2004fd731b195d33a79", + "size": 1662, + "filename": "gI1os_q0iEpflxrOfRBVDXqVoWN3Tz7Dav_7IT--THQ=.pem", + "location": "security-state-staging/intermediates/f18299c6-f4da-4af4-9ec8-a8d711749b1c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "gI1os/q0iEpflxrOfRBVDXqVoWN3Tz7Dav/7IT++THQ=", + "crlite_enrolled": false, + "id": "bd0ecefd-cffd-4cd5-8a92-4ea3a0be8a89", + "last_modified": 1576536530882 + }, + { + "schema": 1576536082901, + "derHash": "cGag9C9TDg21r+5yo7BN5hTn0jBcZ9EsdWuyFeN8uXU=", + "subject": "CN=emSign ECC Class 3 CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MG4xCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSMwIQYDVQQDExplbVNpZ24gRUNDIENsYXNzIDMgQ0EgLSBHMw==", + "whitelist": false, + "attachment": { + "hash": "a70534399006c5c9656d36fab666a70380eef9fb1048307630726609ee92f9e0", + "size": 1146, + "filename": "jCaXOAfJZSEcZ2svKHJ5NTIJpwnQiYsnkj_rOSON6UU=.pem", + "location": "security-state-staging/intermediates/87469897-2f9a-45ba-aabb-01e0de7e07a6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jCaXOAfJZSEcZ2svKHJ5NTIJpwnQiYsnkj/rOSON6UU=", + "crlite_enrolled": false, + "id": "a029188c-564e-4bac-988a-0d1d88ede978", + "last_modified": 1576536530747 + }, + { + "schema": 1576536116411, + "derHash": "Y6g2ncgkpCvHrm7l0mqv0y30r2d8oYuUG3pX4zseNVk=", + "subject": "CN=emSign Class 2 CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGoxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDExZlbVNpZ24gQ2xhc3MgMiBDQSAtIEcx", + "whitelist": false, + "attachment": { + "hash": "eed8ae8522e552261846735911703422edcda86f9319535078afc6d728d2a768", + "size": 1585, + "filename": "3bEiK1EHsF_yyH5kHV9snYO-hS_ijijvRBCS1wrrAuc=.pem", + "location": "security-state-staging/intermediates/d8c04dd9-2d2c-4510-a92e-286b8f977661.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3bEiK1EHsF/yyH5kHV9snYO+hS/ijijvRBCS1wrrAuc=", + "crlite_enrolled": false, + "id": "26f75550-6f7c-4915-a0c5-6ce0fc78a16b", + "last_modified": 1576536530666 + }, + { + "schema": 1562108584121, + "derHash": "8AMiyXtXzqbWSmodNlxsa38OujCJh2wUMOEGPoqCpnY=", + "subject": "CN=UbiquiTLS™ DV RSA Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGAMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEmMCQGA1UEAwwdVWJpcXVpVExT4oSiIERWIFJTQSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "34402ac995c9387b9c9835b6d19bbf5f738839e57491436e8fb9d880eabb284d", + "size": 2129, + "filename": "4FOjiY6IYxhWCosn04P8_UB9mAXrdVXjkoJw0SF4xqM=.pem", + "location": "security-state-staging/intermediates/8314b196-222a-4ea4-b73e-05d946a6b0f3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4FOjiY6IYxhWCosn04P8/UB9mAXrdVXjkoJw0SF4xqM=", + "crlite_enrolled": false, + "id": "6a542733-c365-45a6-88e1-581fbf55eea9", + "last_modified": 1562108584867 + }, + { + "schema": 1562108576476, + "derHash": "z8tgwfAYDGjj6l0ktKBenZkA2Hw9g9UDzhaQs8FlZFg=", + "subject": "CN=Qualified e-Szigno QCP CA 2012,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHlF1YWxpZmllZCBlLVN6aWdubyBRQ1AgQ0EgMjAxMjEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "0918a8dff15d27cbb4e8b360eb83374b19fa2947fb8fabb2c8200569d2b9b5ca", + "size": 1780, + "filename": "xa1dpEOu-k1shciGkpd-djS0kvHJPOmnvqja-16S2o0=.pem", + "location": "security-state-staging/intermediates/edcd1fb2-b2cf-4e62-a12a-5ba7788690ad.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xa1dpEOu+k1shciGkpd+djS0kvHJPOmnvqja+16S2o0=", + "crlite_enrolled": false, + "id": "0f39cd6a-43ef-4feb-8856-6d300edaa87c", + "last_modified": 1562108577246 + }, + { + "schema": 1562108557320, + "derHash": "CSgpQz0jGUn0qbxmbL9Us6on1768oEjXXlkJPhWnLqU=", + "subject": "CN=TeliaSonera Class 2 CA v2,O=TeliaSonera,C=SE", + "subjectDN": "MEcxCzAJBgNVBAYTAlNFMRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEiMCAGA1UEAwwZVGVsaWFTb25lcmEgQ2xhc3MgMiBDQSB2Mg==", + "whitelist": false, + "attachment": { + "hash": "a3a1b4dfa388dfe0d25ab5674cfaeaf78ef03e5364e2cd9de1328b58f2a891b3", + "size": 2528, + "filename": "lqMfOTYct9rMx_Y2LpHI8aZt9xgWHX_TwLLQ51NQl04=.pem", + "location": "security-state-staging/intermediates/d6d3711a-b7d2-4f85-a846-aca44739e149.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lqMfOTYct9rMx/Y2LpHI8aZt9xgWHX/TwLLQ51NQl04=", + "crlite_enrolled": false, + "id": "0edb2d0c-78d4-4869-9399-77844e0f7698", + "last_modified": 1562108558066 + }, + { + "schema": 1562108515200, + "derHash": "Ze7AzGyXDMHNc2WRFdyNkE5vEubcj9Tdo51UyzAiR4A=", + "subject": "CN=GlobalSign Organization Validated ECC CA - SHA256 - G4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MGkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMT8wPQYDVQQDEzZHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0ZWQgRUNDIENBIC0gU0hBMjU2IC0gRzQ=", + "whitelist": false, + "attachment": { + "hash": "865c9143e047b0121c8b2a402263e27dd9fbbd276406fe62b420613e3e28edda", + "size": 1394, + "filename": "4Xv9MflABt7Iaf_CjuYWBVtq_uG5yZJdQ3s5VSjCGoM=.pem", + "location": "security-state-staging/intermediates/cb10ff6b-2fd7-4bd4-b66d-314238afca50.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4Xv9MflABt7Iaf/CjuYWBVtq/uG5yZJdQ3s5VSjCGoM=", + "crlite_enrolled": false, + "id": "d1ef766b-9934-4f69-b604-dcdd7cc578aa", + "last_modified": 1562108515952 + }, + { + "schema": 1562108509878, + "derHash": "Yelzden22pgv9cGeL5TmbE41toN847kU0iRcf19lgl8=", + "subject": "CN=Sectigo ECC Domain Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGPMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "4d1b7deaa365566deaed85adb3c7b35bb377cf800b13c07a8ae1e78b76e74645", + "size": 1329, + "filename": "6YBE8kK4d5J1qu1wEjyoKqzEIvyRY5HyM_NB2wKdcZo=.pem", + "location": "security-state-staging/intermediates/6cf98cdf-f1ba-4bfe-9130-5511f8e29381.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "6YBE8kK4d5J1qu1wEjyoKqzEIvyRY5HyM/NB2wKdcZo=", + "crlite_enrolled": false, + "id": "b57ef74e-655f-4231-a30d-f2cfe2066f04", + "last_modified": 1562108510628 + }, + { + "schema": 1562108436479, + "derHash": "2oVGgW2JHBJB6Th95DbRuffqcNuh6z0l9YJxzoFqerw=", + "subject": "CN=Apple Public Server ECC CA 2 - G1,O=Apple Inc.,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSowKAYDVQQDEyFBcHBsZSBQdWJsaWMgU2VydmVyIEVDQyBDQSAyIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "96414faae6fe080e422a2e6dffda79775450c27bd46afac2672914234e85706a", + "size": 1358, + "filename": "kKxRozcrd_eR6KCzq3nM59ZL2rXyJMQOx5NjXVurVaw=.pem", + "location": "security-state-staging/intermediates/08d896b9-e7d5-440e-a842-0552e86ff576.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "kKxRozcrd/eR6KCzq3nM59ZL2rXyJMQOx5NjXVurVaw=", + "crlite_enrolled": false, + "id": "1d2ac87d-efa3-4196-9b3d-7656b94384ac", + "last_modified": 1562108437238 + }, + { + "schema": 1562108416654, + "derHash": "nkNiqRiokAmHfHuLGQ52OuASrUfBzKX8zxZvwJK8Kt4=", + "subject": "CN=NETLOCK Trust Qualified QSCD CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MGExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMSgwJgYDVQQDDB9ORVRMT0NLIFRydXN0IFF1YWxpZmllZCBRU0NEIENB", + "whitelist": false, + "attachment": { + "hash": "a9db5eb17262cb517f68c8b87a2d5cdf87701cef4f06e04fc0682b56add2277d", + "size": 2081, + "filename": "olqhNjTm2Sp9m1ZCWJdK_014dkyXVLKhU2hA03JqJDI=.pem", + "location": "security-state-staging/intermediates/e8f32acc-3396-4ecf-80ac-5467edd7dc61.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "olqhNjTm2Sp9m1ZCWJdK/014dkyXVLKhU2hA03JqJDI=", + "crlite_enrolled": false, + "id": "2a3597e8-9e52-40c9-8dcc-bdcde662da91", + "last_modified": 1562108417422 + }, + { + "schema": 1562108404463, + "derHash": "uITtZSdDNodifTUVfpBGkNLf9qXc084me7rxWcBvUFQ=", + "subject": "CN=Qualified e-Szigno CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MH4xCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEjMCEGA1UEAwwaUXVhbGlmaWVkIGUtU3ppZ25vIENBIDIwMDkxHzAdBgkqhkiG9w0BCQEWEGluZm9AZS1zemlnbm8uaHU=", + "whitelist": false, + "attachment": { + "hash": "bf48fc2c3b14d73b262a97912ba1131615bfc904ad5308aa6b2e6cf791f65740", + "size": 1756, + "filename": "Z91mM3fIfALcmTPBS54fQeHpI3uGuUg9kgkk7DDv-Fc=.pem", + "location": "security-state-staging/intermediates/177a6f68-3005-4b93-8a1d-b32344504ae0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Z91mM3fIfALcmTPBS54fQeHpI3uGuUg9kgkk7DDv+Fc=", + "crlite_enrolled": false, + "id": "88820ee2-60db-4e83-807f-ac34e6f6689a", + "last_modified": 1562108405220 + }, + { + "schema": 1562108402917, + "derHash": "pfL9DWbbTdd6KRTtPHR8vZfnNM9OK28hf7QapOr96tI=", + "subject": "CN=NetLock Minősített Eat. Spec. (Class Q Legal Spec.) Kiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIG3MQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTFFMEMGA1UEAww8TmV0TG9jayBNaW7FkXPDrXRldHQgRWF0LiBTcGVjLiAoQ2xhc3MgUSBMZWdhbCBTcGVjLikgS2lhZMOz", + "whitelist": false, + "attachment": { + "hash": "960b0e99960f66aedab8032a321d21b1ee47b83c0bf8a84a35997f846a5cb818", + "size": 2203, + "filename": "ZFsXBjpAqEkgk5r4JAL_ikmxkDiIWah9rrr4UEJ-IYE=.pem", + "location": "security-state-staging/intermediates/f4f080e9-09db-4a3b-b6ca-670808743e87.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ZFsXBjpAqEkgk5r4JAL/ikmxkDiIWah9rrr4UEJ+IYE=", + "crlite_enrolled": false, + "id": "b99f664a-470e-47f9-8280-2596d76a8af8", + "last_modified": 1562108403692 + }, + { + "schema": 1562108393006, + "derHash": "WUXcU0sLHzdzXVrtTrXWZBcvKFruYKnnsYJFqg4RmPY=", + "subject": "CN=FujiSSL SHA2 Domain Secure Site CA,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MHUxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzETMBEGA1UEBxMKU2hpYnV5YS1rdTEUMBIGA1UEChMLTmlqaW1vIEsuSy4xKzApBgNVBAMTIkZ1amlTU0wgU0hBMiBEb21haW4gU2VjdXJlIFNpdGUgQ0E=", + "whitelist": false, + "attachment": { + "hash": "e26de9b3dc4701004f0c895f5950b7c3b99233b7974b7618e6c960f0736fd2ed", + "size": 2138, + "filename": "vaPiW3esM9sfzdxIpWhz2dO053le6lZAjqtv-y_vZN8=.pem", + "location": "security-state-staging/intermediates/c8cb6374-215c-4f0c-bd04-6477b7579404.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vaPiW3esM9sfzdxIpWhz2dO053le6lZAjqtv+y/vZN8=", + "crlite_enrolled": false, + "id": "16682234-e483-4e72-9b5a-9a6efd36c796", + "last_modified": 1562108393755 + }, + { + "schema": 1562108349322, + "derHash": "XmCWWARGGTCsJ7bb40RWeerkExwir7iK96z2oZF7Wro=", + "subject": "CN=SecureTrust Domain Validation CA\\, Level 1,O=SecureTrust,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MHwxMjAwBgNVBAMTKVNlY3VyZVRydXN0IERvbWFpbiBWYWxpZGF0aW9uIENBLCBMZXZlbCAxMRQwEgYDVQQKEwtTZWN1cmVUcnVzdDEQMA4GA1UEBxMHQ2hpY2FnbzERMA8GA1UECBMISWxsaW5vaXMxCzAJBgNVBAYTAlVT", + "whitelist": false, + "attachment": { + "hash": "808680a0c2b3da12440074c2cecf8ba7b7a50796c469c901bf2bae7963658f5d", + "size": 1618, + "filename": "3MJ7kgH4AG-ip6CMzIBTeYe9X-9dSxFjCbnEIuYJlWA=.pem", + "location": "security-state-staging/intermediates/cfd0f4ac-b77c-4563-aeb5-243676326507.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3MJ7kgH4AG+ip6CMzIBTeYe9X+9dSxFjCbnEIuYJlWA=", + "crlite_enrolled": false, + "id": "9bc337d9-2ebf-4b9a-a405-087a0a528af3", + "last_modified": 1562108350078 + }, + { + "schema": 1562108339272, + "derHash": "GPzgkvo93kVUIjrnmwxJmiD0EUw2MHnSPY6v3JZzk2k=", + "subject": "CN=eMudhra ECC Domain Validation Secure Server CA,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIGVMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxNzA1BgNVBAMTLmVNdWRocmEgRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "201778bdc7d2f1a79fae87f89e7f3c87563fb5ae692e5bef6d8a17870194a3d0", + "size": 1345, + "filename": "-OTJ6M1upuT7uMaCSaNjlP_D0AMYrqjwUWCEue0H24E=.pem", + "location": "security-state-staging/intermediates/776486c5-65f5-4d53-a6e6-5d510795f51c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+OTJ6M1upuT7uMaCSaNjlP/D0AMYrqjwUWCEue0H24E=", + "crlite_enrolled": false, + "id": "7c0ed329-430c-4414-a659-6daec3ec0bc0", + "last_modified": 1562108340038 + }, + { + "schema": 1562108324028, + "derHash": "cjdtWQ2aZl05alHSrqkzRjj9SxPW9emTKYMi80Tyt54=", + "subject": "CN=TrustSign ECC DV CA,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MGMxCzAJBgNVBAYTAkZSMRAwDgYDVQQIEwdIZXJhdWx0MRQwEgYDVQQHEwtNb250cGVsbGllcjEOMAwGA1UEChMFWml3aXQxHDAaBgNVBAMTE1RydXN0U2lnbiBFQ0MgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c7de9b26efc4eb3648124f025156bdbc60d2ee4df9bc5f2b40e0f3889a11aa13", + "size": 1276, + "filename": "DuV9kinKceC5mT-5PkiRrL4kz_gqWVCOloO5GTLSA8I=.pem", + "location": "security-state-staging/intermediates/829cc9be-f9c4-483a-8537-7de6820e7357.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DuV9kinKceC5mT+5PkiRrL4kz/gqWVCOloO5GTLSA8I=", + "crlite_enrolled": false, + "id": "cd8781e3-476c-41fc-b598-6657d68d188e", + "last_modified": 1562108324796 + }, + { + "schema": 1562108298021, + "derHash": "HhdBoS642ivXbqlsBPUgNZg5cQ9iDoCVL0jdAkChLNg=", + "subject": "CN=GlobalSign Organization Validated CA - SHA256 - G4,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MGUxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTswOQYDVQQDEzJHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0ZWQgQ0EgLSBTSEEyNTYgLSBHNA==", + "whitelist": false, + "attachment": { + "hash": "f56fe012352f32771440b0a10b06d66c2f469fb83a06518748eaa15ab9720834", + "size": 1622, + "filename": "S7kwF_US-qCLAH7QPb4nX6Ms8I_NUy0GV9_-wVRxQe4=.pem", + "location": "security-state-staging/intermediates/30b37c7c-3a4d-4dfb-b2c4-e2d5477cf21a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "S7kwF/US+qCLAH7QPb4nX6Ms8I/NUy0GV9/+wVRxQe4=", + "crlite_enrolled": false, + "id": "8957e3a4-614b-4b99-a5c4-476646f00273", + "last_modified": 1562108298801 + }, + { + "schema": 1562025723206, + "derHash": "sM1q57niDsX4MP7gH2ZtXZDm4inQa8RqMKzO3+yIlkg=", + "subject": "CN=GlobalSign ECC CloudSSL CA - SHA384 - G3,O=GlobalSign nv-sa,C=BE", + "subjectDN": "MFsxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTEwLwYDVQQDEyhHbG9iYWxTaWduIEVDQyBDbG91ZFNTTCBDQSAtIFNIQTM4NCAtIEcz", + "whitelist": false, + "attachment": { + "hash": "de1af3b87a33aa08369966e57403b2c34643335f40db514a7afc6b97b106c162", + "size": 1394, + "filename": "jvkkZQCW-mhUvg-i2GTn-E3FaOYoVhJ1eCS-o6Q4yNk=.pem", + "location": "security-state-staging/intermediates/9cc4ce11-6d6f-43c7-bb98-74044eaff317.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jvkkZQCW+mhUvg+i2GTn+E3FaOYoVhJ1eCS+o6Q4yNk=", + "crlite_enrolled": false, + "id": "64bc9cda-86f9-4b20-91ec-08b77c65c922", + "last_modified": 1562025723952 + }, + { + "schema": 1562025696418, + "derHash": "gwy0Q+kSkB77qP+Pjhu86XAyqpEjjoGdi9bq/NWFLVA=", + "subject": "CN=SSLs.com ECC DV Secure Server CA,O=SSLs.com,L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRAwDgYDVQQHEwdQaG9lbml4MREwDwYDVQQKEwhTU0xzLmNvbTEpMCcGA1UEAxMgU1NMcy5jb20gRUNDIERWIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c384b3ab12ff6ea1b44a206f1afe0a9e294295d9b659f35735ad3b8f63df3777", + "size": 1293, + "filename": "dxXXi-wgMRVEkMxf8oNkW9S3knJacN3pyTKktpz6CO8=.pem", + "location": "security-state-staging/intermediates/e058b6fd-1598-40fa-bfc8-88653250ee88.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dxXXi+wgMRVEkMxf8oNkW9S3knJacN3pyTKktpz6CO8=", + "crlite_enrolled": false, + "id": "f9d6e124-3ddc-44f9-bbe1-dbb93eca419a", + "last_modified": 1562025697178 + }, + { + "schema": 1562025688012, + "derHash": "sFR/1Gj7tuM3/gGwEINOU7TFsSZJ7O52+uw4uGQPiHg=", + "subject": "CN=Sectigo RSA Pro Series Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGIMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxMDAuBgNVBAMTJ1NlY3RpZ28gUlNBIFBybyBTZXJpZXMgU2VjdXJlIFNlcnZlciBDQQ==", + "whitelist": false, + "attachment": { + "hash": "21a5a5d98c08860c8bbdac09ecacbf28db1bf7fd896db33c56327c2680b27302", + "size": 1699, + "filename": "xTMa5n3gNYAxLuPmUb1OHXP8yMoC1p7qRTc2QnDh85w=.pem", + "location": "security-state-staging/intermediates/680c172b-fc67-4690-b97d-d1dea9a9f9be.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xTMa5n3gNYAxLuPmUb1OHXP8yMoC1p7qRTc2QnDh85w=", + "crlite_enrolled": false, + "id": "dce12f32-7494-4a0b-a01f-b3b7fcaa5481", + "last_modified": 1562025688751 + }, + { + "schema": 1562025646434, + "derHash": "j3zEVemlUHgEEgZV1xORhiU+Q7AEIuc0JjoHadL4n30=", + "subject": "CN=ACCVCA-130,OU=PKIACCV,O=ACCV,C=ES", + "subjectDN": "MEMxEzARBgNVBAMMCkFDQ1ZDQS0xMzAxEDAOBgNVBAsMB1BLSUFDQ1YxDTALBgNVBAoMBEFDQ1YxCzAJBgNVBAYTAkVT", + "whitelist": false, + "attachment": { + "hash": "c7a672c81ca3a88f9f1026542e8a440443a8270bb0d1179ac7f197bfae1d5169", + "size": 2686, + "filename": "Y4wPdQMFev6SAz6Zy7McH45VYp5uADyuOVtFnivRtu4=.pem", + "location": "security-state-staging/intermediates/45659546-7b93-4a34-9576-89edb332e236.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Y4wPdQMFev6SAz6Zy7McH45VYp5uADyuOVtFnivRtu4=", + "crlite_enrolled": false, + "id": "fdbfe4a6-8fe3-4795-a5fa-49db19f65800", + "last_modified": 1562025647179 + }, + { + "schema": 1562025585399, + "derHash": "cJvk6rCjchI28osquA92/aJRMwsygvUV6l4LbHmuZyk=", + "subject": "CN=RUB-Chipcard CA G2,O=Ruhr-Universitaet Bochum,C=DE", + "subjectDN": "ME0xCzAJBgNVBAYTAkRFMSEwHwYDVQQKDBhSdWhyLVVuaXZlcnNpdGFldCBCb2NodW0xGzAZBgNVBAMMElJVQi1DaGlwY2FyZCBDQSBHMg==", + "whitelist": false, + "attachment": { + "hash": "5b3189496744b3baca10b9e288920f3c93f91a4cb9238792c8a84cb327282206", + "size": 1947, + "filename": "1LhrLd2v38EP1hLDpF6VhNqMjyRxu9cRaZL0r98r04o=.pem", + "location": "security-state-staging/intermediates/f9495b7a-e1af-4b97-bc3c-208ef9ce1a7e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1LhrLd2v38EP1hLDpF6VhNqMjyRxu9cRaZL0r98r04o=", + "crlite_enrolled": false, + "id": "25ac7183-b98c-44a4-b022-6d05aaf05e96", + "last_modified": 1562025586178 + }, + { + "schema": 1562025573252, + "derHash": "cLm6WVQSz4YUt2dH/Wg8yidZ9CZCFkg0++/diFBcTxw=", + "subject": "CN=emSign ECC Device CA - G3,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MG0xCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSIwIAYDVQQDExllbVNpZ24gRUNDIERldmljZSBDQSAtIEcz", + "whitelist": false, + "attachment": { + "hash": "d7af7c53238ed1dc9fffed6f3e5bce922f646c3ff9a60630c499d766352973cc", + "size": 1142, + "filename": "yETFaiH0jqwEN-yzu6pHbtmjgE5XzT3dvmi_Wzb31hM=.pem", + "location": "security-state-staging/intermediates/b6e31f0d-7208-4e42-8621-2efeaacbb876.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "yETFaiH0jqwEN+yzu6pHbtmjgE5XzT3dvmi/Wzb31hM=", + "crlite_enrolled": false, + "id": "c71399ad-5c58-4c11-9f7a-9147f50eae00", + "last_modified": 1562025573987 + }, + { + "schema": 1562025553755, + "derHash": "LoFrz4UtgwJF3cNTS8+FpTsLGZkaUlzTKGJv10pA/II=", + "subject": "CN=GIS CA,OU=CA,O=GAZINFORMSERVICE Company limited,C=RU", + "subjectDN": "MFYxCzAJBgNVBAYTAlJVMSkwJwYDVQQKDCBHQVpJTkZPUk1TRVJWSUNFIENvbXBhbnkgbGltaXRlZDELMAkGA1UECwwCQ0ExDzANBgNVBAMMBkdJUyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "4cedd57a38da64d6828aeb1b9ecfff34356abc080d413388c1ee191b7f2655dd", + "size": 1601, + "filename": "Bmyhj612-woocGRpHnc19r3dxL8Sz3qG4LDIr6VB3hY=.pem", + "location": "security-state-staging/intermediates/1c1b4e26-24a6-4505-9e5b-06e5e18d585a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Bmyhj612+woocGRpHnc19r3dxL8Sz3qG4LDIr6VB3hY=", + "crlite_enrolled": false, + "id": "03ccc153-2946-4d3d-be6c-b0881bebecf6", + "last_modified": 1562025554496 + }, + { + "schema": 1562025502424, + "derHash": "CsK2CG6xDbVXajobjrDkNECCwbfYMlBPy/laSATVGDQ=", + "subject": "CN=CERTSIGN FOR BANKING SIMPLE SSL TEST CA V3,OU=Certificat de test Test certificate,O=certSIGN,C=RO", + "subjectDN": "MIGDMQswCQYDVQQGEwJSTzERMA8GA1UEChMIY2VydFNJR04xLDAqBgNVBAsTI0NlcnRpZmljYXQgZGUgdGVzdCBUZXN0IGNlcnRpZmljYXRlMTMwMQYDVQQDEypDRVJUU0lHTiBGT1IgQkFOS0lORyBTSU1QTEUgU1NMIFRFU1QgQ0EgVjM=", + "whitelist": false, + "attachment": { + "hash": "301362625731dac5eb7b81495d7d67c27b4fb1d65a5cd95523b7a30f919868a6", + "size": 1658, + "filename": "BvkH8t9guVZzP7noiYDJM6l1ZztKvb9J8-54ik-6WFk=.pem", + "location": "security-state-staging/intermediates/de79005d-2e2f-4dc0-86e8-f105ed580da7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BvkH8t9guVZzP7noiYDJM6l1ZztKvb9J8+54ik+6WFk=", + "crlite_enrolled": false, + "id": "1489788e-e63f-4fdc-8e4e-1a06fd2d279c", + "last_modified": 1562025503177 + }, + { + "schema": 1562025496364, + "derHash": "ek/LoHn1vdzwp2w7A6N34VWlMAlHShs+uPNJYaU72pw=", + "subject": "CN=SECOM Passport for Member PUB CA8,OU=SECOM Passport for Member 2.0 PUB,O=SECOM Trust Systems CO.\\,LTD.,C=JP", + "subjectDN": "MIGMMQswCQYDVQQGEwJKUDElMCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU0VDT00gUGFzc3BvcnQgZm9yIE1lbWJlciAyLjAgUFVCMSowKAYDVQQDEyFTRUNPTSBQYXNzcG9ydCBmb3IgTWVtYmVyIFBVQiBDQTg=", + "whitelist": false, + "attachment": { + "hash": "c0684ee95f098f8e4e9d458872bee1f661763e16e08e9d6468a417a6866b21c0", + "size": 1695, + "filename": "knWHoMFrEpDYcH_bcs_miCxeOB-UDZ0Od9J53hcgFTo=.pem", + "location": "security-state-staging/intermediates/edd28ea1-49ec-45fe-9115-70331fce9330.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "knWHoMFrEpDYcH/bcs/miCxeOB+UDZ0Od9J53hcgFTo=", + "crlite_enrolled": false, + "id": "d9b97f0b-1f33-4059-afc0-b9b22421d0da", + "last_modified": 1562025497120 + }, + { + "schema": 1562025471549, + "derHash": "HZNobKQscDlPvcK8H5hGHRmHHCoAB4uBVJkxLtn2/gw=", + "subject": "CN=NetLock Üzleti Eat. (Class B Legal) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIG0MQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTFCMEAGA1UEAww5TmV0TG9jayDDnHpsZXRpIEVhdC4gKENsYXNzIEIgTGVnYWwpIFRhbsO6c8OtdHbDoW55a2lhZMOz", + "whitelist": false, + "attachment": { + "hash": "4fba3ae97dd053930e8514eb3fdcdbc3ccccbadc3b59f72c390b9e8968b894f8", + "size": 2198, + "filename": "_vwER7WMeggAoauXgB8U4heDYF3dmMm_S99ZfAayhZ8=.pem", + "location": "security-state-staging/intermediates/77e0aaee-29fa-489c-be2e-6dfde8a27857.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "/vwER7WMeggAoauXgB8U4heDYF3dmMm/S99ZfAayhZ8=", + "crlite_enrolled": false, + "id": "7174ab51-9ffe-493a-89bf-49620f52a798", + "last_modified": 1562025472308 + }, + { + "schema": 1562025422689, + "derHash": "eBrNIJ07hz8Uj12zHGgK2tztQCONjBvxotVTOR+l0PM=", + "subject": "CN=Certigna Entity Code Signing CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGIMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQRhDBROVFJGUi00ODE0NjMwODEwMDAzNjEoMCYGA1UEAwwfQ2VydGlnbmEgRW50aXR5IENvZGUgU2lnbmluZyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "08a562df9f7a4bc1c71eb903365f2523a36f48e8c67b7ab282f68e32bc060c3f", + "size": 2523, + "filename": "u3ZvFIlkZqOQDQbj9Abh3WXDOESr4pASOdoob9Oo2YI=.pem", + "location": "security-state-staging/intermediates/2eb117ef-3a3b-4901-9201-beb77a5e0cc8.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "u3ZvFIlkZqOQDQbj9Abh3WXDOESr4pASOdoob9Oo2YI=", + "crlite_enrolled": false, + "id": "0fb33d2f-f64b-456b-ac79-80910985aadd", + "last_modified": 1562025423424 + }, + { + "schema": 1562025388878, + "derHash": "8mA2cL7erR2XfWmS+mVU5spZW8UPOwP0Ftzw8g2sNsI=", + "subject": "CN=NETLOCK Trust Advanced Plus CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MGAxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMScwJQYDVQQDDB5ORVRMT0NLIFRydXN0IEFkdmFuY2VkIFBsdXMgQ0E=", + "whitelist": false, + "attachment": { + "hash": "540e442f30e4d92cc77c092424cd284037ee17d43f66a1f403d30efc07c2135e", + "size": 2077, + "filename": "mgqek_kZMpAjKUdlEROIUnDVMUVD0aenZnLSASM3iuY=.pem", + "location": "security-state-staging/intermediates/51f17241-57ee-4ea5-a20b-800c39580154.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "mgqek/kZMpAjKUdlEROIUnDVMUVD0aenZnLSASM3iuY=", + "crlite_enrolled": false, + "id": "b922d207-71dc-413f-b0fe-110a94999f23", + "last_modified": 1562025389609 + }, + { + "schema": 1562025373104, + "derHash": "+GhNKBK6mKUv6UUoxMsVI3ii1zqCiBCox7hSmHXGRnQ=", + "subject": "CN=Qualified Pseudonymous e-Szigno CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGLMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xMDAuBgNVBAMMJ1F1YWxpZmllZCBQc2V1ZG9ueW1vdXMgZS1Temlnbm8gQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "347542aaaf94389452ec241deb4e9b0659bd7e3fbfa082767360ebd2bdc7f554", + "size": 1776, + "filename": "Q-MM9a5IVpTJcjzEB7Wyr_KGLpH4y_1V4wt5h4NJjQc=.pem", + "location": "security-state-staging/intermediates/84a1e40e-aa66-419a-a6e0-b29ad9504f47.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Q+MM9a5IVpTJcjzEB7Wyr/KGLpH4y/1V4wt5h4NJjQc=", + "crlite_enrolled": false, + "id": "2bda9927-7780-4c65-80cf-88e65081a941", + "last_modified": 1562025373847 + }, + { + "schema": 1562025357329, + "derHash": "rUeIOkjIajRp4yyXKzmj7hVYBNMr9T/wAgALyhHSldk=", + "subject": "CN=NetLock Közjegyzői Eat. (Class A Legal) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIG5MQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTFHMEUGA1UEAww+TmV0TG9jayBLw7Z6amVneXrFkWkgRWF0LiAoQ2xhc3MgQSBMZWdhbCkgVGFuw7pzw610dsOhbnlraWFkw7M=", + "whitelist": false, + "attachment": { + "hash": "98303cceb1fcd3ad4d4b766a36ae284b0b6b241967294bab6fa6f499c160964b", + "size": 2203, + "filename": "EZuZGAg4_KSV7oRx6yGMpZK9lx1i1gH3UJCYV6YepzM=.pem", + "location": "security-state-staging/intermediates/00bf4a30-9a87-4885-a9ec-6c5c8d3d8a9a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "EZuZGAg4/KSV7oRx6yGMpZK9lx1i1gH3UJCYV6YepzM=", + "crlite_enrolled": false, + "id": "a705c138-e0f7-4581-9978-d55ab5621403", + "last_modified": 1562025358063 + }, + { + "schema": 1562025337064, + "derHash": "qlNFLViQaevZFCODT+woinisxqe51NDxQ9jJLguD2Ok=", + "subject": "CN=CERTSIGN FOR BANKING QUALIFIED DS PRODUCTION CA V3,OU=Certificat de productie Production certificate,O=certSIGN,C=RO", + "subjectDN": "MIGWMQswCQYDVQQGEwJSTzERMA8GA1UECgwIY2VydFNJR04xNzA1BgNVBAsMLkNlcnRpZmljYXQgZGUgcHJvZHVjdGllIFByb2R1Y3Rpb24gY2VydGlmaWNhdGUxOzA5BgNVBAMMMkNFUlRTSUdOIEZPUiBCQU5LSU5HIFFVQUxJRklFRCBEUyBQUk9EVUNUSU9OIENBIFYz", + "whitelist": false, + "attachment": { + "hash": "eaa0de7b4753356246d652e36cc56d60e8a923001719aa842e83a1fbfb036ca1", + "size": 1683, + "filename": "lgaybEWhquJ9CUpEl0ci8n7TT_mQu7ls6VLD9gC0u2g=.pem", + "location": "security-state-staging/intermediates/b16e7431-1a00-4dad-9c47-46be383bcfe4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "lgaybEWhquJ9CUpEl0ci8n7TT/mQu7ls6VLD9gC0u2g=", + "crlite_enrolled": false, + "id": "515bc65f-fec1-4f6b-98db-30c867438ca2", + "last_modified": 1562025337821 + }, + { + "schema": 1562025318342, + "derHash": "GJWNA6+0CWh6G8Jjhg0Nc1oloASrYODw5F1jM1h0N64=", + "subject": "CN=GoGetSSL Business Validation CA SHA2,OU=GoGetSSL Certification Authority,O=EnVers Group SIA,C=LV", + "subjectDN": "MIGCMQswCQYDVQQGEwJMVjEZMBcGA1UECgwQRW5WZXJzIEdyb3VwIFNJQTEpMCcGA1UECwwgR29HZXRTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLTArBgNVBAMMJEdvR2V0U1NMIEJ1c2luZXNzIFZhbGlkYXRpb24gQ0EgU0hBMg==", + "whitelist": false, + "attachment": { + "hash": "bbda288904603261a16251c2a9b280cba6dabc71d51dcff1d3c4f6b0447c1a3b", + "size": 1752, + "filename": "WhJb1Rhj6-UXftVUkeVV5CiOZwfWF4gdskEVvyWpNxM=.pem", + "location": "security-state-staging/intermediates/1ce075cb-f977-4ccb-b47f-6aedf5a3162b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WhJb1Rhj6+UXftVUkeVV5CiOZwfWF4gdskEVvyWpNxM=", + "crlite_enrolled": false, + "id": "bf5482ce-06f3-4976-a653-c8d2aced7246", + "last_modified": 1562025319075 + }, + { + "schema": 1562025298880, + "derHash": "rftaDyWTCRIrzR8DAmAHmIJRclX1s40z+Nq5c8tg4LY=", + "subject": "CN=FujiSSL ECC Domain Secure Site CA,O=Nijimo K.K.,L=Shibuya-ku,ST=Tokyo,C=JP", + "subjectDN": "MHQxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzETMBEGA1UEBxMKU2hpYnV5YS1rdTEUMBIGA1UEChMLTmlqaW1vIEsuSy4xKjAoBgNVBAMTIUZ1amlTU0wgRUNDIERvbWFpbiBTZWN1cmUgU2l0ZSBDQQ==", + "whitelist": false, + "attachment": { + "hash": "a956c6214bc00a3983c1ec59a5c5b4fef7452a3a94862da4f2b186ac1f059736", + "size": 1297, + "filename": "2rgbkrRXysWSf_k1kOj6EDKWKuKW-YVR38ExFhGbHac=.pem", + "location": "security-state-staging/intermediates/bfdd4e7d-8b03-4761-b3d1-ab4103ab8df3.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "2rgbkrRXysWSf/k1kOj6EDKWKuKW+YVR38ExFhGbHac=", + "crlite_enrolled": false, + "id": "f1917f9a-0c69-4510-a86a-08ce3db84cf9", + "last_modified": 1562025299610 + }, + { + "schema": 1562025292866, + "derHash": "dK/O7OBzFYe0XDzae8IQ/JF1AqMjQB6TLoZbw9eX56s=", + "subject": "CN=Atos TrustedRoot Issuing CA for WWE 2015,O=Atos,C=DE", + "subjectDN": "ME8xMTAvBgNVBAMMKEF0b3MgVHJ1c3RlZFJvb3QgSXNzdWluZyBDQSBmb3IgV1dFIDIwMTUxDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRF", + "whitelist": false, + "attachment": { + "hash": "0c5d1ea52b767af0853d6af41c73f1d188589a66e9c919e2858bc429241f4ffc", + "size": 1573, + "filename": "IA32tKC4ljNAfmi44SQR9xeWWNipPKjmUmjtzoT6pkY=.pem", + "location": "security-state-staging/intermediates/345d4a3f-ed22-4e47-af57-c4da221aa0cd.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IA32tKC4ljNAfmi44SQR9xeWWNipPKjmUmjtzoT6pkY=", + "crlite_enrolled": false, + "id": "69330f6b-0bbf-4528-a402-c89b6262cce4", + "last_modified": 1562025293606 + }, + { + "schema": 1562025276325, + "derHash": "2eRFsixvyzeylvzRMxSGVpZRqNuYBxdT/vxz0sl79zI=", + "subject": "CN=Qualified KET e-Szigno CA 2018,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJzAlBgNVBAMMHlF1YWxpZmllZCBLRVQgZS1Temlnbm8gQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "6b304876a14dedb4738c9754f4bc04eec54cc78cd3212181152603a8af2bcd15", + "size": 2231, + "filename": "oknJfA6HpIW3Guv29R8tlmS5hAgGjhhUfpBsZPu2Pds=.pem", + "location": "security-state-staging/intermediates/2563636d-ab39-462e-8e03-eeda6388ea2a.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "oknJfA6HpIW3Guv29R8tlmS5hAgGjhhUfpBsZPu2Pds=", + "crlite_enrolled": false, + "id": "0a5a0741-ed12-460d-8ae6-94cb14567a42", + "last_modified": 1562025277120 + }, + { + "schema": 1562025250085, + "derHash": "YK+eXznYc7I2vhQrxwbaVxhJrtf65jX8WhRhoM90WcU=", + "subject": "CN=Qualified e-Szigno Organization CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGIMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xHDAaBgNVBGEME1ZBVEhVLTIzNTg0NDk3LTItNDExMDAuBgNVBAMMJ1F1YWxpZmllZCBlLVN6aWdubyBPcmdhbml6YXRpb24gQ0EgMjAxNg==", + "whitelist": false, + "attachment": { + "hash": "dc2952bb2d73640240643f13334e8d01c2d9e778dde282b5c91837df5e8fae4e", + "size": 2601, + "filename": "WWpF25055wfjcZBv75P1HqErKnpSgGY5RThLltVZ-uo=.pem", + "location": "security-state-staging/intermediates/4a7927f3-053b-4fb2-b88a-f0952955b74b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "WWpF25055wfjcZBv75P1HqErKnpSgGY5RThLltVZ+uo=", + "crlite_enrolled": false, + "id": "e0a7a8fd-452f-48c1-a2d9-8dc311bf2cf1", + "last_modified": 1562025250818 + }, + { + "schema": 1562025247064, + "derHash": "yucvZtYa+5ppczjo9TWNgHG6+krk0nF8fmNftepD02U=", + "subject": "CN=CERTSIGN FOR BANKING SIMPLE SSL PRODUCTION CA V3,OU=Certificat de productie Production certificate,O=certSIGN,C=RO", + "subjectDN": "MIGUMQswCQYDVQQGEwJSTzERMA8GA1UECgwIY2VydFNJR04xNzA1BgNVBAsMLkNlcnRpZmljYXQgZGUgcHJvZHVjdGllIFByb2R1Y3Rpb24gY2VydGlmaWNhdGUxOTA3BgNVBAMMMENFUlRTSUdOIEZPUiBCQU5LSU5HIFNJTVBMRSBTU0wgUFJPRFVDVElPTiBDQSBWMw==", + "whitelist": false, + "attachment": { + "hash": "9c05002f5f1bd3a2e682040ad3ca45aa65b80fbea14f287d0b612d125aae2b87", + "size": 1678, + "filename": "dSEYX-UXMMv7WVy6VlLFnfcCjxt0nH-xwgRrYqDvmpA=.pem", + "location": "security-state-staging/intermediates/86434fba-a8fc-495a-9af1-290826501bfc.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "dSEYX+UXMMv7WVy6VlLFnfcCjxt0nH+xwgRrYqDvmpA=", + "crlite_enrolled": false, + "id": "9a546d40-8e30-4cfa-b711-298337b46008", + "last_modified": 1562025247802 + }, + { + "schema": 1562025210934, + "derHash": "9Rjwu3FlIfCib9tAwwT/m4L72+esvUa/DvI6GAGI61w=", + "subject": "CN=Apple Public Server RSA CA 2 - G1,O=Apple Inc.,C=US", + "subjectDN": "ME4xCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSowKAYDVQQDEyFBcHBsZSBQdWJsaWMgU2VydmVyIFJTQSBDQSAyIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "6dce70aef7f5bbcfc26a7b76bf285c6b1f15fd77bc1eb1294734cad5fb239722", + "size": 1634, + "filename": "GszFzuV0vZD9UeeCY9kvsItQltvQTZsjBfB9INf-GEk=.pem", + "location": "security-state-staging/intermediates/38288d50-1e5f-4fae-8d28-8249bf4ffc30.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "GszFzuV0vZD9UeeCY9kvsItQltvQTZsjBfB9INf+GEk=", + "crlite_enrolled": false, + "id": "cc783f4c-70a8-49b7-b913-26b52dfbccbc", + "last_modified": 1562025211668 + }, + { + "schema": 1562025170804, + "derHash": "Viy7y96+6zy1WUa9ziSMpKYj0rpud7Y7dU06Vx9n36I=", + "subject": "CN=Fraunhofer User CA - G02,OU=Fraunhofer Corporate PKI,O=Fraunhofer,L=Muenchen,ST=Bayern,C=DE", + "subjectDN": "MIGMMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjETMBEGA1UECgwKRnJhdW5ob2ZlcjEhMB8GA1UECwwYRnJhdW5ob2ZlciBDb3Jwb3JhdGUgUEtJMSEwHwYDVQQDDBhGcmF1bmhvZmVyIFVzZXIgQ0EgLSBHMDI=", + "whitelist": false, + "attachment": { + "hash": "432002444ca45071e8819aab9fc1a1245de7b320fabca6185adea7a8e46ddb6f", + "size": 2024, + "filename": "tI25lyfua-OITC74D7J6Z7TyLi_ZfzDhng7frVgmB8Q=.pem", + "location": "security-state-staging/intermediates/50b461d3-a1bf-4841-8d0b-db184a8ef60c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tI25lyfua+OITC74D7J6Z7TyLi/ZfzDhng7frVgmB8Q=", + "crlite_enrolled": false, + "id": "a40de547-e3aa-46b0-bb6e-12b28685b132", + "last_modified": 1562025171553 + }, + { + "schema": 1562025130010, + "derHash": "uVrlT4OOOr8LV6zMGxJm3GjHo/p3QBX6Eo1gzdGq4oA=", + "subject": "CN=TeliaSonera Class 1 CA v2,O=TeliaSonera,C=FI", + "subjectDN": "MEcxCzAJBgNVBAYTAkZJMRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEiMCAGA1UEAwwZVGVsaWFTb25lcmEgQ2xhc3MgMSBDQSB2Mg==", + "whitelist": false, + "attachment": { + "hash": "158d92505eb3b45b3e6b4f6ad2ec4039c093d61b3bfe9fd73d31588465d0a2da", + "size": 2532, + "filename": "7iiRGudmfB5TF0Xjv7u_ECYjflwjOVVvuWfXqeDL9v8=.pem", + "location": "security-state-staging/intermediates/caafc12b-ae2e-4f72-b6dc-37355ce3511f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "7iiRGudmfB5TF0Xjv7u/ECYjflwjOVVvuWfXqeDL9v8=", + "crlite_enrolled": false, + "id": "08d229d0-4919-4ab4-9baa-f3b645c75af6", + "last_modified": 1562025130755 + }, + { + "schema": 1562025114922, + "derHash": "qYyM7ZP5pDYxq+RXOGTgbFGSkAcj6X0e7SwNfGiy0Hk=", + "subject": "CN=Advanced Code Signing Class2 e-Szigno CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGOMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xHDAaBgNVBGEME1ZBVEhVLTIzNTg0NDk3LTItNDExNjA0BgNVBAMMLUFkdmFuY2VkIENvZGUgU2lnbmluZyBDbGFzczIgZS1Temlnbm8gQ0EgMjAxNg==", + "whitelist": false, + "attachment": { + "hash": "64067aab0874bc037193b6acdb923c5b3f8be5f6ce9e88eeefe2ebd7d958132b", + "size": 2609, + "filename": "xFdYXbpSYOTcKmpFvTeBEjUJFCvx6SNaltd9jtCg594=.pem", + "location": "security-state-staging/intermediates/8c58155b-730e-4bd1-a4a4-a9d2120083b4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "xFdYXbpSYOTcKmpFvTeBEjUJFCvx6SNaltd9jtCg594=", + "crlite_enrolled": false, + "id": "f3a00705-bb5f-44e5-aefb-59098a06f4e5", + "last_modified": 1562025115705 + }, + { + "schema": 1562025111941, + "derHash": "e88cihLuCyhUobQQcGUrAyXn0MILnETUrOnGQzh/FDE=", + "subject": "CN=Class3 KET e-Szigno CA 2018,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MHcxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxJDAiBgNVBAMMG0NsYXNzMyBLRVQgZS1Temlnbm8gQ0EgMjAxOA==", + "whitelist": false, + "attachment": { + "hash": "b8363cf53cbc0d53826cb443efcf270b8480d872fe5f866c272fe115ac7daa95", + "size": 2227, + "filename": "9cUMZguTNlS7SsqERbenmwQ2nFpq1XEyXFYq20LBYKQ=.pem", + "location": "security-state-staging/intermediates/1e730201-a474-4fd9-b805-d7a04496b3b5.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "9cUMZguTNlS7SsqERbenmwQ2nFpq1XEyXFYq20LBYKQ=", + "crlite_enrolled": false, + "id": "a393de41-fe5f-4381-951c-6e5fcf3cb617", + "last_modified": 1562025112674 + }, + { + "schema": 1562025107419, + "derHash": "WNehl/CabqVSuOprGlMYWgMKOtjVIiDADETj9FDk+5A=", + "subject": "CN=NETLOCK Trust CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MFIxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMRkwFwYDVQQDDBBORVRMT0NLIFRydXN0IENB", + "whitelist": false, + "attachment": { + "hash": "162d2ef1f5f7a945a8033cb0bb83b55948d4915840c9e5f98f83b3666f2aec62", + "size": 2060, + "filename": "JNdSz2IBG96B7N8LkEnVH6myMdo7MCXP8kJ-NPi5A64=.pem", + "location": "security-state-staging/intermediates/645b58d9-0779-4656-9fe8-ac5478c57d4f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "JNdSz2IBG96B7N8LkEnVH6myMdo7MCXP8kJ+NPi5A64=", + "crlite_enrolled": false, + "id": "9784e5cb-7f50-43e5-853d-bce88eb86d00", + "last_modified": 1562025108176 + }, + { + "schema": 1562025105176, + "derHash": "2C+H+T0x1fyBjdZr1Q5/MZrhefwcXQBUe2WOjrP0zlY=", + "subject": "CN=NETLOCK Trust Advanced CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MFsxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMSIwIAYDVQQDDBlORVRMT0NLIFRydXN0IEFkdmFuY2VkIENB", + "whitelist": false, + "attachment": { + "hash": "d2114d074fdd6d510393f4e4475f26c915693c1226b230324cdcc50ab4dec84b", + "size": 2073, + "filename": "vUlRVy11U4UcMGkV_J50oacQuaDzZcAvkc6eUz9yR9c=.pem", + "location": "security-state-staging/intermediates/ad1cf639-62d7-4f1c-ae01-76b1bce15693.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "vUlRVy11U4UcMGkV/J50oacQuaDzZcAvkc6eUz9yR9c=", + "crlite_enrolled": false, + "id": "4772163f-50e2-43cd-9d63-17d1e8176bd8", + "last_modified": 1562025105914 + }, + { + "schema": 1562025089412, + "derHash": "fOHbfRbpv+gGkyFKPXxiY6okeJ45kBfmnt5IAuz29xE=", + "subject": "CN=NETLOCK Trust Qualified SCD CA,O=NETLOCK Ltd.,L=Budapest,C=HU", + "subjectDN": "MGAxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTkVUTE9DSyBMdGQuMScwJQYDVQQDDB5ORVRMT0NLIFRydXN0IFF1YWxpZmllZCBTQ0QgQ0E=", + "whitelist": false, + "attachment": { + "hash": "efcaaf465785dda6f37158642f960522b0fbb28b652b8dd1d415feb8b3843a40", + "size": 2077, + "filename": "e2y-IcyyWA7mZKQQFWLVH34cdYOha_33T_SE2Yj7jp8=.pem", + "location": "security-state-staging/intermediates/da84341e-9c4a-411d-81b2-cba5084edf77.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e2y+IcyyWA7mZKQQFWLVH34cdYOha/33T/SE2Yj7jp8=", + "crlite_enrolled": false, + "id": "33b62fc5-1349-4933-8a0b-bac57c2adc38", + "last_modified": 1562025090175 + }, + { + "schema": 1562025022401, + "derHash": "Jb/bHF/izOBR7G378rsk54yS+WmxuzeGfa7fk9Gnrn4=", + "subject": "CN=SHECA Extended Validation SSL CA,O=UniTrust,C=CN", + "subjectDN": "MEsxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEpMCcGA1UEAwwgU0hFQ0EgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTU0wgQ0E=", + "whitelist": false, + "attachment": { + "hash": "42b647c52ed57012729814fa390a3a85ee801287cea6820f002c3e2d0f8cb467", + "size": 1959, + "filename": "J1sjCSbhps4X1wNpt4UhFDB8y_McQPnMa3S-sE4q0u0=.pem", + "location": "security-state-staging/intermediates/270af4bc-d20d-421d-baa5-121348e70203.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "J1sjCSbhps4X1wNpt4UhFDB8y/McQPnMa3S+sE4q0u0=", + "crlite_enrolled": false, + "id": "d83176fc-a4d2-461d-84e7-446080d64684", + "last_modified": 1562025023133 + }, + { + "schema": 1562025020924, + "derHash": "qfjQ7GD3LYrIefbjzDgOUACYvACAwHrFOfrmglQ//AI=", + "subject": "CN=UbiquiTLS™ DV ECC Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGAMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEmMCQGA1UEAwwdVWJpcXVpVExT4oSiIERWIEVDQyBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "1cf7f75e73f68c6177fe790e1d4a9c2fe8833648fe4b568093a3cd04201f3bce", + "size": 1293, + "filename": "PCnX_sRleYJP1z0cO3yKQbR6-NN9ywVZIPJzzytrEH8=.pem", + "location": "security-state-staging/intermediates/322e50ca-ba44-4200-89bd-bdf704bce13c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "PCnX/sRleYJP1z0cO3yKQbR6+NN9ywVZIPJzzytrEH8=", + "crlite_enrolled": false, + "id": "da8c9975-55f9-4df4-8621-e6a574681b68", + "last_modified": 1562025021653 + }, + { + "schema": 1562025007428, + "derHash": "klO/tmjz50OlJeSLX3UKimYDX4Bil8JfgTTcisljW9g=", + "subject": "CN=JCSI TLSSign Public CA,O=Cybertrust Japan Co.\\,Ltd.,C=JP", + "subjectDN": "MFIxCzAJBgNVBAYTAkpQMSIwIAYDVQQKExlDeWJlcnRydXN0IEphcGFuIENvLixMdGQuMR8wHQYDVQQDExZKQ1NJIFRMU1NpZ24gUHVibGljIENB", + "whitelist": false, + "attachment": { + "hash": "b6c1ba4e4707652fffa0f770954eae8f4d1bb58417950efc707c48d89d30379b", + "size": 2044, + "filename": "qU78sJ3fTvtBYPBxk_PfpIZWlA7ptK9MTP1UDx9LWdc=.pem", + "location": "security-state-staging/intermediates/27590e7a-01bd-4184-a4db-a987fa097a73.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "qU78sJ3fTvtBYPBxk/PfpIZWlA7ptK9MTP1UDx9LWdc=", + "crlite_enrolled": false, + "id": "2038b122-978e-4534-b273-996f254ac1a8", + "last_modified": 1562025008171 + }, + { + "schema": 1562024987874, + "derHash": "xW8PKGYYweew4RLJe57pb+tNceeUlsFR+h/oqM69Bs0=", + "subject": "CN=NetLock CodeSign CA,OU=Certification Services,O=NetLock Ltd.,L=Budapest,C=HU", + "subjectDN": "MHYxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEVMBMGA1UECgwMTmV0TG9jayBMdGQuMR8wHQYDVQQLDBZDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzMRwwGgYDVQQDDBNOZXRMb2NrIENvZGVTaWduIENB", + "whitelist": false, + "attachment": { + "hash": "a44be497ab772a1eb6e68b4bcd8d66f873262b6f8c218a461eb1a2916e56e4b6", + "size": 2109, + "filename": "e9x__uYSnevFMntPG5wGBZVLvAL83lTxZlVS9aGJ_-I=.pem", + "location": "security-state-staging/intermediates/3e50c772-6e6b-44a6-a9ca-2b159d8e0abb.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "e9x//uYSnevFMntPG5wGBZVLvAL83lTxZlVS9aGJ/+I=", + "crlite_enrolled": false, + "id": "5f1127b1-8e1f-4f9e-9512-92bcde094385", + "last_modified": 1562024988606 + }, + { + "schema": 1562024961614, + "derHash": "dJzFKc2qua4oWBR8SwAa4dW+BY/BZcPnSvlwQTHF4cE=", + "subject": "CN=nazwaSSL,OU=http://nazwa.pl,O=nazwa.pl S.A.,C=PL", + "subjectDN": "MFIxCzAJBgNVBAYTAlBMMRYwFAYDVQQKDA1uYXp3YS5wbCBTLkEuMRgwFgYDVQQLDA9odHRwOi8vbmF6d2EucGwxETAPBgNVBAMMCG5hendhU1NM", + "whitelist": false, + "attachment": { + "hash": "897f59110ae0cae8614eb96580a96fd7cb3225869d9da4c4530b1281bf753e8a", + "size": 1597, + "filename": "sPhpJMOX1_QEIjMN_nLy-YioxkO0pBGSK7c-Mj9wybw=.pem", + "location": "security-state-staging/intermediates/ca6a636d-2b32-4c6b-91b3-5dba3a3cbee6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "sPhpJMOX1/QEIjMN/nLy+YioxkO0pBGSK7c+Mj9wybw=", + "crlite_enrolled": false, + "id": "d51ec63a-9f60-4dd7-b26b-29c22b1fe537", + "last_modified": 1562024962345 + }, + { + "schema": 1562024939817, + "derHash": "xjVDcpo3DCaVK0fh0dGuqEyxsH8bD5ZML+3cUj/Xx5U=", + "subject": "CN=Advanced Class 2 e-Szigno CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGFMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xKjAoBgNVBAMMIUFkdmFuY2VkIENsYXNzIDIgZS1Temlnbm8gQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "whitelist": false, + "attachment": { + "hash": "b0b37eca6a850f10df2d2f0f58151a4dde91bb13056fb825499ebe669a693f7c", + "size": 1768, + "filename": "TldDn68qkoctgHZQtrMac-o1wa3jCibE3kU6UUsRCkk=.pem", + "location": "security-state-staging/intermediates/6cc7acac-4c52-41c2-aa25-5c066d9c2b1d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "TldDn68qkoctgHZQtrMac+o1wa3jCibE3kU6UUsRCkk=", + "crlite_enrolled": false, + "id": "5f2ec7fc-3106-4948-a7e0-9fa8387772ef", + "last_modified": 1562024940568 + }, + { + "schema": 1562024930729, + "derHash": "bqy5/qbMa/RZW0nCIsJgBNiTceuThK5I7Be5V4SUhlE=", + "subject": "CN=GENIOUS RSA Domain Validation Secure Server CA,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIGPMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxNzA1BgNVBAMTLkdFTklPVVMgUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "e1cfbd7e1821deb85427599da6afff700f4fe6bbd50a6617c015a36c7081220d", + "size": 2178, + "filename": "fpm00n1Dh8cybnaCvT8lZPwH6X-P_MKtYFzCSSgBorQ=.pem", + "location": "security-state-staging/intermediates/ce167ca1-2411-4863-820e-956dec419f09.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "fpm00n1Dh8cybnaCvT8lZPwH6X+P/MKtYFzCSSgBorQ=", + "crlite_enrolled": false, + "id": "34d6c620-1aeb-4e81-b738-dc70ae0d0f79", + "last_modified": 1562024931469 + }, + { + "schema": 1562024913272, + "derHash": "UEjbq/Qb2e99UAlAX0G2dxrIiN4+rJCzBY9VWWS35F8=", + "subject": "CN=eMudhra RSA Domain Validation Secure Server CA,O=eMudhra Technologies Limited,L=Bengaluru,ST=Karnataka,C=IN", + "subjectDN": "MIGVMQswCQYDVQQGEwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCZW5nYWx1cnUxJTAjBgNVBAoTHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxNzA1BgNVBAMTLmVNdWRocmEgUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "00d80076905e12a61bd293836d9efa12e45d6f81308bde3faf55d751f38d04b5", + "size": 2186, + "filename": "17G136GRsFqQDON1NPTY6yR_UA1QudGkzCd3wo7sczM=.pem", + "location": "security-state-staging/intermediates/38dffa99-f6ff-42b2-9d56-b21dbb250217.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "17G136GRsFqQDON1NPTY6yR/UA1QudGkzCd3wo7sczM=", + "crlite_enrolled": false, + "id": "d2e8bfc6-485a-4d20-9405-978f2701ee3f", + "last_modified": 1562024914008 + }, + { + "schema": 1562024890738, + "derHash": "vAh4y7xODa96naRkqxYmKiNb/a7TO5+VaboY/zSZdYA=", + "subject": "CN=TrustAsia EV SSL CA - C3,O=TrustAsia Technologies Inc.,C=CN", + "subjectDN": "MFYxCzAJBgNVBAYTAkNOMSQwIgYDVQQKDBtUcnVzdEFzaWEgVGVjaG5vbG9naWVzIEluYy4xITAfBgNVBAMMGFRydXN0QXNpYSBFViBTU0wgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "9a649e99d2774f04cb0fae10618205891e66d84669058b04828430921f6da774", + "size": 1691, + "filename": "3zvhr9FLox_6HK-CLvR_wE4XKQjtqDvzNPxSr0M8jeY=.pem", + "location": "security-state-staging/intermediates/3b83a2b4-a052-4057-8763-f1e172ed3331.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "3zvhr9FLox/6HK+CLvR/wE4XKQjtqDvzNPxSr0M8jeY=", + "crlite_enrolled": false, + "id": "c43c72cc-d8f7-448e-b348-fab85a1d5fad", + "last_modified": 1562024891481 + }, + { + "schema": 1562024861561, + "derHash": "KDymk5UwwbVQORUFGTY3iuNocZZ7A+TC58JD8Uln3rE=", + "subject": "CN=Advanced Code Signing Class3 e-Szigno CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGOMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xHDAaBgNVBGEME1ZBVEhVLTIzNTg0NDk3LTItNDExNjA0BgNVBAMMLUFkdmFuY2VkIENvZGUgU2lnbmluZyBDbGFzczMgZS1Temlnbm8gQ0EgMjAxNg==", + "whitelist": false, + "attachment": { + "hash": "12682a46c377cf0af1a09b1c08ef498ef4f1a96910c1f5442eb2b93d74c1a63c", + "size": 2609, + "filename": "IRoGHWUMgrIjO51yPcDjQ7km1QjZxz45uOOJou6I2pc=.pem", + "location": "security-state-staging/intermediates/fa8ed706-b516-4b1c-8125-839eea7101ec.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "IRoGHWUMgrIjO51yPcDjQ7km1QjZxz45uOOJou6I2pc=", + "crlite_enrolled": false, + "id": "0b5fdf3a-67b2-4466-b789-59a7d9a1889a", + "last_modified": 1562024862308 + }, + { + "schema": 1562024844261, + "derHash": "c2uZbTOWhHKcQ8s5fRuysvP0p4FqXjxdWJID+IXF1Hw=", + "subject": "CN=Certigna Identity Plus CA,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "MIGCMQswCQYDVQQGEwJGUjESMBAGA1UECgwJREhJTVlPVElTMRwwGgYDVQQLDBMwMDAyIDQ4MTQ2MzA4MTAwMDM2MR0wGwYDVQRhDBROVFJGUi00ODE0NjMwODEwMDAzNjEiMCAGA1UEAwwZQ2VydGlnbmEgSWRlbnRpdHkgUGx1cyBDQQ==", + "whitelist": false, + "attachment": { + "hash": "f191c1abecb352ea767687d0c74090ae61fb68eaf1b3c23628b26f00a9933fe1", + "size": 2519, + "filename": "ywhSp1oF34bfFheBzKO3KqbsXi-qQbyfXu94SMYBxwg=.pem", + "location": "security-state-staging/intermediates/ac1807a4-6a83-4902-a18f-cc1d275b7d34.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "ywhSp1oF34bfFheBzKO3KqbsXi+qQbyfXu94SMYBxwg=", + "crlite_enrolled": false, + "id": "e7d0d7ea-579e-4b38-893f-de28d80bc3ba", + "last_modified": 1562024844993 + }, + { + "schema": 1562024840500, + "derHash": "ATeet7hfaO3CRltCQ4CXaH6+Hq5JMZY5v7NC78PO+h0=", + "subject": "CN=CERTSIGN FOR BANKING QUALIFIED DS TEST CA V3,OU=Certificat de test Test certificate,O=certSIGN,C=RO", + "subjectDN": "MIGFMQswCQYDVQQGEwJSTzERMA8GA1UEChMIY2VydFNJR04xLDAqBgNVBAsTI0NlcnRpZmljYXQgZGUgdGVzdCBUZXN0IGNlcnRpZmljYXRlMTUwMwYDVQQDEyxDRVJUU0lHTiBGT1IgQkFOS0lORyBRVUFMSUZJRUQgRFMgVEVTVCBDQSBWMw==", + "whitelist": false, + "attachment": { + "hash": "bf0841437cff0c49b20954c90edf74119d9005723c6fac5a22f5b7e9949b7562", + "size": 1658, + "filename": "KWZVh2Uo0VJTsQaEe-fd3OibRGv0EhYSgphzM6kiYeY=.pem", + "location": "security-state-staging/intermediates/4e2829f2-e770-4a7b-bf5f-26dc917d6fd4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "KWZVh2Uo0VJTsQaEe+fd3OibRGv0EhYSgphzM6kiYeY=", + "crlite_enrolled": false, + "id": "9ad29fec-b9bc-433a-96c0-21647e2caaa0", + "last_modified": 1562024841250 + }, + { + "schema": 1562024797879, + "derHash": "TJGYtnNVCFh5mtJ0TMCDwboAJ+d9O4/W1Wz1NiDQmeI=", + "subject": "CN=emSign Device CA - G1,OU=emSign PKI,O=eMudhra Technologies Limited,C=IN", + "subjectDN": "MGkxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR4wHAYDVQQDExVlbVNpZ24gRGV2aWNlIENBIC0gRzE=", + "whitelist": false, + "attachment": { + "hash": "8956d067022b96359ff3d30c6bcb351e2ba5eb96b50837b275bedd8c2468cc4f", + "size": 1585, + "filename": "Ivw7wv5XpckfdFlOxj37SBDwiI0WAhkp1WOGE3r2BvU=.pem", + "location": "security-state-staging/intermediates/20d32f63-93e8-4cc0-9495-4924fe795c22.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ivw7wv5XpckfdFlOxj37SBDwiI0WAhkp1WOGE3r2BvU=", + "crlite_enrolled": false, + "id": "74cd2ebb-684e-40ec-a833-df95792630aa", + "last_modified": 1562024798607 + }, + { + "schema": 1562024792628, + "derHash": "cD6goXPiBCNV8d/rEHkpLUHqaMBGydRcxg3FQQEE9Hg=", + "subject": "CN=SSLs.com RSA DV Secure Server CA,O=SSLs.com,L=Phoenix,ST=Arizona,C=US", + "subjectDN": "MG8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRAwDgYDVQQHEwdQaG9lbml4MREwDwYDVQQKEwhTU0xzLmNvbTEpMCcGA1UEAxMgU1NMcy5jb20gUlNBIERWIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "9ff134c8ecf90893574b07ad8b3c077f8d5360967fc1094e63da8fb49c97f7e3", + "size": 2129, + "filename": "rvbtHIVTfnQ4hhhu4JyOGPoRZ3c9W-uCcbjlRAfUPXM=.pem", + "location": "security-state-staging/intermediates/a97db1f4-38f2-4210-9c42-fd651e18de13.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "rvbtHIVTfnQ4hhhu4JyOGPoRZ3c9W+uCcbjlRAfUPXM=", + "crlite_enrolled": false, + "id": "a11d7ecc-7398-48bb-89de-0e90aa0466c9", + "last_modified": 1562024793393 + }, + { + "schema": 1562024763424, + "derHash": "f6T/aOwEqZ11KNUIX5SQf00d0cU4G6zcgy7VyWAhRnY=", + "subject": "CN=Sectigo RSA Domain Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGPMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxNzA1BgNVBAMTLlNlY3RpZ28gUlNBIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "c5bc13ba28e3fad9bb69c25f294e5bebe315acc4947330a5cd0d2ce7928e2994", + "size": 2166, + "filename": "4a6cPehI7OG6cuDZka5NDZ7FR8a60d3auda-sKfg4Ng=.pem", + "location": "security-state-staging/intermediates/c2e0bcef-51d3-4306-93e1-9a40a8e77b7e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "4a6cPehI7OG6cuDZka5NDZ7FR8a60d3auda+sKfg4Ng=", + "crlite_enrolled": false, + "id": "6598d3bd-466d-49c7-b158-9ec07c6d53ac", + "last_modified": 1562024764178 + }, + { + "schema": 1562024709140, + "derHash": "QPR5Vb6j6nJsraxdP+YSl/57665bBQnh73spyDj0nSA=", + "subject": "CN=Atos TrustedRoot Client Issuing CA 2015,O=Atos,C=DE", + "subjectDN": "ME4xMDAuBgNVBAMMJ0F0b3MgVHJ1c3RlZFJvb3QgQ2xpZW50IElzc3VpbmcgQ0EgMjAxNTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "whitelist": false, + "attachment": { + "hash": "dd579a2edbed1e53c1f47b7a826616a0d492b19006df01fe4ca95fec7212d82c", + "size": 1569, + "filename": "tjnIUgKXmzH2ph7qdeq_3nm1xMqgGPtNJ8mM19Zcu-M=.pem", + "location": "security-state-staging/intermediates/f292bc25-f1b7-45ff-9df5-d56c9893901c.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "tjnIUgKXmzH2ph7qdeq/3nm1xMqgGPtNJ8mM19Zcu+M=", + "crlite_enrolled": false, + "id": "4f00eb7d-2e3c-402b-9dbb-97c1958dac20", + "last_modified": 1562024709874 + }, + { + "schema": 1562024693355, + "derHash": "4SHBaU2nN8F7hkSK7cYU7r15Rqe0uR+zACW2NgcCOeo=", + "subject": "CN=TU Dortmund Chipcard CA 2,O=Technische Universitaet Dortmund,C=DE", + "subjectDN": "MFwxCzAJBgNVBAYTAkRFMSkwJwYDVQQKDCBUZWNobmlzY2hlIFVuaXZlcnNpdGFldCBEb3J0bXVuZDEiMCAGA1UEAwwZVFUgRG9ydG11bmQgQ2hpcGNhcmQgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "0bc67e75f1a550023d6bf6415f76c4bbfa22728373423b60d4e7e69eaa7bc87b", + "size": 1967, + "filename": "-p4rAAgYbHPbwWrr_BlNozrzb4GHt5BSuZwneaDRlOs=.pem", + "location": "security-state-staging/intermediates/41136d39-9edf-4465-ab5f-af073bc975d6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+p4rAAgYbHPbwWrr/BlNozrzb4GHt5BSuZwneaDRlOs=", + "crlite_enrolled": false, + "id": "f972f74e-4035-4b85-850c-b92110d4c782", + "last_modified": 1562024694110 + }, + { + "schema": 1562024655926, + "derHash": "CA5+NrPH+pbsxn239NQc7OHRlEAa8ZakpHt5vcSXBXQ=", + "subject": "CN=FR03,OU=0002 48146308100036,O=DHIMYOTIS,C=FR", + "subjectDN": "ME4xCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlESElNWU9USVMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxDTALBgNVBAMMBEZSMDM=", + "whitelist": false, + "attachment": { + "hash": "0e878e377f71dc53a8708f58d1a029dab3d1ba19aae19818ecaa81b11ff7f98c", + "size": 2361, + "filename": "5aXOeu2kcFD4gRajILogB4nDzNx-0ZsGl_VZJJ4Bcck=.pem", + "location": "security-state-staging/intermediates/ed40f0b5-5f1f-4858-b1a9-0e0e454d7a2f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "5aXOeu2kcFD4gRajILogB4nDzNx+0ZsGl/VZJJ4Bcck=", + "crlite_enrolled": false, + "id": "f6f0bd28-e216-439a-b50e-0eb0e9c39fd4", + "last_modified": 1562024656668 + }, + { + "schema": 1562024619702, + "derHash": "EMP0Dpu+F7SPCzx8IpyrCiCHH86+zMz2orsyDwYAxV0=", + "subject": "CN=GENIOUS ECC Domain Validation Secure Server CA,O=Genious Communications,L=Marrakech,ST=Marrakech,C=MA", + "subjectDN": "MIGPMQswCQYDVQQGEwJNQTESMBAGA1UECBMJTWFycmFrZWNoMRIwEAYDVQQHEwlNYXJyYWtlY2gxHzAdBgNVBAoTFkdlbmlvdXMgQ29tbXVuaWNhdGlvbnMxNzA1BgNVBAMTLkdFTklPVVMgRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "whitelist": false, + "attachment": { + "hash": "f3a06e4e586746c718894b7aa2c2e9bd671253ab6b338d4df2627f4a2d14d8d7", + "size": 1337, + "filename": "1cqz0JJYdVUqvZUaW4P8qx8yIL49CzeX5G4oCYvH4Rk=.pem", + "location": "security-state-staging/intermediates/1a177cfa-33d9-4c35-9cdd-4a73c7973fc6.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "1cqz0JJYdVUqvZUaW4P8qx8yIL49CzeX5G4oCYvH4Rk=", + "crlite_enrolled": false, + "id": "7c266e3a-9521-4d0f-9d73-3c71c87172f8", + "last_modified": 1562024620466 + }, + { + "schema": 1562023939469, + "derHash": "0DSxh1G+4Qqq+UwvFDUNP2VOW5NNDdpZKzHlgYekiVI=", + "subject": "CN=emSign Device CA - C1,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFgxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEeMBwGA1UEAxMVZW1TaWduIERldmljZSBDQSAtIEMx", + "whitelist": false, + "attachment": { + "hash": "b85abdf53b134a2c1985785da850d5d78657dd1abcc923a85eeaf3cf7d536125", + "size": 1540, + "filename": "W7hjn_Iwl5OzSJsB1o-RIXEUKhNjgqs-85cgtFliENM=.pem", + "location": "security-state-staging/intermediates/7214b980-7213-4794-a274-6fe5036d46c7.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "W7hjn/Iwl5OzSJsB1o+RIXEUKhNjgqs+85cgtFliENM=", + "crlite_enrolled": false, + "id": "b06160c1-50d8-44e1-9e0d-33cf64458a5c", + "last_modified": 1562023940210 + }, + { + "schema": 1562023913211, + "derHash": "9bzzqnotFhoYISBPUlM3xs+UnnNa09M25HZLJadsKlo=", + "subject": "CN=TrustSign RSA DV CA,O=Ziwit,L=Montpellier,ST=Herault,C=FR", + "subjectDN": "MGMxCzAJBgNVBAYTAkZSMRAwDgYDVQQIEwdIZXJhdWx0MRQwEgYDVQQHEwtNb250cGVsbGllcjEOMAwGA1UEChMFWml3aXQxHDAaBgNVBAMTE1RydXN0U2lnbiBSU0EgRFYgQ0E=", + "whitelist": false, + "attachment": { + "hash": "e7641150501f3bf2c280ba7a371a352089b2a8c465adf46df5630b5d4e2d3556", + "size": 2113, + "filename": "jEvvAqAI3_5hDMtldisHHkOwAFpxjTzbkoL_r-GTKJU=.pem", + "location": "security-state-staging/intermediates/00ed82e9-9d56-4db6-bd16-63bf411f4e53.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "jEvvAqAI3/5hDMtldisHHkOwAFpxjTzbkoL/r+GTKJU=", + "crlite_enrolled": false, + "id": "f14daf92-a223-40b9-8d99-37d837b4cc5c", + "last_modified": 1562023913938 + }, + { + "schema": 1562023856875, + "derHash": "M3rFbzn7iHe58FJFVLdV0YNagH/8kFjfxt4bcH9pYSM=", + "subject": "CN=Certum Class I CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MHYxCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGjAYBgNVBAMTEUNlcnR1bSBDbGFzcyBJIENB", + "whitelist": false, + "attachment": { + "hash": "1c717a79efa6612fceeb4dd338a92b801131469bafdebec7a6c46a0ecbc22049", + "size": 2414, + "filename": "Wr7s1eee1m0JS_6tdr5Yu_rJOpqsjsb8IeNchmSjkL4=.pem", + "location": "security-state-staging/intermediates/475a2a7a-38fc-478b-ae6a-f0659a08775d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Wr7s1eee1m0JS/6tdr5Yu/rJOpqsjsb8IeNchmSjkL4=", + "crlite_enrolled": false, + "id": "b84bfc47-4590-4d6c-b85e-ceb5465f3c63", + "last_modified": 1562023857605 + }, + { + "schema": 1562023830806, + "derHash": "V0NCratO1573gY6CmQWOHAcawOQx/kIcq7Yt9NTp5MU=", + "subject": "CN=Atos TrustedRoot Issuing CA for Primetals 2015,O=Atos,C=DE", + "subjectDN": "MFUxNzA1BgNVBAMMLkF0b3MgVHJ1c3RlZFJvb3QgSXNzdWluZyBDQSBmb3IgUHJpbWV0YWxzIDIwMTUxDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRF", + "whitelist": false, + "attachment": { + "hash": "5ade36d683c37e2eead15d6a475ad0cea29346f577f4600faa9a32397f52bb2c", + "size": 1581, + "filename": "Ed79H7X5vQv3PR5-yCPH4ihzJhZFJlA-HsWA-xT7qAI=.pem", + "location": "security-state-staging/intermediates/8efad479-fc51-47cc-b880-7913a377291d.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Ed79H7X5vQv3PR5+yCPH4ihzJhZFJlA+HsWA+xT7qAI=", + "crlite_enrolled": false, + "id": "05eaf66f-1805-4619-9d87-bf6d7bfd6e36", + "last_modified": 1562023831534 + }, + { + "schema": 1562023819546, + "derHash": "u2YddQxTFmGBgHpomP1GQGXOWSmJhq1m2db//LvUc4o=", + "subject": "CN=NetLock Expressz Eat. (Class C Legal) Tanúsítványkiadó,OU=Tanúsítványkiadók (Certification Services),O=NetLock Kft.,L=Budapest,C=HU", + "subjectDN": "MIG1MQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTFDMEEGA1UEAww6TmV0TG9jayBFeHByZXNzeiBFYXQuIChDbGFzcyBDIExlZ2FsKSBUYW7DunPDrXR2w6FueWtpYWTDsw==", + "whitelist": false, + "attachment": { + "hash": "cf7c4f8886152f1548287e1f1314115b3db7ea5a596a43daa585a1a0e2f9e79c", + "size": 2198, + "filename": "-QUSW1ozNhu9biYeUP4qycv4BLZu3JGAci7jNGIo4PI=.pem", + "location": "security-state-staging/intermediates/408172d6-6f39-4e21-9ed3-d79b6dd424a4.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "+QUSW1ozNhu9biYeUP4qycv4BLZu3JGAci7jNGIo4PI=", + "crlite_enrolled": false, + "id": "9ccfdb3e-9fe2-4d0c-a088-5e0ba1450d2e", + "last_modified": 1562023820285 + }, + { + "schema": 1559867684823, + "derHash": "9nIr5ErNrltaizsLSvn0v15fvDlhz1JsvZdp0cbhSFk=", + "subject": "CN=Sectigo ECC Domain Validation Secure Server CA 2,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB", + "subjectDN": "MIGRMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxOTA3BgNVBAMTMFNlY3RpZ28gRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0EgMg==", + "whitelist": false, + "attachment": { + "hash": "ad8ca4ef91dbda033c17e8dbf198a410d147227e58e0981a280cafe1867ccc27", + "size": 1329, + "filename": "Js263FpzCgFxGxbtSYGcowFcKG4SjKAkg5R8oucb6bk=.pem", + "location": "security-state-staging/intermediates/42ec9fe3-884f-4466-82dc-da23d6c795ca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "Js263FpzCgFxGxbtSYGcowFcKG4SjKAkg5R8oucb6bk=", + "crlite_enrolled": false, + "id": "814ba8eb-43a2-4b3e-9b40-cf9cb1b220b3", + "last_modified": 1559867685643 + }, + { + "schema": 1559867457298, + "derHash": "0OOap9L6U1gQCKFdglxX0lvUkkeDRDH4oieinCgKHAw=", + "subject": "CN=Advanced Pseudonymous e-Szigno CA 2009,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGKMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xLzAtBgNVBAMMJkFkdmFuY2VkIFBzZXVkb255bW91cyBlLVN6aWdubyBDQSAyMDA5MR8wHQYJKoZIhvcNAQkBFhBpbmZvQGUtc3ppZ25vLmh1", + "whitelist": false, + "attachment": { + "hash": "28e4ef20b258d0cd99dc8ed4bbb7f34994c1e6d05e6098c32453f0ff79651007", + "size": 1776, + "filename": "32Xpx0UBvg8xkUwG8UN0XoE1SdCOILkoXK07BeFi7sc=.pem", + "location": "security-state-staging/intermediates/75abdb27-3ceb-4f0f-8207-29811aa50f7b.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "32Xpx0UBvg8xkUwG8UN0XoE1SdCOILkoXK07BeFi7sc=", + "crlite_enrolled": false, + "id": "c4f80d62-a618-4681-a631-871006820e7b", + "last_modified": 1559867458091 + }, + { + "schema": 1559867028528, + "derHash": "opwQSxAMOnkzRz5i5L5jcdZToWBNBO2q0CyVgGBlzuM=", + "subject": "CN=Advanced eIDAS Class2 e-Szigno CA 2016,O=Microsec Ltd.,L=Budapest,C=HU", + "subjectDN": "MIGHMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xHDAaBgNVBGEME1ZBVEhVLTIzNTg0NDk3LTItNDExLzAtBgNVBAMMJkFkdmFuY2VkIGVJREFTIENsYXNzMiBlLVN6aWdubyBDQSAyMDE2", + "whitelist": false, + "attachment": { + "hash": "bcff9a806f140a24db8d80fb25e53c8830ceeeeaad81257e333c72d97c0070d8", + "size": 2597, + "filename": "cnBaSiDjhxiyXuUvokBKGtmAg7IeqDnVkrWBvwTBrJU=.pem", + "location": "security-state-staging/intermediates/9fcf94ff-9fb8-4a24-a227-cc9c133af787.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "cnBaSiDjhxiyXuUvokBKGtmAg7IeqDnVkrWBvwTBrJU=", + "crlite_enrolled": false, + "id": "312fdb56-c1ae-466b-92aa-cce0b03303a3", + "last_modified": 1559867029341 + }, + { + "schema": 1559866984705, + "derHash": "EdkzDRojmFCN1Q0wlOsosbRJANmSj4wisl73p4G9tAM=", + "subject": "CN=SecureTrust Organization Validation CA\\, Level 1,O=SecureTrust,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MIGCMTgwNgYDVQQDEy9TZWN1cmVUcnVzdCBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBDQSwgTGV2ZWwgMTEUMBIGA1UEChMLU2VjdXJlVHJ1c3QxEDAOBgNVBAcTB0NoaWNhZ28xETAPBgNVBAgTCElsbGlub2lzMQswCQYDVQQGEwJVUw==", + "whitelist": false, + "attachment": { + "hash": "2fbb26d89a7df4499089ee4d2d7d54042f375261e865316c51caa6ea3d367c5f", + "size": 1642, + "filename": "naxobvdvRxrUbxomHwj81l5L4m0vZobqUIuxo5hwUQY=.pem", + "location": "security-state-staging/intermediates/db3a6f95-518e-4c87-a822-e3be67b873a0.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "naxobvdvRxrUbxomHwj81l5L4m0vZobqUIuxo5hwUQY=", + "crlite_enrolled": false, + "id": "0446d2af-40a8-49d1-b3fe-9e83881afe72", + "last_modified": 1559866985488 + }, + { + "schema": 1559866972593, + "derHash": "3SM1XGHVmXBCMARyl5XK73ev2zl2enlXQNTgTLYVipk=", + "subject": "CN=SecureTrust Extended Validation CA\\, Level 1,O=SecureTrust,L=Chicago,ST=Illinois,C=US", + "subjectDN": "MH4xNDAyBgNVBAMTK1NlY3VyZVRydXN0IEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EsIExldmVsIDExFDASBgNVBAoTC1NlY3VyZVRydXN0MRAwDgYDVQQHEwdDaGljYWdvMREwDwYDVQQIEwhJbGxpbm9pczELMAkGA1UEBhMCVVM=", + "whitelist": false, + "attachment": { + "hash": "d0105c5ecad0b2ab41480518899e2653e240f06b4a8927ca3d5283c60f63c72e", + "size": 1634, + "filename": "DfrsmfuM5QjUozBeqZ75qybJwEDwRIG64DTyM3Kx5Wg=.pem", + "location": "security-state-staging/intermediates/b46f9bc3-cb55-4302-b52b-e519808c0128.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "DfrsmfuM5QjUozBeqZ75qybJwEDwRIG64DTyM3Kx5Wg=", + "crlite_enrolled": false, + "id": "110bade2-6494-40e6-869c-71f45a348718", + "last_modified": 1559866973381 + }, + { + "schema": 1559866970155, + "derHash": "zx6hXcnAWrxyrw5ixI2TQ0rgJxsapDGL41RBJtJLYYQ=", + "subject": "CN=Certum Extended Validation CA,OU=Certum Certification Authority,O=Unizeto Technologies S.A.,C=PL", + "subjectDN": "MIGCMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQDEx1DZXJ0dW0gRXh0ZW5kZWQgVmFsaWRhdGlvbiBDQQ==", + "whitelist": false, + "attachment": { + "hash": "e34ed85d8f7cd8d8572bc535cef388fdeca726b0b1f570db12ecb2c658c3cfaf", + "size": 1727, + "filename": "M9jfvUTOEXqakcClNZh0xICWc12quCpR9R8l41FetpI=.pem", + "location": "security-state-staging/intermediates/aa0b4687-ec1c-483c-878f-31363ebb0565.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "M9jfvUTOEXqakcClNZh0xICWc12quCpR9R8l41FetpI=", + "crlite_enrolled": false, + "id": "52a1bed3-2ae3-4c96-8566-936ef8a25df3", + "last_modified": 1559866970939 + }, + { + "schema": 1559866925659, + "derHash": "PUUR0KgKqUmm2ZslOhc0cXl8RFkYemMp5zbDfLVJPkY=", + "subject": "CN=emSign ECC Device CA - C3,OU=emSign PKI,O=eMudhra Inc,C=US", + "subjectDN": "MFwxCzAJBgNVBAYTAlVTMRMwEQYDVQQLEwplbVNpZ24gUEtJMRQwEgYDVQQKEwtlTXVkaHJhIEluYzEiMCAGA1UEAxMZZW1TaWduIEVDQyBEZXZpY2UgQ0EgLSBDMw==", + "whitelist": false, + "attachment": { + "hash": "01af63ee9e2b37ec4c8865ff21ae0e4cdb096992d7907932ef24acf8eedfdc46", + "size": 1098, + "filename": "bpFe8tgoaUQ4GlAAa0fUVmVkLEKAO1rs9xELs8ndVnE=.pem", + "location": "security-state-staging/intermediates/1d4a8c13-e9b9-41ba-ab80-6e38c400e23f.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "bpFe8tgoaUQ4GlAAa0fUVmVkLEKAO1rs9xELs8ndVnE=", + "crlite_enrolled": false, + "id": "e7091d6a-54ab-4e1a-ace6-0cf33cabe180", + "last_modified": 1559866926464 + }, + { + "schema": 1559866615732, + "derHash": "0fJlasg4JzmjsIfEerXKuUWjLxYrYUnDCHg8fgaviug=", + "subject": "CN=TeliaSonera Email CA v4,O=TeliaSonera,C=SE", + "subjectDN": "MEUxCzAJBgNVBAYTAlNFMRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEgMB4GA1UEAwwXVGVsaWFTb25lcmEgRW1haWwgQ0EgdjQ=", + "whitelist": false, + "attachment": { + "hash": "50a551c554a5b7ee132109ae7bf90dc4b34f3526462e40e5e30612608a8a6257", + "size": 2528, + "filename": "t0Th3Tcrbh3qBKdPTiMoiViMapdZEGG29yWyhWFOnu8=.pem", + "location": "security-state-staging/intermediates/a4ac8e27-46ae-4971-8848-eb9b6cfdfa8e.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "t0Th3Tcrbh3qBKdPTiMoiViMapdZEGG29yWyhWFOnu8=", + "crlite_enrolled": false, + "id": "f80be745-7d54-490b-8882-57a4fbcdda3f", + "last_modified": 1559866616522 + }, + { + "schema": 1559866415399, + "derHash": "mUQsj4OjxQkMpQwcCx3ksy7UGP8Kp8MkDpEjAVnz578=", + "subject": "CN=GDCA TrustAUTH R4 IV SSL CA,O=Global Digital Cybersecurity Authority Co.\\, Ltd.,C=CN", + "subjectDN": "MG4xCzAJBgNVBAYTAkNOMTkwNwYDVQQKDDBHbG9iYWwgRGlnaXRhbCBDeWJlcnNlY3VyaXR5IEF1dGhvcml0eSBDby4sIEx0ZC4xJDAiBgNVBAMMG0dEQ0EgVHJ1c3RBVVRIIFI0IElWIFNTTCBDQQ==", + "whitelist": false, + "attachment": { + "hash": "c9c27dde9ab53fa7533998d21fe575c8d337e49fedce536ed71cebe74d6a82cb", + "size": 2052, + "filename": "h9ljtPIsvf1wnqfE3LU5OJSEC20BaNan3CxC8wbqBnI=.pem", + "location": "security-state-staging/intermediates/b5bec3ea-ca92-493c-af9a-8fe3ef022434.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "h9ljtPIsvf1wnqfE3LU5OJSEC20BaNan3CxC8wbqBnI=", + "crlite_enrolled": false, + "id": "ae50a764-96b4-4d80-b152-ecb7f2d01363", + "last_modified": 1559866416201 + }, + { + "schema": 1559865883837, + "derHash": "7toVugALAG6tSaIbvnafO6bOdckknwEU2N2ILfwPLBs=", + "subject": "CN=GoGetSSL Extended Validation CA SHA2,OU=GoGetSSL Certification Authority,O=EnVers Group SIA,C=LV", + "subjectDN": "MIGCMQswCQYDVQQGEwJMVjEZMBcGA1UECgwQRW5WZXJzIEdyb3VwIFNJQTEpMCcGA1UECwwgR29HZXRTU0wgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLTArBgNVBAMMJEdvR2V0U1NMIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgU0hBMg==", + "whitelist": false, + "attachment": { + "hash": "3f571450d63eacb78406320bff9a534186fb3c5714e38ef5e9421cc30dd0f091", + "size": 1752, + "filename": "BPECx6wNq90p8gu0YpJ1Fc0Dx7h0SmkteDK5xDmXT5Y=.pem", + "location": "security-state-staging/intermediates/69c28a8d-33ee-4e47-bb72-44abfc083dca.pem", + "mimetype": "application/x-pem-file" + }, + "pubKeyHash": "BPECx6wNq90p8gu0YpJ1Fc0Dx7h0SmkteDK5xDmXT5Y=", + "crlite_enrolled": false, + "id": "30f7ecb1-568f-400d-8562-fef6ac31428f", + "last_modified": 1559865884636 + } + ], + "timestamp": 1710946623378 +} diff --git a/services/settings/dumps/security-state/moz.build b/services/settings/dumps/security-state/moz.build new file mode 100644 index 0000000000..9133cd4e3e --- /dev/null +++ b/services/settings/dumps/security-state/moz.build @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_TARGET_FILES.defaults.settings["security-state"] += [ + "intermediates.json", + "onecrl.json", +] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DIST_SUBDIR = "browser" diff --git a/services/settings/dumps/security-state/onecrl.json b/services/settings/dumps/security-state/onecrl.json new file mode 100644 index 0000000000..48e394966a --- /dev/null +++ b/services/settings/dumps/security-state/onecrl.json @@ -0,0 +1,24125 @@ +{ + "data": [ + { + "schema": 1709917272661, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884400", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRowGAYDVQQDExF2VHJ1cyBFQ0MgUm9vdCBDQQ==", + "serialNumber": "baFk8S+rVizrFzxGvKqfqQvu0kY=", + "id": "4a2a8154-e012-418f-8557-a9c3bb5ef380", + "last_modified": 1710189695302 + }, + { + "schema": 1709717394495, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884400", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRowGAYDVQQDExF2VHJ1cyBFQ0MgUm9vdCBDQQ==", + "serialNumber": "F7oJqB+ONoNowl5eHOOl8oSDne0=", + "id": "dd781eef-2b60-4ff1-a1d1-2c65c31915fa", + "last_modified": 1710189695297 + }, + { + "schema": 1709917272890, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884400", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEMxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRYwFAYDVQQDEw12VHJ1cyBSb290IENB", + "serialNumber": "VmPk4uhKrU+Ar6D+FKt4T+wADJs=", + "id": "4c28a487-8372-4061-9be2-a4f5090ab231", + "last_modified": 1710189695292 + }, + { + "schema": 1709917272733, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884400", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxJzAlBgNVBAMTHklkZW5UcnVzdCBDb21tZXJjaWFsIFJvb3QgQ0EgMQ==", + "serialNumber": "fgr3g+MTaN10FgQg4sbqcA==", + "id": "2fe0159b-6ae2-4f7b-a92b-63c27e5056fa", + "last_modified": 1710189695287 + }, + { + "schema": 1709917272813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1884400", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEMxCzAJBgNVBAYTAkNOMRwwGgYDVQQKExNpVHJ1c0NoaW5hIENvLixMdGQuMRYwFAYDVQQDEw12VHJ1cyBSb290IENB", + "serialNumber": "H6Q9cmNee/OBNe7znM/J3cNHeYY=", + "id": "f5b7873a-fe31-44a0-b75c-fbbeebfc5a35", + "last_modified": 1710189695282 + }, + { + "schema": 1709632813150, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1879046", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#10)", + "name": "Information Security Certification Authority", + "created": "" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "MgQS30wvsQLzmAyqdrphCuYshJI=", + "id": "2e32f465-fab4-4365-94d8-9a83041e660a", + "last_modified": 1709673810455 + }, + { + "schema": 1701882093709, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+9NZ3e1owffcJjCxzZg==", + "id": "65d83e3c-5ab9-4590-95b2-ee177b89547f", + "last_modified": 1701989913617 + }, + { + "schema": 1701882093644, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2Mg==", + "serialNumber": "AYvTImnKqrjuZSIFUekO", + "id": "301b43d3-faae-4eea-b24e-02b0cc7be59b", + "last_modified": 1701989913611 + }, + { + "schema": 1701882093777, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "serialNumber": "e/t0kC1D4yfk2h1ZMQtnWQ==", + "id": "e173a743-042f-4c36-8e50-b98bdff3dcc1", + "last_modified": 1701989913607 + }, + { + "schema": 1701882093585, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQQ==", + "serialNumber": "IAYFFnADFaURrma5fvkV", + "id": "565e5f90-0c48-487d-9e23-9c964b57d89d", + "last_modified": 1701989913602 + }, + { + "schema": 1701882093526, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "VGFdl5j7DGyBksFR1YNATA==", + "id": "454aeab5-3d95-4887-968d-423ab4af7153", + "last_modified": 1701989913597 + }, + { + "schema": 1701882093343, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMw==", + "serialNumber": "HNeGYx7Az7oLUvqbXgKHsQ==", + "id": "5576448f-2e6b-4c82-a8f0-dbf690002993", + "last_modified": 1701989913592 + }, + { + "schema": 1701882093467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "e/t0YVkawWMuSUOpB25CTA==", + "id": "f3114136-5675-454c-90b5-e583e6d1070a", + "last_modified": 1701989913587 + }, + { + "schema": 1701882093281, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+8/plc0m7j0VAXMe3Uw==", + "id": "7315548e-19d6-4de7-ab7e-8c0926fdc7c7", + "last_modified": 1701989913582 + }, + { + "schema": 1701882093406, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+8BfgHWec1wpYnjJfWw==", + "id": "6c106e28-6bbd-4687-acb1-9827e8d00e80", + "last_modified": 1701989913578 + }, + { + "schema": 1701882093220, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "serialNumber": "e/t0l4vp8EFvTZYIZx6sCA==", + "id": "487a9ee7-501f-41af-a749-a32c1a84b460", + "last_modified": 1701989913573 + }, + { + "schema": 1701882093159, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "ebn+/Yqlu90gx9UvnLIR3A==", + "id": "0dabbda1-fccc-4655-92cd-2c522df8ddc9", + "last_modified": 1701989913568 + }, + { + "schema": 1701882092970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "ebn++YolR0KyD62h0lLRCA==", + "id": "da8fa73c-a137-40c3-80fc-1e43bc3a3e30", + "last_modified": 1701989913562 + }, + { + "schema": 1701882093097, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AMPUKMN4jsXBS0QHU7GpitY=", + "id": "37643ddd-4c01-4ef6-a947-956b1f922ae7", + "last_modified": 1701989913557 + }, + { + "schema": 1701882092905, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "ebn/AqIzpseA3+sPCEyJNQ==", + "id": "b8851d23-c0fe-41b0-b0ba-1fd7272e18c4", + "last_modified": 1701989913551 + }, + { + "schema": 1701882093032, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn++HV415u57d5JnqCe4Q==", + "id": "df1a5381-0327-4dc6-932d-cbc159e0e13c", + "last_modified": 1701989913546 + }, + { + "schema": 1701882092845, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "serialNumber": "e/t0atOh/Uwt/9H5vCMEBQ==", + "id": "2834fc99-2082-468d-9b04-871e343d005c", + "last_modified": 1701989913542 + }, + { + "schema": 1701882092782, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "ebn+/9oibkBARSOKUyIxWg==", + "id": "30de3b62-e83e-42ab-aeaf-2843347900ca", + "last_modified": 1701989913537 + }, + { + "schema": 1701771234578, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQQ==", + "serialNumber": "IAYFFnADGFeSYBrLdeEn", + "id": "9fa11b1f-6221-4d3d-94d0-505a1e762846", + "last_modified": 1701989913532 + }, + { + "schema": 1701882093846, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "e/t0Wa+agKTlghDnapXPGQ==", + "id": "33a1ac70-1262-4ee7-b69a-25b437e4ea31", + "last_modified": 1701989913527 + }, + { + "schema": 1701882092657, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "e/t0ZHYGHEhBodtF6A4M1A==", + "id": "bd9b826f-8c96-4520-8423-2b74b60fa56e", + "last_modified": 1701989913522 + }, + { + "schema": 1701882092719, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "e/t0aKogKAc6O5JxqkuL3g==", + "id": "5cd67dd7-52d3-48c6-a98d-dafe4fbd52a3", + "last_modified": 1701989913518 + }, + { + "schema": 1701882093919, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1868611", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "serialNumber": "e/t0hWXMQylnjZPQNaIDwg==", + "id": "99ed2fd4-470a-4f90-b8da-b75fea3d6466", + "last_modified": 1701989913513 + }, + { + "schema": 1699660807926, + "details": { + "bug": "1864724", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#9)", + "name": "Information Security Certification Authority", + "created": "2023-11-14T23:39:21Z" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "RR0ycJaMGZl88uTjuXcI7+J28xg=", + "id": "4c0da88a-8e3a-4cf7-ad6e-e162ed953867", + "last_modified": 1700154823756 + }, + { + "schema": 1695233106940, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AIEIODzAB3XEDG1za+Mwiw==", + "id": "c61e3ace-0d80-4231-bb38-1b43eda1a555", + "last_modified": 1695656154676 + }, + { + "schema": 1695233106875, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AJBuE8344SrHVZEVqVicPc0=", + "id": "020dd8e8-f2f3-4df3-9105-c8fe8025dc48", + "last_modified": 1695656154672 + }, + { + "schema": 1695233106813, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "ZVQgp61GpFI5YQ4bRYdLwQ==", + "id": "77bb5e0c-284c-44c9-9214-6cf56f99d884", + "last_modified": 1695656154666 + }, + { + "schema": 1695233106625, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "IyygpSElAUZRSSMeuADPTQ==", + "id": "e7a8a28c-c37a-4ba4-9838-8758a71d94a9", + "last_modified": 1695656154662 + }, + { + "schema": 1695233106687, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "WAE9mhEYoCYAhMiVAzSf2w==", + "id": "ffcc30b7-72fd-4685-8b88-b699f8b5c79b", + "last_modified": 1695656154658 + }, + { + "schema": 1695233106750, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "JGSVSBLLB3o3k9sPvSI4mw==", + "id": "3654949f-c25a-406d-8b8d-df8b4034cb4f", + "last_modified": 1695656154653 + }, + { + "schema": 1695233106559, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkNOMSYwJAYDVQQKDB1CRUlKSU5HIENFUlRJRklDQVRFIEFVVEhPUklUWTEdMBsGA1UEAwwUQkpDQSBHbG9iYWwgUm9vdCBDQTI=", + "serialNumber": "TpwBfSjFJUcBb/KpgVpdYQ==", + "id": "39178428-e554-40e7-a140-87ba0b0be0c7", + "last_modified": 1695656154649 + }, + { + "schema": 1695233106361, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "APodqurJs6X6V5gLmXTaMQ==", + "id": "8ca2eb18-2c8c-4b4b-b563-602528e055b8", + "last_modified": 1695656154642 + }, + { + "schema": 1695233106424, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "Bw1L/czFd0GTl4v2bxUqqw==", + "id": "45b56260-4f0b-4282-bb16-4eb5616f4ac0", + "last_modified": 1695656154637 + }, + { + "schema": 1695233106233, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "JvosL2pTlZtYGv5T23DhhQ==", + "id": "62ce964e-afbf-4559-a423-0e1268199f97", + "last_modified": 1695656154631 + }, + { + "schema": 1695233106494, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AIyKF6j474AWe01PLn9A6I8=", + "id": "44401e05-83ee-4123-aa97-657dac5af39f", + "last_modified": 1695656154625 + }, + { + "schema": 1695233106297, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AO9kCADxUMyMns2lqg7Meks=", + "id": "8df8c0c8-48e4-4e4f-9686-f48878240126", + "last_modified": 1695656154620 + }, + { + "schema": 1695233106167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMg==", + "serialNumber": "AhnBWsAlobClwdnVAQ==", + "id": "ad321118-51d7-4544-9a57-53d0a3eff4ea", + "last_modified": 1695656154615 + }, + { + "schema": 1695233106102, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMw==", + "serialNumber": "AhZo2NZbxDIOW45edg==", + "id": "311a723a-6087-4413-a7ff-aa611498b7d2", + "last_modified": 1695656154609 + }, + { + "schema": 1695233105962, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "a8MYySrNF2PrQchvr0f3", + "id": "5883c669-dc18-4a23-bffc-377abbdf3510", + "last_modified": 1695656154605 + }, + { + "schema": 1695128491981, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "AhZo8c0KKo+EfYqtNA==", + "id": "093bf38b-89f3-4a90-8a2b-6135f27d2d9b", + "last_modified": 1695656154600 + }, + { + "schema": 1695233106030, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1854222", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0E=", + "serialNumber": "QAE0sQ0AAAAAAAAMzpf7sg==", + "id": "372aab81-e6cd-4a31-973c-a11deb1615d9", + "last_modified": 1695656154596 + }, + { + "schema": 1687366866913, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZpZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "Bn+UV0u3B10+SJZceDIkqnVP7Q==", + "id": "88a4c6f0-b0d5-4c45-a2d3-f5f300e17530", + "last_modified": 1687555476962 + }, + { + "schema": 1687366866850, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "ATM4DqRE5RnVV9xRbtyxIA==", + "id": "73e74bab-993d-48e2-87e7-c1bea0f2fbdd", + "last_modified": 1687555476959 + }, + { + "schema": 1687366866789, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "D/c121neYxk/D6cHkJnJag==", + "id": "a4bd1805-6d9a-48c8-a826-4b4c3c50803e", + "last_modified": 1687555476955 + }, + { + "schema": 1687366866722, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "A6mtV7QV7U9QJ/wpP9+ZSA==", + "id": "9850ff6e-2aa1-47fd-aeb8-b0cce7d7e09c", + "last_modified": 1687555476951 + }, + { + "schema": 1687366866262, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDQ=", + "serialNumber": "Bn+UV1qIYqkHLjI5w3zroSdOGA==", + "id": "32bf2581-8b69-4354-a3da-ca680f4bbe43", + "last_modified": 1687555476948 + }, + { + "schema": 1687366866202, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxTch2GE4M", + "id": "dc13a853-9a6f-452d-ae5c-55ae3a619e3a", + "last_modified": 1687555476944 + }, + { + "schema": 1687366866135, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSNA==", + "serialNumber": "Af6lgUR+O/07uBwkmA==", + "id": "c5da3220-66e8-462b-9f51-35b9f9100843", + "last_modified": 1687555476940 + }, + { + "schema": 1687366865870, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "ME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx", + "serialNumber": "PEPNzdzyOwBPDqBz/D6jiQ==", + "id": "07bd2323-afb5-4fa9-a5af-4cccc128300e", + "last_modified": 1687555476937 + }, + { + "schema": 1687366866071, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "bZV3eq2xb5OVSxd1Dy3UZQ==", + "id": "6d7f1461-1f08-43ea-8d66-455e1f7b9d31", + "last_modified": 1687555476933 + }, + { + "schema": 1687366865938, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=", + "serialNumber": "AMPgD58ALsVZugLEpfJgDF8=", + "id": "6481361a-431c-46ca-b2c3-b7a64f64d15c", + "last_modified": 1687555476929 + }, + { + "schema": 1687366865605, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "HvRxsAh7DvnMRPr2huB1Tg==", + "id": "8b5c0517-8a0a-426b-9664-af01ab04378e", + "last_modified": 1687555476925 + }, + { + "schema": 1687366865669, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "DXXttb+BXjVafdzhhw52Dw==", + "id": "936a5b7b-4334-4eee-915b-d2a4d6954b39", + "last_modified": 1687555476921 + }, + { + "schema": 1687366865264, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxUe1Moeja", + "id": "deaa1159-16b5-4465-8c2a-87d30508c6ac", + "last_modified": 1687555476917 + }, + { + "schema": 1687366865538, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDM=", + "serialNumber": "Bn+UV1j+VbnuP3WDHUfwfSJsig==", + "id": "6d25f277-2c36-471b-a737-661d60ce0a1b", + "last_modified": 1687555476914 + }, + { + "schema": 1687366866654, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "TratZOoVuiM8MrDUkWcAYw==", + "id": "fac52369-ab34-4764-91cd-62a52dec1c66", + "last_modified": 1687555476910 + }, + { + "schema": 1687366865128, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMQ==", + "serialNumber": "AgPzWIgWFg4KRSfypQ==", + "id": "6f11e321-6538-4c9b-9351-a624aa1d53d5", + "last_modified": 1687555476907 + }, + { + "schema": 1687366866385, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "CvM4MnwdSLpBdT6+uepgKQ==", + "id": "c160df97-49c5-4b82-93e9-33dcbd783079", + "last_modified": 1687555476903 + }, + { + "schema": 1687366865471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "Dpq1BnIreDRj8Qu7F1QiMA==", + "id": "6e289f72-eaff-4d55-86cb-17f9c5b2d6f2", + "last_modified": 1687555476900 + }, + { + "schema": 1687366866520, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "C55eWeVBBf4v9C3uHJ+ZOA==", + "id": "3cace980-e586-4d0b-be6c-3b466497eb80", + "last_modified": 1687555476896 + }, + { + "schema": 1687366865737, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDI=", + "serialNumber": "Bn+UV1Xxh6kfgWPz5iRiAXf/OA==", + "id": "3bf88a76-7257-48f4-8fea-76565915fa17", + "last_modified": 1687555476893 + }, + { + "schema": 1687366866453, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "Cga+zSqxNLb+9CuGehOOsg==", + "id": "76eadd5a-04ee-459b-af06-432358bc554f", + "last_modified": 1687555476889 + }, + { + "schema": 1687366866580, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMw==", + "serialNumber": "Af6lgMJYpzHLw7Oeqw==", + "id": "ce7f6f92-0b4d-4d93-a6cf-3a3feffcf744", + "last_modified": 1687555476885 + }, + { + "schema": 1687361572033, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMQ==", + "serialNumber": "AfD3nV54J/tAqRKzEA==", + "id": "32fbefa7-28e4-46fb-a179-a330fb68e144", + "last_modified": 1687555476882 + }, + { + "schema": 1687366866324, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "UHWLsGjHymv4TMlQq8JlOQ==", + "id": "092c33f5-1261-4d0b-8315-069a627c3079", + "last_modified": 1687555476878 + }, + { + "schema": 1687366866002, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "serialNumber": "BntQXCplJ7wevi2i0ZmY7bibLA==", + "id": "4a50c4d3-c2c6-4e46-98cc-88454562751d", + "last_modified": 1687555476875 + }, + { + "schema": 1687366865807, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "serialNumber": "Bn+UV1CMZIwJymUucXkYMOclkg==", + "id": "35c910b9-3508-4514-b0eb-98ea555d2ce3", + "last_modified": 1687555476871 + }, + { + "schema": 1687366865196, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "serialNumber": "Bn+UV4WH6Kx33rJTMlu8mYtWDQ==", + "id": "1cd3cb08-9861-4abe-b8f4-799bbc6f0122", + "last_modified": 1687555476867 + }, + { + "schema": 1687366864987, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMg==", + "serialNumber": "AfCcWw6iKTfPnuRBbA==", + "id": "7fb3f673-2a47-4f02-a9a1-88d4efb6b82c", + "last_modified": 1687555476864 + }, + { + "schema": 1687366865064, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "DgAfiI9+uYqO/PusjSLxcw==", + "id": "cc976612-1cea-4e6f-b18c-66067e04cd2b", + "last_modified": 1687555476860 + }, + { + "schema": 1687366865404, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "AfCcV4rg6fwYVYZ8ZA==", + "id": "b66a0745-4a49-4305-a103-5deb35e9e240", + "last_modified": 1687555476856 + }, + { + "schema": 1687366865336, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1839689", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "Zj4is/c8y3jcdDaTU6skww==", + "id": "cf4eb09c-f76e-4c23-815e-65e5ff13f033", + "last_modified": 1687555476852 + }, + { + "schema": 1684515701546, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "KHpJphmDZxRIPBZGR/82CA==", + "id": "da20b172-c699-4bb4-99c1-040d26ecea90", + "last_modified": 1684959777375 + }, + { + "schema": 1684515701327, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "MtYr/GdQGss=", + "id": "6a886cc5-94b2-4a3f-9417-63964ba98da1", + "last_modified": 1684959777372 + }, + { + "schema": 1684515701470, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "T9L6yA/FobXp6H4lKG9bgQ==", + "id": "650d8f85-0ba9-4d79-a7a7-214542d0765c", + "last_modified": 1684959777368 + }, + { + "schema": 1684515701621, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbml0cnVzdDElMCMGA1UEAwwcVUNBIEV4dGVuZGVkIFZhbGlkYXRpb24gUm9vdA==", + "serialNumber": "R0aPxVlgX1X+t6mSbk0YTw==", + "id": "e2c5475d-ca6d-4376-bc39-a550070fd4e4", + "last_modified": 1684959777364 + }, + { + "schema": 1684515700901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "DTVetRXgI5CCmvFrSBGi+w==", + "id": "f85ea05b-2fb5-4694-8278-bcd2eadf69ab", + "last_modified": 1684959777360 + }, + { + "schema": 1684515700754, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "PcAH56XNTxuCv3DL7xgR4g==", + "id": "e3e3c365-b36a-4c29-b8a0-aea044831183", + "last_modified": 1684959777357 + }, + { + "schema": 1684515701253, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "OXQnZqWVgle5u3wWTzhUJg==", + "id": "7050eabc-bf45-44c2-beea-f8192aaf3fcc", + "last_modified": 1684959777353 + }, + { + "schema": 1684515701400, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "PLTWlkfKJt1jQIE1gZUBPw==", + "id": "cd3b6317-1206-4e0c-ad14-df4e140f8e14", + "last_modified": 1684959777349 + }, + { + "schema": 1684515701177, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGDMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MScwJQYDVQQDEx5DZXJ0dW0gR2xvYmFsIFNlcnZpY2VzIENBIFNIQTI=", + "serialNumber": "ANMsRVbdSs/gKE0TaNyDdYI=", + "id": "8f8510a7-0f46-482a-b64c-7ec8940bce59", + "last_modified": 1684959777345 + }, + { + "schema": 1684515701110, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "W0naHidw4ro=", + "id": "8e836d5f-90db-40b7-87e6-f9ad34f430f0", + "last_modified": 1684959777341 + }, + { + "schema": 1684515700683, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "RtNp7fstuGGz2s6j3yzzMg==", + "id": "9c9e62fd-f5ea-4279-9bd1-64fd3505d4a6", + "last_modified": 1684959777337 + }, + { + "schema": 1684515700610, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBIDI=", + "serialNumber": "ALhZFHE/V9+PMcAzPdLWGXojF7Tr", + "id": "58aa1558-2da2-425a-bf2a-1032b20158f0", + "last_modified": 1684959777333 + }, + { + "schema": 1684515700972, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "XkXCVpudK72cE8vm41R9AQ==", + "id": "7730004d-3de6-4732-bd77-219cee8b3ef0", + "last_modified": 1684959777330 + }, + { + "schema": 1684515700259, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxhDQpanb+qFnGG4+Orw==", + "id": "2cd63e86-b487-4dbd-8b71-ebdaddd44d91", + "last_modified": 1684959777326 + }, + { + "schema": 1684515700540, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "KeKCeAGZ2nV6Gzfgc0Z7NQ==", + "id": "dcbe324c-ba16-4667-8127-5ea13a329448", + "last_modified": 1684959777322 + }, + { + "schema": 1684515700404, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDElMCMGA1UEAwwcVUNBIEV4dGVuZGVkIFZhbGlkYXRpb24gUm9vdA==", + "serialNumber": "UuhDsZZ+VM6gWSw3oW+alA==", + "id": "63273f3c-6d24-447c-86e9-b7f0ffe1c015", + "last_modified": 1684959777318 + }, + { + "schema": 1684515700333, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "C1C7rAKtMNU=", + "id": "2e37c7be-f0af-482c-b4bf-27aaf11562c0", + "last_modified": 1684959777313 + }, + { + "schema": 1684515700046, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "cetBW+6jOSy8e7tQnpyhZg==", + "id": "3a60fc31-af7c-438a-bc3e-505eee8df89f", + "last_modified": 1684959777309 + }, + { + "schema": 1684515700191, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGDMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MScwJQYDVQQDEx5DZXJ0dW0gR2xvYmFsIFNlcnZpY2VzIENBIFNIQTI=", + "serialNumber": "ANMsRVbdSs/gKE0TaNyDdYA=", + "id": "d563ed80-0a00-43fb-b29e-0b4bbe360918", + "last_modified": 1684959777304 + }, + { + "schema": 1684515700118, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "serialNumber": "dGUXetoc8PMhu+yUKqxr+w==", + "id": "be2dbc33-d916-4e48-9795-55ea058ef8ee", + "last_modified": 1684959777300 + }, + { + "schema": 1684515699834, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFYxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFTATBgNVBAMMDENGQ0EgRVYgUk9PVA==", + "serialNumber": "APNIyc+EuEJyvJY=", + "id": "2a619003-53f7-42b6-bcc2-3379929bcc29", + "last_modified": 1684959777296 + }, + { + "schema": 1684515699976, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmxgz92/E4QuySEiLjkzA==", + "id": "1b7d47e4-97c5-4e99-b529-48d109b0096c", + "last_modified": 1684959777292 + }, + { + "schema": 1684515700469, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "ZHTJPiF08nD3crsRjhjSiA==", + "id": "30dbade3-e94a-46bc-a34e-fbe2ac114c5f", + "last_modified": 1684959777289 + }, + { + "schema": 1684368006449, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "CNO7HB1sjEGMTCY4N8Ghfg==", + "id": "40e9c0fd-2aca-46de-938b-a710faa650bf", + "last_modified": 1684959777285 + }, + { + "schema": 1684515700824, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=", + "serialNumber": "ALsCK/mrS/IL9O0gFtqEn74=", + "id": "d348f16a-7846-464d-85b5-bcd98b4e3420", + "last_modified": 1684959777281 + }, + { + "schema": 1684515701040, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw+nLg2EjG", + "id": "ab310b23-e989-466e-a624-d3234f51637a", + "last_modified": 1684959777277 + }, + { + "schema": 1684515699904, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1834089", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "serialNumber": "Rbw6WjuXYmYHkr+VBZ3r0Q==", + "id": "fa261b5e-10ea-48ac-9980-a1b8e90ccbae", + "last_modified": 1684959777273 + }, + { + "schema": 1667948172583, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACvTVIJbw7FTewAAAAAAKw==", + "id": "16fa23a9-426e-453b-b97d-08cb0963d8f9", + "last_modified": 1668019772820 + }, + { + "schema": 1667949844423, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACwRiHK6+SsuVgAAAAAALA==", + "id": "3ec71f0c-0af6-48ba-83b8-3e548dd158ca", + "last_modified": 1668019772816 + }, + { + "schema": 1667949854725, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAAC1fPfgqiW1qPwAAAAAALQ==", + "id": "ac3c05d0-19fe-4604-88e4-5ea41fb45980", + "last_modified": 1668019772811 + }, + { + "schema": 1667949864355, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAAC6Z2wc3jQ8AtgAAAAAALg==", + "id": "93883a39-3b9e-4a28-bca9-a5dcb8bee0aa", + "last_modified": 1668019772807 + }, + { + "schema": 1667949874112, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "FXIsRTjN5Q5sv09I9SHCW+ybWl4=", + "id": "3290a1c8-488c-4e3c-ac3f-42af3b984bf4", + "last_modified": 1668019772802 + }, + { + "schema": 1667949883348, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "EHggWWIQxb/ACSziq8oYkHl2bgY=", + "id": "13980bfc-b1f6-4b4c-89e7-98bdf169afe5", + "last_modified": 1668019772798 + }, + { + "schema": 1667949892901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "Xc7VBkyeNRPAUkrUmXL7xdN+dxM=", + "id": "54567fd7-0d13-4055-b85b-1339f08e4149", + "last_modified": 1668019772793 + }, + { + "schema": 1667949901536, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "ZX7r/Ku0sI/8Q5RlFeAvMkBXI88=", + "id": "09a2b860-f9f0-464b-9935-8a8e7b1d9c1f", + "last_modified": 1668019772789 + }, + { + "schema": 1667949910316, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "FxNWRy0L493JawO2EDwVrKeDc4Y=", + "id": "f9a52b2a-41cd-4966-bcd7-e09ecb5d1dbb", + "last_modified": 1668019772785 + }, + { + "schema": 1667949921626, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "S2Abtk5IaNhXLKh+ft8aUXtlvb0=", + "id": "12758e0c-4506-43d2-bfd9-f99392ed6993", + "last_modified": 1668019772780 + }, + { + "schema": 1667949930650, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "B01+Be34iFzayyTwOfpAYpqt//g=", + "id": "b090a9be-cf80-4714-bd1f-228ab0792930", + "last_modified": 1668019772776 + }, + { + "schema": 1667949938639, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "FptidE6MfHc4i6qL2PEK1BQhLSY=", + "id": "475cb3f8-074e-46b9-bbfa-b67f992cd6fa", + "last_modified": 1668019772771 + }, + { + "schema": 1667949948207, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "BfSjNAkPexroNnfUdmv9MqceCFE=", + "id": "e33e4420-66bb-42d1-9172-80840967574c", + "last_modified": 1668019772767 + }, + { + "schema": 1667949956060, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1798526", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "MBr6yKy20as0LrOeaEvZEtnx3c4=", + "id": "16669ad4-4832-45b3-ba8c-0ba0d5168918", + "last_modified": 1668019772762 + }, + { + "schema": 1666793396849, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjElMCMGA1UEAxMcRGlnaUNlcnQgVExTIFJTQTQwOTYgUm9vdCBHNQ==", + "serialNumber": "AtNop7tpO9uOIlnnWF07FQ==", + "id": "7f4ca046-4c67-4f9e-b18d-2e29c3d1f4f4", + "last_modified": 1666890762281 + }, + { + "schema": 1666803676145, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENB", + "serialNumber": "DaLXyI4fEuNfySxqGup6Yg==", + "id": "8ec6c47e-5f7d-4964-a286-82f03cb6fb51", + "last_modified": 1666890762277 + }, + { + "schema": 1666803676210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==", + "serialNumber": "Wjs+TxAJkGNaRsp4NJA1o5GwjfI=", + "id": "9cc25742-ce43-4248-9d43-8820c6eb74fb", + "last_modified": 1666890762272 + }, + { + "schema": 1666803676266, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENB", + "serialNumber": "A88eZXjGXrL3zaF3t/3DZQ==", + "id": "568c903e-fa5d-40ef-840c-bc1da1c40e10", + "last_modified": 1666890762268 + }, + { + "schema": 1666803676321, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "DFWl6NPPT7NZ8Asrci/u+Q==", + "id": "a11df4a5-0f2b-4742-b1ca-ea31a3d914d4", + "last_modified": 1666890762263 + }, + { + "schema": 1666803676385, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENB", + "serialNumber": "Bg1bPA4gYzI0+zXlAjzBaA==", + "id": "14eab4d0-2527-4efd-9638-007972887f6d", + "last_modified": 1666890762258 + }, + { + "schema": 1666803676444, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0E=", + "serialNumber": "BJs5bzwFPCdTNiFgnspdPw==", + "id": "14bde8e3-5111-41b5-82ed-eaccbf95e6d8", + "last_modified": 1666890762254 + }, + { + "schema": 1666803676504, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0Ex", + "serialNumber": "AQAARzAgXda7", + "id": "1ec6bcd8-bcea-4379-9179-b67e2fb39e46", + "last_modified": 1666890762249 + }, + { + "schema": 1666803676561, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0Ex", + "serialNumber": "AQAART30i+dG", + "id": "2e250d19-c890-47b5-8b74-b837b1fc0504", + "last_modified": 1666890762245 + }, + { + "schema": 1666803676616, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0Ex", + "serialNumber": "AQAAPggPTZo3", + "id": "a2d89d38-7d86-4191-bd48-58d16f785c85", + "last_modified": 1666890762240 + }, + { + "schema": 1666803676672, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1797566", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0E=", + "serialNumber": "BDg2SqweGZEe3TK+yl8pgw==", + "id": "c0ee4e00-9725-4b60-a216-fb53a77bc3e6", + "last_modified": 1666890762236 + }, + { + "schema": 1658780918737, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AIY8dWQRlYVPtDE4oKDPiqM=", + "id": "bdda0336-55a8-4832-a9b3-6b7354c1b8c9", + "last_modified": 1658781354245 + }, + { + "schema": 1658524023193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "AOyKJpsJ606dAAAAAFHTlCM=", + "id": "d5b211d1-3823-455f-b899-943fa7a22325", + "last_modified": 1658781354240 + }, + { + "schema": 1658415295650, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "bFCNH7HjZSeDCfiVmX/pi27hhTo=", + "id": "d90a8c2a-a3ac-4a80-913f-e3ef106b5a25", + "last_modified": 1658524022912 + }, + { + "schema": 1658509269089, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "KDJazoaNkWBzaqFejzCWGYW8tXA=", + "id": "68da3983-0e15-4dd3-a5ac-316c35ba600c", + "last_modified": 1658524022906 + }, + { + "schema": 1658509269176, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+ksuMtrmM50i7EI59Jg==", + "id": "4b0590a3-19fa-4448-93fe-cad3b5d71b89", + "last_modified": 1658524022900 + }, + { + "schema": 1658509269261, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "brVxsa7mf2A83Fn+i2ON1sbojjY=", + "id": "3d2e5005-215b-47d2-8c20-5dd99e555f7a", + "last_modified": 1658524022894 + }, + { + "schema": 1658509269338, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "RnQ3c1m3p0qL2FCUxcs=", + "id": "9199c8b8-8de6-461b-b27c-6ebad811b4ee", + "last_modified": 1658524022888 + }, + { + "schema": 1658509269416, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+lynSA1/bkfMXz197IQ==", + "id": "707e4d42-68fd-47e6-add7-2df327f04ea7", + "last_modified": 1658524022881 + }, + { + "schema": 1658509269494, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBFQ0MgdjM=", + "serialNumber": "e/GB4yUfq7dMuFKpU2KBeN190ZQ=", + "id": "d6a2ad66-a03e-4afe-b99b-ba79cf7aa008", + "last_modified": 1658524022875 + }, + { + "schema": 1658509269569, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGyMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMUAwPgYDVQQKDDdFLVR1xJ9yYSBFQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMSYwJAYDVQQLDB1FLVR1Z3JhIFNlcnRpZmlrYXN5b24gTWVya2V6aTEoMCYGA1UEAwwfRS1UdWdyYSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "f2zef2Nhb6hfxGPaS28zGg==", + "id": "7c819fbd-a020-4f06-b6b8-722a3da344c3", + "last_modified": 1658524022869 + }, + { + "schema": 1658509269645, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "cm9BpFxFXG4stv1jUrZ3FLf1rG0=", + "id": "6d31ae9e-8cd5-4f8a-b7c4-d172439b5953", + "last_modified": 1658524022863 + }, + { + "schema": 1658509269722, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "SCPl2iC4QBaDzF19wh01IN1pC8E=", + "id": "a4887a77-831c-4711-af24-07937e55187f", + "last_modified": 1658524022858 + }, + { + "schema": 1658509269799, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "A5OHqBosxo3yi6IThdc7Gw==", + "id": "f4057762-2bf1-4eca-94b4-d19490af03d5", + "last_modified": 1658524022852 + }, + { + "schema": 1658509269875, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkRFMRUwEwYDVQQKEwxELVRydXN0IEdtYkgxIjAgBgNVBAMTGUQtVFJVU1QgRVYgUm9vdCBDQSAxIDIwMjA=", + "serialNumber": "aOXtSmaheM68bssu1KMZXQ==", + "id": "ca21614a-dd04-438e-a28f-a447f8960984", + "last_modified": 1658524022846 + }, + { + "schema": 1658509269951, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGPMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UEAxMpU3RhcmZpZWxkIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "JK8Hqnl17No=", + "id": "b27de3e0-8871-48a4-89a4-f291db1fa342", + "last_modified": 1658524022840 + }, + { + "schema": 1658509270028, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "RtNeHdGDGl5GcClskrn1MyXsOgw=", + "id": "22c3006f-fe93-4d66-bcb4-433b3ed5d264", + "last_modified": 1658524022834 + }, + { + "schema": 1658509270105, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "D4eqe7f6LmSYid+PYT6gIA==", + "id": "e5c7cf09-13db-44ec-b92e-28b0beaf61b4", + "last_modified": 1658524022828 + }, + { + "schema": 1658509270182, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBSU0EgdjM=", + "serialNumber": "MTj/mLGszHK9+8ZdK9jLyD81/s8=", + "id": "1ad933b9-7368-47b2-ac85-cce413a93311", + "last_modified": 1658524022822 + }, + { + "schema": 1658509270258, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "ebn+k89ygqpeXFadjirI4Q==", + "id": "3088d6d6-ef73-4186-921b-99582d0b4bed", + "last_modified": 1658524022816 + }, + { + "schema": 1658509270333, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByemYg==", + "id": "a27a2f42-90ed-4bd3-b4bf-e4f7ada2c5ad", + "last_modified": 1658524022810 + }, + { + "schema": 1658509270410, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "RnQ3eBYmHQ562+LMtfw=", + "id": "33a7c8c2-65fb-485c-94ee-120f1806b356", + "last_modified": 1658524022804 + }, + { + "schema": 1658509270486, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "RnQ3d5IJc/pIL+KNlGI=", + "id": "30569ad8-6b6b-47d3-824f-281705bb902d", + "last_modified": 1658524022798 + }, + { + "schema": 1658509270562, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "BVvfXK5S+qafHhZ6+eRmoRThd6s=", + "id": "a29a1042-a0b9-460b-acd3-50aef4a6506e", + "last_modified": 1658524022792 + }, + { + "schema": 1658509270638, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "RJFfnnSa469Pm2f2/xyCtF9ES78=", + "id": "b7c78efa-37d0-4930-a7ad-e6a1627e6664", + "last_modified": 1658524022786 + }, + { + "schema": 1658509270714, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "IlWarOIZWhjPjkBIlrlBMqjcTM8=", + "id": "a7147cdb-6eae-44b2-981b-645d31cc1cd3", + "last_modified": 1658524022780 + }, + { + "schema": 1658509270792, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Rak6/TBd/rPNBTHcDjKc9CpOmdM=", + "id": "883980a0-2571-4833-82f7-c20764968d8a", + "last_modified": 1658524022774 + }, + { + "schema": 1658509270869, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGPMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UEAxMpU3RhcmZpZWxkIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "ANLaqyqZFi9j", + "id": "bb4240bd-1213-4108-8077-22b3b5542483", + "last_modified": 1658524022768 + }, + { + "schema": 1658509270946, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "RsdODEh3IULjetdRFS8=", + "id": "9602b3c5-0e0c-4b86-8290-8399ffcdc709", + "last_modified": 1658524022762 + }, + { + "schema": 1658509271021, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1780845", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "eBBe+EEsYfO5HQknVwW+v1EKKd0=", + "id": "167174a3-3097-4b96-819b-424db5cb3d4e", + "last_modified": 1658524022755 + }, + { + "schema": 1647880608051, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGlMQswCQYDVQQGEwJFUzFDMEEGA1UEBww6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTEbMBkGA1UECgwSQUMgQ2FtZXJmaXJtYSBTLkEuMRIwEAYDVQQFEwlBODI3NDMyODcxIDAeBgNVBAMMF0dMT0JBTCBDT1JQT1JBVEUgU0VSVkVS", + "serialNumber": "cvJ358Jb7tncxn4=", + "id": "eceb6ef3-59c8-4099-ba60-031815ad1b1e", + "last_modified": 1648145180742 + }, + { + "schema": 1648054860859, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGlMQswCQYDVQQGEwJFUzFDMEEGA1UEBww6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTEbMBkGA1UECgwSQUMgQ2FtZXJmaXJtYSBTLkEuMRIwEAYDVQQFEwlBODI3NDMyODcxIDAeBgNVBAMMF0dMT0JBTCBDT1JQT1JBVEUgU0VSVkVS", + "serialNumber": "B9mxhOTwQxA=", + "id": "c0618ef6-a6d2-448a-9c1c-cb2c2b7e4c86", + "last_modified": 1648145180736 + }, + { + "schema": 1648054860946, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "evszTxhTzfGyltzw89K2fA==", + "id": "7372a1f0-cb36-4ecc-845f-4fe230d3b415", + "last_modified": 1648145180730 + }, + { + "schema": 1648054861039, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGIxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjEfMB0GA1UEAwwWR0RDQSBUcnVzdEFVVEggUjUgUk9PVA==", + "serialNumber": "F7Ot0kCjuSA=", + "id": "5034c97f-832c-4d83-acae-05666e29f506", + "last_modified": 1648145180723 + }, + { + "schema": 1648054861121, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGMxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xNDAyBgNVBAMMK1N0YWF0IGRlciBOZWRlcmxhbmRlbiBEb21laW4gU2VydmVyIENBIDIwMjA=", + "serialNumber": "E6jLybNc4Vs6mOwPv4ezOA4Gtq8=", + "id": "ae2dba14-429b-4f98-abfa-0b38e64af09e", + "last_modified": 1648145180717 + }, + { + "schema": 1648054861200, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMg==", + "serialNumber": "AvpjwbwFBQphBhtsnYT+lw==", + "id": "fce3b423-5ff6-4591-80c2-1dbb5d0e91d4", + "last_modified": 1648145180711 + }, + { + "schema": 1648054861383, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "eAMYKYIz6v1CqTryiwPINw==", + "id": "8c971048-148f-432f-bddd-17f6b4bba709", + "last_modified": 1648145180697 + }, + { + "schema": 1648054861470, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "eEqpJ4UK3c1w4qeeJgI8xg==", + "id": "80873811-39fc-4aac-884f-ff2c22fbac97", + "last_modified": 1648145180689 + }, + { + "schema": 1648054861550, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGIxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjEfMB0GA1UEAwwWR0RDQSBUcnVzdEFVVEggUjUgUk9PVA==", + "serialNumber": "Ib1iJz+4Aag=", + "id": "fc17ff5d-3da1-473f-bbac-f4375ac730ab", + "last_modified": 1648145180681 + }, + { + "schema": 1648054861633, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGIxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjEfMB0GA1UEAwwWR0RDQSBUcnVzdEFVVEggUjUgUk9PVA==", + "serialNumber": "Jofc/+DvJno=", + "id": "dfc610e9-d8a4-4b00-9353-aba552831786", + "last_modified": 1648145180675 + }, + { + "schema": 1648054861715, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "eAMapQq7bryFyqrojWQvYA==", + "id": "9d18c8d0-3918-4c6b-8085-eb12e55adc23", + "last_modified": 1648145180669 + }, + { + "schema": 1648054861822, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGIxCzAJBgNVBAYTAkNOMTIwMAYDVQQKDClHVUFORyBET05HIENFUlRJRklDQVRFIEFVVEhPUklUWSBDTy4sTFRELjEfMB0GA1UEAwwWR0RDQSBUcnVzdEFVVEggUjUgUk9PVA==", + "serialNumber": "Y1SwpvX/WSo=", + "id": "a117dc41-ef7f-4cbf-97e8-978b9e653b54", + "last_modified": 1648145180663 + }, + { + "schema": 1648054861899, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "HWoKeJCECRm+uJ7rxJqiiA==", + "id": "8ca0cf31-3b80-4751-a021-08f346882481", + "last_modified": 1648145180657 + }, + { + "schema": 1648054861985, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1761053", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "MlFBKQ8jIyZrnSHB976ldQ==", + "id": "145d4c51-f1f0-4570-a1f2-93d69f26a48e", + "last_modified": 1648145180650 + }, + { + "schema": 1636216607840, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmwxw==", + "id": "88b9b4f9-e3a5-43a2-9161-e072ca0cc25d", + "last_modified": 1636587776892 + }, + { + "schema": 1636563658712, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw9RUMnLWK", + "id": "efb30e9b-2f9c-4a52-8936-21ef6086685a", + "last_modified": 1636587776881 + }, + { + "schema": 1636563659207, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw3g==", + "id": "57eb9e47-5a49-4af2-b264-b5a22220ab9a", + "last_modified": 1636587776871 + }, + { + "schema": 1636563659891, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxDxYqdHHX", + "id": "c20e8a7f-f7f7-46a5-9201-f3eaa13b8379", + "last_modified": 1636587776862 + }, + { + "schema": 1636563660914, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw/QH4w0Am", + "id": "e71d344b-abc9-42a4-9c71-864ae546731c", + "last_modified": 1636587776852 + }, + { + "schema": 1636563661399, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw6riAVioU", + "id": "0d25e750-23fc-4def-91a0-d10338926f6a", + "last_modified": 1636587776842 + }, + { + "schema": 1636563661862, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmwzA==", + "id": "52c32a3f-26c3-4cf2-88af-8e4777c464c0", + "last_modified": 1636587776833 + }, + { + "schema": 1636563662298, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxLQbUNQw0", + "id": "1c913f51-5d9d-43b3-85d0-7b98c4068e00", + "last_modified": 1636587776825 + }, + { + "schema": 1636563662784, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOQ==", + "serialNumber": "YDQ5U886jKQrtEpEKSy69A==", + "id": "2d7659c9-1483-48f2-b928-9d36ee801409", + "last_modified": 1636587776818 + }, + { + "schema": 1636563663321, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw/ofYnXPM", + "id": "789fd5b6-aabe-480f-92ac-784705c030ff", + "last_modified": 1636587776809 + }, + { + "schema": 1636563663779, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5AAn", + "id": "e0a9e5b9-f604-482b-a536-f892bfadd85e", + "last_modified": 1636587776802 + }, + { + "schema": 1636563664227, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxLgSn7Cvs", + "id": "3de39048-1720-4a67-afd4-31748a913013", + "last_modified": 1636587776796 + }, + { + "schema": 1636563664685, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxB+QOrp3P", + "id": "7a7aa30b-0f35-42af-bf31-95699333167c", + "last_modified": 1636587776790 + }, + { + "schema": 1636563665204, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxEAXK2OI4", + "id": "c1103392-2dbb-4244-b864-33c628f2477f", + "last_modified": 1636587776784 + }, + { + "schema": 1636563665753, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOQ==", + "serialNumber": "ShA9s+HZJmJlKzvcauXC8w==", + "id": "8f26fd75-b45a-451d-b518-85636b5eb118", + "last_modified": 1636587776778 + }, + { + "schema": 1636563666256, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw7w==", + "id": "05fbcacd-8c76-47d9-a972-1cc17893b1cf", + "last_modified": 1636587776772 + }, + { + "schema": 1636563666710, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw7A==", + "id": "81cbd120-6dce-4214-babf-3d72b3e9540b", + "last_modified": 1636587776766 + }, + { + "schema": 1636563667150, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1740553", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw7g==", + "id": "831b8d50-c98c-4501-b867-ecb0dd8efc3d", + "last_modified": 1636587776760 + }, + { + "schema": 1628162493146, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGyMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMUAwPgYDVQQKDDdFLVR1xJ9yYSBFQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMSYwJAYDVQQLDB1FLVR1Z3JhIFNlcnRpZmlrYXN5b24gTWVya2V6aTEoMCYGA1UEAwwfRS1UdWdyYSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "Hw7EA7OAHK0=", + "id": "d89a9081-6e26-4a42-b6c4-18e38f13ef99", + "last_modified": 1628187467330 + }, + { + "schema": 1628182861743, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "MDDVc2QS5c7G0NCvmmiplA==", + "id": "d372bf4e-4b8a-44de-a5d1-347c9a51a0c7", + "last_modified": 1628187467323 + }, + { + "schema": 1628182862133, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "L+DBBkUO02gMUQKcjFQSXQ==", + "id": "c260bac4-affa-44d2-b24b-adfc5554c274", + "last_modified": 1628187467316 + }, + { + "schema": 1628182862520, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "fJ89pt+eKHEPYd6utwukkQ==", + "id": "78b9dd10-b742-4858-b94e-6ad55e93c917", + "last_modified": 1628187467309 + }, + { + "schema": 1628182862924, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "RZcy2PMYy3WTovRoD5Dq2Q==", + "id": "f1edcd78-8102-4d1c-8693-f27cf623cf03", + "last_modified": 1628187467302 + }, + { + "schema": 1628182863378, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "fbycYVUFmocbhMf3pItxYg==", + "id": "e6e07cff-3ed9-4ab2-9d86-c32a4e23fb3f", + "last_modified": 1628187467295 + }, + { + "schema": 1628182863782, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDkxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKEwZBbWF6b24xGTAXBgNVBAMTEEFtYXpvbiBSb290IENBIDE=", + "serialNumber": "BntQWB5VRYI8C6YvYwneX8pJTA==", + "id": "ecdafe75-3436-4a5b-a54e-0bbd396587d4", + "last_modified": 1628187467288 + }, + { + "schema": 1628182864213, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1724254", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZpZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "BntQUp3WX1ga1fXFWlp5GKFvkw==", + "id": "330279a7-9158-4a4e-a6c0-25b7bec287d9", + "last_modified": 1628187467281 + }, + { + "schema": 1625675805133, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1719704", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#8)", + "name": "Information Security Certification Authority", + "created": "2021-07-08T19:58:35Z" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "EIn8PxnJA/ODm89wuX3Hz6rT1N0=", + "id": "9d1000df-4c91-448e-9fe1-9f8b613ed89d", + "last_modified": 1625780246117 + }, + { + "schema": 1623872990204, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1717548", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#7)", + "name": "Information Security Certification Authority", + "created": "2021-06-21T23:32:05Z" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "JUSY/Krdlt5RgUcu0HsWP/eeatM=", + "id": "cd5f0792-b5d2-4215-a5eb-92fc483c3491", + "last_modified": 1624320350090 + }, + { + "schema": 1623343005748, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw3A==", + "id": "e636d421-a9d7-4b6e-bbef-7906c353fe16", + "last_modified": 1623704995554 + }, + { + "schema": 1623430871646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw6A==", + "id": "1d532138-95fa-40b6-8b46-3ee2ed5bdf01", + "last_modified": 1623704995547 + }, + { + "schema": 1623430872139, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw2A==", + "id": "88fd7cf5-cce3-4e51-9eff-babee459ea89", + "last_modified": 1623704995541 + }, + { + "schema": 1623430872492, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxOcmQOQlG", + "id": "1cae78df-87ab-4eac-9dbb-0bce926c5ab2", + "last_modified": 1623704995535 + }, + { + "schema": 1623430872841, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACibG+ewD5dDkQAAAAAAKA==", + "id": "ce8c083c-3299-4953-87e1-8df1f2812326", + "last_modified": 1623704995528 + }, + { + "schema": 1623430873206, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACZe5cvvEBR5+wAAAAAAJg==", + "id": "0a0b4a57-ff5d-40c1-a2ba-1bcc7046375d", + "last_modified": 1623704995522 + }, + { + "schema": 1623430873553, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw5w==", + "id": "886fbb3c-369f-492b-9c18-38149806e4ec", + "last_modified": 1623704995516 + }, + { + "schema": 1623430873904, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxO4BPu8QB", + "id": "bfa3b660-2bf2-441a-aff9-729462358b01", + "last_modified": 1623704995510 + }, + { + "schema": 1623430874259, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "XwrMtlpr+AA=", + "id": "97e02932-5dce-4416-9a29-cdfd121e3c98", + "last_modified": 1623704995504 + }, + { + "schema": 1623430874616, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw/NTHlDtA", + "id": "49a7c6d9-a7c0-48ce-9994-c4fa2f80b4c4", + "last_modified": 1623704995498 + }, + { + "schema": 1623430874970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw5iEok84W", + "id": "1200a9e0-9941-4a5a-9087-a3b57a70e9cb", + "last_modified": 1623704995492 + }, + { + "schema": 1623430875338, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw9A==", + "id": "4b2d509c-25fe-457e-91c1-e40a2dc25748", + "last_modified": 1623704995486 + }, + { + "schema": 1623430875704, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw9x1drG6M", + "id": "5208bb98-b3d7-4403-b730-07ffe6bd26d6", + "last_modified": 1623704995480 + }, + { + "schema": 1623430876051, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw6Q==", + "id": "25b6c611-ba22-4785-8605-3b8ee3457e96", + "last_modified": 1623704995474 + }, + { + "schema": 1623430876446, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw9qa54MPC", + "id": "4064de78-318b-483c-b0a3-87b6eedc264c", + "last_modified": 1623704995468 + }, + { + "schema": 1623430876796, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBFQ0MgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACN48RhkZdNnwgAAAAAAIw==", + "id": "7c31d0ae-6f27-414a-a7b4-26cfffb369aa", + "last_modified": 1623704995462 + }, + { + "schema": 1623430877167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmwvQ==", + "id": "6ee2d819-5afb-42bb-9bb1-46529bfbf7a2", + "last_modified": 1623704995456 + }, + { + "schema": 1623430877879, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxPZI44LvC", + "id": "d75ec81f-b339-4a75-be3a-ab0931322df9", + "last_modified": 1623704995444 + }, + { + "schema": 1623430878238, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACfH3q9H1sAS/gAAAAAAJw==", + "id": "516167e0-c6bd-4698-8f88-1c98516e48dd", + "last_modified": 1623704995438 + }, + { + "schema": 1623430878583, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2Mg==", + "serialNumber": "AWdfrHKZTHS/GmftwbOt", + "id": "a6aba345-d4f9-4788-a036-80bcf6abb25d", + "last_modified": 1623704995432 + }, + { + "schema": 1623430878951, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxNoSipKqS", + "id": "a5d25ee4-d8f9-408c-8e97-5818c3d19d99", + "last_modified": 1623704995426 + }, + { + "schema": 1623430879305, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxUPJ3pYat", + "id": "f943c526-f041-4010-92ba-e301a9dc23f5", + "last_modified": 1623704995420 + }, + { + "schema": 1623430879670, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBFQ0MgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACQE5VNAXy2q0wAAAAAAJA==", + "id": "b1063a80-2ca7-42f4-b3a6-a82288b4e79e", + "last_modified": 1623704995411 + }, + { + "schema": 1623430880022, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmwzg==", + "id": "996e5584-8a77-4240-8d2e-5b6cbd526782", + "last_modified": 1623704995402 + }, + { + "schema": 1623430880406, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBSU0EgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACnZBRRmNdVOvQAAAAAAKQ==", + "id": "41fc6d67-3f65-433d-8bdb-6e1c8c463c9f", + "last_modified": 1623704995394 + }, + { + "schema": 1623430880761, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmwzQ==", + "id": "1cb53090-dfac-44bc-ae32-2fa0c751cf46", + "last_modified": 1623704995388 + }, + { + "schema": 1623430881120, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxMewN/wn+", + "id": "1b26456c-5397-40e2-b61c-0423567480bd", + "last_modified": 1623704995382 + }, + { + "schema": 1623430881582, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmwyg==", + "id": "ac4e4330-c61d-432c-994a-91aa6774d011", + "last_modified": 1623704995376 + }, + { + "schema": 1623430881931, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEcMBoGA1UEAwwTQWZmaXJtVHJ1c3QgUHJlbWl1bQ==", + "serialNumber": "C8/PN1nC9YY=", + "id": "f4edca07-b569-4048-86e1-5bf7af25f21f", + "last_modified": 1623704995370 + }, + { + "schema": 1623430882302, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgQ29tbWVyY2lhbA==", + "serialNumber": "KSY1fqXKEz4=", + "id": "9a9a6dad-99ab-4a86-93df-0b250f9a8b8b", + "last_modified": 1623704995364 + }, + { + "schema": 1623430882652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGEMRIwEAYDVQQFEwlHNjMyODc1MTAxCzAJBgNVBAYTAkVTMScwJQYDVQQKEx5BTkYgQXV0b3JpZGFkIGRlIENlcnRpZmljYWNpb24xFDASBgNVBAsTC0FORiBDQSBSYWl6MSIwIAYDVQQDExlBTkYgU2VjdXJlIFNlcnZlciBSb290IENB", + "serialNumber": "AWIQNZ+riuI=", + "id": "853d0435-9992-416a-b3b8-bd5e609f493f", + "last_modified": 1623704995358 + }, + { + "schema": 1623430882994, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "PYR8G0q7MgI=", + "id": "ddf55257-2005-4d63-b6d9-64e2b1fe437b", + "last_modified": 1623704995352 + }, + { + "schema": 1623430883338, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEcMBoGA1UEAwwTQWZmaXJtVHJ1c3QgUHJlbWl1bQ==", + "serialNumber": "XVRFCaDWCqc=", + "id": "7b11d8a9-7ad2-4623-ad06-1569943adf06", + "last_modified": 1623704995345 + }, + { + "schema": 1623430883687, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw6g==", + "id": "787d7083-587b-4f12-912e-4974a34d2a96", + "last_modified": 1623704995339 + }, + { + "schema": 1623430884045, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw5wAk5B1F", + "id": "ab5edea7-4588-4692-bc0a-76ac0450c5ba", + "last_modified": 1623704995334 + }, + { + "schema": 1623430884395, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "Lu17PatkWxA=", + "id": "9127cef5-9c3f-4e3b-a867-c19d65303e73", + "last_modified": 1623704995328 + }, + { + "schema": 1623430884750, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0M=", + "serialNumber": "EHyqEuzWjFQ=", + "id": "43e15fc8-f1f9-4739-98c1-4a707ab668c1", + "last_modified": 1623704995322 + }, + { + "schema": 1623430885103, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw1w==", + "id": "dc1b8b63-5599-4031-b836-5fabfbc1f33c", + "last_modified": 1623704995315 + }, + { + "schema": 1623430885520, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0M=", + "serialNumber": "K0T3E98XDBY=", + "id": "af4bd6df-6c32-438f-b252-0fdea75804d6", + "last_modified": 1623704995309 + }, + { + "schema": 1623430885923, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxMAYCzmC+", + "id": "a49a0143-eec8-4c40-9d22-7fc2ea88cb57", + "last_modified": 1623704995298 + }, + { + "schema": 1623430886313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmxaudci1hl", + "id": "1bc829aa-7024-496f-8fc1-56abb10e3001", + "last_modified": 1623704995290 + }, + { + "schema": 1623430886670, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxbrFQrX42", + "id": "8a7f89de-7fbe-4e9e-87ac-3a0c541f584f", + "last_modified": 1623704995284 + }, + { + "schema": 1623430887042, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw+A==", + "id": "bbab867d-2bc9-4f2c-9722-210ea15a690c", + "last_modified": 1623704995277 + }, + { + "schema": 1623430887386, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxNzRrFTcO", + "id": "832e41d2-7e6a-4d92-8369-8fa21c5f44c5", + "last_modified": 1623704995270 + }, + { + "schema": 1623430887737, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "CqFYlqTRr4ANoWkO9KOvtA==", + "id": "7f126b2b-465d-4dcf-9cdc-dfbab87bc39f", + "last_modified": 1623704995264 + }, + { + "schema": 1623430888150, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw4g==", + "id": "d2edb576-dcde-4858-9e3f-4f527cd012e1", + "last_modified": 1623704995258 + }, + { + "schema": 1623430888509, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BUNA0KLEzIER+qg3fUbgbw==", + "id": "86177050-853f-4092-989f-4110638c01b6", + "last_modified": 1623704995252 + }, + { + "schema": 1623430888856, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw4w==", + "id": "bedec840-c2be-4f63-ac59-a30a92d4adbc", + "last_modified": 1623704995245 + }, + { + "schema": 1623430889200, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw5Q==", + "id": "a078358d-2e5b-4334-82dd-2359e1d28ce3", + "last_modified": 1623704995240 + }, + { + "schema": 1623430889548, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBFQ0MgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACVxr+DVruei9QAAAAAAJQ==", + "id": "82c755db-09ee-4347-8991-0e87cc4a34d9", + "last_modified": 1623704995234 + }, + { + "schema": 1623430889943, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "WvgOmMFTClg=", + "id": "88e71de1-8a27-4515-9282-7f703e944456", + "last_modified": 1623704995228 + }, + { + "schema": 1623430890312, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgQ29tbWVyY2lhbA==", + "serialNumber": "Yxv5DIqwLIE=", + "id": "72307f37-7189-49a6-b868-b802f0263dec", + "last_modified": 1623704995222 + }, + { + "schema": 1623430890668, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmwxg==", + "id": "fbc5b1f5-76ca-4a1b-b18d-ed97c9814bab", + "last_modified": 1623704995216 + }, + { + "schema": 1623430891026, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==", + "serialNumber": "bItGbUoEQ1rR+8w=", + "id": "c793f1b4-a8c9-455d-afca-7a3fb007e0de", + "last_modified": 1623704995210 + }, + { + "schema": 1623430891392, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgQ29tbWVyY2lhbA==", + "serialNumber": "W0aZkOx1nTQ=", + "id": "ef373304-8be1-4cfc-a25b-a50cc64de91b", + "last_modified": 1623704995204 + }, + { + "schema": 1623430891739, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw7trmzuUT", + "id": "fbfcd386-e7ca-4215-b360-bdd1b3939edf", + "last_modified": 1623704995198 + }, + { + "schema": 1623430892094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw8Q==", + "id": "00de2e37-d3aa-4716-9f58-531d769ea5cf", + "last_modified": 1623704995192 + }, + { + "schema": 1623430892479, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBFQ0MgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNw==", + "serialNumber": "MwAAACaasC3eKau89wAAAAAAJg==", + "id": "c26d58d0-63a0-42c9-bbb7-07b3475d7093", + "last_modified": 1623704995186 + }, + { + "schema": 1623430892827, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw2w==", + "id": "a889f192-fd04-4d3b-8a91-a3a63ad7086b", + "last_modified": 1623704995180 + }, + { + "schema": 1623430893172, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw5Sn05J7O", + "id": "43f59029-61a2-4bac-a3c5-dcf1bf7d3dce", + "last_modified": 1623704995174 + }, + { + "schema": 1623430893522, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1716034", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "I5AVx/Z4gEY=", + "id": "f8af8efe-9ba8-49a3-a662-4a5c5b338a9a", + "last_modified": 1623704995168 + }, + { + "schema": 1622538398718, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1713980", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#6)", + "name": "Information Security Certification Authority", + "created": "2021-06-02T19:29:08Z" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "WHfTDG8PPDZ/jDTyhv2lKEO7ufM=", + "id": "befbd0cf-44bd-4bde-bb2c-bfd439e50dba", + "last_modified": 1622665272229 + }, + { + "schema": 1620318295703, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1709666", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#5)", + "name": "Information Security Certification Authority", + "created": "2021-05-06T00:05:29Z" + }, + "enabled": true, + "issuerName": "MH0xCzAJBgNVBAYTAktaMRMwEQYDVQQIEwpOdXItU3VsdGFuMRMwEQYDVQQHEwpOdXItU3VsdGFuMQ0wCwYDVQQKEwRJU0NBMTUwMwYDVQQDEyxJbmZvcm1hdGlvbiBTZWN1cml0eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "IwlJjil8X/4=", + "id": "bd3be540-4f0c-4a0c-9524-98f6c6cfc2c6", + "last_modified": 1620319815847 + }, + { + "schema": 1617724184855, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxXe/dFYgE618njIJQIA==", + "id": "10e751e1-7d56-4e08-956b-c95db44550c9", + "last_modified": 1618007740387 + }, + { + "schema": 1617814860968, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "dR4/Pels1yiyZmLFUjJYew==", + "id": "6f25596b-57e5-4b88-9db2-ee03c5fd22fa", + "last_modified": 1618007740379 + }, + { + "schema": 1617814861327, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxXEoSYXSKUE0XIUepzA==", + "id": "bee5eac7-68b3-4f7c-a4c1-be834cb3a0d9", + "last_modified": 1618007740372 + }, + { + "schema": 1617814861673, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgRTQ2", + "serialNumber": "EdccJIaHkGp/hWS8eBD9S1/D", + "id": "654e2958-5b35-4f43-b056-0670873d9ce5", + "last_modified": 1618007740365 + }, + { + "schema": 1617814862028, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxdoF+NjfFVfXELUw2vw==", + "id": "d52d566d-3170-4de0-9b72-480f29a507cb", + "last_modified": 1618007740359 + }, + { + "schema": 1617814862463, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxOvLZ5POG", + "id": "4af94082-5a8b-44f4-8522-3fad8de6dac0", + "last_modified": 1618007740352 + }, + { + "schema": 1617814862915, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "CLh6UBu+nNotFk0+OVG/VQ==", + "id": "161b661f-dbd6-464d-bb85-0fae6bcdc3c0", + "last_modified": 1618007740345 + }, + { + "schema": 1617814863272, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "C2qzsD6xqfbEYJJqqM3+sw==", + "id": "7918c367-5cdc-4ca3-a026-f4a9339d27fb", + "last_modified": 1618007740338 + }, + { + "schema": 1617814863692, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxcvqTGlt0", + "id": "94a2c5bf-772c-4ea9-98d4-6c331403ccb4", + "last_modified": 1618007740331 + }, + { + "schema": 1617814864143, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "DywQyVsGwJN/uNRJ+D6FaQ==", + "id": "a83c8c7c-6b48-4c51-b91f-a028b0bef291", + "last_modified": 1618007740324 + }, + { + "schema": 1617814864492, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "CIjNUl8ZJERNFKWCkd65Ug==", + "id": "2026e117-31bd-42a3-803d-58b5757fc5c6", + "last_modified": 1618007740316 + }, + { + "schema": 1617814864840, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxdOXafW7r", + "id": "f257ecff-d2d1-4650-925f-fe0311dd0d4c", + "last_modified": 1618007740306 + }, + { + "schema": 1617814865204, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2", + "serialNumber": "EdccItot5O/ZQ0miQN9opU7N", + "id": "6a5104df-f85c-45b5-940a-d1408793a461", + "last_modified": 1618007740300 + }, + { + "schema": 1617814865557, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxPO1ARoQF", + "id": "a901306e-9341-44a1-904f-1a1941429c8b", + "last_modified": 1618007740293 + }, + { + "schema": 1617814865915, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "CFE9WpEE7UIP80unUKjpiA==", + "id": "84e3e9ae-d686-42ef-ab63-064a5f4a512c", + "last_modified": 1618007740286 + }, + { + "schema": 1617814866382, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGMxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEvMC0GA1UEAwwmZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "BVQjPBnG8p6lFfhbaJfb7A==", + "id": "f23070d5-5360-483a-b584-8c0685a20c0c", + "last_modified": 1618007740279 + }, + { + "schema": 1617814866772, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1703616", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxdeNaJz67b17uAq2d2A==", + "id": "bcfcff4b-a211-4896-87d9-e16e845db359", + "last_modified": 1618007740272 + }, + { + "schema": 1614215515705, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1694779", + "who": "", + "why": "", + "name": "", + "created": "2021-02-25T00:48:11Z" + }, + "enabled": true, + "issuerName": "MFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENB", + "serialNumber": "AJiY6A==", + "id": "bc098dc1-29b5-4604-a794-449f31ee0753", + "last_modified": 1614217822898 + }, + { + "schema": 1612858930249, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "W/3RuxUtEGuqbW0X5zxWHw3ZyMo=", + "id": "e51b8276-12aa-46b1-af3c-fef58c6de7ba", + "last_modified": 1612908330359 + }, + { + "schema": 1612890051994, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "JPDbtZwIoyFn12ZNYmpf4xQ=", + "id": "62d73ea5-abc0-4208-936f-0348b627ac06", + "last_modified": 1612908330352 + }, + { + "schema": 1612890052292, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "Dth08OTAV/Y=", + "id": "3914db66-10f2-4a0b-bd42-243d6f7f3d8f", + "last_modified": 1612908330345 + }, + { + "schema": 1612890052592, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "Xu60SnDhjmPJiY8gLLrBZJFO3AU=", + "id": "1e4492aa-5c27-462d-9545-39df8c0c02dd", + "last_modified": 1612908330337 + }, + { + "schema": 1612890052888, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "SJgt4qkssznhyPkzNYJ10+T4glU=", + "id": "8199aafc-a9a2-41f8-abde-2aca626c7174", + "last_modified": 1612908330331 + }, + { + "schema": 1612890053202, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "BmKgCIAEmP5NgQsku4ItC9I7kNw=", + "id": "b92b1d62-0d79-4cba-8559-47615d5acae5", + "last_modified": 1612908330325 + }, + { + "schema": 1612890053564, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "ELYvNFph62GEp7EGyAV+lFPiB9U=", + "id": "7b0153fc-dc56-4f66-a252-6902604f9b1f", + "last_modified": 1612908330318 + }, + { + "schema": 1612890053858, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "Ae5fIi3nG0Ol1Gafng==", + "id": "90944665-506c-40ae-9889-1a4d2b1b0671", + "last_modified": 1612908330309 + }, + { + "schema": 1612890054203, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "ftbnnMmtgcTIGT75XUQodw40Exc=", + "id": "5f8fac48-6b82-40ab-be40-d9c33205add1", + "last_modified": 1612908330303 + }, + { + "schema": 1612890054558, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hNwI=", + "id": "312fe464-b344-454b-9ed5-290e541aa68d", + "last_modified": 1612908330297 + }, + { + "schema": 1612890054896, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGSS4=", + "id": "b3ec6778-f717-4f73-9969-59798168124c", + "last_modified": 1612908330291 + }, + { + "schema": 1612890055229, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "PHHm78lGIbrsbmkykNoIxhV3Qh8=", + "id": "d9f028ea-0b76-472f-b42b-2b993e9c3bb7", + "last_modified": 1612908330285 + }, + { + "schema": 1612890055533, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABMYnlWSU=", + "id": "d9d5e031-e3a2-4b6f-9103-9f531e8209b0", + "last_modified": 1612908330279 + }, + { + "schema": 1612890055831, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "Uk/B8W400XArhKE/sEK7zHw8kDI=", + "id": "1c498b17-f0ba-44bf-be93-0b75897d40d1", + "last_modified": 1612908330273 + }, + { + "schema": 1612890056175, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw7XaNcO3O", + "id": "2a5fa167-3546-4fcb-99e3-906479017d85", + "last_modified": 1612908330267 + }, + { + "schema": 1612890056478, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "RwexAExyiQfNNUdV9yI=", + "id": "8b462d07-92db-41e0-80d1-992fdc12eefd", + "last_modified": 1612908330261 + }, + { + "schema": 1612890056792, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "K4kdt/YIdYM=", + "id": "f1fa7c0d-fa11-46dc-9f19-36d0ed7000f3", + "last_modified": 1612908330256 + }, + { + "schema": 1612890057087, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABMYnlW/Q=", + "id": "c77d383c-1cb3-4016-bbeb-999500192bc6", + "last_modified": 1612908330250 + }, + { + "schema": 1612890057382, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hQUM=", + "id": "5354b86e-7606-474c-a6cb-a227fcca5b77", + "last_modified": 1612908330244 + }, + { + "schema": 1612890057680, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hPxE=", + "id": "5e691223-e105-41f2-8150-3cec1821d86b", + "last_modified": 1612908330238 + }, + { + "schema": 1612890057975, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hRQw=", + "id": "1e8e9129-92c9-4f0f-92b1-c1a516ff6146", + "last_modified": 1612908330232 + }, + { + "schema": 1612890058367, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691771", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hQvk=", + "id": "f941831e-5c17-4ca8-8988-a6d64fa82f0a", + "last_modified": 1612908330225 + }, + { + "schema": 1612554105658, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1691227", + "who": "dkeeler@mozilla.com", + "why": "unauthorized issuance", + "name": "unauthorized google.lk certificate", + "created": "2021-02-07T00:09:20Z" + }, + "enabled": true, + "subject": "MBQxEjAQBgNVBAMTCWdvb2dsZS5saw==", + "pubKeyHash": "FeeuQMxLP3Iipab+Pn3Ef25G7poiUYOdspbWKtoqDfc=", + "id": "07b35e26-7e9f-490d-a90f-fa6847466994", + "last_modified": 1612664984721 + }, + { + "schema": 1611344508583, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1688277", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#4)", + "name": "Information Security Certification Authority", + "created": "2021-01-22T20:13:32Z" + }, + "enabled": true, + "issuerName": "MFMxNTAzBgNVBAMTLEluZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "e8S4MvOlwiPqsJG22mv4T3ELQkA=", + "id": "d6ae9798-3c19-4985-8586-1b3cf30ab705", + "last_modified": 1611358319989 + }, + { + "schema": 1607462846244, + "details": { + "bug": "1680927", + "who": "dkeeler@mozilla.com", + "why": "Kazakhstan MITM (#3)", + "name": "Information Security Certification Authority CA", + "created": "2020-12-07T23:52:21Z" + }, + "enabled": true, + "issuerName": "MFYxODA2BgNVBAMTL0luZm9ybWF0aW9uIFNlY3VyaXR5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENBMQ0wCwYDVQQKEwRJU0NBMQswCQYDVQQGEwJLWg==", + "serialNumber": "KH3ODOPG96qjP/ll526pjIJKWds=", + "id": "e93dad8d-45ef-4174-bc5f-2cce9ce52c24", + "last_modified": 1607546180239 + }, + { + "schema": 1606160498787, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "GxL0QbvG2zbxZzhJGclbCxYkXEc=", + "id": "41d4025d-a847-4b7d-bd0e-6befd03c6460", + "last_modified": 1606328856922 + }, + { + "schema": 1606237253826, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "QAF04JJ0ObjtC66DDuB/SA==", + "id": "3ba0026a-766a-48c1-a17f-d03ec43379fe", + "last_modified": 1606328856914 + }, + { + "schema": 1606237254204, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "QAF04JJlL8YLqYHff02SmA==", + "id": "6263a3b5-3a6b-4fad-93f5-7a69c61562a7", + "last_modified": 1606328856906 + }, + { + "schema": 1606237254567, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "bomLaB2wkfxTPIzWP9gA5w==", + "id": "59885864-5341-4f5a-90f1-59055aca15f5", + "last_modified": 1606328856898 + }, + { + "schema": 1606237254923, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "LwO3QHf+WENPNhPDs41au1LN0w4=", + "id": "31c86b9b-97b4-4e8b-b036-0bb4b49290f6", + "last_modified": 1606328856889 + }, + { + "schema": 1606237255322, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "Qd4GA1RV7pjIJpSgkKlGME8u/4c=", + "id": "eca7098d-b54e-46d4-b783-f8ec21706c93", + "last_modified": 1606328856881 + }, + { + "schema": 1606237255684, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "Q6kODxh1MBAjWD4n6PuAue3GSw4=", + "id": "70b80622-a284-461b-a9e2-3dbd9c007dc2", + "last_modified": 1606328856873 + }, + { + "schema": 1606237256050, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "Nq+beJUJjRiUhESAu1k7k6Z0RT0=", + "id": "685ba4b6-398c-4ad9-9689-76fff19e53b6", + "last_modified": 1606328856865 + }, + { + "schema": 1606237256404, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "ReeMpfX7014KbBcz+Bi7VA==", + "id": "a70b4027-bd52-4437-a4f5-c4f8523d59a8", + "last_modified": 1606328856857 + }, + { + "schema": 1606237256765, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "c9pa+iPZP7qELgog9AHJ2G4k/F0=", + "id": "73cc9f1e-073e-4f9d-86dd-ba593662a14c", + "last_modified": 1606328856849 + }, + { + "schema": 1606237257131, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "Ae5fIk5ZxNFubPUxYw==", + "id": "17086fc0-bc58-40ee-8523-e2d76f855c11", + "last_modified": 1606328856840 + }, + { + "schema": 1606237257476, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "YUrgi0DHsxHYXt49k0VA2g==", + "id": "07110549-cea6-49b6-aee6-ffec223e88ca", + "last_modified": 1606328856832 + }, + { + "schema": 1606237257827, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Cvhc+o++pVw9AL6N9N0w6D7ygkQ=", + "id": "c1fc820f-b6fa-47bc-83fe-0de5842cf130", + "last_modified": 1606328856824 + }, + { + "schema": 1606237258260, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "GJF9m6GiOakqINltfXbZQtxKEGU=", + "id": "b00e2c36-61a3-4a00-b63b-e6355b86aed0", + "last_modified": 1606328856816 + }, + { + "schema": 1606237258635, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1679183", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "dmnAQKXSXYptTvdbyxROUqOQWJ0=", + "id": "3854a122-e277-4af3-8819-cf6e7d2bde38", + "last_modified": 1606328856808 + }, + { + "schema": 1605744672266, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdBIENB", + "serialNumber": "YQbgdgAAAAAABA==", + "id": "5e2484ae-7825-4ed0-8f75-00ad399983c3", + "last_modified": 1605833130345 + }, + { + "schema": 1605805268081, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "GvbZeo/wso3urAGlJMh6bzLnnfY=", + "id": "a9026db6-597b-40d2-9d83-bca127f77906", + "last_modified": 1605833130337 + }, + { + "schema": 1605805268441, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "dai5tSMbn5UK7WK6+0ZxRw==", + "id": "68c90a6f-5401-44b7-b25d-e54377d96f7a", + "last_modified": 1605833130329 + }, + { + "schema": 1605805268949, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "BoL7H4F3dqV5kSw+2RDv8Q==", + "id": "ab74320e-97a9-46f8-ad2c-56b559c97b5d", + "last_modified": 1605833130321 + }, + { + "schema": 1605805269328, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "Bf8hBaUt76ku6AB5KoFdkA==", + "id": "aa17e7fa-44bd-42f3-b581-959a186a421b", + "last_modified": 1605833130313 + }, + { + "schema": 1605805270416, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGMxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xNDAyBgNVBAMMK1N0YWF0IGRlciBOZWRlcmxhbmRlbiBEb21laW4gU2VydmVyIENBIDIwMjA=", + "serialNumber": "DHG5F7l+J207REqX6P129dCmM1Y=", + "id": "c5862b61-90db-45fd-be2e-191654adafcc", + "last_modified": 1605833130297 + }, + { + "schema": 1605805271001, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzM=", + "serialNumber": "dJ4w/GwP08WekbUIXvYTsQrO+a8=", + "id": "2ed55472-2fca-4f88-a4d3-6430ebcfe887", + "last_modified": 1605833130289 + }, + { + "schema": 1605805271578, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "D5JnAg9adbdm5Das0NhZBA==", + "id": "4096586d-5ab7-4d99-8cad-0851589758d5", + "last_modified": 1605833130281 + }, + { + "schema": 1605805272154, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdBIENB", + "serialNumber": "GCS4aQAAAAAACw==", + "id": "cd70af23-8e21-4c6e-853f-663a72ace8ba", + "last_modified": 1605833130273 + }, + { + "schema": 1605805272681, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdBIENB", + "serialNumber": "QyMLpAAAAAAABg==", + "id": "46bde686-bcd3-40e2-b870-94d077364822", + "last_modified": 1605833130265 + }, + { + "schema": 1605805273245, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "d+erf7BRCIYZoI7WwMt/6rzpJBY=", + "id": "db99b9fd-21b8-465d-beb1-3d62c0d02b02", + "last_modified": 1605833130257 + }, + { + "schema": 1605805273804, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "SKQC3bXe/VCsz8D88T8=", + "id": "8c9f276c-18ac-43d2-be34-b2fc879c6190", + "last_modified": 1605833130249 + }, + { + "schema": 1605805274355, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "AsGJSGnUUCnrODgadbthZw==", + "id": "09a57059-648a-4b0a-a021-e3a43d057d38", + "last_modified": 1605833130241 + }, + { + "schema": 1605805274890, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "BmdUhbrY7ZII2zyybFAaEw==", + "id": "db912b82-5b3c-4bf9-877c-b35ea22edbee", + "last_modified": 1605833130233 + }, + { + "schema": 1605805275306, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "DCzgwS1angzJReCv7Xcalg==", + "id": "17d4fa79-78bf-4472-8a80-65151706027a", + "last_modified": 1605833130225 + }, + { + "schema": 1605805275716, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "TbiYNjM7FrxEHV9PxgvGfc/wvSg=", + "id": "32b2fee6-46df-4686-acca-8ed67e5d8a42", + "last_modified": 1605833130217 + }, + { + "schema": 1605805276096, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "ODVFFOhzXWvRkIGhqNL3P4BwTRA=", + "id": "70bec368-2a5c-4ca4-b210-78ae19fff42f", + "last_modified": 1605833130209 + }, + { + "schema": 1605805276583, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "Dy7r7RLmbxPEc/M/+nbVCw==", + "id": "ce7389c4-d142-4258-9e1b-a30ee9398926", + "last_modified": 1605833130201 + }, + { + "schema": 1605805276938, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "TeYBCZy0d/TPcAJ6FN6M20qNJKc=", + "id": "665cb523-8317-466a-8a04-7893e9e68a7f", + "last_modified": 1605833130193 + }, + { + "schema": 1605805277295, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "SYSzK6SV0MYd40vPFNOjWu5QhkQ=", + "id": "92821be2-e278-4214-b517-1cbc171d1241", + "last_modified": 1605833130185 + }, + { + "schema": 1605805277676, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "BeizThxeIodIgkMRs8LrVw==", + "id": "f863e679-95a7-4481-a5de-11b1fddd1798", + "last_modified": 1605833130177 + }, + { + "schema": 1605805278024, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "RUME8OY/YBHyokbgxoTKpPcoiHY=", + "id": "9f47e911-04fe-49e5-bc72-179f32d65702", + "last_modified": 1605833130170 + }, + { + "schema": 1605805278367, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "PdQW/4AvIwpSzL1h30myhgjnfno=", + "id": "afa81c00-2b9c-4acb-b206-dc75bed4d7df", + "last_modified": 1605833130162 + }, + { + "schema": 1605805278768, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGMxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xNDAyBgNVBAMMK1N0YWF0IGRlciBOZWRlcmxhbmRlbiBEb21laW4gU2VydmVyIENBIDIwMjA=", + "serialNumber": "EHDoZA3ClX5CJu5GKdvXUZn+Hr4=", + "id": "69aa0c32-b427-4e45-a7e3-3aa5bef3e847", + "last_modified": 1605833130154 + }, + { + "schema": 1605805279149, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "DXEchC/p1n6dzCKiHLZc3w==", + "id": "e0d2b6bf-e37e-4179-91d0-8c1ab6355075", + "last_modified": 1605833130145 + }, + { + "schema": 1605805279538, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENB", + "serialNumber": "HlNa/3/6dnNEqFtSStnQKX1dumU=", + "id": "d45e5544-227f-437c-921e-bbb75b6ddb0c", + "last_modified": 1605833130137 + }, + { + "schema": 1605805279895, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "CgZFWoVH8W4JmjaEZIlOZA==", + "id": "339013cc-143e-4922-a688-8b6478c5c01e", + "last_modified": 1605833130129 + }, + { + "schema": 1605805280254, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "YVQpZ/rcAgCYQ5mgLIWRrgmF5YY=", + "id": "4503d351-700c-4af5-9b32-a3e8314572f0", + "last_modified": 1605833130121 + }, + { + "schema": 1605805280608, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "b6PMduOT1igm2b5XrSbN1ayRYD0=", + "id": "823b3acc-228f-43ce-a7b4-151cf75934d3", + "last_modified": 1605833130114 + }, + { + "schema": 1605805280996, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "dR4+6PDu0/tJvB0t+q2Tjw==", + "id": "c27287e9-1385-490f-bc19-1e8d45a0a89a", + "last_modified": 1605833130106 + }, + { + "schema": 1605805281341, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "BkoduoMprQURTuDY6pi+fA==", + "id": "9bad30d8-9199-432e-841f-37a48d5fc662", + "last_modified": 1605833130098 + }, + { + "schema": 1605805281691, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENB", + "serialNumber": "DxaDT4KRkeMq/3spObeaUg==", + "id": "0741e031-349c-4928-afa5-7a98a5f89928", + "last_modified": 1605833130090 + }, + { + "schema": 1605805282124, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "AenYn2YG9Tfnb02L9w==", + "id": "26aeffc6-86c2-419e-90a0-f1a9d741985c", + "last_modified": 1605833130082 + }, + { + "schema": 1605805282467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "AenYn3DXZQryO4NWdA==", + "id": "036c673a-922b-4d5b-8ac2-a677c16f1739", + "last_modified": 1605833130075 + }, + { + "schema": 1605805282841, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "dR4+7KSgWczvgqiIFI922Q==", + "id": "ffd6f99d-e200-4013-87ad-386d7f8ab887", + "last_modified": 1605833130067 + }, + { + "schema": 1605805283196, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "dgvbZsDHLBWQOFhRyINfIm288GM=", + "id": "d88b550b-1601-495c-8416-754dcaa1c786", + "last_modified": 1605833130059 + }, + { + "schema": 1605805283559, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BCshiSJ3NHQyikS/bV3uPQ==", + "id": "3cb9bf01-1dc7-4f4a-ab59-6f4aedbb13f2", + "last_modified": 1605833130051 + }, + { + "schema": 1605805283910, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGMxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xNDAyBgNVBAMMK1N0YWF0IGRlciBOZWRlcmxhbmRlbiBEb21laW4gU2VydmVyIENBIDIwMjA=", + "serialNumber": "erEwoq3oXeVG3OPjTKvIN7H3JdQ=", + "id": "3a7e18e9-b8a4-413d-9af6-7ab91773332b", + "last_modified": 1605833130041 + }, + { + "schema": 1605805284256, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "H4YDb0uIAMdUnNyPR/pbe3BPlSU=", + "id": "c96bdfc7-55dc-45e2-840a-9554bbad29b4", + "last_modified": 1605833130029 + }, + { + "schema": 1605805284605, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABNumCOV0=", + "id": "548a971f-0a10-404a-9c23-6b95c7a3093d", + "last_modified": 1605833130016 + }, + { + "schema": 1605805284959, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdCIENB", + "serialNumber": "MwAAABbN/xkAVVRYtwAAAAAAFg==", + "id": "7a2e0fee-04cb-45bd-ba87-c080899ae002", + "last_modified": 1605833130003 + }, + { + "schema": 1605805285386, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "CvZcvnGmEFQSk2xCwhibpw==", + "id": "ea049bf5-9917-4bfd-a3a5-b4f776ebc61f", + "last_modified": 1605833129994 + }, + { + "schema": 1605805285768, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABNuk6OrM=", + "id": "8326925e-0757-4f5d-b837-5557ffd6be0a", + "last_modified": 1605833129986 + }, + { + "schema": 1605805286123, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxKjAoBgNVBAMTIUlkZW5UcnVzdCBQdWJsaWMgU2VjdG9yIFJvb3QgQ0EgMQ==", + "serialNumber": "CgFCgAAAAVEl13NhAAAAAg==", + "id": "9227e662-5fd7-4cf7-a4f5-d6101493251b", + "last_modified": 1605833129978 + }, + { + "schema": 1605805286524, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1678378", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "AkSadIfHzbm3VI4r7Gsfyg==", + "id": "2b1efa09-4c1b-4d9c-9cea-ef42544b4e2c", + "last_modified": 1605833129970 + }, + { + "schema": 1604163572181, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674587", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNTETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "Ae5fInnr9AhpWVIjkw==", + "id": "3719f39f-d274-4708-96d8-75eca5eab6f4", + "last_modified": 1604616023875 + }, + { + "schema": 1604163626424, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674587", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MGAxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMTAvBgNVBAMMKFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBJbnRlcm1lZGlhaXIgQ0E=", + "serialNumber": "akWWZTytiy58nyLOsqpnaQ==", + "id": "9b13e978-d61b-4572-8227-5a297f82a11e", + "last_modified": 1604616023867 + }, + { + "schema": 1604163626757, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1674587", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": false, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "Irmw1g==", + "id": "af04f4c1-0721-4205-b839-a3694ceb5f95", + "last_modified": 1604616023858 + }, + { + "schema": 1599939719570, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1664854", + "who": "jjones@mozilla.com", + "why": "", + "name": "revoked.badssl.com certificate", + "created": "2020-09-14T18:43:23Z" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQQ==", + "serialNumber": "A3G1iob2zpw+y3v0L5II/A==", + "id": "53648e4e-4a7e-4c75-858e-cab93f2a31d1", + "last_modified": 1600115137321 + }, + { + "schema": 1591645303251, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=", + "serialNumber": "GA==", + "id": "b6826693-3d91-40e3-8d2b-265e4c7cda80", + "last_modified": 1591722356162 + }, + { + "schema": 1591649654954, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "GA==", + "id": "557d080d-b0ea-4b1e-b4c0-960ca0cd3175", + "last_modified": 1591722356159 + }, + { + "schema": 1591649655330, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0E=", + "serialNumber": "AtusdpjPnzfY+uarbdje+A==", + "id": "108255c9-fab2-4918-88b9-d26df1cac1f2", + "last_modified": 1591722356155 + }, + { + "schema": 1591649655703, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "APReKEnjxshvEQowJWkFghM=", + "id": "68428bfd-380c-4b10-9ec1-183f6b40ce05", + "last_modified": 1591722356152 + }, + { + "schema": 1591649656080, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "eUvtSxcPn58fPCIEBd5eFw==", + "id": "83bc86bf-c02f-448a-97d3-7b81b115d16e", + "last_modified": 1591722356149 + }, + { + "schema": 1591649656453, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeyFg==", + "id": "bf068164-e35f-4bec-b1cc-cc7ff5379682", + "last_modified": 1591722356145 + }, + { + "schema": 1591649656882, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "AeUosKc0AFkLsGdjaw==", + "id": "8e2ca07d-ab48-4d70-ae2a-c2a6e389f5e6", + "last_modified": 1591722356142 + }, + { + "schema": 1591649657251, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "AeEjUghuH7H0d7NMnA==", + "id": "152d4c2b-ce88-4a14-ad3f-57fcf1f2b163", + "last_modified": 1591722356138 + }, + { + "schema": 1591649657620, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "SBtqDm4P/739RPqw/wc=", + "id": "38ebfa60-5de1-4b3c-af35-c4418e230210", + "last_modified": 1591722356135 + }, + { + "schema": 1591649657992, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "AeNmeE7qYUep9C4NnA==", + "id": "15d07b9c-17fb-4e80-abf0-2f1e717492d0", + "last_modified": 1591722356130 + }, + { + "schema": 1591649658361, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxKjAoBgNVBAMTIUlkZW5UcnVzdCBQdWJsaWMgU2VjdG9yIFJvb3QgQ0EgMQ==", + "serialNumber": "APTRqBUm1okACgR2cZ8cfyQ=", + "id": "8729e8d8-fe54-4368-8514-134c5d170341", + "last_modified": 1591722356126 + }, + { + "schema": 1591649658734, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "DQ==", + "id": "3fd06aee-629f-4979-8323-0465a9fa5500", + "last_modified": 1591722356122 + }, + { + "schema": 1591649659106, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "Aw==", + "id": "3b0ad68b-7bd2-499c-9a38-54f6a33a74c2", + "last_modified": 1591722356119 + }, + { + "schema": 1591649659485, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "Ag==", + "id": "b0757116-5f7e-4633-917b-3ac2ba7663e4", + "last_modified": 1591722356115 + }, + { + "schema": 1591649659854, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "BQ==", + "id": "18be7382-53ee-4963-b3bd-fde64e8aa4de", + "last_modified": 1591722356111 + }, + { + "schema": 1591649660220, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "AQ==", + "id": "70d7ef28-9c7b-4737-bf78-12469fc8c17e", + "last_modified": 1591722356108 + }, + { + "schema": 1591649660590, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "FQ==", + "id": "310af8c7-a837-4f5d-acd8-f150fdb1a228", + "last_modified": 1591722356105 + }, + { + "schema": 1591649660960, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "Dw==", + "id": "ec09837a-c496-4f2a-a2e3-d7b8255d8aa4", + "last_modified": 1591722356101 + }, + { + "schema": 1591649661328, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABb3uz2uVMNhTQ=", + "id": "327af663-e668-4828-8223-42d7cbec9bd6", + "last_modified": 1591722356098 + }, + { + "schema": 1591649661696, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABak8+W6/V88R0=", + "id": "0a0a2bb9-943b-4e99-aad6-608eff1fc6e8", + "last_modified": 1591722356095 + }, + { + "schema": 1591649662070, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABcxl+EFyHWQKE=", + "id": "e5f98ef5-611a-457c-876f-fef1890ea5f3", + "last_modified": 1591722356091 + }, + { + "schema": 1591649662438, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHAxCzAJBgNVBAYTAk5MMRcwFQYDVQRhDA5OVFJOTC0zMDIzNzQ1OTEgMB4GA1UECgwXUXVvVmFkaXMgVHJ1c3RsaW5rIEIuVi4xJjAkBgNVBAMMHVF1b1ZhZGlzIFF1YWxpZmllZCBXZWIgSUNBIEcx", + "serialNumber": "Vuxo8erTcb0tPvSne8vm7poAGfg=", + "id": "4ca3a08c-7c00-4006-9afe-3233f2be1d1e", + "last_modified": 1591722356088 + }, + { + "schema": 1591649662815, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHAxCzAJBgNVBAYTAk5MMRcwFQYDVQRhDA5OVFJOTC0zMDIzNzQ1OTEgMB4GA1UECgwXUXVvVmFkaXMgVHJ1c3RsaW5rIEIuVi4xJjAkBgNVBAMMHVF1b1ZhZGlzIFF1YWxpZmllZCBXZWIgSUNBIEcx", + "serialNumber": "JN7EJQf8huzLxEjHdisrDOgr0sM=", + "id": "36f591c2-4c2a-4514-a7da-dcef49e32884", + "last_modified": 1591722356085 + }, + { + "schema": 1591649663183, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzM=", + "serialNumber": "NVdKOsmxSGeTCfVPNC95i7XTw/4=", + "id": "5b7b7dfc-f4ff-4d72-9d4e-9ee696b6306f", + "last_modified": 1591722356082 + }, + { + "schema": 1591649663553, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzM=", + "serialNumber": "ZyJMAoxOQNnj8J4Hj3cObklOwII=", + "id": "e993fef8-58b5-4dd5-9d85-c158fea34a38", + "last_modified": 1591722356078 + }, + { + "schema": 1591649663925, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxaePNG8wi", + "id": "01de91b2-85b7-44d8-8a29-6e92c2030014", + "last_modified": 1591722356075 + }, + { + "schema": 1591649664299, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxaI50lNZi", + "id": "a848ed94-c8c9-4f2d-afaf-b148b98db582", + "last_modified": 1591722356071 + }, + { + "schema": 1591649664675, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AWGuIAXOPxJ++I3XJRux", + "id": "c0f8f918-79ab-482a-9706-e23f19c61e97", + "last_modified": 1591722356067 + }, + { + "schema": 1583869297369, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "BuhGJy8fCo/RhFzjafbV", + "id": "0b3cb4f0-b5ff-4816-adf3-a1698c3458f9", + "last_modified": 1584052365964 + }, + { + "schema": 1584034820412, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "CY639U9sn0sX18dB44VklbKesdk=", + "id": "00ef184d-e7d9-4dc3-98d6-2db2b75087ae", + "last_modified": 1584052365961 + }, + { + "schema": 1584034820824, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "FO1+kHW2roaOGjsCT4qUr8j127o=", + "id": "5af568ce-9c5a-430c-aba1-b68ceabe05ff", + "last_modified": 1584052365958 + }, + { + "schema": 1584034821215, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "YnphsQ5/Xye+O+telM9/9Eje4cU=", + "id": "ee4e0d21-2e0e-47e4-a708-9a6225d9069f", + "last_modified": 1584052365956 + }, + { + "schema": 1584034821655, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "Gf80Vp02a6H2bo2VMu4F0FW53R0=", + "id": "3dd81a10-316e-4311-81f4-6ba5dabf5ae7", + "last_modified": 1584052365953 + }, + { + "schema": 1584034822061, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "BQjRlltFCk5Wcqhsy4QVhw==", + "id": "e3d96b23-470d-4cb3-8f5d-b5270c783e0d", + "last_modified": 1584052365950 + }, + { + "schema": 1584034822464, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "exeWtGPKX/5WcqdQhOc8fQ==", + "id": "4806b099-4614-40a8-a483-e9e6188116d8", + "last_modified": 1584052365947 + }, + { + "schema": 1584034822846, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "Z8aU3fhNIStWcqv0tJZTgg==", + "id": "ca822ca1-0b9c-4524-a953-d101f593eba7", + "last_modified": 1584052365944 + }, + { + "schema": 1584034823229, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "M0ZhG4oxvRRWcqjOeSs9yA==", + "id": "29bb0c13-74dd-4a55-9883-0e961e1942ea", + "last_modified": 1584052365941 + }, + { + "schema": 1584034823601, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "QDHn+nHfbVLgWJSmBrXX8w==", + "id": "d4ebcb06-cb47-45ae-b5f9-d6674826e686", + "last_modified": 1584052365938 + }, + { + "schema": 1582486896281, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "Or+7fO17j//ZhiuTTAKvlg==", + "id": "1409bc48-a188-478f-9155-3c123a564ed6", + "last_modified": 1582590432698 + }, + { + "schema": 1582566002614, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=", + "serialNumber": "Btbu", + "id": "f5c9bdce-783b-4f33-8e9f-9612f211c845", + "last_modified": 1582590432695 + }, + { + "schema": 1582566002989, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=", + "serialNumber": "BHpY", + "id": "557694d9-82a5-4dc3-a735-b3f1af73aded", + "last_modified": 1582590432692 + }, + { + "schema": 1582566003376, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "LiPbNfQ597/RWOBNV1MXoA==", + "id": "829e3d9e-06f4-4002-a810-36dd34eee245", + "last_modified": 1582590432689 + }, + { + "schema": 1582566003760, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "eWPQh1DffhyNUeYDl8YVtA==", + "id": "1aa0c34a-ddd4-4cf0-9279-64d791e62070", + "last_modified": 1582590432687 + }, + { + "schema": 1582566004167, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "Bx243avyP23U1FKvQBJ36w==", + "id": "0e1f48ab-30d2-4e63-9780-9b6672a6ca1a", + "last_modified": 1582590432684 + }, + { + "schema": 1582566004551, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "SQI5AVoNzAF3OWyiM2Cj4ljeFC0=", + "id": "b8ec1e7e-6280-458d-9c29-a3e851f00122", + "last_modified": 1582590432681 + }, + { + "schema": 1582566004927, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "TLBJ4w6JFdOEMMEXiHXM1A==", + "id": "d00ae455-12cf-4ea5-ae0c-a2f06705f821", + "last_modified": 1582590432677 + }, + { + "schema": 1582566005308, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "HkSTzpMySJNT6+Pl3MpFlQ==", + "id": "75c8f23b-549a-403b-be9d-b731572438a5", + "last_modified": 1582590432674 + }, + { + "schema": 1582566005683, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "BHpU", + "id": "ed327a30-25e2-4875-a463-4a15b1faaa1a", + "last_modified": 1582590432671 + }, + { + "schema": 1582566006061, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "BHpS", + "id": "ae949154-cc09-479b-b394-a3f30a5390a1", + "last_modified": 1582590432668 + }, + { + "schema": 1582566006451, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "BHpR", + "id": "f9a51bb4-e3f9-4932-b1f5-631c3bdccdc1", + "last_modified": 1582590432663 + }, + { + "schema": 1582566006822, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "BHpT", + "id": "1b8b4d93-eaf2-4424-a34b-2f1968461dc5", + "last_modified": 1582590432658 + }, + { + "schema": 1582566007187, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0E=", + "serialNumber": "BJJx", + "id": "04ad7a19-5455-4052-8002-c9de365cde8e", + "last_modified": 1582590432654 + }, + { + "schema": 1582566007571, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBHbG9iYWwgU2VydmljZXMgQ0E=", + "serialNumber": "RlOxph66LcejLvk5Wk74jA==", + "id": "d796075a-7084-4a8c-840b-8dc6e026f461", + "last_modified": 1582590432651 + }, + { + "schema": 1582566007956, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByekcQ==", + "id": "7d902b2c-9b95-4a5e-8a31-08bc5da7bd3e", + "last_modified": 1582590432649 + }, + { + "schema": 1582566008341, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByemYQ==", + "id": "15a865a0-d558-4f88-9892-3a271a5f6528", + "last_modified": 1582590432646 + }, + { + "schema": 1582566008742, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "GAoX7ScPfxz8YoiFM5eqvg==", + "id": "e8a4b4f0-615a-4bf0-b48e-93b5884296f9", + "last_modified": 1582590432643 + }, + { + "schema": 1582566009112, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "Ae5fFxAYBjmCt/6FYA==", + "id": "c21f9163-b036-4ac7-b2e9-f7ab2b665298", + "last_modified": 1582590432640 + }, + { + "schema": 1582566009491, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "AfJAQRZysbylo3/lCg==", + "id": "77ac5cd3-7279-4240-b4eb-62004c39d6bf", + "last_modified": 1582590432637 + }, + { + "schema": 1582566009869, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "AfJAQQv5hQk4X1fUHQ==", + "id": "5f42b2aa-15a8-42a0-b469-f104e228af12", + "last_modified": 1582590432634 + }, + { + "schema": 1582566010243, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "EdccO1mbICQnbPFsBI2427NJ", + "id": "2eb7cd83-059a-40ff-ab3e-146204bc3abc", + "last_modified": 1582590432631 + }, + { + "schema": 1582566010616, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "Yxhd22Oak3mBIvwX0BVTSfw=", + "id": "bd9a292d-f2c4-4308-be5a-687bc05da029", + "last_modified": 1582590432629 + }, + { + "schema": 1582566010982, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "AeEjUi4YCM8xJiOwqA==", + "id": "7551c683-55c9-4b0f-9d65-a1db506f682b", + "last_modified": 1582590432626 + }, + { + "schema": 1582566011356, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "SETcxilXmeYyN/2STTs=", + "id": "07dcda66-29df-4978-b75a-e3a0dd844b54", + "last_modified": 1582590432623 + }, + { + "schema": 1582566011728, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "AecHzcaPEeFvu7X4TQ==", + "id": "f66167d5-097d-4b1f-bfb8-667015b1e683", + "last_modified": 1582590432620 + }, + { + "schema": 1582566012112, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "AecHzeH0hsl7/sQXWA==", + "id": "c6225619-2a00-4f0f-844c-25be3e08ab8c", + "last_modified": 1582590432617 + }, + { + "schema": 1582566012488, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "AenYn3utvYQ+obXTYQ==", + "id": "0f8df12d-f783-4562-98fe-d57baeea9889", + "last_modified": 1582590432614 + }, + { + "schema": 1582566012856, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMw==", + "serialNumber": "AfCcW0UoBqS7QoIgZQ==", + "id": "b3615945-89df-482d-b8cf-68df333347de", + "last_modified": 1582590432611 + }, + { + "schema": 1582566013230, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSNA==", + "serialNumber": "AfCcW3AFptyG4vme8w==", + "id": "03ca2e4b-c3ab-4bc0-8ce1-01bd8f454496", + "last_modified": 1582590432608 + }, + { + "schema": 1582566013606, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MG0xCzAJBgNVBAYTAk5MMUQwQgYDVQQKDDthZ2VudHNjaGFwIENlbnRyYWFsIEluZm9ybWF0aWVwdW50IEJlcm9lcGVuIEdlem9uZGhlaWRzem9yZzEYMBYGA1UEAwwPWm9yZyBDU1AgQ0EgRzIx", + "serialNumber": "AOc8WaTzibK6426cQCzYSn0=", + "id": "1570bd10-b7c0-4dbd-9e15-ba1e0115e9c2", + "last_modified": 1582590432605 + }, + { + "schema": 1582566013971, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "VUQCtMaN0r1Jo6pasF0CjQ==", + "id": "6a554d48-b068-47bb-931d-22b8cb7b66d0", + "last_modified": 1582590432602 + }, + { + "schema": 1582566014354, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "UnMUjvgeTcJJkqVP72pF8w==", + "id": "a5b1499f-c072-4a3d-9033-22bbdc09c23e", + "last_modified": 1582590432599 + }, + { + "schema": 1582566014726, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "TkUbErttua5HoElQBoHrfg==", + "id": "f3c4284d-2654-478c-9a44-a99544a88e88", + "last_modified": 1582590432596 + }, + { + "schema": 1582566015097, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "ZG/V3gQ1QzdJo6nRSP5AjQ==", + "id": "437f5819-57b9-4aa5-b4a2-89adda81489b", + "last_modified": 1582590432593 + }, + { + "schema": 1582566015470, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "BA==", + "id": "d572f4d1-985f-4ac4-8a8f-1a17109e0504", + "last_modified": 1582590432590 + }, + { + "schema": 1582566015857, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "Cw==", + "id": "ccd96d5a-16fe-4fd8-bd6e-294a66abd88e", + "last_modified": 1582590432587 + }, + { + "schema": 1582566016231, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "Dg==", + "id": "1d856404-8fcc-498e-8f8a-c2016e6d7c55", + "last_modified": 1582590432584 + }, + { + "schema": 1582566016663, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odQ==", + "serialNumber": "DA==", + "id": "8b502698-fba2-4372-9e7d-988420335d31", + "last_modified": 1582590432581 + }, + { + "schema": 1582566017047, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Rj+ThwsAA8DqoQLXSaqqElyK9P4=", + "id": "5e3489f3-78b6-48ed-afd7-ccb86042749f", + "last_modified": 1582590432579 + }, + { + "schema": 1582566017432, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDMgRzM=", + "serialNumber": "bKpOjcdardvXVCsjEOCqoP1/zsE=", + "id": "eaa05242-5992-4dc8-aa3d-1063279ec952", + "last_modified": 1582590432576 + }, + { + "schema": 1582566017800, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDMgRzM=", + "serialNumber": "PGx6vOZ0LowycWshA7l2/cS9l5E=", + "id": "787c516b-e616-459d-b9c5-04473ac6d15b", + "last_modified": 1582590432573 + }, + { + "schema": 1582566018184, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "ANDXcnKaBBeX+J7a44L5HxE=", + "id": "804079f6-b969-4a06-a646-d1307ab4b213", + "last_modified": 1582590432570 + }, + { + "schema": 1582566018565, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "APhdLxkMYJ8UlLKN+cHR50w=", + "id": "fc817b05-b53b-4642-83d1-365b983ec797", + "last_modified": 1582590432568 + }, + { + "schema": 1582566018953, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "NXkdh5JRbWGxHEvvr3bB2g==", + "id": "88c23adc-fb99-49f4-802f-8ed1e55fa814", + "last_modified": 1582590432565 + }, + { + "schema": 1582566019323, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "EGhKDYboQ1ktFnRqiBUvgQ==", + "id": "e1d374e5-cdba-4122-b154-0daee677c7ad", + "last_modified": 1582590432562 + }, + { + "schema": 1582566019700, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AIFRrXKLZ6V/pFUkjYHTV/k=", + "id": "12593d1d-d952-4029-908f-eb19f099740c", + "last_modified": 1582590432559 + }, + { + "schema": 1582566020072, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AJk4uNYGKOpZLiYBD9Jm6BE=", + "id": "fff43ffb-3ec4-4644-9da7-f936fa622581", + "last_modified": 1582590432556 + }, + { + "schema": 1582566020468, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AWWEyekrgDP1hvA8XKDc", + "id": "c793250e-087c-4160-8177-344d33fd3577", + "last_modified": 1582590432554 + }, + { + "schema": 1582566020850, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AV/EmsRwXPDZBTXjz0SR", + "id": "74ac3328-fd34-4415-ba06-c228eae47e78", + "last_modified": 1582590432551 + }, + { + "schema": 1582566021219, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "AWBLIvZ2PwkB7gSDa5c8", + "id": "b3b20ec6-e123-4a70-92ea-cb05df99ef75", + "last_modified": 1582590432548 + }, + { + "schema": 1581450099900, + "details": { + "bug": "1614521", + "who": "jc@mozilla.com", + "why": "Migrate NSS-exceptions to OneCRL - Explicitly Distrust DigiNotar Root CA", + "name": "", + "created": "2020-02-12T18:20:39Z" + }, + "enabled": true, + "subject": "MF8xCzAJBgNVBAYTAk5MMRIwEAYDVQQKEwlEaWdpTm90YXIxGjAYBgNVBAMTEURpZ2lOb3RhciBSb290IENBMSAwHgYJKoZIhvcNAQkBFhFpbmZvQGRpZ2lub3Rhci5ubA==", + "pubKeyHash": "qQOvjAe7kbDZ4/OjDG1TM5/FvUfl1r20dlmIYMBooCQ=", + "id": "ebb80027-85c8-4050-9811-0652c73e3b77", + "last_modified": 1581628280099 + }, + { + "schema": 1581547556681, + "details": { + "bug": "1614521", + "who": "jc@mozilla.com", + "why": "Migrate NSS-exceptions to OneCRL - Explicitly Distrust TURKTRUST Mis-issued Intermediate CA 1, Bug 825022", + "name": "", + "created": "2020-02-12T18:21:29Z" + }, + "enabled": true, + "issuerName": "MIGsMT0wOwYDVQQDDDRUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU3VudWN1IFNlcnRpZmlrYXPEsSBIaXptZXRsZXJpMQswCQYDVQQGEwJUUjFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEthc8SxbSAgMjAwNQ==", + "serialNumber": "CCc=", + "id": "b9e6b58e-c59b-43d4-a0fd-40fcadf0b3ac", + "last_modified": 1581628280094 + }, + { + "schema": 1581547557131, + "details": { + "bug": "1614521", + "who": "jc@mozilla.com", + "why": "Migrate NSS-exceptions to OneCRL - Explicitly Distrust TURKTRUST Mis-issued Intermediate CA 2, Bug 825022", + "name": "", + "created": "2020-02-12T18:21:41Z" + }, + "enabled": true, + "issuerName": "MIGsMT0wOwYDVQQDDDRUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU3VudWN1IFNlcnRpZmlrYXPEsSBIaXptZXRsZXJpMQswCQYDVQQGEwJUUjFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEthc8SxbSAgMjAwNQ==", + "serialNumber": "CGQ=", + "id": "f48493ce-ff35-4488-bcf3-d7346a1141a2", + "last_modified": 1581628280088 + }, + { + "schema": 1575747699300, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=", + "serialNumber": "Fw==", + "id": "6be93d43-7f5b-46f9-a179-5a2e682f379a", + "last_modified": 1578526641344 + }, + { + "schema": 1575764418632, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=", + "serialNumber": "FQ==", + "id": "71f868b0-b192-42d8-a686-e63c8c7a4e3e", + "last_modified": 1578526641340 + }, + { + "schema": 1575764419006, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=", + "serialNumber": "GQ==", + "id": "37aaea0b-4b99-4995-b895-8254fbbe0cd4", + "last_modified": 1578526641336 + }, + { + "schema": 1575764419373, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "Fw==", + "id": "40043aba-106e-43ce-9e6e-0a13cae6a921", + "last_modified": 1578526641333 + }, + { + "schema": 1575764419744, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "FQ==", + "id": "c635cb90-ac7d-437f-a7dc-648ec7d434ca", + "last_modified": 1578526641329 + }, + { + "schema": 1575764420117, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "GQ==", + "id": "4dc0a9ae-a508-4b91-a2f8-3c128adcd432", + "last_modified": 1578526641326 + }, + { + "schema": 1575764420495, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "RdPlwMK9qnqd94F6tsE=", + "id": "17a4a929-e971-4b1e-9402-b7644d83d388", + "last_modified": 1578526641321 + }, + { + "schema": 1575764420869, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGPoA=", + "id": "53120599-7e6b-41ba-8c6b-d82525a1749e", + "last_modified": 1578526641317 + }, + { + "schema": 1575764421248, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hUtc=", + "id": "dd4426d9-4d64-4fad-90fa-2774fff02db7", + "last_modified": 1578526641313 + }, + { + "schema": 1575764421626, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABM6eH/qw=", + "id": "232533b8-b226-4a59-bf8c-21b0f7c9199a", + "last_modified": 1578526641310 + }, + { + "schema": 1575764421998, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABRE8CNrY=", + "id": "eb4129da-cca8-48d8-b8dc-b4d4ef0a90e3", + "last_modified": 1578526641306 + }, + { + "schema": 1575764422366, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "RdPlxJr86INKbkDk2xA=", + "id": "9e5679e5-6b4b-421a-bd75-15e03c209017", + "last_modified": 1578526641302 + }, + { + "schema": 1575764422737, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "R8MQABidwEEcnz5UaEE=", + "id": "20cbe74b-3fe4-4871-b992-9592f016e6d5", + "last_modified": 1578526641298 + }, + { + "schema": 1575764423112, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "R8MP/4phmjf1qC7wtXU=", + "id": "530ff9d6-ad60-4bcf-bfbd-214c77a5ab96", + "last_modified": 1578526641294 + }, + { + "schema": 1575764423478, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABRE7wSlU=", + "id": "2717db18-3442-4354-a639-2cdd657046a6", + "last_modified": 1578526641291 + }, + { + "schema": 1575764423851, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "AenYpKYtKygROYKpTQ==", + "id": "b5d201b0-f01c-4690-a327-21ac922697af", + "last_modified": 1578526641287 + }, + { + "schema": 1575764424225, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMzg0IC0gRzQ=", + "serialNumber": "AenYpMwYI2wqWMGvjA==", + "id": "8f0a9c13-700c-41e6-b10f-4403a70f1204", + "last_modified": 1578526641284 + }, + { + "schema": 1575764424593, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "SKQC3+1iPpeb8z1VOJk=", + "id": "ca502e89-7dc2-43d8-84eb-c5a16f6001c6", + "last_modified": 1578526641280 + }, + { + "schema": 1575764424967, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "AewckkDe/S5AXXxHdA==", + "id": "ed050f24-eca7-46a3-bf94-9c91a460c7d3", + "last_modified": 1578526641276 + }, + { + "schema": 1575764425341, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "R8MQAMBL+oomVLdB7Cs=", + "id": "2c1850a5-b636-4ed1-99d7-7f9b4174d259", + "last_modified": 1578526641272 + }, + { + "schema": 1575764425712, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMzg0IC0gRzQ=", + "serialNumber": "Ae5fFZoAV4HO31tqWQ==", + "id": "c7c75e4a-0358-4533-a7d3-db856f1293c5", + "last_modified": 1578526641269 + }, + { + "schema": 1575764426110, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "Ae5fFX7QOMo4XX885w==", + "id": "4a7dd82f-32e4-4a9d-85a6-5a6eec9f886c", + "last_modified": 1578526641265 + }, + { + "schema": 1575764426498, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGUAQ=", + "id": "65bcc99d-7d0f-4337-bcb5-1ccd788bec65", + "last_modified": 1578526641262 + }, + { + "schema": 1575764426868, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "Rea3w+z9S6JLMemr8IU=", + "id": "273eb9b0-c06e-4950-bf46-b2ea8ee0bd01", + "last_modified": 1578526641258 + }, + { + "schema": 1575764427240, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABMxvQqqM=", + "id": "7ba7d48b-afaa-42be-87b2-d44718d6c896", + "last_modified": 1578526641254 + }, + { + "schema": 1575764427615, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABK84yimo=", + "id": "903326e6-d357-44c7-a48f-559f97fc61c9", + "last_modified": 1578526641250 + }, + { + "schema": 1575764427997, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABM6da+eQ=", + "id": "b0c360b2-8d9b-4810-aada-9c74747559ab", + "last_modified": 1578526641246 + }, + { + "schema": 1575764428368, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGQLI=", + "id": "0737d7ed-405d-43a9-bf11-e0d676fe3a18", + "last_modified": 1578526641242 + }, + { + "schema": 1575764428736, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABMxvQrsk=", + "id": "98968b25-36b9-4973-8297-1c6d3c53523e", + "last_modified": 1578526641239 + }, + { + "schema": 1575764429115, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS0wKwYDVQQDEyRHbG9iYWxTaWduIENBIGZvciBBQVRMIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "Rea3r9ymcb22XRTz2sA=", + "id": "29e9a3db-3c28-4c21-855d-a90dcacf30de", + "last_modified": 1578526641235 + }, + { + "schema": 1575764429479, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hO1g=", + "id": "9f4bd983-d203-40ee-861b-bd9de6740eea", + "last_modified": 1578526641231 + }, + { + "schema": 1575764429874, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABIBnBkGY=", + "id": "01dba4b4-1925-4382-9428-6c1ac594421e", + "last_modified": 1578526641227 + }, + { + "schema": 1575764430260, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGPC4=", + "id": "395befa8-cc25-4a09-a1c2-15280fd295e7", + "last_modified": 1578526641223 + }, + { + "schema": 1575764430632, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hPWs=", + "id": "05298fda-f0a3-4fa2-9345-9b155f8ac2a2", + "last_modified": 1578526641219 + }, + { + "schema": 1575764430998, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hORY=", + "id": "66ef4cf4-6934-4c0d-8f59-208ef6ed9a0e", + "last_modified": 1578526641215 + }, + { + "schema": 1575764431368, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABJ/vSI2Q=", + "id": "685a6776-16b3-4463-9df7-008c8a4fab4a", + "last_modified": 1578526641211 + }, + { + "schema": 1575764431743, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "R8MQUj1TSc3d6WuO8n8=", + "id": "a4882416-9b70-43ba-a958-0a83ae056b28", + "last_modified": 1578526641207 + }, + { + "schema": 1575764432148, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxKjAoBgNVBAMTIUlkZW5UcnVzdCBQdWJsaWMgU2VjdG9yIFJvb3QgQ0EgMQ==", + "serialNumber": "APkwq5XRBo9G6W+Me8AbALI=", + "id": "f8ef2d36-8449-43fa-a771-37446f1e8365", + "last_modified": 1578526641204 + }, + { + "schema": 1575764432528, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "CgFBQgAAAUjHb/QhAAAAAg==", + "id": "1ebd8951-a677-4455-a63c-b1067a8c39ff", + "last_modified": 1578526641199 + }, + { + "schema": 1575764432895, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxJzAlBgNVBAMTHklkZW5UcnVzdCBDb21tZXJjaWFsIFJvb3QgQ0EgMQ==", + "serialNumber": "AKuJlbkX3duYPoRHyxCLixE=", + "id": "9c624327-49e7-45d5-9989-e1befff3a995", + "last_modified": 1578526641195 + }, + { + "schema": 1575764433266, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "CgFBQgAAAUitpA75AAAAAg==", + "id": "98b5110e-5ff7-4637-8048-337de479ddc1", + "last_modified": 1578526641192 + }, + { + "schema": 1575764433632, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxKjAoBgNVBAMTIUlkZW5UcnVzdCBQdWJsaWMgU2VjdG9yIFJvb3QgQ0EgMQ==", + "serialNumber": "IxyzTMi3hUiMqMKFEVmUsA==", + "id": "df4285cb-d4ec-46da-9cb7-d999c14a2a64", + "last_modified": 1578526641188 + }, + { + "schema": 1575764434008, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "GlpqEDdi55FMvqfsM5o69Q==", + "id": "96718c7b-a7a0-4df7-81fb-142c0913297f", + "last_modified": 1578526641184 + }, + { + "schema": 1575764434376, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "Kjk8JSCZz05Lc99cVI4VZA==", + "id": "577dcd25-091f-40b8-be75-857c51310715", + "last_modified": 1578526641180 + }, + { + "schema": 1575764434744, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDgxCzAJBgNVBAYTAkVTMRQwEgYDVQQKDAtJWkVOUEUgUy5BLjETMBEGA1UEAwwKSXplbnBlLmNvbQ==", + "serialNumber": "KQiQ7Gy+wFpJo6kX14gDJA==", + "id": "f674ff8a-8512-424b-a484-92b1d37eaf02", + "last_modified": 1578526641177 + }, + { + "schema": 1575764435118, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "If8VaaoKoUovkCLO8Xxepg==", + "id": "019c646f-10d4-4a2c-98d0-e4d099b695a3", + "last_modified": 1578526641173 + }, + { + "schema": 1575764435486, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGrMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjEzMDEGA1UEAxMqVHJ1c3R3YXZlIE9yZ2FuaXphdGlvbiBJc3N1aW5nIENBLCBMZXZlbCAyMR8wHQYJKoZIhvcNAQkBFhBjYUB0cnVzdHdhdmUuY29t", + "serialNumber": "a0nSBQ==", + "id": "12307747-aa87-4f71-a288-fbb2d746c962", + "last_modified": 1578526641170 + }, + { + "schema": 1575764435856, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QZCruw==", + "id": "927c3aed-3e02-4cb0-bd37-b6edca4d7be3", + "last_modified": 1578526641166 + }, + { + "schema": 1575764436224, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AL8nS46x5IonF4Zd2FEJXQ==", + "id": "ca7aa8e1-098e-49d7-85c2-b5eecb2ba9b5", + "last_modified": 1578526641162 + }, + { + "schema": 1575764436597, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=", + "serialNumber": "NUuDOhKpuOw=", + "id": "65ec956e-d17e-4798-85df-a8aec5ec1727", + "last_modified": 1578526641158 + }, + { + "schema": 1575764436972, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=", + "serialNumber": "ANXTyHU0MhfU", + "id": "1c2f9486-f4b0-45a3-9e2f-7fc13a3034ae", + "last_modified": 1578526641154 + }, + { + "schema": 1575764437346, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTI=", + "serialNumber": "AILD6oiQKBkO", + "id": "dd14f3ab-2dea-4a49-983c-049f41a2f0c2", + "last_modified": 1578526641150 + }, + { + "schema": 1575764437718, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGcMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0x", + "serialNumber": "AMEqE1/Azg/O", + "id": "9f4ca90c-4cce-4c50-a983-fe2699084e84", + "last_modified": 1578526641147 + }, + { + "schema": 1575764438089, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=", + "serialNumber": "AKYNiDIZo/1Z", + "id": "5e3cd56a-8d87-4c27-bbce-10f8a606dddf", + "last_modified": 1578526641143 + }, + { + "schema": 1575764438461, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTI=", + "serialNumber": "CvPmEkBHF1I=", + "id": "2712cf5f-ee17-419d-9f34-4765019a8096", + "last_modified": 1578526641139 + }, + { + "schema": 1572997851534, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2Mg==", + "serialNumber": "AW0FsQ3o0dDj9mBWCmqb", + "id": "cf5688ad-8671-4487-aac5-092c069fa6d0", + "last_modified": 1572997851898 + }, + { + "schema": 1572997851167, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDEgRzM=", + "serialNumber": "ZIiz/9LGv7OdO/Ban8BUUAqNdyM=", + "id": "da1dc583-9a6f-404a-9387-f20c79a90dd4", + "last_modified": 1572997851522 + }, + { + "schema": 1572997850771, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDEgRzM=", + "serialNumber": "QPYGU0PATLZx6cglDpDr1Y3YblU=", + "id": "77c9c8e1-2407-40f4-98a0-aff0f57f5a86", + "last_modified": 1572997851155 + }, + { + "schema": 1572997850402, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "VlTXamae7rQY2vYswRlrObSA6Jk=", + "id": "1aa8cb43-b425-4bc0-a6a5-4efa70464892", + "last_modified": 1572997850756 + }, + { + "schema": 1572997850033, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/Vsw==", + "id": "5a6d4421-18f7-4218-98b2-8bf8889c84d6", + "last_modified": 1572997850390 + }, + { + "schema": 1572997849667, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDEgRzM=", + "serialNumber": "FYi7Goq0FxNJiIxZbyHjnx4Yzj4=", + "id": "5ce4a32b-7796-4461-ae06-1a674568349c", + "last_modified": 1572997850022 + }, + { + "schema": 1572997849306, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "SUd4pH2sURzNAxRtgemvs0SlWs4=", + "id": "4981c836-17b0-4a3e-8a35-b69228c35ff2", + "last_modified": 1572997849655 + }, + { + "schema": 1572997848948, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "HZxbikClihb+1PM2ck0SDEZ8/dI=", + "id": "96ac4980-b6a4-4636-86b7-6e25a51952fe", + "last_modified": 1572997849295 + }, + { + "schema": 1572997848567, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "E4dQDEI92Bh+MVs2aGdlIW8rkDU=", + "id": "4bb979d3-75b7-4c89-95ce-2f5546a7ac9c", + "last_modified": 1572997848936 + }, + { + "schema": 1572997848205, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDMgRzM=", + "serialNumber": "Ih46G2P4jpnblyKBdWivHMRTVG0=", + "id": "a15c4eac-cae6-44c5-9771-a04506e6aa78", + "last_modified": 1572997848555 + }, + { + "schema": 1572997847722, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "aigK80YjKDHJW0LCnSTEqCzDNeM=", + "id": "8e74eae1-8c6e-4d04-a196-f45bf5fa837a", + "last_modified": 1572997848194 + }, + { + "schema": 1572997847352, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "abLRzPAuINzJXGKJT3+eX1/AV78=", + "id": "eb428217-5b2d-45e3-a542-8426d8cc97de", + "last_modified": 1572997847710 + }, + { + "schema": 1572997846983, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "IBhwLg4/l5vcFjA30Du20n1cSyI=", + "id": "533df81c-b06f-489b-8077-22660e1e4637", + "last_modified": 1572997847340 + }, + { + "schema": 1572997846615, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "N1uK21hd0mVD7EMLhwBTDH4Q/Z4=", + "id": "9bdb8697-2a72-45f8-9e72-9552a8c1cffb", + "last_modified": 1572997846971 + }, + { + "schema": 1572997846251, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "eXbhfvF+r14Z6p4bhGUShI+ougg=", + "id": "78788abb-6a2a-4ff3-9048-78a304323c50", + "last_modified": 1572997846603 + }, + { + "schema": 1572997845892, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "bj5n4ozUm/ABHHVnnBXHLTgE0d4=", + "id": "874032b3-094e-463f-84db-4e8093708ded", + "last_modified": 1572997846240 + }, + { + "schema": 1572997845523, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/Vlg==", + "id": "e71cefb0-abba-4c0c-901e-fdf4a7ce5d3c", + "last_modified": 1572997845880 + }, + { + "schema": 1572997845152, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "aJ22x4QeS1sJqBhO8/15GTsRgkc=", + "id": "dcd10cf8-d28d-48fc-84eb-c472189c6b79", + "last_modified": 1572997845511 + }, + { + "schema": 1572997844783, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/VlQ==", + "id": "2a78f5d9-e749-4351-ba1d-4f9af4b597cd", + "last_modified": 1572997845140 + }, + { + "schema": 1572997844424, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/Vnw==", + "id": "90b1db90-a7a5-4d6f-866e-1df53bc211dc", + "last_modified": 1572997844771 + }, + { + "schema": 1572997843852, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Cik=", + "id": "d69a8534-6767-4914-829f-951f6ff51629", + "last_modified": 1572997844412 + }, + { + "schema": 1572997843485, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "asr1yYUnTFAnuikoMAbW5MTxWps=", + "id": "77e343fa-74f6-415b-9976-2e78ace45b29", + "last_modified": 1572997843840 + }, + { + "schema": 1572997843115, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "Fl3SlbNRj/u5Z4ldjyMiig==", + "id": "731e185c-aac8-49a9-b4e7-6ca1fad82e5e", + "last_modified": 1572997843473 + }, + { + "schema": 1572997842756, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "Z4m9+d/aQi4hDVLz8EBcIg==", + "id": "6ca403f7-45d0-4a12-bd05-2c32cdc86905", + "last_modified": 1572997843104 + }, + { + "schema": 1572997842393, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAk5MMTAwLgYDVQQKDCdNaW5pc3RlcmllIHZhbiBJbmZyYXN0cnVjdHV1ciBlbiBNaWxpZXUxJDAiBgNVBAMMG01pbkllbk0gT3JnYW5pc2F0aWUgQ0EgLSBHMg==", + "serialNumber": "Dfp5FitCFjNH/o5ZOoSezg==", + "id": "f9b36fec-d45c-440d-88f2-d2c36bee2d1f", + "last_modified": 1572997842745 + }, + { + "schema": 1572997842017, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOTA3BgNVBAMMMFN0YWF0IGRlciBOZWRlcmxhbmRlbiBBdXRvbm9tZSBBcHBhcmF0ZW4gQ0EgLSBHMg==", + "serialNumber": "ATE7Ew==", + "id": "d9b41fd7-f743-4353-bf99-4beb27bdb962", + "last_modified": 1572997842381 + }, + { + "schema": 1572997841651, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGwxCzAJBgNVBAYTAk5MMTAwLgYDVQQKDCdNaW5pc3RlcmllIHZhbiBJbmZyYXN0cnVjdHV1ciBlbiBNaWxpZXUxKzApBgNVBAMMIk1pbkllbk0gQXV0b25vbWUgQXBwYXJhdGVuIENBIC0gRzI=", + "serialNumber": "Nfxxz8wMz36ZChR92NTKnw==", + "id": "d77cda54-e1bc-49da-a0f2-38175d2d1d5c", + "last_modified": 1572997842005 + }, + { + "schema": 1572997841289, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MG4xCzAJBgNVBAYTAk5MMSAwHgYDVQQKDBdNaW5pc3RlcmllIHZhbiBEZWZlbnNpZTE9MDsGA1UEAww0TWluaXN0ZXJpZSB2YW4gRGVmZW5zaWUgQ2VydGlmaWNhdGllIEF1dG9yaXRlaXQgLSBHMg==", + "serialNumber": "LhEjLBXVlzPRy33vUU/jBQ==", + "id": "0e4176d5-5815-445f-81f9-6b4edac54c36", + "last_modified": 1572997841639 + }, + { + "schema": 1572997840927, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ATE1Sw==", + "id": "a60a1f5f-94fa-43b0-bc3a-f852c20dfc7a", + "last_modified": 1572997841277 + }, + { + "schema": 1572997840565, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ATE3ew==", + "id": "9d5e3a33-5509-4bff-8b5d-6aeb04494a5c", + "last_modified": 1572997840915 + }, + { + "schema": 1572997840204, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "AeOpMBz8cgY4P5pTHQ==", + "id": "514984a3-db29-411c-9350-35d6d994a842", + "last_modified": 1572997840554 + }, + { + "schema": 1572997839836, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMQ==", + "serialNumber": "bkepyaVT48LOHxRO132s5w==", + "id": "6f57620c-a646-4b75-9434-6cae78d38d20", + "last_modified": 1572997840191 + }, + { + "schema": 1572997839477, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMw==", + "serialNumber": "bkepzLRaKceweNAboyESYQ==", + "id": "5e742292-83b0-46ab-b2b5-ea22e98fe988", + "last_modified": 1572997839824 + }, + { + "schema": 1572997839109, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSMg==", + "serialNumber": "bkepys5/hGUZLuczKycnww==", + "id": "099bfb33-fc5e-4b63-b407-2144887aa248", + "last_modified": 1572997839465 + }, + { + "schema": 1572997838748, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKExlHb29nbGUgVHJ1c3QgU2VydmljZXMgTExDMRQwEgYDVQQDEwtHVFMgUm9vdCBSNA==", + "serialNumber": "bkepzk9Gwj3iSerMOJRTcw==", + "id": "fced4db1-a5c4-4f22-82d8-846ad03df6a4", + "last_modified": 1572997839097 + }, + { + "schema": 1572997838380, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "MBKb/YDRk9az4jTk", + "id": "e51d407f-0292-4711-ad07-8adf26f3b482", + "last_modified": 1572997838736 + }, + { + "schema": 1572997838015, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "AeOugCbbW0HiVsKjUQ==", + "id": "50850fa6-d0db-43c3-85af-6561d45a52fd", + "last_modified": 1572997838368 + }, + { + "schema": 1572997837654, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "DSNHQZKDDI0wevn963sxJw==", + "id": "6aaac9fc-8121-4101-8fbb-852f45faa0f1", + "last_modified": 1572997838003 + }, + { + "schema": 1572997837278, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcz", + "serialNumber": "C2vQOwiS6Py0zO9c8RQ+yw==", + "id": "e53ccec5-36c3-4972-8418-fb4ec45ba45c", + "last_modified": 1572997837642 + }, + { + "schema": 1572997836922, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjE5MDcGA1UECxMwKGMpIDIwMDggR2VvVHJ1c3QgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTYwNAYDVQQDEy1HZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzM=", + "serialNumber": "UoQqGaXHe6Tn5f/4MZM9bQ==", + "id": "ce154029-f803-4a1f-b0d6-1509d1f68e37", + "last_modified": 1572997837267 + }, + { + "schema": 1572997836561, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJTAjBgNVBAMTHERpZ2lDZXJ0IFRyYW5zaXRpb24gUlNBIFJvb3Q=", + "serialNumber": "CSBH6lLcNFtGyTwmGjUPtg==", + "id": "a141c0fe-7121-47bc-8ee0-ce353590daad", + "last_modified": 1572997836910 + }, + { + "schema": 1572997836202, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "DamhD9I49FBVW2Q22Z85pA==", + "id": "eb820465-1626-4a4f-a084-f33c6e168b26", + "last_modified": 1572997836550 + }, + { + "schema": 1572997835836, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcy", + "serialNumber": "Dn3kbM04dGYpYMWEN9lNFw==", + "id": "33a8dd93-74e0-4e52-931b-50d0cb6ce367", + "last_modified": 1572997836189 + }, + { + "schema": 1572997835466, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjE5MDcGA1UECxMwKGMpIDIwMDcgR2VvVHJ1c3QgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTYwNAYDVQQDEy1HZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "NWH2OKvBlcu/fP2nA9reyQ==", + "id": "cb77731b-b0f6-453d-99f5-a734389a8126", + "last_modified": 1572997835824 + }, + { + "schema": 1572997835103, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0E=", + "serialNumber": "AixZSF45DYuCn2ghjGPEYw==", + "id": "f0034e6e-0c4f-46d5-a0b8-b3a159ead7f0", + "last_modified": 1572997835454 + }, + { + "schema": 1572997834739, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "QITDXD5mXLU=", + "id": "f1f0b5e2-88f4-4a2b-b09e-761dea882f74", + "last_modified": 1572997835091 + }, + { + "schema": 1572982913548, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==", + "serialNumber": "ALu77u40E1O5", + "id": "2a9b5e2b-9287-4e06-9385-68a865e9373a", + "last_modified": 1572997834727 + }, + { + "schema": 1568310940897, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMw==", + "serialNumber": "H0ZpvlsmQE2QLTqXSDrK4g==", + "id": "b7257700-620e-4dd9-9d69-5cb01a38e079", + "last_modified": 1568310941289 + }, + { + "schema": 1568310940486, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMg==", + "serialNumber": "JxLAYQXlXzf2wpMVxAUkPw==", + "id": "06a25f8a-d351-4d4d-aa77-36d38eaa91a6", + "last_modified": 1568310940884 + }, + { + "schema": 1568310940083, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "APeSt8SBjARY", + "id": "a2ee1ce4-dee5-48d9-9a6c-7c31abc0cacc", + "last_modified": 1568310940474 + }, + { + "schema": 1568310939648, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "MsKCw6ASAH4=", + "id": "b87689df-e12f-4a2f-84a8-b62d79ab776a", + "last_modified": 1568310940070 + }, + { + "schema": 1568310939223, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "ALNpo1yEOMIuR5TLwIEiPg==", + "id": "45196b4f-c18b-4267-bca1-ab2d95a051e1", + "last_modified": 1568310939634 + }, + { + "schema": 1568310938819, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AJHE7Dx8fWBV", + "id": "3688b8d1-d3a6-4759-a49c-02380f919348", + "last_modified": 1568310939211 + }, + { + "schema": 1568310938420, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "JD4RO0Oolos=", + "id": "ff8a8a0b-db6e-433b-bfa8-1e7500e3983c", + "last_modified": 1568310938806 + }, + { + "schema": 1568310938009, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxWpuP9VVz", + "id": "41a21017-a9cb-45ef-99f1-ab1eea6bbe48", + "last_modified": 1568310938407 + }, + { + "schema": 1568310937606, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0Ex", + "serialNumber": "AQAAOct/vFej", + "id": "d08d299b-f89d-45a9-94fc-cc7a01a97adc", + "last_modified": 1568310937997 + }, + { + "schema": 1568310937193, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "duIdILCWeCm7g2bzVNjyc3z3hUM=", + "id": "4b65928e-123d-4cca-ad65-d38064175528", + "last_modified": 1568310937593 + }, + { + "schema": 1568310936785, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHAxCzAJBgNVBAYTAk5MMRcwFQYDVQRhDA5OVFJOTC0zMDIzNzQ1OTEgMB4GA1UECgwXUXVvVmFkaXMgVHJ1c3RsaW5rIEIuVi4xJjAkBgNVBAMMHVF1b1ZhZGlzIFF1YWxpZmllZCBXZWIgSUNBIEcx", + "serialNumber": "DKKQ8bcYDwn4Pows6KhzW6JxMYw=", + "id": "d218169c-f196-4f86-8f71-60582ba8f4e0", + "last_modified": 1568310937180 + }, + { + "schema": 1568310936383, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "TRRJlNTx8n7cQvNehM76B+mI/gI=", + "id": "d418fa14-6b51-477e-a854-bb1a8219461a", + "last_modified": 1568310936772 + }, + { + "schema": 1568310935975, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABThOepKci5Vzg=", + "id": "040501f8-3330-4cf6-81f9-f8718593309b", + "last_modified": 1568310936369 + }, + { + "schema": 1568310935577, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABG", + "id": "a8c71f16-0d7c-4d1c-ba4a-01047d0dec59", + "last_modified": 1568310935963 + }, + { + "schema": 1568310935182, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5ABI", + "id": "64f0a9c6-671c-459b-a43e-91ddd5fb76a5", + "last_modified": 1568310935564 + }, + { + "schema": 1568310934783, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOjA4BgNVBAMMMVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBQZXJzb29uIENBIC0gRzM=", + "serialNumber": "MpyrUqgKLj8=", + "id": "62e6a159-87ba-4177-9ca1-5d44c11983a9", + "last_modified": 1568310935170 + }, + { + "schema": 1568310934381, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMw==", + "serialNumber": "cguCxFXg1BY=", + "id": "a0cfd917-03cd-4a5b-841f-c495ec9e7d08", + "last_modified": 1568310934770 + }, + { + "schema": 1568310933970, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byc3Cw==", + "id": "0b64b385-38c0-4c05-a4e8-33422625cf29", + "last_modified": 1568310934368 + }, + { + "schema": 1568310933543, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDtQ==", + "id": "1ef257c4-c7dc-4c85-9c96-577da982a917", + "last_modified": 1568310933957 + }, + { + "schema": 1568310933146, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeYsg==", + "id": "563fcdcc-b8b3-42a0-807a-409f7191aa13", + "last_modified": 1568310933531 + }, + { + "schema": 1568310932725, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfFng==", + "id": "72344480-7dd6-42c8-94c4-29af6d626430", + "last_modified": 1568310933132 + }, + { + "schema": 1568310932300, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDrQ==", + "id": "830048f4-fae3-40b4-8cff-ffbde2037783", + "last_modified": 1568310932712 + }, + { + "schema": 1568310931872, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byekbw==", + "id": "18c1a175-78a3-4fab-983c-67f9ef96e903", + "last_modified": 1568310932287 + }, + { + "schema": 1568310931477, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "serialNumber": "BkQ=", + "id": "08a295c5-979f-4e11-ad58-8383c3585b1c", + "last_modified": 1568310931860 + }, + { + "schema": 1568310931080, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "SAOpMdpJPNmCofX5swZ5oQ==", + "id": "e4d4b36b-eefc-411b-9167-f7793e512b52", + "last_modified": 1568310931464 + }, + { + "schema": 1568310930677, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAklUMRwwGgYDVQQKExNUcnVzdCBJdGFsaWEgUy5wLkEuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSUwIwYDVQQDExxUcnVzdCBJdGFsaWEgQ2xhc3MgMiBDQSAtIEcz", + "serialNumber": "WuRlPlB5dcPHk+Ni2m98uQ==", + "id": "8cf5ab64-83b5-4a73-8688-1a057b251bde", + "last_modified": 1568310931067 + }, + { + "schema": 1568310930279, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "ZGOlNeh1j5diJSijGSk5yw==", + "id": "73e00d24-12f9-4dd1-8f13-0a65385ef608", + "last_modified": 1568310930665 + }, + { + "schema": 1568310929872, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "APx4jVLURBZ4JDuYgssVtA==", + "id": "e7ca09c8-4234-490d-afa0-5b4e08fa8aba", + "last_modified": 1568310930267 + }, + { + "schema": 1568310929472, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAk5MMREwDwYDVQQKEwhLUE4gQi5WLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEXMBUGA1UEAxMOS1BOIENsYXNzIDIgQ0E=", + "serialNumber": "BzFJVBKUYLx5d6jqaoqnQA==", + "id": "ae981d0c-5c32-48eb-8ea1-0df3d2cccee5", + "last_modified": 1568310929859 + }, + { + "schema": 1568310929067, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAk1LMRcwFQYDVQQKEw5LSUJTIEFEIFNrb3BqZTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEWMBQGA1UEAxMNS0lCUyBWZXJiYSBDQQ==", + "serialNumber": "GWX2i+qVAVmIAm9D6fgusw==", + "id": "ecb04561-0292-4553-ad7b-46b3d9672be7", + "last_modified": 1568310929459 + }, + { + "schema": 1568310928669, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAk1LMRcwFQYDVQQKEw5LSUJTIEFEIFNrb3BqZTEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEqMCgGA1UEAxMhS2lic1RydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "XtiXZFzoWEohcuXO3Qakfg==", + "id": "9dff3ebc-a2f7-4a0b-8848-a5fb837b6aa8", + "last_modified": 1568310929054 + }, + { + "schema": 1568310928258, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byekbg==", + "id": "b0f10c1c-321a-4505-b582-9d02f5931519", + "last_modified": 1568310928656 + }, + { + "schema": 1568310927824, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABSOXEhcs=", + "id": "b9195480-9315-460b-83d6-ac84a68ca9e8", + "last_modified": 1568310928245 + }, + { + "schema": 1568310927420, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAktSMRMwEQYDVQQKEwpLRUNBLCBJbmMuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSIwIAYDVQQDExlDcm9zc0NlcnQgQ2xhc3MgMiBDQSAtIEcz", + "serialNumber": "H4L5/FnWXnXdXs/8x+BDpw==", + "id": "6d2b4fdb-b26b-4372-8cdd-b46fb144b987", + "last_modified": 1568310927811 + }, + { + "schema": 1568310927025, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "Ajp1", + "id": "e0b17c8f-15e4-44a6-ad56-30c577569939", + "last_modified": 1568310927407 + }, + { + "schema": 1568310926625, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjE5MDcGA1UECxMwKGMpIDIwMDggR2VvVHJ1c3QgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTYwNAYDVQQDEy1HZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzM=", + "serialNumber": "Bl3KfQ+qIpjDCrBhKUDefQ==", + "id": "5f65ca3e-3163-4e97-965c-e7e09771652b", + "last_modified": 1568310927013 + }, + { + "schema": 1568310926222, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGYMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjE5MDcGA1UECxMwKGMpIDIwMDcgR2VvVHJ1c3QgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTYwNAYDVQQDEy1HZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "D4IrRtQ6ByW09Im/6D1i3Q==", + "id": "42fa7785-9d20-408f-99c9-7bdead71e6f5", + "last_modified": 1568310926613 + }, + { + "schema": 1568310925821, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BwSvmwHgIISTeM4uX8FUyg==", + "id": "7e94d5de-af8e-4d7a-9e8d-8711bafa9f6b", + "last_modified": 1568310926210 + }, + { + "schema": 1568310925425, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAk1LMRcwFQYDVQQKEw5LSUJTIEFEIFNrb3BqZTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEWMBQGA1UEAxMNS0lCUyBWZXJiYSBDQQ==", + "serialNumber": "fafwGJHMSPO0K7nm6dSxiA==", + "id": "b3e2c2e6-8196-49cf-a6dc-2141120f3173", + "last_modified": 1568310925808 + }, + { + "schema": 1568310925018, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfNeQ==", + "id": "79f6982d-ec58-47a8-a0e6-6656ba34898c", + "last_modified": 1568310925412 + }, + { + "schema": 1568310924586, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBHMg==", + "serialNumber": "BC05mGqcJqxN4m1fywzqdQ==", + "id": "fb25b1f9-7f4a-4b2c-8287-0500b69ce285", + "last_modified": 1568310925005 + }, + { + "schema": 1568310924187, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBHMg==", + "serialNumber": "Cx0MvOeigXRY2Z4Uxr0Ypw==", + "id": "4c4bd6ef-75b4-4822-8c9a-94b4f2c25591", + "last_modified": 1568310924574 + }, + { + "schema": 1568310923780, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBHMg==", + "serialNumber": "A8Ir2pM+N2ukJUbcpQ9bGA==", + "id": "cdb14ecd-3eab-487c-af09-568a7061ada8", + "last_modified": 1568310924175 + }, + { + "schema": 1568310923377, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBHMg==", + "serialNumber": "BiVxMmicX7C3qkZW3rCNuQ==", + "id": "978c23a3-8253-4a4a-98fb-ce1577248752", + "last_modified": 1568310923768 + }, + { + "schema": 1568310922955, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDrA==", + "id": "5f215007-f8d7-47d1-a416-4eea3f882168", + "last_modified": 1568310923363 + }, + { + "schema": 1568310922514, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Hw==", + "id": "e077fd72-0683-4631-afb2-658d83b69cbe", + "last_modified": 1568310922942 + }, + { + "schema": 1568310922110, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "HQ==", + "id": "2ab14be6-2d83-4d55-b283-bc55bdcb9511", + "last_modified": 1568310922501 + }, + { + "schema": 1568310921707, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Gw==", + "id": "43a57665-1f18-4c9a-8ed0-9ce125639ce4", + "last_modified": 1568310922097 + }, + { + "schema": 1568310921304, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Hg==", + "id": "8a038b9b-b34b-43e5-af7c-b33f9dd560a3", + "last_modified": 1568310921694 + }, + { + "schema": 1568310920899, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "Gg==", + "id": "42002b34-779f-465c-b7f8-7dc081ba9558", + "last_modified": 1568310921290 + }, + { + "schema": 1568310920499, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAzIFJvb3QgQ0E=", + "serialNumber": "Gg==", + "id": "42ef45ee-073b-4069-a61a-0c1fb0186ae3", + "last_modified": 1568310920886 + }, + { + "schema": 1568310920098, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "esKlE8WJEhw=", + "id": "b718bb4b-c956-4b64-b215-9d1a452892cb", + "last_modified": 1568310920486 + }, + { + "schema": 1568154087410, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "b+PF5BvGM1c=", + "id": "d1e28ebb-c18a-478f-97a4-2cf4f5e4e78b", + "last_modified": 1568310920085 + }, + { + "schema": 1566360892224, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1569803", + "who": "wthayer@mozilla.com", + "why": "MITM root", + "name": "", + "created": "2019-08-17T19:57:48Z" + }, + "enabled": true, + "issuerName": "MCwxCzAJBgNVBAYTAktaMR0wGwYDVQQDExRRYXpuZXQgVHJ1c3QgTmV0d29yaw==", + "serialNumber": "PZV/yh+MD8hpLAGZTrVSMQNbnfE=", + "id": "6f4ad196-1063-4754-a565-7c8a8beef513", + "last_modified": 1566360892591 + }, + { + "schema": 1566360891850, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1572287", + "who": "wthayer@mozilla.com", + "why": "registry compromise", + "name": "", + "created": "2019-08-17T19:35:20Z" + }, + "enabled": true, + "issuerName": "MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "AKmRGsnMtC8mchZkrfEPQvs=", + "id": "112fbc85-4e1a-4740-978d-a717fd02b1eb", + "last_modified": 1566360892211 + }, + { + "schema": 1566243714956, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1572287", + "who": "wthayer@mozilla.com", + "why": "registry compromise", + "name": "", + "created": "2019-08-17T19:34:58Z" + }, + "enabled": true, + "issuerName": "MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "APoMQ61QytU9iMPwSvd7ZcY=", + "id": "e172b2a0-7704-4195-8210-22b6c3011640", + "last_modified": 1566360891835 + }, + { + "schema": 1557423813412, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSkwJwYDVQQDEyBXSVNlS2V5IENlcnRpZnlJRCBQb2xpY3kgR0IgQ0EgMQ==", + "serialNumber": "CYut7lnH+rk=", + "id": "badf76bb-8e38-4908-905c-7cc1e4183814", + "last_modified": 1557423813772 + }, + { + "schema": 1557423813034, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSkwJwYDVQQDEyBXSVNlS2V5IENlcnRpZnlJRCBQb2xpY3kgR0IgQ0EgMQ==", + "serialNumber": "WGOh1+g/sGA=", + "id": "d63543f2-cf39-4f45-a7db-f1ae7c4de703", + "last_modified": 1557423813399 + }, + { + "schema": 1557423812664, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSkwJwYDVQQDEyBXSVNlS2V5IENlcnRpZnlJRCBQb2xpY3kgR0IgQ0EgMQ==", + "serialNumber": "Ls/8dlIf8aY=", + "id": "340201c2-23e9-4a8e-87f8-e016599f7982", + "last_modified": 1557423813021 + }, + { + "schema": 1557423812293, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBSb290IEdCIENB", + "serialNumber": "FQPkzAAAAAAACQ==", + "id": "863b367b-27a8-4038-94bf-9d2073f23dbb", + "last_modified": 1557423812652 + }, + { + "schema": 1557423811929, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSkwJwYDVQQDEyBXSVNlS2V5IENlcnRpZnlJRCBQb2xpY3kgR0IgQ0EgMQ==", + "serialNumber": "awVJ9wiyAL4=", + "id": "95174994-bdcb-4220-b6c2-79e1e1907c8b", + "last_modified": 1557423812279 + }, + { + "schema": 1557423811544, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "IbZ3foy9Dqg=", + "id": "5ab2bc48-a4f2-492b-8d2c-058829911fa5", + "last_modified": 1557423811915 + }, + { + "schema": 1557423811172, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMg==", + "serialNumber": "Y13XZEprZXM=", + "id": "5774207d-0be6-47c1-8387-298765987c16", + "last_modified": 1557423811531 + }, + { + "schema": 1557423810787, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMw==", + "serialNumber": "AJCbfptY83jP", + "id": "e6b04c52-601f-4e00-84c1-c22e842788ec", + "last_modified": 1557423811158 + }, + { + "schema": 1557423810418, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJERTErMCkGA1UECgwiVC1TeXN0ZW1zIEVudGVycHJpc2UgU2VydmljZXMgR21iSDEfMB0GA1UECwwWVC1TeXN0ZW1zIFRydXN0IENlbnRlcjElMCMGA1UEAwwcVC1UZWxlU2VjIEdsb2JhbFJvb3QgQ2xhc3MgMg==", + "serialNumber": "ANMiAfMpLlCc", + "id": "c12c5823-b883-4043-8938-0b7076b80cc0", + "last_modified": 1557423810775 + }, + { + "schema": 1557423810044, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "ARM=", + "id": "c859b962-72c4-4b7e-985c-a658294cd2dd", + "last_modified": 1557423810404 + }, + { + "schema": 1557423809671, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "JytnIpdF0kOL+XdBhq69", + "id": "8b701444-ba44-47e5-9bbf-0acc8b34e465", + "last_modified": 1557423810031 + }, + { + "schema": 1557423809286, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "KOdv6Js6N0Y=", + "id": "6d37f8c3-2730-4e2f-a408-cbe1a5db9c22", + "last_modified": 1557423809658 + }, + { + "schema": 1557423808886, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "DO4I8+h6DJo=", + "id": "2c75add5-a852-4f37-ab30-d3aba4474edc", + "last_modified": 1557423809273 + }, + { + "schema": 1557423808512, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "Yr4AWtEG96k=", + "id": "a1786ea8-964d-43ff-8034-d92f732d1a8a", + "last_modified": 1557423808874 + }, + { + "schema": 1557423808123, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "YP6Ri0pXsgE=", + "id": "99218c6d-b76d-4cf4-b759-36881f8ddad2", + "last_modified": 1557423808499 + }, + { + "schema": 1557423807737, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "OI4Oqw3W3F0=", + "id": "efda5b9b-146f-4890-be8d-b6ac58b1df1b", + "last_modified": 1557423808110 + }, + { + "schema": 1557423807356, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTQwMgYDVQQDDCtTU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "KVbQJstVhmo=", + "id": "512284c2-8bac-43f3-83b9-6edb4395f99b", + "last_modified": 1557423807724 + }, + { + "schema": 1557423806979, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "d5NjXwLBHrU=", + "id": "71545b94-9fc0-4513-8959-9e210ee4b90f", + "last_modified": 1557423807343 + }, + { + "schema": 1557423806596, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "CW8YlnNuJhQ=", + "id": "fe70826c-b167-4421-bcbf-ef1a3c07721e", + "last_modified": 1557423806965 + }, + { + "schema": 1557423806210, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "MiKihpTXxZk=", + "id": "cbd66b64-8fdc-46f2-8e15-586692a5647b", + "last_modified": 1557423806583 + }, + { + "schema": 1557423805838, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "BcKlTXFc9mE=", + "id": "c626e9e1-956c-4e87-8ba5-c66eda0b47b8", + "last_modified": 1557423806198 + }, + { + "schema": 1557423805449, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "S4USm53cbIU=", + "id": "7065ee43-7c3c-4e7f-bf03-0112c648ada7", + "last_modified": 1557423805823 + }, + { + "schema": 1557423804871, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "QAFtPnuRTwc=", + "id": "abe92cd0-41c3-4dba-a494-d4af228d40de", + "last_modified": 1557423805436 + }, + { + "schema": 1557423804502, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "YgfyfeCfPTU=", + "id": "d0a3950b-8d10-4cc4-94aa-edb22c99c8b0", + "last_modified": 1557423804858 + }, + { + "schema": 1557423804121, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "G4u0Q4CNE10=", + "id": "d221d415-8f82-46a9-8c10-a2f86aabf445", + "last_modified": 1557423804489 + }, + { + "schema": 1557423803745, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlNDMSAwHgYDVQQKDBdJbnRlckNsb3VkIFZlbnR1cmVzIEluYzEuMCwGA1UEAwwlSW50ZXJDbG91ZCBWZW50dXJlcyBDQSBmb3IgRVYgU1NMIFJTQQ==", + "serialNumber": "VfaCotN4OFo=", + "id": "a5441ef9-557c-43ba-9cb0-54dbd81c2934", + "last_modified": 1557423804106 + }, + { + "schema": 1557423803357, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "UqgFJ65x5nU=", + "id": "d1592e83-6c04-4c20-ba07-56d72f8eb0f1", + "last_modified": 1557423803732 + }, + { + "schema": 1557423802954, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxMjAwBgNVBAMMKVNTTC5jb20gRW50ZXJwcmlzZSBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIx", + "serialNumber": "T9HiLhDlNNU=", + "id": "d31f4890-5ce5-4b86-9414-d68b47aa9861", + "last_modified": 1557423803344 + }, + { + "schema": 1557423802580, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHkxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxNTAzBgNVBAMMLFNTTC5jb20gRVYgRW50ZXJwcmlzZSBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIx", + "serialNumber": "ITFvl6W0i6I=", + "id": "696f4fd0-6433-43a9-97b9-19b1ee50d529", + "last_modified": 1557423802942 + }, + { + "schema": 1557423802214, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE3MDUGA1UEAwwuU1NMLmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==", + "serialNumber": "WHsQEmHMXeM=", + "id": "7299b7c4-27a9-423e-8e37-788987824972", + "last_modified": 1557423802567 + }, + { + "schema": 1557423801848, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "cvzm3dCJNB4=", + "id": "c91f53c0-d5f6-4a48-af64-92ccb6ee8473", + "last_modified": 1557423802202 + }, + { + "schema": 1557423801470, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRUND", + "serialNumber": "XlXE8yhjjQI=", + "id": "65386622-c28f-47ad-b627-b83ea70cc9f0", + "last_modified": 1557423801835 + }, + { + "schema": 1557423801095, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTEwLwYDVQQDDChTU0wuY29tIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNB", + "serialNumber": "KMkysqB5Rt0=", + "id": "dc2fd3db-10b1-413d-94c4-fc4791e7675f", + "last_modified": 1557423801458 + }, + { + "schema": 1557423800730, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGBMQswCQYDVQQGEwJTQzEOMAwGA1UECAwFTWFow6kxETAPBgNVBAcMCFZpY3RvcmlhMSAwHgYDVQQKDBdJbnRlckNsb3VkIFZlbnR1cmVzIEluYzEtMCsGA1UEAwwkSW50ZXJDbG91ZCBFViBTU0wgQ2VydGlmaWNhdGUgQ0EgUlNB", + "serialNumber": "MMznHIjZYpQ=", + "id": "1de4a0c1-51d7-47f7-a73d-fd70920f6a34", + "last_modified": 1557423801083 + }, + { + "schema": 1557423800345, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlNDMQ4wDAYDVQQIDAVNYWjDqTERMA8GA1UEBwwIVmljdG9yaWExIDAeBgNVBAoMF0ludGVyQ2xvdWQgVmVudHVyZXMgSW5jMSowKAYDVQQDDCFJbnRlckNsb3VkIFNTTCBDZXJ0aWZpY2F0ZSBDQSBSU0E=", + "serialNumber": "cTxxiZ4LCOQ=", + "id": "dcd65990-542f-4ef1-99eb-e2f25e6239b7", + "last_modified": 1557423800717 + }, + { + "schema": 1557423799967, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxMjAwBgNVBAMMKVNTTC5jb20gRW50ZXJwcmlzZSBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIx", + "serialNumber": "Ti0dlrzCXKM=", + "id": "ae5978b3-3be3-4707-86ac-6c16d078fe5b", + "last_modified": 1557423800332 + }, + { + "schema": 1557423799592, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QZCs1Q==", + "id": "f68dfc42-bb98-48be-987b-192b59c043d5", + "last_modified": 1557423799954 + }, + { + "schema": 1557423799216, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw5A==", + "id": "74b4b2a3-32bf-4413-80ae-d470b0375797", + "last_modified": 1557423799579 + }, + { + "schema": 1557423798845, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "YgZcSJ6iN8fEC7ejOJsdDrm5o1g=", + "id": "22e259c3-376e-432e-ad1c-774a8988cf96", + "last_modified": 1557423799203 + }, + { + "schema": 1557423798467, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzM=", + "serialNumber": "Yj9Q2DoRGS8RFsxLEnheErA5ayQ=", + "id": "29ae1b28-d084-4bc4-a971-732d22ec7b6e", + "last_modified": 1557423798832 + }, + { + "schema": 1557423798089, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "BaYifVmclf61WjM6m2tUE0US22M=", + "id": "2bff0030-9997-4539-a23d-4c0a85d8b7f2", + "last_modified": 1557423798452 + }, + { + "schema": 1557423797714, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "fSwlctGYM5wOZ6S6SbpfaQoLCUw=", + "id": "c1c0d933-48d3-40c3-8c22-834c1278e810", + "last_modified": 1557423798076 + }, + { + "schema": 1557423797339, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxJzAlBgNVBAMTHklkZW5UcnVzdCBDb21tZXJjaWFsIFJvb3QgQ0EgMQ==", + "serialNumber": "QAFmaeynntX5i5Op972mww==", + "id": "cf97d13a-4b9e-4432-8d02-d1c1ce666e03", + "last_modified": 1557423797701 + }, + { + "schema": 1557423796944, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxJzAlBgNVBAMTHklkZW5UcnVzdCBDb21tZXJjaWFsIFJvb3QgQ0EgMQ==", + "serialNumber": "QAFqJwTmxn8ekC5jaM8HfA==", + "id": "5f5cd087-f151-408e-a230-28976be63ce8", + "last_modified": 1557423797326 + }, + { + "schema": 1557423796539, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "CoIvSK+bMR8=", + "id": "a8275fcc-230f-4da3-b3f9-2b998b34a8ec", + "last_modified": 1557423796916 + }, + { + "schema": 1557423796155, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "CwMSuo/L5Nw=", + "id": "016ad479-aed7-4229-bbec-3b8a698a744b", + "last_modified": 1557423796527 + }, + { + "schema": 1557423795782, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "BlGKWhgGzQg=", + "id": "696b55ff-816d-4e13-85d5-e35387da6767", + "last_modified": 1557423796142 + }, + { + "schema": 1557423795405, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "H/Zi2f0Ammc=", + "id": "cbfd548b-c765-423d-8433-41975a623435", + "last_modified": 1557423795768 + }, + { + "schema": 1557423795027, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "ECuIk98JkK0=", + "id": "e474c95a-05e4-4a63-9968-dae47f44f7c4", + "last_modified": 1557423795392 + }, + { + "schema": 1557423794655, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "HLxkVswit4o=", + "id": "4294786a-6fe8-40f4-b20b-6a2b25c681f4", + "last_modified": 1557423795014 + }, + { + "schema": 1557423794271, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "L6vsVC+Y8pqDd1s/rJ5FIA==", + "id": "1b40a3a7-5ec6-486e-a3fb-6310b2c1dbfd", + "last_modified": 1557423794643 + }, + { + "schema": 1557423793901, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "ATQlK79hz4d80DloGRPBZw==", + "id": "8b267277-7d4e-41d3-ae64-edd7b5c86ee1", + "last_modified": 1557423794257 + }, + { + "schema": 1557423793529, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "f+Y5UbJ8hTM=", + "id": "51120bb0-9083-41ed-b17d-dcf7cf06d817", + "last_modified": 1557423793888 + }, + { + "schema": 1557423793128, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFEMEIGA1UEAxM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBFQ0MgUm9vdENBIDIwMTU=", + "serialNumber": "aS7BLpeXXic=", + "id": "b0161c6a-ee77-4061-99f2-43c6fda85243", + "last_modified": 1557423793515 + }, + { + "schema": 1557423792744, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGmMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxNQ==", + "serialNumber": "OikIZKlOYFA=", + "id": "db1636a2-2737-4628-8bf8-f30a058481d6", + "last_modified": 1557423793115 + }, + { + "schema": 1557423792366, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGmMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxNQ==", + "serialNumber": "T0gk6O+tlMk=", + "id": "9a54465a-708d-4d1c-8bf3-4758d39019b0", + "last_modified": 1557423792731 + }, + { + "schema": 1557423791306, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGmMQswCQYDVQQGEwJHUjEPMA0GA1UEBxMGQXRoZW5zMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxNQ==", + "serialNumber": "L15JXAiBVUc=", + "id": "7888917e-fadc-4fdb-86b0-f3f44aa27e08", + "last_modified": 1557423792353 + }, + { + "schema": 1557423790929, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "ecsPdIBLahQ=", + "id": "74f5ed52-6ea0-4f9b-bfbd-767a419ef64f", + "last_modified": 1557423791293 + }, + { + "schema": 1557423790556, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOjA4BgNVBAMMMVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBQZXJzb29uIENBIC0gRzM=", + "serialNumber": "NcGIcro2KS4=", + "id": "fc3d95a1-e196-4f06-8d95-f3c4701709ad", + "last_modified": 1557423790916 + }, + { + "schema": 1557423790192, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG/MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTMwMQYDVQQDEypFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBFQzE=", + "serialNumber": "cHaafQAAAABR1JZn", + "id": "22b33327-c482-499d-96fc-e2b3339c9e10", + "last_modified": 1557423790544 + }, + { + "schema": 1557423789806, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp", + "serialNumber": "Uc4FAg==", + "id": "6beb01af-11fe-48f0-ae22-0d00ac1440f0", + "last_modified": 1557423790180 + }, + { + "schema": 1557423789434, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "UdNg7Q==", + "id": "76694fab-fc61-4f8b-85a5-4a4ee0c3b069", + "last_modified": 1557423789793 + }, + { + "schema": 1557423789066, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG/MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMTIgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTMwMQYDVQQDEypFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBFQzE=", + "serialNumber": "BFM5+gAAAABR1JIX", + "id": "7c92ac8b-d796-4ab4-b2ff-bf3b9cd6b8ed", + "last_modified": 1557423789422 + }, + { + "schema": 1557423788677, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgTmV0d29ya2luZw==", + "serialNumber": "UyCS4eAKFbU=", + "id": "7d845a2f-7ff3-4199-ac42-9bfbfec8bb51", + "last_modified": 1557423789054 + }, + { + "schema": 1557423788301, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEcMBoGA1UEAwwTQWZmaXJtVHJ1c3QgUHJlbWl1bQ==", + "serialNumber": "csPGf5ssRXs=", + "id": "e1788b2b-bb05-4928-b751-b0da7343b062", + "last_modified": 1557423788664 + }, + { + "schema": 1557423786400, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0M=", + "serialNumber": "CtBCjxRUFHU=", + "id": "d825f516-fb33-410c-9102-22c7f542b0a1", + "last_modified": 1557423788289 + }, + { + "schema": 1557423786029, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEfMB0GA1UEAwwWQWZmaXJtVHJ1c3QgQ29tbWVyY2lhbA==", + "serialNumber": "LAfP+WzoaJo=", + "id": "99e4c959-2076-4273-8bc6-cf447754a3d4", + "last_modified": 1557423786386 + }, + { + "schema": 1557423785649, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "AI8KQNd3KXH0AAAAAFHTksc=", + "id": "ce729dfc-a683-405d-aa9b-a0307ba3f9df", + "last_modified": 1557423786016 + }, + { + "schema": 1557423785273, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "UdNg0A==", + "id": "a3ebf683-2201-4958-9400-068835bf2a73", + "last_modified": 1557423785636 + }, + { + "schema": 1557423784897, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "CkGcaQQF8iUjbrKwr6T69w==", + "id": "61ad3107-4089-4659-b9a0-4fbd07ff462c", + "last_modified": 1557423785260 + }, + { + "schema": 1557423784508, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "OT3HaBAsXSO1EA749ZVxbg==", + "id": "688a97d9-9bd1-4fcb-a3f4-6b2a5f877119", + "last_modified": 1557423784883 + }, + { + "schema": 1557423784129, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMSIwIAYDVQQKExlLUE4gQ29ycG9yYXRlIE1hcmtldCBCLlYuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSgwJgYDVQQDEx9LUE4gQ29ycG9yYXRlIE1hcmtldCBDbGFzcyAyIENB", + "serialNumber": "ArgkEK/Zm1qPInQleVefMg==", + "id": "0e822b98-13ea-42f2-9526-5812e92793cd", + "last_modified": 1557423784493 + }, + { + "schema": 1557423783745, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "ZtxTeyP3ul+7qG4Ps6pxYQ==", + "id": "f116a26d-be81-43bd-9dad-ad748be946ab", + "last_modified": 1557423784116 + }, + { + "schema": 1557423783347, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "CKyPU/WFC2jREm9pSj63yg==", + "id": "92004088-7b9b-4f30-b095-a2dbcf0f4ef0", + "last_modified": 1557423783732 + }, + { + "schema": 1557423782968, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "bHgAjOXoRfKYXBrnK+DHng==", + "id": "30941089-e421-428f-b298-579c25569f74", + "last_modified": 1557423783334 + }, + { + "schema": 1557423782593, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "dri/O9LSPOfkoL9YoireCA==", + "id": "d28b3446-6119-4b3a-971c-42d989ae5dfe", + "last_modified": 1557423782954 + }, + { + "schema": 1557423782216, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFgxEzARBgoJkiaJk/IsZAEZFgNDT00xEzARBgoJkiaJk/IsZAEZFgNBQkIxDDAKBgNVBAoTA0FCQjEeMBwGA1UEAxMVQUJCIEludGVybWVkaWF0ZSBDQSA1", + "serialNumber": "FCPkDQAAAAAABA==", + "id": "ebe02656-332e-4df1-9254-099aab288a38", + "last_modified": 1557423782580 + }, + { + "schema": 1557423781840, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "f0J8K+PdFynpy646qUsWpw==", + "id": "44b30047-2bbd-4483-97cd-11ba40d34830", + "last_modified": 1557423782203 + }, + { + "schema": 1557423781462, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ZasMZAjKdfoGGP8auMEHMg==", + "id": "2b871e22-453e-4f69-9693-677f79c72767", + "last_modified": 1557423781826 + }, + { + "schema": 1557423781087, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "dOKgNVsKclBA1Ajo/4zYOw==", + "id": "9d2fb829-6906-4279-b25a-42ac52c896e6", + "last_modified": 1557423781450 + }, + { + "schema": 1557423780701, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ZUzcezQWkm8X0uCnEzzoZw==", + "id": "8e589379-a05b-4a53-8891-337e01835b79", + "last_modified": 1557423781075 + }, + { + "schema": 1557423780317, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "c7UqiCphESsnodANu5EvIA==", + "id": "e1d493ae-6c0b-4d8b-90b3-43a8a4b527f0", + "last_modified": 1557423780686 + }, + { + "schema": 1557423779945, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "bElJNaCQlbSnDkqEEPkrzQ==", + "id": "b9de3761-bfd0-4720-ba30-c6c93fd69cdc", + "last_modified": 1557423780304 + }, + { + "schema": 1557423779533, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGRMQswCQYDVQQGEwJOTDEiMCAGA1UEChMZS1BOIENvcnBvcmF0ZSBNYXJrZXQgQi5WLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazE9MDsGA1UEAxM0S1BOIENvcnBvcmF0ZSBNYXJrZXQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "SYh0NsP2N0/Dxw8T+IJvmw==", + "id": "feb077f0-6334-4fed-995e-02fd556ab42f", + "last_modified": 1557423779933 + }, + { + "schema": 1557423779157, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGRMQswCQYDVQQGEwJOTDEiMCAGA1UEChMZS1BOIENvcnBvcmF0ZSBNYXJrZXQgQi5WLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazE9MDsGA1UEAxM0S1BOIENvcnBvcmF0ZSBNYXJrZXQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "c05oq3l3A48UTQMC6xDPmA==", + "id": "17d8e83c-cd28-4eb2-a657-41424cfb04c7", + "last_modified": 1557423779520 + }, + { + "schema": 1557423778777, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "WGAsRfJbqjpTgxfybB+hRQ==", + "id": "4e9f0249-f4aa-4752-9618-a71c816a4ec4", + "last_modified": 1557423779143 + }, + { + "schema": 1557423778399, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "BlHrBttW8NGd18BEPdUVSg==", + "id": "daf1ff08-33e6-493d-b0ba-6bf7ff8510f6", + "last_modified": 1557423778763 + }, + { + "schema": 1557423778021, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "OcFolrgIjwbaL1r7F81ymg==", + "id": "881e4a9f-da95-465e-a7bf-045e115b74f3", + "last_modified": 1557423778386 + }, + { + "schema": 1557423777634, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "HYwtWO0UsO9eaJk85kvH3w==", + "id": "278bd7c1-d82d-4578-8ae1-17a1f81db49d", + "last_modified": 1557423778007 + }, + { + "schema": 1557423777259, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHNA==", + "serialNumber": "HkWAATSKOQL+bu2Kr0Tjsg==", + "id": "429954c1-ca6a-4dc3-af3e-f166285bcc12", + "last_modified": 1557423777622 + }, + { + "schema": 1557423776878, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "ZajZKYDkNzvlLcysCHjIYg==", + "id": "08663e30-875e-484c-ae2c-bba329bbbe7a", + "last_modified": 1557423777246 + }, + { + "schema": 1557423776505, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydvrg==", + "id": "12c94c82-ae15-4f02-9c99-b52a53e018e5", + "last_modified": 1557423776865 + }, + { + "schema": 1557423776126, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeO8A==", + "id": "a0dd8ab2-8769-426b-97d6-0e97143c61bb", + "last_modified": 1557423776492 + }, + { + "schema": 1557423775747, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHNA==", + "serialNumber": "Z8ItzUSc94z2xEuisg0j4w==", + "id": "f8ef406f-9483-4f48-98a1-7aef32ed8dca", + "last_modified": 1557423776113 + }, + { + "schema": 1557423775371, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "M3kpri5YHC6QvsSGsV43Gw==", + "id": "8f5fbed4-5ce7-4eb2-b226-b610ca5b3024", + "last_modified": 1557423775734 + }, + { + "schema": 1557423774990, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "Jb1BExLrPLdnbYvQdtKm7g==", + "id": "4f47f19e-d703-4a88-9d44-f5bdfe041e65", + "last_modified": 1557423775358 + }, + { + "schema": 1557423774612, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "a+1Y1DOEid7V14UXFrl5AA==", + "id": "01af31d0-1545-48e3-b2d8-59dc3152a8ec", + "last_modified": 1557423774977 + }, + { + "schema": 1557423774219, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "bymtvcoxZCxPWB3g+emE5w==", + "id": "bc4f1c74-469a-441c-a5c1-c6baa44a5866", + "last_modified": 1557423774599 + }, + { + "schema": 1557423773844, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "b+Rtg7qNXBaYW+jVP5TYEw==", + "id": "bf6c5391-5ea0-4057-b695-9b1fb94ccaa3", + "last_modified": 1557423774204 + }, + { + "schema": 1557423773466, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "FqCOjyfKZi1+9zJ8x/SRyQ==", + "id": "6bac3e61-4061-4696-bc93-d72f41c5a3c0", + "last_modified": 1557423773829 + }, + { + "schema": 1557423773086, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "B3TduD7dsIRRFuvx6NqUjw==", + "id": "02a491f6-bf7b-441b-8f13-8213a4a006fe", + "last_modified": 1557423773453 + }, + { + "schema": 1557423772706, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "NyoSnmvYrQid5NSZ0HAKFQ==", + "id": "47a33daf-2c4f-455d-aa78-8d7094c35313", + "last_modified": 1557423773073 + }, + { + "schema": 1557423772334, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "T413k3/wlSA8EV9AyZKKzg==", + "id": "0def93c8-5106-45f7-af8c-0f4e0a26c41f", + "last_modified": 1557423772692 + }, + { + "schema": 1557423771961, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAk5MMREwDwYDVQQKEwhLUE4gQi5WLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEXMBUGA1UEAxMOS1BOIENsYXNzIDIgQ0E=", + "serialNumber": "NgpYt43seMqHJofUSGQM3g==", + "id": "3bd57f79-2482-4141-89c7-8f9ea9933dee", + "last_modified": 1557423772321 + }, + { + "schema": 1557423771582, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAk1ZMSQwIgYDVQQKExtNU0MgVHJ1c3RnYXRlLmNvbSBTZG4uIEJoZC4xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMTH01TQyBUcnVzdGdhdGUuY29tIENsYXNzIDIgQ0EtRzM=", + "serialNumber": "RwAisG4tCM7UNFjwHPY+Yw==", + "id": "9e219ac4-ba0b-4045-bbd2-5b22a9e47d6a", + "last_modified": 1557423771947 + }, + { + "schema": 1557423771035, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAk1ZMSQwIgYDVQQKExtNU0MgVHJ1c3RnYXRlLmNvbSBTZG4uIEJoZC4xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMTH01TQyBUcnVzdGdhdGUuY29tIENsYXNzIDIgQ0EtRzM=", + "serialNumber": "BeKfxLaKl34S8yFIynon4g==", + "id": "01b2f9f3-ee15-41a2-9d30-b0380f2db79f", + "last_modified": 1557423771570 + }, + { + "schema": 1557423770337, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeuLw==", + "id": "8fd446dd-a079-47d3-97a2-f950665b405b", + "last_modified": 1557423771021 + }, + { + "schema": 1557423769641, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEvMC0GA1UEAwwmZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzI=", + "serialNumber": "APdOGAyZ4nuNn3lPsbfAv0g=", + "id": "82065756-9ce7-4d71-a1a7-c58330bdecde", + "last_modified": 1557423770324 + }, + { + "schema": 1557423768908, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "FrTxzDM8QlIX94F6bzmmRg==", + "id": "27d7ce50-fa45-4279-bf44-be6d999c6eb2", + "last_modified": 1557423769626 + }, + { + "schema": 1557423768205, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "FNURHo65qOyqf1vp2eA6Dg==", + "id": "efb8234d-8502-4875-b668-7592cc057ce8", + "last_modified": 1557423768895 + }, + { + "schema": 1557423767655, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIIBDDELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1BRFJJRDEPMA0GA1UEBwwGTUFEUklEMTowOAYDVQQLDDFzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzMSkwJwYDVQQLDCBDSEFNQkVSUyBPRiBDT01NRVJDRSBST09UIC0gMjAxNjESMBAGA1UEBRMJQTgyNzQzMjg3MRgwFgYDVQRhDA9WQVRFUy1BODI3NDMyODcxGzAZBgNVBAoMEkFDIENBTUVSRklSTUEgUy5BLjEpMCcGA1UEAwwgQ0hBTUJFUlMgT0YgQ09NTUVSQ0UgUk9PVCAtIDIwMTY=", + "serialNumber": "RUqLEbThNfI=", + "id": "21aab5cc-3031-4881-b735-d8c95d0a412c", + "last_modified": 1557423768189 + }, + { + "schema": 1557423766965, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIIBCDELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1BRFJJRDEPMA0GA1UEBwwGTUFEUklEMTowOAYDVQQLDDFzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzMScwJQYDVQQLDB5HTE9CQUwgQ0hBTUJFUlNJR04gUk9PVCAtIDIwMTYxEjAQBgNVBAUTCUE4Mjc0MzI4NzEYMBYGA1UEYQwPVkFURVMtQTgyNzQzMjg3MRswGQYDVQQKDBJBQyBDQU1FUkZJUk1BIFMuQS4xJzAlBgNVBAMMHkdMT0JBTCBDSEFNQkVSU0lHTiBST09UIC0gMjAxNg==", + "serialNumber": "EQhxWldLpE0=", + "id": "0ad26162-a542-4876-98c6-5468afc3ce62", + "last_modified": 1557423767640 + }, + { + "schema": 1557258110291, + "details": { + "bug": "", + "who": "", + "why": "", + "name": "", + "created": "" + }, + "enabled": true, + "issuerName": "MIGqMQswCQYDVQQGEwJFUzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJtYSBTLkEuMRIwEAYDVQQFEwlBODI3NDMyODcxSzBJBgNVBAcTQk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCBodHRwczovL3d3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTEdMBsGA1UEAxMUQUMgQ2FtZXJmaXJtYSAtIDIwMDk=", + "serialNumber": "Aw==", + "id": "d65240be-ff5e-47d9-90ba-93266235feaf", + "last_modified": 1557423766949 + }, + { + "schema": 1554493303242, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539007", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2019-03-26T04:37:00Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNDAyBgNVBAMTK0RpZ2lDZXJ0IFNIQTIgRXh0ZW5kZWQgVmFsaWRhdGlvbiBTZXJ2ZXIgQ0E=", + "serialNumber": "BFXgjf2ribj8GNKFDT/8+g==", + "id": "6ef701b6-5e38-4a9a-b6b9-2c1beed8d2a9", + "last_modified": 1554504516759 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AK8tb0kLM7VzymmWJsfD3A==", + "id": "d3f0522b-2465-405f-9f7b-60b4855dcffb", + "last_modified": 1552492951595 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AJDvcXc7qWv3KL5T8LZTzg==", + "id": "bf4d016f-5663-41ab-878f-3125a4ed37d3", + "last_modified": 1547837142956 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "atcGz1KKGVTDnXW50L1A", + "id": "e247e73f-9f9d-4a95-be21-189451d6547d", + "last_modified": 1547837142641 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "T8WaANEU0o+TGJUhLg2r", + "id": "59f41045-3213-41e4-a57f-b80f6e718319", + "last_modified": 1547837142322 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AITPqUzKZv4eLCI4WjAAJw==", + "id": "b77e2e29-365d-47ee-ba09-7737a5c9ead3", + "last_modified": 1547837141991 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDElMCMGA1UEAwwcVUNBIEV4dGVuZGVkIFZhbGlkYXRpb24gUm9vdA==", + "serialNumber": "WfGaLerthIxK19OQTLUGWg==", + "id": "40c1362f-3787-4984-a6a5-0990d7e49e37", + "last_modified": 1547837141665 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290", + "serialNumber": "U3PRzjiR2zDYPa19QJGeCQ==", + "id": "b2eb4727-dee0-4f6d-a738-e7f7fac9e141", + "last_modified": 1547837141343 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/VqQ==", + "id": "e38378f4-32d4-4c07-93a5-482380ef4765", + "last_modified": 1547837141021 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzM=", + "serialNumber": "e41LsLkwucx+Zibwpajkmngu5js=", + "id": "847dbfce-baf3-4983-a1ee-d88788cbbf14", + "last_modified": 1547837140703 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "T96lTZ6OiZgO5HXc2chfmg==", + "id": "a07d0ace-fd5c-4c75-a10b-f61f902ba77b", + "last_modified": 1547837140382 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "XLd9GTWYtJqCVi/S1qbPCA==", + "id": "28365130-2f06-4e4d-8c88-bdb5b67dadb5", + "last_modified": 1547837140050 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "dmmZvl03B2fppSvWZ2AhmQ==", + "id": "70388522-f69e-46af-bad5-030de539f8b8", + "last_modified": 1547837139726 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "T94ltso4x5zl5SmPzGxaRQ==", + "id": "ff260017-eece-4203-96f7-7fe544eef7ea", + "last_modified": 1547837139403 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ANWeOrGre+B3kYmkmDsQFA==", + "id": "63ba2af1-3b95-4c2f-86de-8d260c0e7515", + "last_modified": 1547837139075 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "NuO1LvUpC8SX29dTTMDyxg==", + "id": "4187cbc1-981b-45d8-aabc-55264528b6be", + "last_modified": 1547837138743 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "PLutAhi0cRaKAH2YfGu0ww==", + "id": "33baf681-54e2-4e2c-a122-c4cd476df1ad", + "last_modified": 1547837138417 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "Aw==", + "id": "1cefaa15-053b-4ab9-a393-ea6bfcbfd990", + "last_modified": 1547837138096 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290", + "serialNumber": "CQ==", + "id": "04ace127-bc46-4db7-945c-4d0c65c0d526", + "last_modified": 1547837137775 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIIBDDELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1BRFJJRDEPMA0GA1UEBwwGTUFEUklEMTowOAYDVQQLDDFzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzMSkwJwYDVQQLDCBDSEFNQkVSUyBPRiBDT01NRVJDRSBST09UIC0gMjAxNjESMBAGA1UEBRMJQTgyNzQzMjg3MRgwFgYDVQRhDA9WQVRFUy1BODI3NDMyODcxGzAZBgNVBAoMEkFDIENBTUVSRklSTUEgUy5BLjEpMCcGA1UEAwwgQ0hBTUJFUlMgT0YgQ09NTUVSQ0UgUk9PVCAtIDIwMTY=", + "serialNumber": "GfVBLKRI50c=", + "id": "6c625a74-448c-4ea8-8a4a-396ea0e9f212", + "last_modified": 1547837137453 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290", + "serialNumber": "Cg==", + "id": "934dba88-38a3-491b-828b-eb3e3c2e793d", + "last_modified": 1547837137131 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290", + "serialNumber": "CA==", + "id": "c2060e12-c016-498e-8f7a-63a84744145c", + "last_modified": 1547837136780 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1521150", + "who": "", + "why": "", + "name": "", + "created": "2019-01-18T11:45:13Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "BA==", + "id": "5d203d0c-a393-4007-bf81-e7468b44a3f9", + "last_modified": 1547837136462 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1513609", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-12-14T18:02:57Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYDVQQDEzNHbG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gU0hBMjU2IC0gRzI=", + "serialNumber": "CpI/GtuuSFspBu4E", + "id": "3a6ab1f4-89cb-4987-b8b2-54797f88930b", + "last_modified": 1544811398633 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "XtxscvsjagmsjDmthiFt", + "id": "b476fea3-2cdb-4481-89f0-913db8d7498a", + "last_modified": 1544194317387 + }, + { + "schema": 1552491702967, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AJWE4kFAkUQDWa/pTgHq2w==", + "id": "236e57d7-b146-4be8-b8d1-29e872612646", + "last_modified": 1544194316803 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "C8qNtOLfRJf1IZuBa5YY", + "id": "c496b743-29d9-421d-be85-cc3f0fe2129c", + "last_modified": 1544194316153 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKDAmBgNVBAMTH1N3aXNzU2lnbiBQbGF0aW51bSBSb290IENBIC0gRzM=", + "serialNumber": "AIDHko+nptD0l30tljroTA==", + "id": "c7938b05-7beb-446a-98ea-c288e33c6fbb", + "last_modified": 1544194315552 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AKrsBzcrYHp2NTXnbO4+Qg==", + "id": "8f563a73-5cae-4aa8-b235-482618a31010", + "last_modified": 1544194314942 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKDAmBgNVBAMTH1N3aXNzU2lnbiBQbGF0aW51bSBSb290IENBIC0gRzM=", + "serialNumber": "APbjF26Q/6hfA1ECXToQLg==", + "id": "589ef449-7393-4474-9c3a-643023fb517c", + "last_modified": 1544194314328 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AJpv8fAILOsYJSFFQ2KtPg==", + "id": "47970400-9add-4b5f-9e45-411f928e4623", + "last_modified": 1544194313711 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5AAf", + "id": "ab409612-f7ba-430b-bd06-59fdb9a9313b", + "last_modified": 1544194313119 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MIGnMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnk=", + "serialNumber": "SUEs5AAh", + "id": "14d8c40f-e1d6-43da-9e12-acbaf631466c", + "last_modified": 1544194312511 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "GPo59VlzNyA=", + "id": "b0e872b6-06dc-4f2e-8944-ce63aa2eb698", + "last_modified": 1544194311831 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESISaOB3ffRv2jD4gcSgycgm", + "id": "40de616f-9872-4e5b-9c79-772291fdddf8", + "last_modified": 1544194311237 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESImf/mZpsO94hCWJFJDUDPZ", + "id": "132dad17-7ebb-482a-bc8a-8b1ab06b6132", + "last_modified": 1544194310607 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAk1LMRcwFQYDVQQKEw5LSUJTIEFEIFNrb3BqZTEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEqMCgGA1UEAxMhS2lic1RydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "CiM0WSw//67JHGWCrlZU4Q==", + "id": "93d297ac-6894-4a83-8406-0ae2585cf9ee", + "last_modified": 1544194310003 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeHHg==", + "id": "e3f98761-7fae-4684-bc3b-8c8da50e5ba1", + "last_modified": 1544194309375 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDqg==", + "id": "2b01f223-8b3a-4da7-8925-44af81bd1279", + "last_modified": 1544194308748 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeuLg==", + "id": "a0a574cb-2efb-42a7-bc70-51ce91e560db", + "last_modified": 1544194308135 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDtw==", + "id": "5050d06f-ae56-40f4-8131-5019beeeec79", + "last_modified": 1544194307534 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bfb2Rs8N620oUcAWs41vjA==", + "id": "d53e3b7e-da21-4cdb-8e68-dacd97d1d079", + "last_modified": 1544194306886 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByemYA==", + "id": "62d36dac-38c1-49d1-b96d-6e018d7a5ef5", + "last_modified": 1544194306284 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAk1LMRcwFQYDVQQKEw5LSUJTIEFEIFNrb3BqZTEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEqMCgGA1UEAxMhS2lic1RydXN0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "UK9vtGRfZt5a3QiUAQUTaQ==", + "id": "783e58c7-e899-4297-af4e-95d3c6253ce7", + "last_modified": 1544194305696 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFTATBgNVBAMMDENGQ0EgRVYgUk9PVA==", + "serialNumber": "ZHKPuIaZ7pI39g==", + "id": "8d006e7e-9f46-4413-909b-52795a0bfff4", + "last_modified": 1544194305082 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkNOMTAwLgYDVQQKDCdDaGluYSBGaW5hbmNpYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFTATBgNVBAMMDENGQ0EgRVYgUk9PVA==", + "serialNumber": "fs7i7UJNxcrb9w==", + "id": "e67cb2f1-f829-4659-8823-831d8abb7a46", + "last_modified": 1544194304064 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "KKdkWIX7TIM=", + "id": "abbc3079-34c6-4101-a577-2db27ca8cbd7", + "last_modified": 1544194302086 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1512640", + "who": "", + "why": "", + "name": "", + "created": "2018-12-07T09:51:34Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "dyRJDorYkgE=", + "id": "7e0b53c6-5915-4be1-847b-4c1785fb50e7", + "last_modified": 1544194300987 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484798", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-12-07T14:23:59Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJESzEQMA4GA1UECAwHRGVubWFyazEpMCcGA1UEBwwgaW5kdXN0cmlwYXJrZW4gMjcsIDI3NTAgQmFsbGVydXAxJjAkBgNVBAoMHVNlbm5oZWlzZXIgQ29tbXVuaWNhdGlvbnMgQS9TMQwwCgYDVQQLDANSJkQxFjAUBgNVBAMMDVNlbm5jb21Sb290Q0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRAc2VubmNvbS5jb20=", + "serialNumber": "APSjDtifEQeh", + "id": "188e1d62-6db0-42ec-b603-ff08649962e1", + "last_modified": 1544194300329 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMTAvBgNVBAMMKFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBJbnRlcm1lZGlhaXIgQ0E=", + "serialNumber": "VAgHVTPgNwAuYo/y/jkd5A==", + "id": "9cb67873-dc81-4fb2-9e08-ff4aacaf9fb2", + "last_modified": 1541184580803 + }, + { + "schema": 1552492991594, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "DDbXyG6tZ+P3+B11YPItUw==", + "id": "ba22132a-97ca-42f1-a521-87fed171e1b7", + "last_modified": 1541184580485 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ArkguT7SeGHAA9ts1IOWWg==", + "id": "76e65b53-7470-4e1d-8b75-672633474bc2", + "last_modified": 1541184580122 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfHkg==", + "id": "b323ad96-398b-4225-aa75-1745251dffcb", + "last_modified": 1541184579802 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeoVA==", + "id": "ad2f2b7a-3f07-4945-8bf6-ec8606df1a00", + "last_modified": 1541184579472 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "CkE6FgBN+nIkANMmgOQkiQ==", + "id": "de69d003-2a1e-4b76-a367-a4cdc26b2a58", + "last_modified": 1541184579160 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0E=", + "serialNumber": "DPWCOBgZnlb4K9ZS7Sft6Q==", + "id": "6c73cf52-b339-421b-8c2d-cb5a6b9f459b", + "last_modified": 1541184578848 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJhg==", + "id": "9d3e708c-2edc-4ffd-8414-cb9dc0edf46e", + "last_modified": 1541184578526 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfNbg==", + "id": "583b6241-3ab7-4c50-90ea-112bff931a2b", + "last_modified": 1541184578212 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQQ==", + "serialNumber": "Ajx7/tYZjpFt5dBByJo9JQ==", + "id": "78be56f6-6b69-42ea-abaa-356d066b3c38", + "last_modified": 1541184577902 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDqw==", + "id": "6ca65a6d-8aeb-47ae-b6b5-6c24509a73b1", + "last_modified": 1541184577589 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeekQ==", + "id": "1ea21ad4-2362-45ba-a738-de15afc58297", + "last_modified": 1541184577274 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDtg==", + "id": "846ddb44-f14a-4712-a1fb-6780e38300b5", + "last_modified": 1541184576961 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "AXscEecDK83Ks/XoEFX8RQ==", + "id": "33b2de43-06a8-4984-85b5-5808c0baa6fd", + "last_modified": 1541184576649 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd7Zg==", + "id": "c164ebb8-e020-45c4-ac6b-9bd57b761aa0", + "last_modified": 1541184576330 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RDQTEx", + "serialNumber": "HQ4ijQIlTBpJKXTUo0geJ5AI4VI=", + "id": "b7bff651-6a6c-45d9-8bb3-d795ed07bdac", + "last_modified": 1541184576013 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1504300", + "who": "", + "why": "", + "name": "", + "created": "2018-11-02T11:49:33Z" + }, + "enabled": true, + "issuerName": "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=", + "serialNumber": "dMGHU/futOojjYQWtax2Rg==", + "id": "a9080b65-0185-4097-ab66-3c059b2a04ad", + "last_modified": 1541184575682 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxSDcTuZO2", + "id": "db09a4fa-41d9-4e6c-b80f-a9be26cc70b2", + "last_modified": 1535652552759 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxSjphH9DY", + "id": "1179bdb8-27ac-4bba-8e7e-f9c065b0dad4", + "last_modified": 1535652552456 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxTNGDGgYt", + "id": "5f5530aa-7a1c-4b8f-bc35-1d123477d217", + "last_modified": 1535652552111 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxSxsaAPaT", + "id": "ac2d20b0-5c89-4575-831a-51ef341f0ebc", + "last_modified": 1535652551805 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxST2Fhyj5", + "id": "225ca652-d633-46e6-aef2-d97708ea696f", + "last_modified": 1535652551494 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MF0xCzAJBgNVBAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTI=", + "serialNumber": "IrmxST2Fhyj5", + "id": "752a5350-9895-434e-abe7-04b85863341e", + "last_modified": 1535652551184 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "Qks10R3Zqs4AAAAAUdNX8Q==", + "id": "bc1bcbe5-4db7-4a41-8f10-e900f4eee969", + "last_modified": 1535652550869 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfTSg==", + "id": "06039f5d-19e5-4760-9a27-656f4c949f71", + "last_modified": 1535652550558 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfTSw==", + "id": "5cddfcfb-2136-4b37-8208-1b0d8dce98c4", + "last_modified": 1535652550249 + }, + { + "schema": 1552492993020, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJkg==", + "id": "1d0f7585-ddb3-44f2-86b5-afacda2fdfdb", + "last_modified": 1535652549936 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeO7w==", + "id": "6183f199-c1c5-4705-a178-911f35de4916", + "last_modified": 1535652549620 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJkA==", + "id": "424db874-09b3-4a92-8fe6-61c6e10a0c69", + "last_modified": 1535652549313 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeO7g==", + "id": "e0cb6242-a46f-4c43-a435-0f8550dd1e6a", + "last_modified": 1535652549004 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "W2qOjVqGcY8=", + "id": "0093c3c1-1c5c-4524-a29b-9d6a857af2d7", + "last_modified": 1535652548629 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "W2qOjVqGcY8=", + "id": "724dc57b-1305-4bc3-82af-90d7457e08b7", + "last_modified": 1535652548311 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1487485", + "who": "", + "why": "", + "name": "", + "created": "2018-08-30T11:09:06Z" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "W2qOjVqGcY8=", + "id": "aa81cbe1-031c-4e40-b2d1-e6b9ec6abd7e", + "last_modified": 1535652547990 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484798", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-08-29T17:35:07Z" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQQ==", + "serialNumber": "CR8HWlsGr6Sdlw/mzOv8gA==", + "id": "cdd584ce-f3a2-4b7a-927e-1d479586f224", + "last_modified": 1535564772911 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1480853", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-08-29T17:33:12Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJSYXBpZFNTTCBTSEEyNTYgQ0E=", + "serialNumber": "L41amoCH4B2agSUpD8Wd2A==", + "id": "8a401bee-cee4-40d8-a61e-903ca0cda17f", + "last_modified": 1535564772595 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1480853", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-08-29T17:32:10Z" + }, + "enabled": true, + "issuerName": "MIGWMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE8MDoGA1UEAxMzQ09NT0RPIFJTQSBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "serialNumber": "AMN6iHtOgy68QBu3kXiaFc8=", + "id": "514b3b98-0554-4aff-b117-faeafe5f4897", + "last_modified": 1535564772273 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "ANdqi8UFCQChm0RchyjMpjY=", + "id": "4307675d-416f-4104-b7ca-6d728920cb91", + "last_modified": 1534541096333 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MDcxFDASBgNVBAoMC1RlbGlhU29uZXJhMR8wHQYDVQQDDBZUZWxpYVNvbmVyYSBSb290IENBIHYx", + "serialNumber": "A3XEm35jzkM3B/8ZhDel7w==", + "id": "68edcd97-5fc4-40ea-b459-306f4339e987", + "last_modified": 1534541095459 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "AISaJcSkfQq9", + "id": "7c47f219-cff9-4fef-a855-007eb636ecb4", + "last_modified": 1534541094267 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MEkxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxIzAhBgNVBAMTGlN3aXNzU2lnbiBQbGF0aW51bSBDQSAtIEcy", + "serialNumber": "SUUmDL8PIBZ0EkIfCV6N", + "id": "7cc984ca-7513-4e31-bf91-f32799747ef9", + "last_modified": 1534541093107 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "TA7JMQ==", + "id": "b4858c0b-2060-4327-9a27-0f3ab43765d6", + "last_modified": 1534541092217 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "RWua3A==", + "id": "fc4c7ef0-5d5e-4a2c-86dc-99f2586d9774", + "last_modified": 1534541091322 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "TA7JGA==", + "id": "2558efc4-46b1-4769-a57e-f8ac3971ed01", + "last_modified": 1534541090496 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "serialNumber": "Beo=", + "id": "9ff5d4c3-8d66-48ff-9add-343a90b031a6", + "last_modified": 1534541089654 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "U4S9ZGx1FCY3wppgMwTn0Q==", + "id": "bd9d1022-f109-47a4-84be-ebf91cc901cb", + "last_modified": 1534541088707 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "JMRsU4iZEfeLdmXeFIjy4w==", + "id": "85acb0b3-8291-4ee5-8137-3147412a22af", + "last_modified": 1534541087581 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "BnG7G8DueagyBXomN87dYA==", + "id": "cde8301e-94ce-4808-935a-8dc1dc550d1f", + "last_modified": 1534541086654 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "PtgfuDU/40rxF87UP4HpIw==", + "id": "c2c44bd7-a0d9-48ac-95b7-9b42250f09bb", + "last_modified": 1534541085650 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIGBMQswCQYDVQQGEwJCUjEtMCsGA1UEChMkQ2VydGlzaWduIENlcnRpZmljYWRvcmEgRGlnaXRhbCBTLkEuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSIwIAYDVQQDExlDZXJ0aXNpZ24gQ2xhc3MgMiBDQSAtIEcz", + "serialNumber": "FdJweu3BTeU/YzTvayJksQ==", + "id": "961d1883-8b9a-4523-8ee5-43ed4ccfc347", + "last_modified": 1534541084620 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "cDosFiyvqdvDoYinOV6PDg==", + "id": "1c8e5f98-3a13-4f90-9d16-d15580d1c3fe", + "last_modified": 1534541083590 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "LiJxautXUrJeoN4q4RX/Rg==", + "id": "3cd4e097-6f1d-44e6-9e51-43ca50114727", + "last_modified": 1534541082464 + }, + { + "schema": 1552492994435, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "aqcSP+AsWAmN9xWwAseOqg==", + "id": "fe1c41a4-4a07-4bfa-b7dc-850433c2e433", + "last_modified": 1534541081450 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByekbQ==", + "id": "0597bdd4-fb5d-4418-860e-f8e23349c9ff", + "last_modified": 1534541080214 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByekbA==", + "id": "b797348e-1bfc-4996-be6e-a9f9cb9856cc", + "last_modified": 1534541079187 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "BILpOL1LYav6JuQxlNetFA==", + "id": "fb097e62-de6b-4b55-afc9-b5102081e107", + "last_modified": 1534541078056 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHNA==", + "serialNumber": "IW5rxECQ5LEyRGPeZE91ug==", + "id": "b83dfdc9-8db9-4668-858f-3fb24473e8e0", + "last_modified": 1534541077133 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHNA==", + "serialNumber": "USISWFWRHGp530VQc2S1/Q==", + "id": "cb72eb7e-7a95-4a55-9e6f-a0b2286287e6", + "last_modified": 1534541076012 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "I5cyb4y1eoVQS44pO3PAww==", + "id": "7e42ae24-9a36-4b9c-805e-786c45bd4b2b", + "last_modified": 1534541074363 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAkdSMRMwEQYDVQQKEwpBTFBIQSBCQU5LMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMRswGQYDVQQDExJBTFBIQSBCQU5LIENBIC0gRzI=", + "serialNumber": "SoA0BJz+EzihvsNlkwlJTg==", + "id": "18832146-8b40-4eb2-992e-bf066afd87e0", + "last_modified": 1534541073330 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Jje4sy72uF/upHdwh0gBGg==", + "id": "12d06d11-7118-4d7d-aad8-d6a3a5abf4dc", + "last_modified": 1534541072103 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "e9fp8poJ4jDqpHxVc+7SoA==", + "id": "c318da0b-ae36-4297-9b3b-54b3704c415a", + "last_modified": 1534541071195 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "H3xY9eXX+IpIl9ixQf3lzQ==", + "id": "a152bd2e-c4dc-4b8a-9367-68d233e3293d", + "last_modified": 1534541070166 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAkdSMRQwEgYDVQQKEwtBREFDT00gUy5BLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEfMB0GA1UEAxMWQURBQ09NIENsYXNzIDIgQ0EgLSBHNA==", + "serialNumber": "YfdCZWyRV1sSx5XxyoXKSQ==", + "id": "5a6a2309-ef7f-4294-a5f5-35713e6b09ef", + "last_modified": 1534541069146 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "SrterKaDHA8hZ+z9gwFXnw==", + "id": "9a9f2ce5-4adf-44ab-9419-46b2a9c23c0b", + "last_modified": 1534541067931 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "HxapAqDOXuN5BHzREkFFtg==", + "id": "ccbb9f74-6a49-488f-9a63-d760e364a097", + "last_modified": 1534541066804 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "MyEj11s2KJH0vdnfUfuNIw==", + "id": "aae675ae-903f-443f-aeb9-b9dbdad319a9", + "last_modified": 1534541065882 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "C95Fob0ttu7S7dIXnjqiBA==", + "id": "d4aebc29-de67-49a0-ba42-255f38d35437", + "last_modified": 1534541064843 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "A7XRsyidfS8L2dlFuTsfGA==", + "id": "f5c969f7-ddec-42f6-9ebf-595fbb7c6fca", + "last_modified": 1534541063714 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byc3DA==", + "id": "2bc17daa-0e8d-4d68-bfa4-ba4e68ab506c", + "last_modified": 1534541062617 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByekcA==", + "id": "5bff7385-6327-424d-9190-6fc5e49e6204", + "last_modified": 1534541061659 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "HvAB3BwhY8g=", + "id": "95957db4-01f6-458e-9702-b6bac846e794", + "last_modified": 1534541060556 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "ThcGy6zgwpM=", + "id": "af35a216-5439-4e22-ac65-06b46b10f1e4", + "last_modified": 1534541059412 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "YfJj9o3IPBI=", + "id": "f5764907-12d4-448f-a9ff-0c7bd49b6e2e", + "last_modified": 1534541058504 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1484351", + "who": "", + "why": "", + "name": "", + "created": "2018-08-17T22:24:14Z" + }, + "enabled": true, + "issuerName": "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==", + "serialNumber": "AahE5mpsDY4=", + "id": "10e71a2b-68ae-4c3e-aece-21b68e0c9c17", + "last_modified": 1534541057381 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "AQ8=", + "id": "c056f51e-560e-4386-ab51-aa1447c3f778", + "last_modified": 1527680138898 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "ARA=", + "id": "7e68e021-3514-4f54-bff4-16562a15fd8b", + "last_modified": 1527680137878 + }, + { + "schema": 1552492995883, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "ARE=", + "id": "680e1552-a9c7-465b-9230-796fbaae08af", + "last_modified": 1527680136981 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ATEz9w==", + "id": "95d4aa6a-e99d-4706-b071-b6c9d18f38f4", + "last_modified": 1527680136034 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "Q704nTrqxVY=", + "id": "1507de3d-b758-4803-8048-edaf5b747848", + "last_modified": 1527680135116 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMw==", + "serialNumber": "SMwb3p7dSlA=", + "id": "57ec979f-fa95-401a-8791-8adf2edd0223", + "last_modified": 1527680134190 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MGkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOjA4BgNVBAMMMVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBQZXJzb29uIENBIC0gRzM=", + "serialNumber": "XJI7ULS6xv8=", + "id": "56e35d02-2284-4f72-9dd2-8606c005b3e7", + "last_modified": 1527680133272 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "eNYPiDzOMtQ=", + "id": "d47cb30c-c61b-4514-9435-14998929b8e1", + "last_modified": 1527680132390 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MIGRMQswCQYDVQQGEwJOTDEiMCAGA1UEChMZS1BOIENvcnBvcmF0ZSBNYXJrZXQgQi5WLjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazE9MDsGA1UEAxM0S1BOIENvcnBvcmF0ZSBNYXJrZXQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "M9pDPXYgyiimYdML5Wg4zQ==", + "id": "ce3da6f2-bb52-4ea8-b2ca-334a49f34494", + "last_modified": 1527680131497 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "X407nWyYC7u8lCrBrW2cRA==", + "id": "3c05d750-a448-4a72-8fe6-337efe7b68f0", + "last_modified": 1527680130603 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMg==", + "serialNumber": "DmbpIZh1fhYcSThCcjaohA==", + "id": "e3c45873-f60c-4a04-be49-48e84b26c842", + "last_modified": 1527680129682 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMg==", + "serialNumber": "TpTE/3d2UBJYfYHw2LSoww==", + "id": "4c6efcb4-ab11-4ebc-a527-bb9d4b5d54a9", + "last_modified": 1527680128734 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "W9KDjZvaDeWwN4jQG9TO3w==", + "id": "4c841dba-55af-4fb2-ac56-9a4e9e090e9d", + "last_modified": 1527680127636 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "RWHsRyzP3KFyjhTLPO4FPA==", + "id": "49638a65-fc28-4f9f-bac5-618e2c4df184", + "last_modified": 1527680126713 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABECVWTJM=", + "id": "fafb930f-555d-4185-b11c-51542f67e9ed", + "last_modified": 1527680125794 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABI75RcWk=", + "id": "cb02a3fb-31a6-4e59-9b7b-298a675dcfaf", + "last_modified": 1527680124869 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABKkKSw14=", + "id": "ea62e720-34bf-43e3-863a-b920c4f9f3c0", + "last_modified": 1527680123965 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byc3Cg==", + "id": "291cf8ea-ad61-4a25-a829-8444ab7ff3e0", + "last_modified": 1527680123024 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABSOXEgNk=", + "id": "a2b79aa8-7b05-43f2-8774-33be4e58e12a", + "last_modified": 1527680122103 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJkw==", + "id": "e6b1307d-9179-4d0c-baf0-486fc60f9e3d", + "last_modified": 1527680121187 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMg==", + "serialNumber": "QHdGjRdEcAz+FjRyuIJmog==", + "id": "e7dd6129-27e9-43c3-832e-465f281653fd", + "last_modified": 1527680120267 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "JekvfVn3h2+OX/V8Ef6vpg==", + "id": "69975669-0ed8-4c98-8bfe-0179eca26f16", + "last_modified": 1527680119342 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "CHF76YGUdPMMCJ4njfsnwQ==", + "id": "f09d52b1-8a8b-4ca6-8d3e-aedcdfa2a2a2", + "last_modified": 1527680118421 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "OBGSpfa3Oz6a7zeF/OywMg==", + "id": "e8d4263a-45aa-4965-b66b-fff4c766fd35", + "last_modified": 1527680117502 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "deh5gFVej9+uQBqlb1fIig==", + "id": "496ee295-b927-4d33-9fd1-fc87b0327af5", + "last_modified": 1527680116475 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "aKsZrWDpsFlVL0xkShb22A==", + "id": "79eccf3a-91ef-4238-b579-373bbfc64546", + "last_modified": 1527680115348 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MHQxCzAJBgNVBAYTAkdCMScwJQYDVQQKEx5Ccml0aXNoIFRlbGVjb21tdW5pY2F0aW9ucyBwbGMxHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxGzAZBgNVBAMTEkJUIENsYXNzIDIgQ0EgLSBHMw==", + "serialNumber": "SFuFrFB7MZnZ6tsqwS47tw==", + "id": "b52359b1-3f41-4c09-bbfa-8b9e507ec567", + "last_modified": 1527680114428 + }, + { + "schema": 1552492997313, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJkQ==", + "id": "783843ba-bada-4793-af9d-ba731980deb8", + "last_modified": 1527680113504 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byeekg==", + "id": "d8d6e770-d17f-4d6e-bbd9-e977a8b433d7", + "last_modified": 1527680112688 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byekaw==", + "id": "7726f4b7-43ab-4446-82de-5b94436243ef", + "last_modified": 1527680111761 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BycpXg==", + "id": "fe0212f9-fd42-4659-8052-86a4c39e358c", + "last_modified": 1527680110845 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byekag==", + "id": "b86acd44-3dc4-4652-b48d-b0903b1ca74a", + "last_modified": 1527680109917 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BycpXw==", + "id": "c8acece6-3bd4-433f-ad93-ac379de4e8d7", + "last_modified": 1527680108997 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "Mi/Y+W40ChdUGpag8vaUjQ==", + "id": "9f89002a-a395-46a0-b050-e5f32733e807", + "last_modified": 1527680108078 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=", + "serialNumber": "eRdKqRQXNv4Vp8qfLP9FiA==", + "id": "1ad54d87-3100-49f5-8fd1-a156f5a479fd", + "last_modified": 1527680107215 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "EOqAPvgqxt8=", + "id": "5e5f0c19-5509-4c2d-96d4-7e89771a75ff", + "last_modified": 1527680106336 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1465399", + "who": "", + "why": "", + "name": "", + "created": "2018-05-30T12:35:03Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "amYSY2usyXU=", + "id": "0a3b7dd0-23ee-4ec6-8237-ceba9815e4ee", + "last_modified": 1527680105419 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "KZlCQ0XnAo+GY3mKKJoNoNucjT0=", + "id": "b604fa72-f222-48f6-a8b7-8c7907a5b697", + "last_modified": 1525201514127 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "JJfQeI7SQbQcPQ8Wc4+X2nlpWho=", + "id": "e5d6417c-1b43-45e0-a9d5-ccad984a9170", + "last_modified": 1525201512695 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "bdheRp0SfvS84GGiPaBnyFhE8EY=", + "id": "8e9d1e04-962c-4495-89be-f25d3cd7d8d3", + "last_modified": 1525201511157 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Z6RtH7xmDM0r66IKSlpCZNrlRfY=", + "id": "cc7d8a02-77da-415a-842b-83d2f60e1bf3", + "last_modified": 1525201509729 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "VOQX5SLKeMGyQdoF0X9h38gYrks=", + "id": "d34174b6-22f9-47b4-af3f-7d78b2ce313f", + "last_modified": 1525201508294 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMg==", + "serialNumber": "ATE7Ow==", + "id": "6d27a296-13e2-4b53-880e-32f7030708a6", + "last_modified": 1525201506960 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGAxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMTAvBgNVBAMMKFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBJbnRlcm1lZGlhaXIgQ0E=", + "serialNumber": "a5DOAqSUlLm2s6kL0x8gkQ==", + "id": "04216f09-9d99-4699-bf56-81cb74464d6e", + "last_modified": 1525201505526 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "AeUotGv9K4mpvLzWxw==", + "id": "ac3d127c-2b79-4a52-94fa-b3ce9f420f2c", + "last_modified": 1525201504399 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFI2MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "Ro51e1DpnjbH3LKdghY=", + "id": "e63eb91d-a23d-4f90-babb-9c4c2baf3da0", + "last_modified": 1525201502761 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "R8MQVHZjYD/8LqGrob8=", + "id": "3264ae57-4d3d-482a-a169-f66652d93ff9", + "last_modified": 1525201501429 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFIxCzAJBgNVBAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIx", + "serialNumber": "B6AKfwrKX6H1AAAAAAAAAAE=", + "id": "7647d289-2123-401d-b316-d84482d851a9", + "last_modified": 1525201500273 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFIxCzAJBgNVBAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIy", + "serialNumber": "CMUHBBak0idMAAAAAAAAAAE=", + "id": "a792f002-d392-4bb5-ac92-94fe71853dbc", + "last_modified": 1525201498941 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMSIwIAYDVQQKExlLUE4gQ29ycG9yYXRlIE1hcmtldCBCLlYuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSgwJgYDVQQDEx9LUE4gQ29ycG9yYXRlIE1hcmtldCBDbGFzcyAyIENB", + "serialNumber": "GARMIB0Iaz3xxucE70O9Qg==", + "id": "dd5c26f0-c931-4d95-ade1-f243ba56a7eb", + "last_modified": 1525201497640 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAktSMRMwEQYDVQQKEwpLRUNBLCBJbmMuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSIwIAYDVQQDExlDcm9zc0NlcnQgQ2xhc3MgMiBDQSAtIEcz", + "serialNumber": "fLpClvRi4IMKsokzVKT9Yg==", + "id": "2e3b108e-3455-44e3-b519-262869934aea", + "last_modified": 1525201496208 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "JFcRHv1L89Vu8gagzuR3Pg==", + "id": "7732cbbc-9821-492e-8f1e-7b8754a1dc12", + "last_modified": 1525201494877 + }, + { + "schema": 1552492998736, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "CAEyq5GePgxvZbmFx5WW6A==", + "id": "5f6842bc-4062-4445-a442-17ec20539f59", + "last_modified": 1525201493653 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAk1ZMR8wHQYDVQQKExZNU0MgVHJ1c3RnYXRlLmNvbSBTZG4uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSowKAYDVQQDEyFNU0MgVHJ1c3RnYXRlLmNvbSBDbGFzcyAyIENBIC0gRzI=", + "serialNumber": "L3UnLdK9iz8XVM1rbm3tTw==", + "id": "f2d9c522-665c-4d09-b6c2-e0653ca8dc8e", + "last_modified": 1525201492418 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAk1ZMSQwIgYDVQQKExtNU0MgVHJ1c3RnYXRlLmNvbSBTZG4uIEJoZC4xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMTH01TQyBUcnVzdGdhdGUuY29tIENsYXNzIDIgQ0EtRzM=", + "serialNumber": "Q0dKwXPiEec83XZPgsQh+g==", + "id": "119dc8cc-5dde-4e71-aa7a-dd2058aaaced", + "last_modified": 1525201490985 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "PpIe9Q1JUVu5nN/+4HWAoA==", + "id": "b06fa973-e8ef-4deb-8dd6-af1f306563b6", + "last_modified": 1525201489551 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Baw9WIPUcpFvYe8bilTVVQ==", + "id": "3105cae8-2380-44c9-943b-62bb59978626", + "last_modified": 1525201488149 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "CpSHXk6RnrLSRVVJhVZEWA==", + "id": "a91e0705-eaae-417a-8cf5-f990c819c96f", + "last_modified": 1525201486895 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "Bxt4PMyN1f5tIXW8W62DhA==", + "id": "ef5a0861-7a80-4bef-8189-6b190481cd0f", + "last_modified": 1525201485456 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "bcIU/gztAKdw8elgpRh2vA==", + "id": "451f9b8a-ce9e-4393-866c-2a62440afa7a", + "last_modified": 1525201484131 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "U51Qij2xILJB29u2m4ePyw==", + "id": "5ae5d4d8-47cd-4519-86f0-9e764f1fd1ec", + "last_modified": 1525201482589 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIGUMQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxRTBDBgNVBAMTPFN5bWFudGVjIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNg==", + "serialNumber": "VedYmG4aoUcioKT467SDcg==", + "id": "5c06e662-9307-463e-bdb9-07abfd4f311d", + "last_modified": 1525201481154 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "HkN+3VDzRBFAw/QQ6XZ2gA==", + "id": "d284e696-c47e-4f9f-97b0-610dc7313438", + "last_modified": 1525201480132 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "XaqJA1pYkpSOSst7Hmcxew==", + "id": "78487925-3b88-480a-ba6b-1d5e139b1ec9", + "last_modified": 1525201478595 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "cIHLIBl0M9N90NNjZwhwSA==", + "id": "289275cc-72e5-4d51-b4f1-07346c5e237e", + "last_modified": 1525201477470 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMSIwIAYDVQQKExlLUE4gQ29ycG9yYXRlIE1hcmtldCBCLlYuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSgwJgYDVQQDEx9LUE4gQ29ycG9yYXRlIE1hcmtldCBDbGFzcyAyIENB", + "serialNumber": "I+zjm9Bi1ZVKLF0R96thFQ==", + "id": "efd22973-74ee-4157-8ddf-057a44f23c03", + "last_modified": 1525201475931 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMS0wKwYDVQQKEyRHZXRyb25pY3MgUGlua1JvY2NhZGUgTmVkZXJsYW5kIEIuVi4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxHTAbBgNVBAMTFEdldHJvbmljcyBDbGFzcyAyIENB", + "serialNumber": "MfSUS8xHwG64IFRIU5IHpw==", + "id": "9e57aa7e-ff8e-468f-98ed-562dd56eee71", + "last_modified": 1525201474704 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMS0wKwYDVQQKEyRHZXRyb25pY3MgUGlua1JvY2NhZGUgTmVkZXJsYW5kIEIuVi4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxHTAbBgNVBAMTFEdldHJvbmljcyBDbGFzcyAyIENB", + "serialNumber": "VhmAg9gQ0IaL5+lKzrKYPQ==", + "id": "a801439e-c1b1-414f-acbb-16b5fe362389", + "last_modified": 1525201473562 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGwxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBLUE4gVGVsZWNvbSBCLlYuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMSEwHwYDVQQDExhLUE4gVGVsZWNvbSBCLlYuIENBIC0gRzI=", + "serialNumber": "I1kCCASG38Q8TKOJaqQtvQ==", + "id": "eca07c7f-1a9c-46f5-a61f-a9696f8bdd7a", + "last_modified": 1525201472349 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MGwxCzAJBgNVBAYTAk5MMRkwFwYDVQQKExBLUE4gVGVsZWNvbSBCLlYuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMSEwHwYDVQQDExhLUE4gVGVsZWNvbSBCLlYuIENBIC0gRzI=", + "serialNumber": "W4sqXNfJgPC3aLKkcOxq9Q==", + "id": "eac5a645-3936-457a-8d93-f2d8acb71e1f", + "last_modified": 1525201471502 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MHwxCzAJBgNVBAYTAk5MMSIwIAYDVQQKExlLUE4gQ29ycG9yYXRlIE1hcmtldCBCLlYuMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVzdCBOZXR3b3JrMSgwJgYDVQQDEx9LUE4gQ29ycG9yYXRlIE1hcmtldCBDbGFzcyAyIENB", + "serialNumber": "F6sWArGVJv7AwBSxbnnqaw==", + "id": "4a21fbd9-4b39-4d8c-b2c9-82ace310f9c7", + "last_modified": 1525201470403 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "JY5zdgD/mG9A4oB/uzdSwQ==", + "id": "859ec3d1-70bd-417d-a3a1-bedc847fb4f3", + "last_modified": 1525201469016 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "VBy0L8eIKnVUGpY97OXrkw==", + "id": "e5ef5cbb-45ad-4ffd-a2db-beb350b195a3", + "last_modified": 1525201468162 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "c+6uFePfrahUGpXs8lhiTw==", + "id": "b93e70b2-98aa-44d3-a62a-4f3bb49f9966", + "last_modified": 1525201467126 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "PZfTkwQ5Yio+HE2mvtFzDg==", + "id": "4d559aad-94e9-4240-93d2-054943a9f31a", + "last_modified": 1525201466102 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHpMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxLjAsBgNVBAsTJVNlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8gRUNWLTExNjA0BgNVBAsTLVZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlckNJQy0xICAoYykwMzEhMB8GA1UECxMYR2VuZXJhbGl0YXQgZGUgQ2F0YWx1bnlhMRIwEAYDVQQDEwlFQy1HRU5DQVQ=", + "serialNumber": "b+8vFPRPzN8+HCEWmIwVNg==", + "id": "047fdabb-c9a2-4eb1-aaa7-469511a8f1db", + "last_modified": 1525201464668 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIIBGDELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMTQwMgYDVQQHEytQYXNzYXRnZSBkZSBsYSBDb25jZXBjaW8gMTEgMDgwMDggQmFyY2Vsb25hMS4wLAYDVQQLEyVTZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvIEVDVi0yMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJDSUMtMiAoYykwMzEfMB0GA1UECxMWVW5pdmVyc2l0YXRzIGkgUmVjZXJjYTEOMAwGA1UEAxMFRUMtVVI=", + "serialNumber": "Y3QACu2RGYVJ6FAnJWZpHA==", + "id": "703b71d5-0bf4-4af5-885b-d5c2165f4062", + "last_modified": 1525201463440 + }, + { + "schema": 1552493000155, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "BqVfPLKBlSg/4Enn+TGdbA==", + "id": "f4e4fccb-6400-4fca-8557-849ba24afac1", + "last_modified": 1525201462192 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "cEBA0P3KPBk/ojwnYepwzg==", + "id": "4c13fe3b-275e-4459-bb38-dc54bf6ba01d", + "last_modified": 1525201460880 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsMH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsMLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLDCxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAwwGRUMtQUND", + "serialNumber": "P0qUU7RhznNP6V9iGYbSbA==", + "id": "4f6b910c-64a4-4919-8ff5-c9fdd1f7d740", + "last_modified": 1525201459447 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsMH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsMLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLDCxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAwwGRUMtQUND", + "serialNumber": "W99Z2UuV5pFP6V8AYIwcVQ==", + "id": "66226a62-8d11-4442-8cd4-65279cdc08fa", + "last_modified": 1525201458032 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsMH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsMLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLDCxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAwwGRUMtQUND", + "serialNumber": "dfE2CNAy9IxP6VwZ2IU2cA==", + "id": "e4012675-11d8-4514-ba99-7a519d9f64c8", + "last_modified": 1525201456987 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsMH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsMLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLDCxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAwwGRUMtQUND", + "serialNumber": "IqW4gO46S81PjTpHBA7mUQ==", + "id": "54088505-0388-44e8-89f3-54dbeee3d2f5", + "last_modified": 1525201455452 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHpMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxLjAsBgNVBAsMJVNlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8gRUNWLTExNjA0BgNVBAsMLVZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlckNJQy0xICAoYykwMzEhMB8GA1UECwwYR2VuZXJhbGl0YXQgZGUgQ2F0YWx1bnlhMRIwEAYDVQQDDAlFQy1HRU5DQVQ=", + "serialNumber": "On0bAstcoxZP6WERe150Gw==", + "id": "d2241dbd-e9df-420f-ba92-0c9c9d8d89f6", + "last_modified": 1525201454025 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIIBGDELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMTQwMgYDVQQHEytQYXNzYXRnZSBkZSBsYSBDb25jZXBjaW8gMTEgMDgwMDggQmFyY2Vsb25hMS4wLAYDVQQLEyVTZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvIEVDVi0yMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJDSUMtMiAoYykwMzEfMB0GA1UECxMWVW5pdmVyc2l0YXRzIGkgUmVjZXJjYTEOMAwGA1UEAxMFRUMtVVI=", + "serialNumber": "JSPC8hAKsUBP6Y3n9JMx8w==", + "id": "a6240c29-985b-41aa-bfef-a4b5b10c43c4", + "last_modified": 1525201452790 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UECgwyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsMH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsMLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLDCxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAwwGRUMtQUND", + "serialNumber": "L5tOVjVGKtFP6V84tGEFPg==", + "id": "7780f12e-5f2c-4382-aa26-25e0b06e2a23", + "last_modified": 1525201451667 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MIHzMQswCQYDVQQGEwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND", + "serialNumber": "IL094GkEPSU+HAucglL0Ig==", + "id": "b9db725d-2c89-42ad-8b0b-807311d0a5cb", + "last_modified": 1525201450130 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "ME4xCzAJBgNVBAYTAk5PMR0wGwYDVQQKDBRCdXlwYXNzIEFTLTk4MzE2MzMyNzEgMB4GA1UEAwwXQnV5cGFzcyBDbGFzcyAyIFJvb3QgQ0E=", + "serialNumber": "KA==", + "id": "3d04d4fa-8dea-49f3-a47c-71e1443a9136", + "last_modified": 1525201448898 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "UzAV4JqeuGY=", + "id": "8e357bb0-7738-4fb6-be98-1c3b0f34b883", + "last_modified": 1525201447362 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "bYuYcMtVvjo=", + "id": "3546a52d-6e2a-4f79-bfda-3c7514eaf39e", + "last_modified": 1525201446066 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1458321", + "who": "", + "why": "", + "name": "", + "created": "2018-05-01T20:04:01Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "WV2iHxGL6Vg=", + "id": "7aee1756-65b5-44d6-b368-bf7345dfb4d9", + "last_modified": 1525201444699 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "R4af5Q==", + "id": "6d5c851b-0161-406e-8d63-8d3db27ca4f8", + "last_modified": 1521539068414 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "R4b2Vg==", + "id": "bfd98186-8469-4f90-9caa-ef94ee7e5793", + "last_modified": 1521539067480 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Hl7MT7aU4GbuanaMzc5eAg==", + "id": "b5d0f6f8-dc6c-42e6-86e3-db86c0f0ef3e", + "last_modified": 1521539066546 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "TrUuR7x7VeU7Qvlwt8Sumw==", + "id": "cd97a51f-ecd0-4cd0-afac-022a5deae387", + "last_modified": 1521539065613 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ajUDfjuO76YmIt3+fyTLXg==", + "id": "6562ca7d-c4df-4c31-93b9-4a4ed561f830", + "last_modified": 1521539064682 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "G0UY3ZCa+JfTgAVgvFA8qg==", + "id": "f31c2c71-e3dc-4f33-a12b-fad7aa9e1b66", + "last_modified": 1521539063753 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "dtULH8kD2mThpR/g1YJEtw==", + "id": "690c5b1d-ff08-4827-b7fe-0b43f8363202", + "last_modified": 1521539062819 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "c5EZjjNc7LMOapbOzjEtJA==", + "id": "bcd2807e-15df-4eba-be88-2677fd17496e", + "last_modified": 1521539061889 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "dn2My7LvPi25AtUw3aPEmQ==", + "id": "1841b57b-5085-4ffd-9b5a-192ed098c6af", + "last_modified": 1521539060941 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Qk03giZwJwxf5QpixTKflQ==", + "id": "01c7e0fd-4973-44d4-829a-896db0c7e1dd", + "last_modified": 1521539060008 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "cepjeevcJiJnbGEvdJE1jg==", + "id": "e27659bd-3699-4eea-afb0-a435dfac9bd1", + "last_modified": 1521539059082 + }, + { + "schema": 1552493001571, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "BtDaTXIs6tBSClhSLPXdYg==", + "id": "33f91455-1f4c-4fd7-aeea-c7f1df9309fd", + "last_modified": 1521539058135 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ej2u88yEVXEb8BP1K49U6Q==", + "id": "af65bc77-69b0-4afb-9451-b8ae0bcc7b62", + "last_modified": 1521539057196 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "H/Vx9uatDIulnLLrZjXEKg==", + "id": "d1b51a6b-bcb0-44d0-830c-394a409c3fa0", + "last_modified": 1521539056260 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "PxYWUib8jdriX5MSGW7Ozw==", + "id": "b8304b34-1fcb-46bd-ac88-796748e0278f", + "last_modified": 1521539055332 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "T3UrJ2tKvT0lyumu37ic6g==", + "id": "629d62ec-34c0-466d-b9ec-4f1991f45ac4", + "last_modified": 1521539054393 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "And3HzRA33dI3K772oqBCw==", + "id": "e7cea443-749b-48c0-90e4-72139c44b9a7", + "last_modified": 1521539053460 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "ZpLTr9toPH+XRF7OITitfw==", + "id": "8e977340-0255-432f-a74f-b45f1d892f59", + "last_modified": 1521539052528 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "TpyAwu1JmlIKD9gyf+0d4w==", + "id": "86c1352b-e5e8-4e7e-9a88-ea8916d08ded", + "last_modified": 1521539051596 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "A09gcxt2IBLNzwqUBAhkDg==", + "id": "bd99f32d-02fb-4a46-9265-8bdcb343b8f8", + "last_modified": 1521539047661 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "dgkExobSurPQq8GYrxxluA==", + "id": "190e2d0d-35d1-47b4-8b07-73df5c649ba4", + "last_modified": 1521539046709 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "UJ3tpbZLsyrhh60M9CMQaQ==", + "id": "b4c69adc-2247-4aed-8cb9-e0fd1bd4e457", + "last_modified": 1521539045779 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "MpG5djdbcIoI5TIkJ7vENA==", + "id": "c2b676e2-f636-4489-ad39-ceadad4d122c", + "last_modified": 1521539044827 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "BlOMLY2hk1OPGflbt/pPXQ==", + "id": "06096001-fd68-4d0e-be8d-832c24695c75", + "last_modified": 1521539043891 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IEVDQyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "APy7Z8kyJRVBcM/oki5xZ2M=", + "id": "71a73415-ba8f-4281-86b4-76311c2f0410", + "last_modified": 1521539042956 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "ANjIV8rkvmb5E3Wf3aPV2ys=", + "id": "ca2547dd-66ec-4991-ab40-362581ebdc69", + "last_modified": 1521539042013 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AKbcLtOIMMwDPSOrzrclZL8=", + "id": "f463f5b3-b729-4da8-afc0-c54e54d857bc", + "last_modified": 1521539041071 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "BQ==", + "id": "821bc34c-45ce-444a-b7c0-0a581a788a52", + "last_modified": 1521539040133 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1447252", + "who": "", + "why": "", + "name": "", + "created": "2018-03-20T09:43:57Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "Ag==", + "id": "490cb9c4-e092-4200-9e4b-be790d4db9b6", + "last_modified": 1521539039175 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1436524", + "who": "wthayer@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-02-13T20:30:40Z" + }, + "enabled": true, + "issuerName": "MGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMSAwHgYDVQQDExd0aGF3dGUgRFYgU1NMIFNIQTI1NiBDQQ==", + "serialNumber": "dqN9ZZM/PfFCXStajJdbtQ==", + "id": "1dea3a27-cf12-440c-b77d-08608dcb4fae", + "last_modified": 1518553957211 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "IsWDgJMv7uY=", + "id": "6252eb0e-d613-4290-a7ab-1b6698146077", + "last_modified": 1518185155027 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "WDFms0iypljMkohTzBvf4GmTOu0=", + "id": "938ea097-b56a-4286-9370-a1dabb46c908", + "last_modified": 1518185154165 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzM=", + "serialNumber": "PybG62jqpxKYOV5MlXAGPJYDy9M=", + "id": "495b31a1-84f2-4a2e-b4b0-f24303c0b114", + "last_modified": 1518185153363 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/SsQ==", + "id": "5373d63b-fdb3-4cf2-82c4-6d5dde47a536", + "last_modified": 1518185152542 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ATEzEQ==", + "id": "e0589829-384d-4cd8-952f-3b4d5b3116f0", + "last_modified": 1518185151698 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMg==", + "serialNumber": "ATE33w==", + "id": "b4208886-7ca1-4d3f-a6b7-271f93aaf6cd", + "last_modified": 1518185150862 + }, + { + "schema": 1552493002970, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byecpw==", + "id": "0a419007-294d-4c7e-8ae4-e26c83633f5a", + "last_modified": 1518185150011 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MHoxCzAJBgNVBAYTAkFVMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazErMCkGA1UEAxMiU3ltYW50ZWMgQXVzdHJhbGlhIENsYXNzIDIgQ0EgLSBHMg==", + "serialNumber": "MZNwCx0BAjzTOgcvHsmdhQ==", + "id": "f7ee0207-7809-4310-a334-906a783c94f9", + "last_modified": 1518185149162 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=", + "serialNumber": "PWwhjEHh0n5G6P8b+bAkcg==", + "id": "8ed4a6a5-4222-44bd-b52a-2fc5b2093fd4", + "last_modified": 1518185148285 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bye/xA==", + "id": "49f10bd9-4827-42f8-b6bb-1136cdb8b1f1", + "last_modified": 1518185147458 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkFVMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEmMCQGA1UEAxMdU3ltYW50ZWMgQXVzdHJhbGlhIENsYXNzIDIgQ0E=", + "serialNumber": "Nn6RHaVImMEtHLbPqlyGEA==", + "id": "49216c75-cdfa-423a-84cb-a4285e9efc65", + "last_modified": 1518185146632 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "X22XOlwfc1Taw/ORwGOIeg==", + "id": "21e8b448-bdc3-4549-8436-7b5218662f7b", + "last_modified": 1518185145800 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1437038", + "who": "", + "why": "", + "name": "", + "created": "2018-02-09T14:05:40Z" + }, + "enabled": true, + "issuerName": "MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=", + "serialNumber": "PgImeGqCkapG6P426Ne85w==", + "id": "9c7330f0-ac62-44f6-9ecc-1f6c8090e22c", + "last_modified": 1518185144953 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1433118", + "who": "jc@mozilla.com", + "why": "key compromise", + "name": "", + "created": "2018-01-30T17:48:22Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSkwJwYDVQQDEyBDZXJ0dW0gRG9tYWluIFZhbGlkYXRpb24gQ0EgU0hBMg==", + "serialNumber": "VEav0UR+l38TpKTRi7sS1g==", + "id": "cb712a6d-3b3d-4642-b0f0-5f765f615559", + "last_modified": 1517334651604 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1432467", + "who": "", + "why": "", + "name": "", + "created": "2018-01-23T13:41:18Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bye9zg==", + "id": "fead3048-84a5-4256-9850-cf412c8cce88", + "last_modified": 1516714883099 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1432467", + "who": "", + "why": "", + "name": "", + "created": "2018-01-23T13:41:18Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeWyA==", + "id": "5d178546-44d0-4756-8bc1-aa84c958ec6d", + "last_modified": 1516714882194 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1432467", + "who": "", + "why": "", + "name": "", + "created": "2018-01-23T13:41:18Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bye9zw==", + "id": "ce6dc2f0-5709-49d6-a762-87e297e2c4f3", + "last_modified": 1516714881354 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1427034", + "who": "mgoodwin@mozilla.com", + "why": "", + "name": "key compromise", + "created": "2018-01-17T13:03:45.551668672Z" + }, + "enabled": true, + "issuerName": "MHAxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xLzAtBgNVBAMTJkRpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgU2VydmVyIENB", + "serialNumber": "Cn+uUpLudsH09lYYIPTK5A==", + "id": "37450b9d-89e2-4144-af21-c01dfb50d5e8", + "last_modified": 1516714880459 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1430498", + "who": "jc@mozilla.com", + "why": "", + "name": "registry problem", + "created": "2018-01-17T00:30:37.551668672Z" + }, + "enabled": true, + "issuerName": "MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "AJk3QFH13eHUHHVnsvwS0Vo=", + "id": "c3485d18-5309-46f4-a324-b72aae76d2fa", + "last_modified": 1516150019120 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1430498", + "who": "jc@mozilla.com", + "why": "", + "name": "registry problem - remove after Feb 2018", + "created": "2018-01-16T14:26:03.648712626Z" + }, + "enabled": true, + "issuerName": "MHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJUWDEQMA4GA1UEBxMHSG91c3RvbjEVMBMGA1UEChMMY1BhbmVsLCBJbmMuMS0wKwYDVQQDEyRjUGFuZWwsIEluYy4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "NlLRZJFLco/An3cLAGjGgQ==", + "id": "ee550d4f-2e5e-4a92-84a0-97595775682d", + "last_modified": 1516119227966 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1430172", + "who": "jc@mozilla.com", + "why": "", + "name": "key compromise", + "created": "2018-01-12T16:24:38.732932137Z" + }, + "enabled": true, + "issuerName": "MIGSMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE4MDYGA1UEAxMvQ09NT0RPIFJTQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0E=", + "serialNumber": "TasC8Zd8BT8kXEE67cFQmA==", + "id": "fd5a2c90-20dc-49fa-8d98-4ce4f75a9e67", + "last_modified": 1515779270416 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1425376", + "who": "jc@mozilla.com", + "why": "", + "name": "key compromise", + "created": "2018-01-12T16:10:12.532698105Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "UDE/uwr4z5V8eZI4+1gkAw==", + "id": "c848badb-926d-4a4b-84ca-776d8e83afbd", + "last_modified": 1515773559723 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1429636", + "who": "jc@mozilla.com", + "why": "", + "name": "key compromise", + "created": "2018-01-12T16:07:30.989396006Z" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEUMBIGA1UEBxMLU2FudGEgQ2xhcmExGjAYBgNVBAoTEUludGVsIENvcnBvcmF0aW9uMSUwIwYDVQQDExxJbnRlbCBFeHRlcm5hbCBJc3N1aW5nIENBIDZC", + "serialNumber": "HwAABsvzDP+DIzUG6QAAAAAGyw==", + "id": "754959ef-52da-40c2-9f01-6d58cee4bb6e", + "last_modified": 1515773559394 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1429055", + "who": "", + "why": "", + "name": "", + "created": "2018-01-09T13:56:39Z" + }, + "enabled": true, + "issuerName": "MFQxCzAJBgNVBAYTAkJNMRkwFwYDVQQKDBBRdW9WYWRpcyBMaW1pdGVkMSowKAYDVQQDDCFRdW9WYWRpcyBFbnRlcnByaXNlIFRydXN0IENBIDIgRzM=", + "serialNumber": "bqapwACCtKhVagTl7cEP7KFbM0E=", + "id": "557dce95-1f9d-4292-b0e3-1e6190ceb6b2", + "last_modified": 1515506204155 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1429055", + "who": "", + "why": "", + "name": "", + "created": "2018-01-09T13:56:39Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "serialNumber": "BFA=", + "id": "8b5b2498-3bda-404a-b778-8231f31548ed", + "last_modified": 1515506203323 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1429055", + "who": "", + "why": "", + "name": "", + "created": "2018-01-09T13:56:39Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfNbw==", + "id": "54bc1bfd-3c57-4698-9702-bf18862330e5", + "last_modified": 1515506202308 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1429055", + "who": "", + "why": "", + "name": "", + "created": "2018-01-09T13:56:39Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290", + "serialNumber": "Eg==", + "id": "bc46d429-2528-4068-b205-b0f853e3865b", + "last_modified": 1515506201306 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1425166", + "who": "jc@mozilla.com", + "why": ".", + "name": "key compromise", + "created": "2017-12-14T17:17:08.723413Z" + }, + "enabled": true, + "issuerName": "MIG0MQswCQYDVQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5jb20sIEluYy4xLTArBgNVBAsTJGh0dHA6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzEzMDEGA1UEAxMqR28gRGFkZHkgU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcy", + "serialNumber": "AOfHzdPzlvw5", + "id": "47a94296-eb80-4f80-a327-d6c0dcf96e83", + "last_modified": 1513273512012 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423400", + "who": "mgoodwin", + "why": "Added per CA request", + "name": "Microsoft IT SSL SHA2 revocation 2", + "created": "2017-12-06T16:51:03Z" + }, + "enabled": true, + "issuerName": "MIGLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRUwEwYDVQQLEwxNaWNyb3NvZnQgSVQxHjAcBgNVBAMTFU1pY3Jvc29mdCBJVCBTU0wgU0hBMg==", + "serialNumber": "WgAFElbyxxPA8BdM4gABAAUSVg==", + "id": "41d101d3-2258-4c62-94b4-288e140a4205", + "last_modified": 1512596195433 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423400", + "who": "mgoodwin", + "why": "Added per CA request", + "name": "Microsoft IT SSL SHA2 revocation 1", + "created": "2017-12-06T16:51:03Z" + }, + "enabled": true, + "issuerName": "MIGLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMRUwEwYDVQQLEwxNaWNyb3NvZnQgSVQxHjAcBgNVBAMTFU1pY3Jvc29mdCBJVCBTU0wgU0hBMg==", + "serialNumber": "WgAFElcDxFjoswSzjAABAAUSVw==", + "id": "d6e37caf-2691-4a82-8682-72b454419d8c", + "last_modified": 1512596194454 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==", + "serialNumber": "em/HTY01Cvv6ITgkH+ftlg==", + "id": "7b48e485-3f00-4838-98d7-78c731f014e7", + "last_modified": 1512488574225 + }, + { + "schema": 1552493004389, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "a2GKnRbYMZ0oZkRzJE8NIw==", + "id": "0efe9970-3656-4aa9-8fd2-cfe290ca2c7e", + "last_modified": 1512488573423 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "QspbHxzWb41SX9TUhF1N1A==", + "id": "bb2d8dfc-6dd0-47c3-a46c-c0f8235418a4", + "last_modified": 1512488572398 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "PbXdgANzAyCOCZ5qa/7E6A==", + "id": "f3f7d94c-c3d6-48f1-b807-43c97784dcdc", + "last_modified": 1512488571476 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "EgtJ1f+/tZwlGfg0Uu7XCQ==", + "id": "331b24c7-b0dd-4289-9ef7-a4f92eb4f449", + "last_modified": 1512488570451 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "TbPyD9NnsEcxyK6LIsr78g==", + "id": "eb7bda20-89d0-4ed5-a731-0896127de01e", + "last_modified": 1512488569530 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "JjjcXrfGjTCi1ug/AEeYlg==", + "id": "0f7d3bf7-ddeb-4c1b-ba24-7ae9157bba15", + "last_modified": 1512488568584 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "HGD2RtvXMaPDqHIPLdXocw==", + "id": "b26128cc-3096-438e-858f-7c2cbaf3c903", + "last_modified": 1512488567585 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "fa9agGguMHfBorMTXXMd9g==", + "id": "2ace54b8-3856-4aa9-bd26-45f573e737ce", + "last_modified": 1512488566638 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "WfPUsnnSF04ShWVYEa/KRA==", + "id": "5bc1d739-7788-4f13-b92c-5b6a8662319b", + "last_modified": 1512488565741 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "LzVYePklc3vH3jkk0BZr9g==", + "id": "bc7eea97-3ee7-49a0-80a7-c0f1a346a1c4", + "last_modified": 1512488564721 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "BWuckD4dPHZYW5ThBsl+aQ==", + "id": "2a474462-256d-4583-9103-297077b85bad", + "last_modified": 1512488563751 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "LJ8wKbrQXgT8VExZ6vEfWA==", + "id": "4a1c1053-a8a9-42ff-bb3e-3ac5c536d5c3", + "last_modified": 1512488562853 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "GskXrIFkzLS+4yohQM9EUA==", + "id": "a3b8a885-9d36-483c-8b4c-c80e8795bb92", + "last_modified": 1512488561945 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "X3iUdzxCEtOAKpiTLsqjBA==", + "id": "7fc162c0-b668-4336-ad9a-cf6ac4bf7f88", + "last_modified": 1512488560934 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "UKM/CNF2OvC4giYnAUG/Ag==", + "id": "7f8575f4-dcd7-4e51-9540-674ea52a0afc", + "last_modified": 1512488559917 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "fZ10MyCe51jAjZCsDgqaxA==", + "id": "a943a2dc-e783-4560-90ec-5b2ce4936f60", + "last_modified": 1512488558952 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "Gz4uHrL2usrTZrPCHeuF5g==", + "id": "cf84a41a-afeb-4727-b51e-f1489075f5a0", + "last_modified": 1512488557959 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "bf8hEJywo1lAp4UNcLl5Ew==", + "id": "26c0ae81-c15c-4f37-930c-f3c39fca709a", + "last_modified": 1512488557038 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "P4sUnc++hlU/bXj0zSTlcQ==", + "id": "1a68fa22-b801-4f56-81b2-a6575dd928ac", + "last_modified": 1512488556115 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "KUZMXOUj2sdY2i2Rfgp/5Q==", + "id": "4c1f25cc-e17b-4ce4-a0c8-ab871a370238", + "last_modified": 1512488555092 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "a9HDb1beqQYmkvFH0qExcg==", + "id": "b876cede-af38-44ff-952b-cf1652e0eff4", + "last_modified": 1512488554136 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "C2tQZWb2eoQD2XC3F5JSzg==", + "id": "67c163b1-7be2-434d-9a17-24ab282e0906", + "last_modified": 1512488553221 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "QjiuX0y1agXQQqmDB2yh3w==", + "id": "966bf4c9-229d-40ea-8783-cdd20a90e7d0", + "last_modified": 1512488552326 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "YwslVqGwc9CHkaZkXNZ4xw==", + "id": "e35a9cd7-72a9-44b6-bb42-c43054e279d3", + "last_modified": 1512488551279 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "KRfQOuBdOSpEmAxSpDZGZg==", + "id": "3901e046-eee7-462e-9207-62a2074d5555", + "last_modified": 1512488550258 + }, + { + "schema": 1552493005810, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "IARKrBjlKQLyVGA4X52L7w==", + "id": "59872bbc-66b1-4627-890b-b8e9aee46d08", + "last_modified": 1512488549244 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "TsaDDThhoyhX10SURO3NMg==", + "id": "477a1238-b526-4dbf-9e7d-8807315f8e5a", + "last_modified": 1512488548206 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "dSBsq/te0hzZauKHgJ3EWg==", + "id": "320c2435-4a3d-4a9c-898e-0be3e7314601", + "last_modified": 1512488547208 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==", + "serialNumber": "JLLEdDl2iHqqyenVWwQ/XA==", + "id": "d1d33ccd-4ea7-4b1f-9969-b77036da72c0", + "last_modified": 1512488546183 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "D4dSwi4udjGtMftKLTSFyg==", + "id": "36e0661b-bbce-412d-b181-f51599b5133f", + "last_modified": 1512488545212 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9Ltmw=", + "id": "7ab02aa8-9300-4e05-bd35-1a8a0dc66114", + "last_modified": 1512488544217 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9Ltm8=", + "id": "3128a9a7-5929-495d-b6e9-d1322d8969cc", + "last_modified": 1512488543316 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "N4XreFRrqFQ=", + "id": "0cf09345-c803-4248-8e8f-4fd437efc225", + "last_modified": 1512488542191 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==", + "serialNumber": "Ah69dEvrzT4=", + "id": "eabda172-8673-4d9d-b955-ae33491273d0", + "last_modified": 1512488541251 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4", + "serialNumber": "RXJFI0h6EJY=", + "id": "0ac03d6a-f013-45f1-a611-8d408d81cda2", + "last_modified": 1512488540265 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290", + "serialNumber": "DA==", + "id": "8374c965-72aa-4411-96a9-d0ed94b6eff5", + "last_modified": 1512488539210 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1423239", + "who": "", + "why": "", + "name": "", + "created": "2017-12-05T15:42:15Z" + }, + "enabled": true, + "issuerName": "MIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwOA==", + "serialNumber": "AklaZYwhC9k=", + "id": "0ef6e97e-f104-472f-a639-f5095bee2cec", + "last_modified": 1512488538203 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MIGcMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0x", + "serialNumber": "APB/jQRgyP8Q", + "id": "89031d16-d554-432c-b874-29611e57ddc7", + "last_modified": 1511530774264 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=", + "serialNumber": "AOVojQRgyPcY", + "id": "365afa37-c1b7-49a1-b426-3ad6f41062ce", + "last_modified": 1511530773342 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTI=", + "serialNumber": "APB/jQRgyPca", + "id": "469dc3f7-856e-46e9-bcd4-4690669a1164", + "last_modified": 1511530772420 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTE=", + "serialNumber": "AOVojQRgyPca", + "id": "350ab323-afb6-4b13-ad9d-4fd751b43b6d", + "last_modified": 1511530771603 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "OeKv0wi+ATDxfQ6CWir1vA==", + "id": "f9cdfe8d-8646-4f51-a52e-2f8e207dd9af", + "last_modified": 1511530770270 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "VNb2Hjai/t7dmCtOzRXXew==", + "id": "58ff4957-a4c8-490b-8ed2-28ec1730c0e5", + "last_modified": 1511530769350 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "CuUEKEJM4xhxlFXraPcSpQ==", + "id": "7601c888-8cc4-4c4a-a1f8-c7035f722d59", + "last_modified": 1511530768331 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "FK+rVRFA0o0PnW+X6V60gQ==", + "id": "94b0d239-ef85-46fd-8af6-4213e19bbe5a", + "last_modified": 1511530767405 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "UT6GtTGbEC6SXJteWAKy2g==", + "id": "f232a4f0-ab91-4cde-acd6-28745c3c2bd0", + "last_modified": 1511530766483 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "c0ENPRDbRozjU83garZrdA==", + "id": "5c07893e-ed64-43c5-aabb-fd7d059100f6", + "last_modified": 1511530765531 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "ODTGURr0vY14WkIt15hHrg==", + "id": "26b8bba0-1be3-4f66-98d3-936a13c810b8", + "last_modified": 1511530764744 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "RkNUwM80Jt7beb4ek+iI8w==", + "id": "e28af90b-6ca1-4335-876b-4afedaea6cf8", + "last_modified": 1511530763884 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "dUIqmrcgq/261bRbo7fM1g==", + "id": "6d8170da-8177-4e04-988b-e24f36c39001", + "last_modified": 1511530762701 + }, + { + "schema": 1552493007210, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "XbPH0u4MjoIrWzN8QCilfg==", + "id": "a44b3f08-cbb8-4fbe-98c0-e7710dd3e81e", + "last_modified": 1511530761466 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "KvQ5AzK6tQy8eBy7NAD/lQ==", + "id": "882fa317-43a7-4515-b79b-437119b72cf3", + "last_modified": 1511530760452 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "UfM8pWkcmmLGRiGIVydmoA==", + "id": "069856d2-6bd0-4b39-a8f5-2debd4535542", + "last_modified": 1511530759416 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "X4C5SJIG0BDeJvpQq4ngCw==", + "id": "9abf8a9e-66d2-4193-9dd8-3d86f482d44f", + "last_modified": 1511530758551 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "CGo/+42e75JBJ2JcOEaMFw==", + "id": "879c2baf-c634-45eb-b80b-c1fb0ceb31d9", + "last_modified": 1511530757370 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "Q1r0dRkkG9miuHj/Y52izw==", + "id": "9e9ecf02-4c1c-4c32-b10a-c7272c416b5f", + "last_modified": 1511530756553 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "WU+jmMhGAumhewqVKrZBmg==", + "id": "27bf3c0e-8868-4274-b37c-b3a4bde99ba1", + "last_modified": 1511530755428 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "d8ToN4Dfs5RqD2yfAp12yQ==", + "id": "972f597f-5333-4ec4-8801-751bb5546abe", + "last_modified": 1511530754297 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "ElBUYv/f+6+gnbAJ23qnAA==", + "id": "6ad92631-7061-4717-93ed-b5f56d8452b1", + "last_modified": 1511530753374 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "DoP7aSdEs/3y+o2Gj9zgWA==", + "id": "16737b7d-6e89-43d1-8a4a-0f1a931371f8", + "last_modified": 1511530752482 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "XOZMbPKQuJEw8Ib5neDVpQ==", + "id": "6d117cdb-5081-48b0-8408-4ca74edb943e", + "last_modified": 1511530751226 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "UN78HLEKf7W9vQYkzYpJnw==", + "id": "e52276ca-6055-4069-8277-e046a6c8dca1", + "last_modified": 1511530750302 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "KNhgX8XuJduYciIyatpOQg==", + "id": "000e9e4e-75dd-46f1-8acc-acdb9844a1ba", + "last_modified": 1511530749075 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "OIJdAvYxHmLb6YaaMmwmjg==", + "id": "b2085d97-60ab-449c-ad46-8ca2818da916", + "last_modified": 1511530747846 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "SrQ125q7UcLfxVKepx+lRg==", + "id": "5b2d0011-e159-4fe2-8d40-72fd5c210474", + "last_modified": 1511530746840 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "EYfoVrySx7V3OUqs4xKvgA==", + "id": "39bf9bb4-2301-4d71-8c46-7a88f5b0cbae", + "last_modified": 1511530745816 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBFQ0MgUm9vdCAtIEcx", + "serialNumber": "EkoaKijVTGVYI5c604iweg==", + "id": "eb900242-3154-40c3-8f90-391c5c15a6d5", + "last_modified": 1511530744778 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEnMCUGA1UEAxMeU3ltYW50ZWMgV2ViIFBLSSBSU0EgUm9vdCAtIEcx", + "serialNumber": "VIFPnH3Io2OmF0J5KK8gzA==", + "id": "0aa3a176-7080-48d4-b8f2-8a3c60a6de88", + "last_modified": 1511530743731 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "ANsAyDuSSs7Z83LfMZ+TDw==", + "id": "f20c25eb-a06f-43ac-a202-710565679819", + "last_modified": 1511530742622 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AIQ8dLGqNIaxxMeg31W16Q==", + "id": "aec665a0-9d68-4342-9743-18efc713e0f8", + "last_modified": 1511530741394 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AIQ8dLGqNIaxxMeg31W16Q==", + "id": "8a10108d-b91c-49da-9748-72421d965126", + "last_modified": 1511530740428 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MIGwMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjE5MDcGA1UECxMwd3d3LmVudHJ1c3QubmV0L0NQUyBpcyBpbmNvcnBvcmF0ZWQgYnkgcmVmZXJlbmNlMR8wHQYDVQQLExYoYykgMjAwNiBFbnRydXN0LCBJbmMuMS0wKwYDVQQDEyRFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "cFbYT3bxd1sAAAAAUdNX8A==", + "id": "892f7600-cbf9-41d7-8854-91e39cca6d64", + "last_modified": 1511530739059 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1420411", + "who": "", + "why": "", + "name": "", + "created": "2017-11-24T13:38:55Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "DNHqTQd9QC+JnMy6AWyhkg==", + "id": "8f08f0c9-78a0-46cf-ac37-4d272b696046", + "last_modified": 1511530738030 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-02T22:09:46Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AxPlMqxkByCn3XNuYMhYNMcp", + "id": "eab2fa01-c3d4-4fd1-b3b8-9d88ed9b6657", + "last_modified": 1509980661621 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723413Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BPVqx4UbKVAbJSFTKwrcFryU", + "id": "f644b647-a845-40ef-9f6c-36744f304d08", + "last_modified": 1509744817871 + }, + { + "schema": 1552493008615, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723410Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BOncXh7IZp1SNydhtUdyh2O2", + "id": "58e3d1dd-4602-4b04-9f99-3f6efe1b0653", + "last_modified": 1509744808464 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723407Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BOc11keA9WJ9R20XQY8hO7yi", + "id": "749a02fe-2f0d-4eeb-84ff-f2730ec10bdf", + "last_modified": 1509744807487 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723405Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BOPwjyn5eqfeoxs7Z0y3vqNN", + "id": "9bcd94b9-c798-43da-9520-f15ffd3418e8", + "last_modified": 1509744806955 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723402Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BONHqLIx/ibQE08IQIyoGaXg", + "id": "1b719105-db02-4597-8d5e-4e60a72167dc", + "last_modified": 1509744802604 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723398Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BOIIipysxAz5xHIMmFRvYchY", + "id": "6f43ef92-37ea-407d-b113-0d1a2f30af6d", + "last_modified": 1509744793918 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723395Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BLlQHJ611eOZuedFrFgVAfAs", + "id": "af6370dc-0ee2-4f46-864b-4aba6dad16aa", + "last_modified": 1509744793363 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723392Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BKrxi2/1iFxHEFzyZvegxq5C", + "id": "8636eb93-74a1-4a6a-9fbe-c027fce6e1f3", + "last_modified": 1509744792878 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723390Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BKobzjrOxa/6kCR0ImKoqaQW", + "id": "87cdeef4-a32f-4f89-90b2-d979766f66fd", + "last_modified": 1509744792287 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723387Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BJDHnthjoDRutxFRJPFnixbU", + "id": "fd062b35-a923-4f40-adb6-b438b21dcebb", + "last_modified": 1509744791739 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723385Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BHT6CK6B569m/dd5dEluBOEd", + "id": "dbd760d1-545a-4c22-9634-8266b173fea7", + "last_modified": 1509744791182 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723382Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "BDV89QWZE9MJYlCpFQUv5Y2W", + "id": "5933c583-8b53-41b9-a23d-3fa849a39f22", + "last_modified": 1509744790658 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723376Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A/99bZCzSpexYL5y6dSryDn3", + "id": "62a22818-bb50-4f99-ae28-194943003e28", + "last_modified": 1509744790097 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723373Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A/7DHCczBnP5qUVh0jF2pvwB", + "id": "930fdbad-652c-445a-be30-1721420d5308", + "last_modified": 1509744789472 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723371Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A+ly3y1rVP59k/MKfcE3DoEq", + "id": "2be75058-2815-46b4-97de-5f800fac23fb", + "last_modified": 1509744776797 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723368Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A+RCQYwhofmXM+/hxdyoUzkI", + "id": "ece94a68-9224-462d-aa7d-174e461c3eac", + "last_modified": 1509744776283 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723366Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A9BRwOwbXRRhCe+kcmglgW3z", + "id": "b17c537a-31c2-4547-90a7-5588e967e0d2", + "last_modified": 1509744775761 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723363Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A8wZnhfuY6VIV1SwGsTGNR7L", + "id": "06dd2b87-9f23-4ecf-a6d7-1c4e248c377d", + "last_modified": 1509744775215 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723361Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A8aDg1/IA4O8gjMPZHVqPI+w", + "id": "e90d77e0-1e41-4a45-9c4b-a0b615b0091d", + "last_modified": 1509744774659 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723358Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A8LV4zckxcwdttbQSk0EPnoA", + "id": "421f4d83-9377-403a-b28b-c7249634e7fc", + "last_modified": 1509744774150 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723356Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A7uy+rmTav6tDH4dRrsnvXGH", + "id": "188d1c6c-c94c-4a1a-8028-cbc7c29069d4", + "last_modified": 1509744773593 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723353Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A7T0V6o47rgCKl3oUb7jF2Ph", + "id": "fee8894c-8624-41fc-ae9a-7180f52714f1", + "last_modified": 1509744773026 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723350Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A7RCxMe1S9Hb7ENzRxl0mxGP", + "id": "fca062d6-5292-4ca1-87ce-a6aa34c6a1ae", + "last_modified": 1509744772514 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723348Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A7GX+szdK8/7Kf0xUuarfyIN", + "id": "fcaebe3c-564b-46c0-a852-a6e51d8a56cf", + "last_modified": 1509744771973 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723345Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A5oET6WBWx72ColKf0txoWyR", + "id": "b6cd6dd6-4173-4584-ad06-d3a6400f4c4c", + "last_modified": 1509744771414 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723342Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A3ZQibPGSZ8nPVbuccaCvUfa", + "id": "c67ca525-ccdb-4ef9-8029-422656cbdbc8", + "last_modified": 1509744766044 + }, + { + "schema": 1552493010014, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723340Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A3WVy2V+2VFkWtMvA6HFwnhq", + "id": "69b9dc1c-9178-4090-82d2-4ecb650bf8a6", + "last_modified": 1509744762617 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723337Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A3UNTBOHUkbq+k999nJeSJdF", + "id": "12f76ac6-cb54-42d2-90e2-a06fbf6cc681", + "last_modified": 1509744758183 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723335Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A3TWA5Aylxw0x8bVvrmUSNJd", + "id": "6765cc1a-15e0-46e8-b74a-30a03f538885", + "last_modified": 1509744757639 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723332Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A1V4dX0tTb1rdTZxdWcuZ7YR", + "id": "fddf483c-b881-4a58-a24c-03b0f46ca701", + "last_modified": 1509744754400 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723329Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "A0BOaf9UbJxzqBudSyes/cEM", + "id": "21c2287c-c873-4a06-bd80-8c2498deee0c", + "last_modified": 1509744746034 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723327Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AzL4tLuklekJ8lSh6VnRMSrk", + "id": "fe19f480-7306-4eb4-a434-bb21e983102e", + "last_modified": 1509744744506 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723324Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AyjNQ4dnGD3FD6WL5gYrYru7", + "id": "22ef2da0-8e63-40fe-8644-667178538911", + "last_modified": 1509744743907 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723321Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AyYMguSo1my449OZq51C3s3Z", + "id": "bbb3fb02-ff86-44d0-859b-9b827c4943ad", + "last_modified": 1509744743316 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723318Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "Ax6Jm7ajV49tqHgf9nYnzRCI", + "id": "f2b5d0ef-312b-4de2-9cd6-e0fb9bf262df", + "last_modified": 1509744742729 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723315Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AxW0+uDsfyCSfhECdsGGpVD8", + "id": "0266840c-ce0c-47c1-8816-2c4f4b29b3f4", + "last_modified": 1509744742199 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1414039", + "who": "jc@mozilla.com", + "why": ".", + "name": "registry problems - remove in Feb 2018", + "created": "2017-11-03T20:43:04.723168Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQDExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMw==", + "serialNumber": "AwBGo0Zmp6KRryAguuMvXATI", + "id": "d5c21685-054a-4233-a633-0af3d769b0e7", + "last_modified": 1509744741583 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407559", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:26:59Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "ARU=", + "id": "b91f4d33-972a-42ab-859f-0d3c8b68efe4", + "last_modified": 1507714028770 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407559", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:26:59Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "RqFXxGPuA18=", + "id": "c75a0024-f08d-49e1-b8fc-1ca9bbb47965", + "last_modified": 1507714027119 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407559", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:26:59Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABQaHhPSY=", + "id": "ddb917af-e251-48bc-91eb-59076ddf8494", + "last_modified": 1507714025550 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407559", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:26:59Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABQaHhNLo=", + "id": "e606e8ce-32ef-4ffe-b9fd-7eced767b9b2", + "last_modified": 1507714024574 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407559", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:26:59Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABQaHhOT4=", + "id": "259ff0a7-151c-4b7e-b449-98922b6bbfe3", + "last_modified": 1507714023126 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407558", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:24:52Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTowOAYDVQQDEzFHbG9iYWxTaWduIFBlcnNvbmFsU2lnbiBQYXJ0bmVycyBDQSAtIFNIQTI1NiAtIEcy", + "serialNumber": "AeNmeF8oVpDp/4GPvA==", + "id": "82e8996b-8424-4e5d-849a-9cb6c134773b", + "last_modified": 1507713902396 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407558", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:24:52Z" + }, + "enabled": true, + "issuerName": "MIGRMQswCQYDVQQGEwJERTEQMA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaVjAxOjA4BgNVBAsMMUNvcHlyaWdodCAoQykgU2llbWVucyBBRyAyMDExIEFsbCBSaWdodHMgUmVzZXJ2ZWQxITAfBgNVBAMMGFNpZW1lbnMgSW50ZXJuZXQgQ0EgVjEuMA==", + "serialNumber": "WW8OCQ==", + "id": "a8ba8579-79d8-494e-b5bc-f9b8a6efae71", + "last_modified": 1507713900874 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407558", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:24:52Z" + }, + "enabled": true, + "issuerName": "MIGRMQswCQYDVQQGEwJERTEQMA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaVjAxOjA4BgNVBAsMMUNvcHlyaWdodCAoQykgU2llbWVucyBBRyAyMDExIEFsbCBSaWdodHMgUmVzZXJ2ZWQxITAfBgNVBAMMGFNpZW1lbnMgSW50ZXJuZXQgQ0EgVjEuMA==", + "serialNumber": "AaoZYg==", + "id": "316627fa-3b68-48e4-909f-9fc58fc69a62", + "last_modified": 1507713899233 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407558", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:24:52Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydCwg==", + "id": "7696b73a-52f8-4902-b6b5-130d22222f15", + "last_modified": 1507713897487 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1407558", + "who": "", + "why": "", + "name": "", + "created": "2017-10-11T10:24:52Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BEeJFwO0nu759EPo9tKluw==", + "id": "113485f8-45db-488e-b45a-7917fcf4878c", + "last_modified": 1507713895927 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1402158", + "who": "mgoodwin@mozilla.com", + "why": ".", + "name": "Certinomis Cross-Signed StartCom certs", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEdMBsGA1UEAxMUQ2VydGlub21pcyAtIFJvb3QgQ0E=", + "serialNumber": "Wu0lOm5kylP5uOu6md4xmWC3AtQ=", + "id": "9945629e-f285-47fd-9241-05cb75c041e6", + "last_modified": 1506372739199 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1402158", + "who": "mgoodwin@mozilla.com", + "why": ".", + "name": "Certinomis Cross-Signed StartCom certs", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEdMBsGA1UEAxMUQ2VydGlub21pcyAtIFJvb3QgQ0E=", + "serialNumber": "Z7mwlz4NA2s+8dnwRzT/RvK9ZZQ=", + "id": "0f1e6e75-0f69-407f-bccd-5838401393eb", + "last_modified": 1506372738063 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1402128", + "who": "mgoodwin@mozilla.com", + "why": "These issue only client certificates and do not need to be trusted for SSL/TLS server authentication", + "name": "Add Cartão de Cidadão to OneCRL", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=", + "serialNumber": "a0zzyZD4OEdRpzTBCGWFnQ==", + "id": "b931ec9c-45a9-4719-b2fe-fd6c903b63f1", + "last_modified": 1506372736800 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1402128", + "who": "mgoodwin@mozilla.com", + "why": "These issue only client certificates and do not need to be trusted for SSL/TLS server authentication", + "name": "Add Cartão de Cidadão to OneCRL", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=", + "serialNumber": "ObszBuNYqt9If26rE5MLnA==", + "id": "1aadc1b7-2a94-4178-9ccb-64ba471135b7", + "last_modified": 1506372735297 + }, + { + "schema": 1552493011443, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1400600", + "who": "mgoodwin@mozilla.com", + "why": "Three cross certificates issued by the Baltimore Cybertrust Root to the Siemens Internet CA V1.0 have not yet been revoked but should be added to OneCRL.", + "name": "Baltimore Cybertrust Cross Certificates issued to Siemens Internet CA V1.0", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "CcaPWuZtcdneSnerYJH33A==", + "id": "2f081df6-2a06-4a11-ba8a-368275363e9c", + "last_modified": 1506372734155 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1400600", + "who": "mgoodwin@mozilla.com", + "why": "Three cross certificates issued by the Baltimore Cybertrust Root to the Siemens Internet CA V1.0 have not yet been revoked but should be added to OneCRL.", + "name": "Baltimore Cybertrust Cross Certificates issued to Siemens Internet CA V1.0", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "DL0FAAzqeadFvWvsl9xaiA==", + "id": "07eff59c-72ff-4fa5-bdf6-756974fea58c", + "last_modified": 1506372733055 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1400600", + "who": "mgoodwin@mozilla.com", + "why": "Three cross certificates issued by the Baltimore Cybertrust Root to the Siemens Internet CA V1.0 have not yet been revoked but should be added to OneCRL.", + "name": "Baltimore Cybertrust Cross Certificates issued to Siemens Internet CA V1.0", + "created": "2017-09-22T18:05:01Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydWSw==", + "id": "f5108d78-08e8-4a73-b7e6-5722bc181931", + "last_modified": 1506372732025 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1394595", + "who": "mgoodwin@mozilla.com", + "why": "Unknown", + "name": "Add Firmaprofesional subCA Santander Digital Signature to OneCRL", + "created": "2017-09-07T11:55:05Z" + }, + "enabled": false, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "QwCyx4wTlCQ=", + "id": "56e5b51e-6b90-4b87-8ca0-4f58d49e648d", + "last_modified": 1504798798154 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1393281", + "who": "mgoodwin@mozilla.com", + "why": "Unknown", + "name": "Add FNMT's AC Representación certificate to OneCRL", + "created": "2017-09-07T11:55:05Z" + }, + "enabled": false, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "YcLU1PaprndVkma5ja/WIQ==", + "id": "756c3e21-c95d-4e5d-b5cb-00da9d658a78", + "last_modified": 1504798796757 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1397312", + "who": "", + "why": "", + "name": "", + "created": "2017-09-06T16:07:44Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==", + "serialNumber": "UWMOvf4tj/x5cQN2PXVSww==", + "id": "0b0401dd-4f39-4740-b911-6ab11ab5edb3", + "last_modified": 1504710470915 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1397312", + "who": "", + "why": "", + "name": "", + "created": "2017-09-06T16:07:44Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "AINVG9I4T2jgQgW4N9SNhw==", + "id": "a1d4104f-7bf3-4c00-9174-29b580030fee", + "last_modified": 1504710469413 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1397312", + "who": "", + "why": "", + "name": "", + "created": "2017-09-06T16:07:44Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==", + "serialNumber": "BUE=", + "id": "d081997f-6074-4f9e-b5b8-aa572a1d3177", + "last_modified": 1504710467840 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1392378", + "who": "", + "why": "", + "name": "", + "created": "2017-08-21T20:42:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeLBg==", + "id": "e130c036-75e7-4e76-a93a-392fb9568b30", + "last_modified": 1503344586238 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381863", + "who": "mgoodwin@mozilla.com", + "why": "CA request", + "name": "TÜRKTRUST EV SSL Sertifikası Hizmetleri H5 - Add TurkTrust Intermediate Certs to OneCRL per CA's request", + "created": "2017-08-21T16:42:55Z" + }, + "enabled": false, + "issuerName": "MIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1", + "serialNumber": "AUMyuCiycPJJ", + "id": "e8c04b9e-6bbd-4fbf-bcb0-a2177ac9c04a", + "last_modified": 1503344585208 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381863", + "who": "mgoodwin@mozilla.com", + "why": "CA request", + "name": "TÜRKTRUST Nesne İmzalama Sertifikası Hizmetleri H5 - Add TurkTrust Intermediate Certs to OneCRL per CA's request", + "created": "2017-08-21T16:42:55Z" + }, + "enabled": false, + "issuerName": "MIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1", + "serialNumber": "Aay2vr4aoUeZ", + "id": "af014072-16c1-4375-94ac-e8ee7611ccd3", + "last_modified": 1503344584093 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1381863", + "who": "mgoodwin@mozilla.com", + "why": "CA request", + "name": "TÜRKTRUST Basit Elektronik Sertifika Hizmetleri H5 - Add TurkTrust Intermediate Certs to OneCRL per CA's request", + "created": "2017-08-21T16:42:55Z" + }, + "enabled": false, + "issuerName": "MIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1", + "serialNumber": "AZkNBFXrl1Zg", + "id": "5b62df54-5b45-424c-b126-a6442013861a", + "last_modified": 1503344582959 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1391095", + "who": ".", + "why": "https://groups.google.com/forum/#!msg/mozilla.dev.security.policy/Qo1ZNwlYKnY/UrAodnoQBwAJ", + "name": "Add AC FNMT Usuarios certificate to OneCRL", + "created": "2017-08-17T17:54:25Z" + }, + "enabled": false, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "RV864VwhzbpUT4KqR1Hr2w==", + "id": "5e3a7625-ad9e-4713-89e7-3cddd3c0a781", + "last_modified": 1503344581693 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1385914", + "who": "", + "why": "", + "name": "", + "created": "2017-07-31T16:00:52Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "serialNumber": "ATk=", + "id": "b2eaf8f3-f13b-4294-9a7c-5f70cda440b0", + "last_modified": 1501513259932 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1385914", + "who": "", + "why": "", + "name": "", + "created": "2017-07-31T16:00:52Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "serialNumber": "ANU=", + "id": "d91f7502-e635-4fa6-b2da-9dcbac7a73b5", + "last_modified": 1501513258828 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1385914", + "who": "", + "why": "", + "name": "", + "created": "2017-07-31T16:00:52Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "QOu0a5Z9rCkw6Nk7Rg1/AQ==", + "id": "c302fc31-c64b-4105-bec4-2895e7201f97", + "last_modified": 1501513257806 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1385914", + "who": "", + "why": "", + "name": "", + "created": "2017-07-31T16:00:52Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==", + "serialNumber": "Xmo3AIW2VHeeJoR0o09RGQ==", + "id": "5be4587b-7e7d-4362-90ae-ec54e5c84a59", + "last_modified": 1501513256610 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1385914", + "who": "", + "why": "", + "name": "", + "created": "2017-07-31T16:00:52Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "SeEzbpTltqUtqW7UiuJ2", + "id": "8dd80d48-07b1-419d-9992-dac30fced89e", + "last_modified": 1501513255352 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "RMgdRGEBv0KzFCjgGFp0Hg==", + "id": "5f913c3e-d525-4ecf-b374-45e1e40d75b1", + "last_modified": 1499778717805 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "E/YGRk12iZqZuMfsIiVaeg==", + "id": "398c4ee1-de04-429c-8dbb-cdc0c232b145", + "last_modified": 1499778716926 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "O2Qh+qhbBRuZA11yDhcLGQ==", + "id": "d87aa7c0-507c-4795-b4c5-812e387c3eab", + "last_modified": 1499778716028 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "D/VlGqmz9Nai1ywCydT/RQ==", + "id": "6094fb26-7fbf-48b0-83b0-42750450cb4e", + "last_modified": 1499778715137 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MIHBMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yaw==", + "serialNumber": "B8f7CHJUqV3VareLPE+2kA==", + "id": "33c37dfe-6270-49e8-b437-e593e6905679", + "last_modified": 1499778714260 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "HVRikKXRQ1ouhOpYcOna/A==", + "id": "18c1851b-db94-4913-8688-368d7ed7bc61", + "last_modified": 1499778713388 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA3IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNA==", + "serialNumber": "RmI44ARDVCUOgXNK9ACAbg==", + "id": "f4213dc2-9eb3-44a8-8be4-accb6d408075", + "last_modified": 1499778712492 + }, + { + "schema": 1552493012878, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "Aj/CJN2QWZAF25GXPXADOA==", + "id": "03a9b3e7-7787-41db-98db-e2fbd024ef45", + "last_modified": 1499778711624 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "CgFBQQAAATjkOB1sAAAAAg==", + "id": "1b74aa7b-2506-48bd-88bc-39af44ee70ce", + "last_modified": 1499778710731 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1379974", + "who": "", + "why": "", + "name": "", + "created": "2017-07-11T14:11:47Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjg=", + "serialNumber": "JGKKnm00uOQ=", + "id": "c6c10916-eb0c-4c4f-a812-4d6390e8a738", + "last_modified": 1499778709784 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1375006", + "who": "", + "why": "", + "name": "", + "created": "2017-06-21T13:43:45Z" + }, + "enabled": true, + "issuerName": "MGsxCzAJBgNVBAYTAlVTMQ0wCwYDVQQKEwRWSVNBMS8wLQYDVQQLEyZWaXNhIEludGVybmF0aW9uYWwgU2VydmljZSBBc3NvY2lhdGlvbjEcMBoGA1UEAxMTVmlzYSBlQ29tbWVyY2UgUm9vdA==", + "serialNumber": "B2VhZAPxCDH3s9Mkbu3HfQ==", + "id": "4961246c-b140-4db0-87d7-d240f049b67e", + "last_modified": 1498049030788 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1375006", + "who": "", + "why": "", + "name": "", + "created": "2017-06-21T13:43:45Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "ANUANvVYN7xqAISA9rvJPzQ=", + "id": "bdb19be5-1d67-49a9-bd7e-a7bbd8dcccad", + "last_modified": 1498049029660 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1375006", + "who": "", + "why": "", + "name": "", + "created": "2017-06-21T13:43:45Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "CgFBQQAAATjtdPY5AAAAAg==", + "id": "fe6d33ba-fc97-485c-9b8a-639dd6d150d8", + "last_modified": 1498049028433 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1374809", + "who": ".", + "why": "Private key leaked", + "name": "Leaked private key for Cisco cert", + "created": "2017-06-21T12:45:09Z" + }, + "enabled": false, + "issuerName": "MF4xCzAJBgNVBAYTAlVTMTAwLgYDVQQKEydIeWRyYW50SUQgKEF2YWxhbmNoZSBDbG91ZCBDb3Jwb3JhdGlvbikxHTAbBgNVBAMTFEh5ZHJhbnRJRCBTU0wgSUNBIEcy", + "serialNumber": "ZhcM4uyLfYi04utzLnOP46Z89nI=", + "id": "bb8ba6ba-4e19-407d-ab9d-9f8aafd2c312", + "last_modified": 1498049027284 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372336", + "who": ".", + "why": "Banca d' Italia does not have a current audit for Baseline Requirements compliance", + "name": "Banca d'Italia Root's X-Certificate", + "created": "2017-06-15T16:43:23Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByemaA==", + "id": "7df83512-01f2-4d2c-af53-fcf4d87defd9", + "last_modified": 1497542609610 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1373288", + "who": ".", + "why": "", + "name": "Revoked intermediates", + "created": "2017-06-15T16:43:23Z" + }, + "enabled": false, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "RsdOCxepZXHEs1ErwPc=", + "id": "b21c90ad-5618-40c1-ac75-eaf73b2c333b", + "last_modified": 1497542608552 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1373288", + "who": ".", + "why": "", + "name": "Revoked intermediates", + "created": "2017-06-15T16:43:23Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByembQ==", + "id": "e7397769-a603-486d-9696-84693b277203", + "last_modified": 1497542607254 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1373288", + "who": ".", + "why": "", + "name": "Revoked intermediates", + "created": "2017-06-15T16:43:23Z" + }, + "enabled": false, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byemaw==", + "id": "148f7a38-535a-4097-a7a5-83abc4e35800", + "last_modified": 1497542606104 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:06Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "MWzraR3LLhU9m/qKEhvVLQ==", + "id": "47786d55-ed71-461d-8ea0-3b074caac874", + "last_modified": 1497362467654 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:05Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=", + "serialNumber": "eViJ2GX26lp5HbF+XNp1kQ==", + "id": "38355da2-f5ab-4882-a7e8-78ea5fc41e39", + "last_modified": 1497362466761 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:04Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=", + "serialNumber": "U3KGm6UTqJ/nsMyteiUa2g==", + "id": "071f5229-616f-4ad1-a506-b48a9dbce21c", + "last_modified": 1497362465694 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:03Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "OgxXyntHYBXnPAHDxY0OXg==", + "id": "95b7cd91-57b5-4e72-aa52-6a0546883af5", + "last_modified": 1497362464680 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:02Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "GTPOETOFf5mIsbuzrojGfw==", + "id": "a7e4d7b5-50b7-412f-8592-ceb4f6db29a8", + "last_modified": 1497362463643 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:01Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=", + "serialNumber": "NpsJHyt3o1U47AAgw3UNXA==", + "id": "f2e9d2ab-7220-460e-b4a4-02f3675e7a66", + "last_modified": 1497362462415 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:01:00Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "RbG+tfPUe/vBRfTZF54i8g==", + "id": "9d320894-4ac4-49fb-bfcb-a71d97ebfe01", + "last_modified": 1497362461438 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:59Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEyMDAGA1UEAxMpVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENBIC0gRzI=", + "serialNumber": "dhjnNtYx6cojdAE55TgIBA==", + "id": "fd51db5c-9e30-4cdb-9736-1f10b80376a9", + "last_modified": 1497362460372 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:58Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "Cf0103tCm9oulH1QK0weTA==", + "id": "a02fbc88-df3e-4d81-b9e3-7a7088e193b0", + "last_modified": 1497362459446 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:57Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "GuJ0aGBYhChXAOljooJZ3A==", + "id": "375b3822-bf9f-4c3d-8633-02f7a03597ac", + "last_modified": 1497362458420 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:56Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "OOkLFZaa4CXGyJlLTIEjUQ==", + "id": "815b19c3-6c68-49fd-b9c4-f488317c9e8e", + "last_modified": 1497362457510 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:55Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "A/kVDQpE7c9h+WxlWQFzSQ==", + "id": "ae9bb73c-5360-4685-a5e9-8098b95617d5", + "last_modified": 1497362456459 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:54Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "VP3bQF/UdNfxq/UOypU1zQ==", + "id": "9e057902-89e3-49eb-9e56-f878c1c05f4f", + "last_modified": 1497362455630 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:53Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "G8sz+bm+vQjTpQNBh5CfMg==", + "id": "d7283b5c-18aa-4cda-9158-fae5ae41ad09", + "last_modified": 1497362454734 + }, + { + "schema": 1552493014308, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:52Z" + }, + "enabled": true, + "issuerName": "MHYxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazEtMCsGA1UEAxMkVmVyaVNpZ24gQ2xhc3MgMyBTU1AgSW50ZXJtZWRpYXRlIENB", + "serialNumber": "PmDn14AwWY28IlJeBXkDvA==", + "id": "43838f76-1ecd-4a7e-9a81-f9f2449f31f1", + "last_modified": 1497362453713 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:51Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "aBXsv0oU3xqh2xkUPOi8", + "id": "380057fa-8d61-4ee2-a6a1-f6bc5d210e40", + "last_modified": 1497362452684 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:50Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzI=", + "serialNumber": "AIZ6Wq/4deFQzwC6NnFpUA==", + "id": "15c3777b-b91b-461c-99c5-761d6185b109", + "last_modified": 1497362451663 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:49Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxITAfBgNVBAMTGFN3aXNzU2lnbiBTaWx2ZXIgQ0EgLSBHMg==", + "serialNumber": "APiyCXmwAUq+95DYa3DmGw==", + "id": "583ee36f-2136-418c-bab7-2d5ed62aad0a", + "last_modified": 1497362450536 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:48Z" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEeMBwGA1UEAxMVU3dpc3Njb20gUm9vdCBFViBDQSAy", + "serialNumber": "AL691kTvkemG9UQNa6McQg8=", + "id": "a2fad4bd-2ce5-4ecf-97e2-1612bde41ed2", + "last_modified": 1497362449522 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:47Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx", + "serialNumber": "AI7cApIcPA3cfSpQMf40onQ=", + "id": "7b9f5e88-b3a1-4071-ad7f-fce8459aa3a5", + "last_modified": 1497362448476 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:46Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx", + "serialNumber": "OUOBG6TE0Lr+uYYGxeVbHg==", + "id": "418575d9-15aa-4b24-9ba5-3764748e3245", + "last_modified": 1497362447464 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:45Z" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEeMBwGA1UEAxMVU3dpc3Njb20gUm9vdCBFViBDQSAy", + "serialNumber": "QFLH3Zrq+I5WQ6TlWzfUxA==", + "id": "65123c45-adf1-40a7-9531-a66d818ae6ab", + "last_modified": 1497362446438 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:44Z" + }, + "enabled": true, + "issuerName": "MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "fqRDfSf8haCEh2nWE6O+bA==", + "id": "07e6dcee-ed47-4aa9-bc69-cd8c50be9eaf", + "last_modified": 1497362445416 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:43Z" + }, + "enabled": true, + "issuerName": "MH0xCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "eohOGeS5ZHJeptyBvCu/mQ==", + "id": "11009f24-3e40-4372-82cb-c640be931a51", + "last_modified": 1497362444493 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:42Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnY=", + "id": "d4cc9f44-f6a3-416d-92f5-d9d1242eff77", + "last_modified": 1497362443469 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:41Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9Ltm0=", + "id": "d448448b-1f23-4815-a563-c4a7996615ce", + "last_modified": 1497362442343 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:40Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnQ=", + "id": "04f35e34-8280-4b48-a5b3-bb61b27445be", + "last_modified": 1497362441423 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:39Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnE=", + "id": "133ff174-70c2-4cdf-b8d4-4bcc3db4b004", + "last_modified": 1497362440295 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:38Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "LYTXWk7gMu8=", + "id": "3b0df03c-394f-43bf-bafd-6da07eca3c10", + "last_modified": 1497362439285 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:37Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "azAcTWL+ijs=", + "id": "0e15f9c5-75e7-4b8c-9f10-99f8998eda98", + "last_modified": 1497362438146 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:36Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "ATE6YA==", + "id": "73604eec-92dd-48a5-a040-b5644c4c9799", + "last_modified": 1497362437119 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:35Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "ATE6Xw==", + "id": "7e3d897e-9724-443f-842b-2124ee1cd062", + "last_modified": 1497362436009 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:34Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "ATE5Ig==", + "id": "cfe99f95-d3d1-4d4e-b312-6a7a0efedf09", + "last_modified": 1497362434869 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:32Z" + }, + "enabled": true, + "issuerName": "MGkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOjA4BgNVBAMMMVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBQZXJzb29uIENBIC0gRzM=", + "serialNumber": "f43O9TualR8=", + "id": "ab95e89b-21cc-49c0-b4ac-0d1a3773b334", + "last_modified": 1497362433845 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:31Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABL07hW2M=", + "id": "a2b93b68-1670-4c9d-a94d-e792364d24d4", + "last_modified": 1497362432728 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:30Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABL07hXdQ=", + "id": "9aba77c3-ae07-443c-a0ba-ca01aadcc3eb", + "last_modified": 1497362431692 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:29Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hRxA=", + "id": "9065a612-801b-4b7b-9e2b-b87d584332a0", + "last_modified": 1497362430669 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:28Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABL07hSVI=", + "id": "b1bb540b-be1b-4011-81df-998edb912fe1", + "last_modified": 1497362429542 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:27Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd/UQ==", + "id": "891cdd11-90ff-435c-9688-1a9e7aeb3a70", + "last_modified": 1497362428621 + }, + { + "schema": 1552493015730, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:26Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd/Tw==", + "id": "43770c43-8568-4d25-b5f2-52623d4fcf78", + "last_modified": 1497362427700 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:25Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeBQg==", + "id": "9ea34ca2-9f9d-483e-8a49-c3a17d055d47", + "last_modified": 1497362426780 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:25Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd/Tg==", + "id": "3d2ac953-a4f2-4f59-a7a6-7c9a8fadf2ca", + "last_modified": 1497362425753 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:23Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd/UA==", + "id": "78529990-77c3-404e-93bf-308ebf7033e9", + "last_modified": 1497362424834 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:22Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd/Ug==", + "id": "91e8f996-738a-4ff5-bf6a-4823c963d8e5", + "last_modified": 1497362423604 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:21Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfJhw==", + "id": "7e2f71f1-6463-460c-b6ce-2a8b25624d3a", + "last_modified": 1497362422585 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:20Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeekA==", + "id": "9a55265f-72e6-4e38-b23b-b3f097136daf", + "last_modified": 1497362421456 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:19Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bycfmw==", + "id": "d29166c7-29e9-4b00-878f-99f6a415a4d2", + "last_modified": 1497362420326 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:18Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BycfpA==", + "id": "b02d1c65-499b-4cf9-a2e5-cd3d172c06f6", + "last_modified": 1497362419199 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:17Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BycpYA==", + "id": "b544067a-575f-417a-ab9f-a9ef880c262e", + "last_modified": 1497362418087 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:16Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydIoA==", + "id": "f91cf838-8125-41b5-9f6f-46891375de0d", + "last_modified": 1497362416981 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:15Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=", + "serialNumber": "GA==", + "id": "a980e313-d1dc-41dd-9bb8-921862a834be", + "last_modified": 1497362415923 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:14Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=", + "serialNumber": "Eg==", + "id": "8e1465de-a917-4608-b44b-ef02800eeb0a", + "last_modified": 1497362414917 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1372586", + "who": "", + "why": "", + "name": "", + "created": "2017-06-13T15:00:00Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEmMCQGA1UEAwwdQ2VydGlub21pcyAtIEF1dG9yaXTDqSBSYWNpbmU=", + "serialNumber": "HQ==", + "id": "d61c0990-8c57-4141-9fe4-d5a0f46371ec", + "last_modified": 1497362413918 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "Rea7UUYH3jl33BryPIo=", + "id": "3f5a142c-40a3-4c89-9df1-a056702b984e", + "last_modified": 1494458678750 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "RvCM2iRdkCE82ZOO2dU=", + "id": "3e1798dd-0356-44c2-917f-76b2f32ee998", + "last_modified": 1494458667510 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExIjAgBgNVBAMTGVRydXN0ZWQgUm9vdCBDQSBTSEEyNTYgRzI=", + "serialNumber": "RdHgEmEIjdyRFWDRRlk=", + "id": "f8ebfd47-13fb-46c7-9b4d-334526c6ded9", + "last_modified": 1494458658573 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "R/j2qA==", + "id": "2ba77577-428a-4f7d-9d54-b088ecb696b2", + "last_modified": 1494458649435 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAlUQUlXQU4tQ0ExEDAOBgNVBAsMB1Jvb3QgQ0ExKjAoBgNVBAMMIVRXQ0EgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QAEy3RIAAAAAAAAMweH5dw==", + "id": "81126721-940c-454b-bd0e-f538cd5ab646", + "last_modified": 1494458639464 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAlRXMRIwEAYDVQQKDAlUQUlXQU4tQ0ExEDAOBgNVBAsMB1Jvb3QgQ0ExKjAoBgNVBAMMIVRXQ0EgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "DL8=", + "id": "ff5d47b7-cb50-46f9-9da8-e778892a2caf", + "last_modified": 1494458631103 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "serialNumber": "AMs=", + "id": "6ff3473b-bd64-4b79-98c4-4622661c8e80", + "last_modified": 1494458620210 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "serialNumber": "AZ0=", + "id": "6aa5cbdb-19fb-4f57-9a4e-ddfbf06f50ae", + "last_modified": 1494458595987 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MDMxCzAJBgNVBAYTAlBUMQ0wCwYDVQQKDARTQ0VFMRUwEwYDVQQDDAxFQ1JhaXpFc3RhZG8=", + "serialNumber": "cx0HrIEQg8JHWTP7DzOxSQ==", + "id": "bcd2cb94-3cd2-49df-a372-50a258804cc1", + "last_modified": 1494458581014 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "serialNumber": "A4g=", + "id": "124dd7d2-59e3-4eb1-b93a-5e9a33cd6e4f", + "last_modified": 1494458571110 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQKDBBWZXJpem9uIEJ1c2luZXNzMREwDwYDVQQLDAhPbW5pUm9vdDEfMB0GA1UEAwwWVmVyaXpvbiBHbG9iYWwgUm9vdCBDQQ==", + "serialNumber": "A4w=", + "id": "0b21265d-dcfb-49c0-b9e9-e900412c1fdf", + "last_modified": 1494458562276 + }, + { + "schema": 1552493017141, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "APt5i5rs4dIIQPwZdk9/ISc=", + "id": "058e5554-d206-4b11-9791-dff120803f83", + "last_modified": 1494458553816 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp", + "serialNumber": "OGPFrg==", + "id": "f27c1519-908d-4dca-a797-024c43844b52", + "last_modified": 1494458545573 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByemaQ==", + "id": "cbc37076-9136-40fa-a079-4d443b7071ca", + "last_modified": 1494458533053 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byemag==", + "id": "b9762946-8b71-40cb-844b-cafa2c0aed12", + "last_modified": 1494458523822 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfDtA==", + "id": "4b8b97f0-8a72-4ff1-af47-2fcd773c900f", + "last_modified": 1494458510233 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydvrw==", + "id": "c3724a4d-2ed5-46a0-87dd-d7d339549c8f", + "last_modified": 1494458498850 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAlRXMQ4wDAYDVQQKEwVUYWlDQTESMBAGA1UECxMJUG9saWN5IENBMSQwIgYDVQQDExtUYWlDQSBJbmZvcm1hdGlvbiBQb2xpY3kgQ0E=", + "serialNumber": "UbQGvw==", + "id": "20dc1a34-8afa-4d47-856f-810cc0a6f229", + "last_modified": 1494458484988 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "LAVIFm0MWZYH+Sv8Vf+IqkM=", + "id": "d8f28ce2-8ee3-41a1-9bc0-ddad36f7c72e", + "last_modified": 1494458469413 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "QM1zZ4GZ4gfwpQtUYye3Ne0=", + "id": "e35ececb-f12b-4fe4-bef8-ff7c3099fbe8", + "last_modified": 1494458454435 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "YUlF+VXF2FWFqCo472HfZlw=", + "id": "0e806ef9-c9d4-455a-a462-cbb897cd8e20", + "last_modified": 1494458439140 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "IHj3eiEK3K1Xrpu1uvtBuvE=", + "id": "023bf60f-9ed8-41ef-8ccb-1bf993f3f973", + "last_modified": 1494458422758 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "RVWTeb5EKqE7cy7MUD2oJ3M=", + "id": "e5723e05-8b15-4cdb-b996-4d36b419a431", + "last_modified": 1494458398872 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1343305", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-05-10T22:53:40Z" + }, + "enabled": true, + "issuerName": "MIG+MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNRW50cnVzdCwgSW5jLjEoMCYGA1UECxMfU2VlIHd3dy5lbnRydXN0Lm5ldC9sZWdhbC10ZXJtczE5MDcGA1UECxMwKGMpIDIwMDkgRW50cnVzdCwgSW5jLiAtIGZvciBhdXRob3JpemVkIHVzZSBvbmx5MTIwMAYDVQQDEylFbnRydXN0IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMg==", + "serialNumber": "UdNjvA==", + "id": "917e8337-dd57-4b8a-8994-2921f2fb69ef", + "last_modified": 1494458377056 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:38Z" + }, + "enabled": true, + "issuerName": "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp", + "serialNumber": "TA5iEg==", + "id": "16319df7-453c-d980-4624-d73d9cf43143", + "last_modified": 1485907697001 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:37Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydSYg==", + "id": "ec171905-2990-30e0-74bb-9fc327c1c4bd", + "last_modified": 1485907696954 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:38Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "Iqpyf/YoGgvHc8HiDAxAI8o=", + "id": "6fa017d2-461c-8de0-f8a1-ab0531097d8e", + "last_modified": 1485907696908 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:38Z" + }, + "enabled": true, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "Hwexgn/ZCJicZPcsIyI8zxQ=", + "id": "4df86909-6b1c-f7cd-c71b-bb4b895d441f", + "last_modified": 1485907696863 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:38Z" + }, + "enabled": true, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "RnQ3dg5KdDZs0nyFZk4=", + "id": "0971a89d-b11f-97c9-2ac2-f706757b75fb", + "last_modified": 1485907696819 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1334069", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-31T23:06:38Z" + }, + "enabled": true, + "issuerName": "MFAxJDAiBgNVBAsTG0dsb2JhbFNpZ24gRUNDIFJvb3QgQ0EgLSBSNDETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbg==", + "serialNumber": "RnQ3dYovwvB0D5q2YGY=", + "id": "d739cdcd-0078-92a9-2c46-6a5231f3d888", + "last_modified": 1485907696773 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "ALxyZmb/WL/wAuUiPK5oK/g=", + "id": "d6bedca3-c2b7-0402-337e-7788b9c97b85", + "last_modified": 1484704581273 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDErMCkGA1UEAxMiQ09NT0RPIFJTQSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "AKrMYlJmUUin8FOM/0TJrmk=", + "id": "42e2ce60-f40f-97a6-dd47-3ea2e2dd72d8", + "last_modified": 1484704580920 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQQ==", + "serialNumber": "e7wSpVxmgAS5/ioLi2iBIA==", + "id": "59b02ba9-97ec-924b-d797-2c61c2be0d87", + "last_modified": 1484704580894 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydrxg==", + "id": "224d1360-5aed-b81f-f24c-3d34e2ca3ec4", + "last_modified": 1484704580868 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byc85g==", + "id": "25a0eefb-aa44-23df-4dda-bb166836d4c1", + "last_modified": 1484704580840 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=", + "serialNumber": "CdYL9vSQCEKzBwjO10ud2w==", + "id": "479ac2a4-7b6a-8db3-c5a2-be10eb5dec57", + "last_modified": 1484704580815 + }, + { + "schema": 1552493018551, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=", + "serialNumber": "fbsHfUkagQtznc3rtY1uDg==", + "id": "b1b1a5db-3c68-21be-8264-7146b0ee9e6b", + "last_modified": 1484704580787 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "AJiU+bpWh2Uc4xFRf8GM9yA=", + "id": "60daf8a1-a929-0177-e7ba-76fff95fd20b", + "last_modified": 1484704580760 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=", + "serialNumber": "Hnms0W0OxHSYE2F0XE97sw==", + "id": "55b72394-f4c1-3001-cf84-10f2068f2768", + "last_modified": 1484704580734 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bye2Cg==", + "id": "e2277fc3-1aac-7c20-0cb7-f4fd6c79eedb", + "last_modified": 1484704580708 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=", + "serialNumber": "CMNfzETd7XxesS9FOUj9Mg==", + "id": "49251465-af0d-3e93-cb1a-9d0b2ac356b2", + "last_modified": 1484704580680 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUw=", + "serialNumber": "XJ8pGvGNM9RIcLUG9YQjLQ==", + "id": "c83b4498-ea23-7723-1c2c-b673115792d8", + "last_modified": 1484704580653 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MGoxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xOzA5BgNVBAMMMlN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBTZXJ2aWNlcyBDQSAtIEcz", + "serialNumber": "e9JTGBe45yw=", + "id": "20d69a85-62b2-72c7-1107-110b43d2aeb2", + "last_modified": 1484704580623 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpGcmF1bmhvZmVyMSEwHwYDVQQLExhGcmF1bmhvZmVyIENvcnBvcmF0ZSBQS0kxIDAeBgNVBAMTF0ZyYXVuaG9mZXIgUm9vdCBDQSAyMDA3", + "serialNumber": "YR0zGQAAAAAAAw==", + "id": "4204c7d6-1838-5925-2461-1bc0e03515d4", + "last_modified": 1484704580597 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "AQw=", + "id": "f8c41076-a3f0-439a-9d5e-41e27e019a77", + "last_modified": 1484704580571 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcx", + "serialNumber": "ESDDtMgFFiaUfKo7HD9qImM7", + "id": "ec0960f7-7ae1-23e7-5006-6652da817daa", + "last_modified": 1484704580544 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "RFlmmjulj6Ve7PfBi44nnw==", + "id": "0e7b9a2c-3604-5b89-4dff-796122174bdc", + "last_modified": 1484704580517 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5UcnVzdCBSb290IENBIEcx", + "serialNumber": "ESBrHE7sFC7CQ8EM681xA3CY", + "id": "0595dc75-9356-ba32-a15e-05e3072b7f54", + "last_modified": 1484704580491 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "AjpW", + "id": "b7e26b4d-bbe1-1c4e-ef9b-12471bcb9bf8", + "last_modified": 1484704580465 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=", + "serialNumber": "a9rf7/BmG9JkKvRuy7J5QA==", + "id": "fcfc3b3c-0a59-d143-f5fd-7600dd8efa87", + "last_modified": 1484704580438 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ZECgRdZEsns=", + "id": "4652f392-127d-a5bf-4ed6-b07b9fa72247", + "last_modified": 1484704580412 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "AJBQSPqrEvDE2Hz8xH39Low=", + "id": "c279dd67-2ce1-e090-1e04-0c11fe3ddf8e", + "last_modified": 1484704580385 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "frj5jTuqBnQ4fljPvVU3KA==", + "id": "85acb18c-16b6-12c7-83ae-1e0d94251362", + "last_modified": 1484704580358 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfFnw==", + "id": "d0da2ea3-1cad-5c9e-4c75-c83acfeabc8d", + "last_modified": 1484704580332 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:58Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByembA==", + "id": "fcd51190-7eaf-291e-b6e5-45e447de7291", + "last_modified": 1484704580305 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1329981", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-18T01:12:57Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "RH7WhshwXRK6f0VfOfjXgQ==", + "id": "003234b2-f425-eae6-9596-040747dab2b9", + "last_modified": 1484704580277 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "SurdtfsuPcXXDpY2LkBpYO6BT7o=", + "id": "034627e4-44c6-fbf2-275a-4ed3431a4094", + "last_modified": 1483471394790 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Er0moq4zwH8ke2pYafIKdg==", + "id": "85c81ed8-787f-ffcf-2a63-1be622db8d04", + "last_modified": 1483471394768 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydiAg==", + "id": "9194c97e-3baa-0446-9642-0d6211c3f019", + "last_modified": 1483471394746 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "eLumDUO40KwnecZLJxFM2A==", + "id": "c2cc63c7-5274-9901-5f67-0a13355a8aa8", + "last_modified": 1483471394725 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Bw==", + "id": "92e2494f-f3cd-7638-7fe1-a3cb8d8939fa", + "last_modified": 1483471394702 + }, + { + "schema": 1552493019982, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy", + "serialNumber": "GpO48aJ8GngtwECqZhm/xA==", + "id": "bdfdc34f-ba9a-7b25-6cb6-24c547eb8a10", + "last_modified": 1483471394681 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFIxCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSgwJgYDVQQDDB9EaWdpZGVudGl0eSBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "Cw==", + "id": "7574eb9e-6978-dcb7-5a6f-d4c3dd855254", + "last_modified": 1483471394658 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "IA==", + "id": "d82f446f-181a-5ac3-0ced-854e3cde100c", + "last_modified": 1483471394635 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "OfJBIhFwAdQ=", + "id": "b8fdddf1-27c1-5f6e-58a1-295e2c8c0ea5", + "last_modified": 1483471394613 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "MABJTA==", + "id": "71e08617-c00a-8b62-53c2-2a61e21e6155", + "last_modified": 1483471394591 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "HA==", + "id": "6a6d36e6-8939-0a83-3fd5-c38652b165ed", + "last_modified": 1483471394569 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:55Z" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSMwIQYDVQQDDBpEaWdpZGVudGl0eSBCdXJnZXIgQ0EgLSBHMg==", + "serialNumber": "DA==", + "id": "460643a1-23f3-1beb-0f14-ecb4b6e26cc9", + "last_modified": 1483471394547 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "CLc=", + "id": "49eb43cf-91d4-66ce-1a86-b8674ff83d4d", + "last_modified": 1483471394524 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEZMBcGA1UEAxMQU2VjdXJlIEdsb2JhbCBDQQ==", + "serialNumber": "QAAnEQ==", + "id": "bb5c827f-3458-93fe-f80f-2982f0d19d34", + "last_modified": 1483471394501 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "AjqK", + "id": "d6f9a6d9-d936-dfaf-d396-8ae96769ef10", + "last_modified": 1483471394478 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfNeA==", + "id": "02ec8a09-2ae4-cd2a-4fa1-5037fa945391", + "last_modified": 1483471394453 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "JLiDzgpL7oFNgJN+jIjt7w==", + "id": "5631f49c-a1fb-803e-ecf2-3ba82ca79f2e", + "last_modified": 1483471394090 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "BwImeaRkSZQLYwFREwKo3R1Jn+8=", + "id": "c2de8edd-fd89-36dd-63d0-d3a1df92274a", + "last_modified": 1483471394067 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx", + "serialNumber": "HxT1XSjIpzjMprp9Qu1gYQ==", + "id": "46d4712b-6db2-ce7e-0efd-675f3be896cf", + "last_modified": 1483471394046 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QDi5sA==", + "id": "8acc0cad-dee8-7e2e-1799-e1a7b8b989c3", + "last_modified": 1483471394023 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "BA==", + "id": "b9ac21bb-8c1e-e562-5418-bbf6f6323c45", + "last_modified": 1483471394000 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "KuzHPJLdK5hNgJRo3R47Ag==", + "id": "91b7f544-9de1-efea-08d4-4ebafc9a3608", + "last_modified": 1483471393978 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "B+U=", + "id": "86fe91df-ebb8-f20d-2031-2bc815b14a25", + "last_modified": 1483471393956 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byeaqw==", + "id": "b97a8dce-04d9-7dba-6463-ae95d61524f4", + "last_modified": 1483471393933 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "LU4d0t7PAsZNgJGZcb+o/w==", + "id": "efdf0a12-4ba1-f84d-a88d-fc384251583c", + "last_modified": 1483471393911 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABIg08FMU=", + "id": "6c0561bc-fea3-ba35-bbdb-2b2672b67d36", + "last_modified": 1483471393889 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "AjqL", + "id": "4be7b89c-27a6-e95e-c6f9-bf652d4a0b97", + "last_modified": 1483471393867 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "FQ==", + "id": "89946691-d008-fd14-bd7a-443206d647c6", + "last_modified": 1483471393845 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydInw==", + "id": "e1a83fb0-93df-deb8-68cd-17f4997ea58b", + "last_modified": 1483471393823 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx", + "serialNumber": "JD1wxDd8IgmiqX7MyPPg1g==", + "id": "f5ad7ea6-4ac9-b9cd-776f-d2afb53fb91b", + "last_modified": 1483471393801 + }, + { + "schema": 1552493021474, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/SqA==", + "id": "ad8a2f72-7124-3e33-3060-414ce2bd8be3", + "last_modified": 1483471393779 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0aW5vbWlzMRcwFQYDVQQLEw4wMDAyIDQzMzk5ODkwMzEdMBsGA1UEAxMUQ2VydGlub21pcyAtIFJvb3QgQ0E=", + "serialNumber": "J8mznxvTvOR5p4Br3a3sm5j5iM0=", + "id": "d3f6b499-297d-e9c8-081c-44e2331bcf29", + "last_modified": 1483471393750 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy", + "serialNumber": "AIChpbGNqu4XKp9J70syKEs=", + "id": "f184ba74-06de-9247-104e-160b6d210962", + "last_modified": 1483471393727 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "Cj0=", + "id": "c5fdcbc2-71d8-0a58-3375-0c7d92526cf1", + "last_modified": 1483471393705 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Aw==", + "id": "0e8a05ee-feae-fc46-15b9-eaa2d11f4a60", + "last_modified": 1483471393683 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABRE7wRk4=", + "id": "ac8d3825-3fef-6d3e-2690-7360d1ef57a4", + "last_modified": 1483471393659 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QDi5sQ==", + "id": "4b03d4c1-705b-458e-5bdc-f729f67eeb91", + "last_modified": 1483471393637 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGsxCzAJBgNVBAYTAklUMQ4wDAYDVQQHDAVNaWxhbjEjMCEGA1UECgwaQWN0YWxpcyBTLnAuQS4vMDMzNTg1MjA5NjcxJzAlBgNVBAMMHkFjdGFsaXMgQXV0aGVudGljYXRpb24gUm9vdCBDQQ==", + "serialNumber": "WJ2qHzWUqTk=", + "id": "f93061a9-8afa-1d74-e063-4134d318f00b", + "last_modified": 1483471393615 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "VUtahOwvvmJFwlvmGDZP5w==", + "id": "9c88bc12-466d-fcdd-cc53-349d4e041332", + "last_modified": 1483471393592 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "L1fHogsVxmfMBka5q4uzaQ==", + "id": "d3c14505-c104-150c-0a2d-e5f9e92b6152", + "last_modified": 1483471393569 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:55Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "cJ+vg4742XhNgJW2ot9eIg==", + "id": "58bf9d8a-9a68-b41f-453c-8af8844bc07c", + "last_modified": 1483471393547 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "LizeWXFWP5pZPI/dLc+PVQ==", + "id": "cb1a1172-56d6-4150-1f50-eb2131f442f5", + "last_modified": 1483471393221 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Ew==", + "id": "a0ff8e3f-e68d-5ee2-21e3-26cd0f46673b", + "last_modified": 1483471393199 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABElatX7I=", + "id": "00ac492e-04f7-ee6d-5fd2-bb12b97a4b7f", + "last_modified": 1483471393177 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAy", + "serialNumber": "ANX8SnNRxCmsE/GCl5hw+8A=", + "id": "a09db9b3-2faa-73ab-67ff-61dcbf700ec7", + "last_modified": 1483471393155 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "VBSf+IncsTB3RZS4KFCJPQ==", + "id": "7e816865-7c1d-2519-f114-e69a280768f4", + "last_modified": 1483471393133 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MFIxCzAJBgNVBAYTAk5MMRkwFwYDVQQKDBBEaWdpZGVudGl0eSBCLlYuMSgwJgYDVQQDDB9EaWdpZGVudGl0eSBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "DA==", + "id": "5b760d02-fdd7-d6be-cb6f-4d30bf97746e", + "last_modified": 1483471393111 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QDi5rw==", + "id": "7de029af-ddf3-02be-ca26-5bb95b080d14", + "last_modified": 1483471393088 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "MABJSw==", + "id": "411b4e82-2ddd-20c2-20b3-8d77145f6901", + "last_modified": 1483471393066 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermwtg==", + "id": "9d8a83d8-d651-42a0-ac1c-0ee414f3e31a", + "last_modified": 1483471393043 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MCgxCzAJBgNVBAYTAkJFMRkwFwYDVQQDExBCZWxnaXVtIFJvb3QgQ0Ey", + "serialNumber": "Nbc68Q8EHza72P/hSWcddw==", + "id": "d0513de2-6da9-d68d-78cc-a2292a9d18fb", + "last_modified": 1483471393021 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Ig==", + "id": "872ba8c8-a236-9ac0-85ea-08630f5b17e2", + "last_modified": 1483471392998 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:52Z" + }, + "enabled": true, + "issuerName": "MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "APdCebq8ZyZr/T0luxlicNw=", + "id": "7a24e461-9fd0-b17f-01e0-f44866b800f1", + "last_modified": 1483471392976 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermw0Q==", + "id": "fff46511-7357-4559-3d36-75fc74034299", + "last_modified": 1483471392954 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "Ajp/", + "id": "272f2a95-6aec-f333-22ad-709d6118a87b", + "last_modified": 1483471392932 + }, + { + "schema": 1552493022903, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Fw==", + "id": "4f0dfd30-9278-4f20-25c3-436798595b84", + "last_modified": 1483471392911 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MD8xCzAJBgNVBAYTAlRXMTAwLgYDVQQKDCdHb3Zlcm5tZW50IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHk=", + "serialNumber": "K1ftto7Xcb0YKwQ6uMvOIA==", + "id": "29eab149-524e-b1da-e2b9-dc4a784ab64b", + "last_modified": 1483471392889 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==", + "serialNumber": "STMAFQ==", + "id": "91609507-ef23-7672-3a5d-06dfb9b0dac4", + "last_modified": 1483471392867 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAx", + "serialNumber": "U+1Y1QpJc0FOR5JdCJ01gQ==", + "id": "79f39790-1151-6bb2-0a50-fc596b82bedd", + "last_modified": 1483471392845 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "TAA2G+UIK6mqznQKBT77NA==", + "id": "d51879d6-0a85-8311-5b14-ad993cec17e6", + "last_modified": 1483471392823 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Iw==", + "id": "532dd854-cdef-6452-2793-1e36d091d9ec", + "last_modified": 1483471392801 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/O5w==", + "id": "756ab100-8a14-daf7-bf06-9fe423863208", + "last_modified": 1483471392779 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0E=", + "serialNumber": "Ajp+", + "id": "41f2401b-5c31-724f-ad6c-28f3f6f47634", + "last_modified": 1483471392757 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MFExCzAJBgNVBAYTAkpQMRMwEQYDVQQKEwpGdWppIFhlcm94MS0wKwYDVQQDEyRGdWppIFhlcm94IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IDI=", + "serialNumber": "AUa47POQ1dN5", + "id": "0956d064-8750-eee2-b0c7-7e1c2d1d6f25", + "last_modified": 1483471392735 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:54Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "Pgyeh2mqlVzqI9hFntRbUQ==", + "id": "134969c5-0bf4-4054-eaa1-b24feaa76aef", + "last_modified": 1483471392712 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1315199", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2017-01-03T18:41:53Z" + }, + "enabled": true, + "issuerName": "MDQxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25h", + "serialNumber": "Gg==", + "id": "141d8e99-193b-d108-382b-7d97e6912d8c", + "last_modified": 1483471392689 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESDYXNBhF+dePFjojs7u2vj1", + "id": "f7f193ca-c34e-866a-8abf-d44188a78cb0", + "last_modified": 1480349168043 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp", + "serialNumber": "TA6BjA==", + "id": "2a9d41c8-d3e7-a40a-a79a-899902aa73cb", + "last_modified": 1480349168021 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "XhcFm2g619rt8Sro+a4rHA==", + "id": "a01f2d94-e1ee-2139-5652-c216331e357a", + "last_modified": 1480349167999 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F6QlB/yX+A==", + "id": "a7fa8b99-3e2a-2c03-dffd-ab341eae59af", + "last_modified": 1480349167976 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydxog==", + "id": "35f6f9f8-eb17-39d2-50a7-2b40f01e2584", + "last_modified": 1480349167954 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CqnbFQ==", + "id": "3b60bc42-674b-7822-113f-c8dc6d1b015e", + "last_modified": 1480349167931 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEfMB0GA1UEAxMWVVROLVVTRVJGaXJzdC1IYXJkd2FyZQ==", + "serialNumber": "Xrr31RF0DoIzMKXS6XtD+g==", + "id": "c0d2651b-f567-0f6d-ce3c-16e7d19390d0", + "last_modified": 1480349167904 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/SnQ==", + "id": "7326aa15-9b3e-44ca-c1c5-0ed1ff29521f", + "last_modified": 1480349167882 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzQ=", + "serialNumber": "H08=", + "id": "2ac02b19-f616-0dc3-6d01-28e1bea3dd93", + "last_modified": 1480349167861 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:22:27Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "NMpMcEnex3eXx4ohk9glcQ==", + "id": "8e453b5c-3f81-2694-2f10-73ec8c406c49", + "last_modified": 1480349167827 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "AQAAAAA=", + "id": "a3b400ad-0b4d-aa11-e8b5-82019fbc84d5", + "last_modified": 1480349167797 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MIGpMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYDVQQLEy8oYykgMjAwNiB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEfMB0GA1UEAxMWdGhhd3RlIFByaW1hcnkgUm9vdCBDQQ==", + "serialNumber": "Ikdj3zYXXGsC/Afm9Tvx+g==", + "id": "e8b2d24e-5aaf-86dd-441e-b4781d5370db", + "last_modified": 1480349167774 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMw==", + "serialNumber": "acI1CFIgmwSFBoU5+ahDgg==", + "id": "938358e4-4c70-beb4-0fbe-02009feee6b6", + "last_modified": 1480349167744 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:43:37Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABA/A35EU=", + "id": "97fbf7c4-3ef2-f54f-0029-1ba6540c63ea", + "last_modified": 1480349167722 + }, + { + "schema": 1552493024471, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMg==", + "serialNumber": "UVKsEezpGWOVQ4W9esstng==", + "id": "c4c22010-0cb9-e9af-005d-2483612d864e", + "last_modified": 1480349167701 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F5Bg/C8eXg==", + "id": "4295bb93-6e34-a58a-4d1c-238615b57cb0", + "last_modified": 1480349167343 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "AImQERVYPoeb", + "id": "4ca8f1a1-0dc0-881f-b497-cc5574f2494d", + "last_modified": 1480349167322 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "J2La+q+JOURNWkX60OP2lQ==", + "id": "8662e9a7-fe16-d1fc-0993-d16dd2f01012", + "last_modified": 1480349167291 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDEgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAxIENB", + "serialNumber": "BAAAAAABHkSl5ao=", + "id": "7cfc5a53-f950-c15b-4dbb-8124fadcf871", + "last_modified": 1480349167269 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==", + "serialNumber": "RUT1Gehd1KKYPfqOlgspoQ==", + "id": "8855fafd-e48b-680d-ff15-a022057d9b9e", + "last_modified": 1480349167246 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:41:26Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABKUXDqxw=", + "id": "0a92faa8-b870-3a38-036e-9b95185fcb6a", + "last_modified": 1480349167224 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "CqZgEvHAsnzkT//QV9KjXw==", + "id": "39008976-298e-f664-50c0-56f8ae6c4df5", + "last_modified": 1480349167202 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR8wHQYDVQQDExZHZW9UcnVzdCBTSEEyNTYgU1NMIENB", + "serialNumber": "OUvvVscW0/NltofkmV9qmg==", + "id": "a5eb3877-5e7d-200a-cee3-f63b8004d58e", + "last_modified": 1480349167179 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "ORFgmCj072NjcJnrxOMfQA==", + "id": "995df7f2-6bd4-12c6-b1aa-fcfe7618b193", + "last_modified": 1480349167156 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9Ltms=", + "id": "460ae779-c915-9daf-9a13-e0bf953322cb", + "last_modified": 1480349167125 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155119", + "who": ".", + "why": ".", + "name": "T-Systems intermediate certificate", + "created": "2015-05-05T11:10:09Z" + }, + "enabled": true, + "issuerName": "MHExCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNEZXV0c2NoZSBUZWxla29tIEFHMR8wHQYDVQQLExZULVRlbGVTZWMgVHJ1c3QgQ2VudGVyMSMwIQYDVQQDExpEZXV0c2NoZSBUZWxla29tIFJvb3QgQ0EgMg==", + "serialNumber": "ARQ=", + "id": "2826cef9-e4b4-53f9-e3cf-c5870fc778dd", + "last_modified": 1480349167102 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:46:49Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "DAk9hy8DhHSo+aQetvPB/fY=", + "id": "b42066e0-0c88-e02b-620f-c41c2118c4e7", + "last_modified": 1480349167079 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1309305", + "who": ".", + "why": ".", + "name": "Hongkong Post e-Cert CA 1-10 certificates", + "created": "2016-11-03T12:48:38Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==", + "serialNumber": "BHk=", + "id": "5048a7c5-79c8-68d7-06a3-19e8ba32e5fc", + "last_modified": 1480349167057 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "OnvXX72mvUI2Id/NMzegmg==", + "id": "92d843e8-4e72-2832-b56f-6e488e677d0f", + "last_modified": 1480349167035 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==", + "serialNumber": "ZgwfEqZnBsUNvNuZ77FbQA==", + "id": "73ae5fed-730d-94db-04ef-2aafd5ff75b8", + "last_modified": 1480349167012 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Qh/QbQ==", + "id": "6e4739fe-1aed-2320-4dc3-832043a31fc8", + "last_modified": 1480349166989 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "L79XLVO2ZmtAu7FAG8Wmzw==", + "id": "7d2a48b3-0b2e-59ae-2002-8edb4da20bd2", + "last_modified": 1480349166968 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "BUrYjru5px1ym4QUN33TOQ==", + "id": "e95bb238-6d35-2cce-9744-d6a672b0a874", + "last_modified": 1480349166946 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESByNJZ5TPjg9iZyL6a/h5Zx", + "id": "51935a37-2964-18cf-b34c-a20c2c2250ea", + "last_modified": 1480349166921 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1314673", + "who": ".", + "why": ".", + "name": "SecureSign Public CA11 intermediate CA cert", + "created": "2016-11-03T12:46:07Z" + }, + "enabled": true, + "issuerName": "MFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RDQTEx", + "serialNumber": "Aw==", + "id": "ec4f91dd-7560-920a-f178-e8ae460dd595", + "last_modified": 1480349166898 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABFUtaxac=", + "id": "8d87b3cd-b954-f4f1-bfb2-a0e60645301c", + "last_modified": 1480349166876 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xLTArBgNVBAMMJFN0YWF0IGRlciBOZWRlcmxhbmRlbiBCdXJnZXIgQ0EgLSBHMg==", + "serialNumber": "ATE3ew==", + "id": "83ac91ce-0f5e-ae4e-2010-b0da5616cd59", + "last_modified": 1480349166855 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "LnfcUaXG/pxV2CpXM5+YSg==", + "id": "4c743a6f-af95-49a6-bd4a-d1ee8160c537", + "last_modified": 1480349166833 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1309305", + "who": ".", + "why": ".", + "name": "Hongkong Post e-Cert CA 1-10 certificates", + "created": "2016-11-03T12:49:39Z" + }, + "enabled": true, + "issuerName": "MEcxCzAJBgNVBAYTAkhLMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMQ==", + "serialNumber": "BGU=", + "id": "4de7908c-45e7-3b7f-a91a-8cefb1ecf830", + "last_modified": 1480349166811 + }, + { + "schema": 1552493026167, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:42:46Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABJZbEU4I=", + "id": "19c9a896-fbf0-8ad0-92cd-4aca2577c006", + "last_modified": 1480349166787 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABIg08D3U=", + "id": "4aec7420-aa59-53b8-1373-d3c0a7ebc837", + "last_modified": 1480349166478 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD8xJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjEXMBUGA1UEAxMORFNUIFJvb3QgQ0EgWDM=", + "serialNumber": "CgFBQgAAAUFcf/EVAAAAAg==", + "id": "d60a94e9-3f7f-a20f-1dcc-c87ccc78fb99", + "last_modified": 1480349166455 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "dItWlz2V62Philqj9m6Pbg==", + "id": "abb0df0d-6716-9a25-ae33-806e93276cd4", + "last_modified": 1480349166433 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CcL+EA==", + "id": "3cef2b9e-ddcd-cc40-8d59-49408409a3bb", + "last_modified": 1480349166405 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1263733", + "who": ".", + "why": ".", + "name": "Disney CA intermediate CA certificate", + "created": "2016-04-12T12:50:50Z" + }, + "enabled": true, + "issuerName": "MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp", + "serialNumber": "TA6EVg==", + "id": "7b09e4fc-2261-e7c6-e926-5a7b5e74fc5e", + "last_modified": 1480349166381 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "GdXz4L1b6FKNCMG9Jz2tjA==", + "id": "35f81fe8-9fa4-760b-9fd0-2de1b0191721", + "last_modified": 1480349166358 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:23:14Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "COwoDFvz7GD8R2K7Lo0rYQ==", + "id": "cc56260c-5f3a-3f4b-c712-78a8e7facd27", + "last_modified": 1480349166330 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MIGKMQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEmMCQGA1UECxMdQ29weXJpZ2h0IChjKSAyMDA1IFdJU2VLZXkgU0ExFjAUBgNVBAsTDUludGVybmF0aW9uYWwxKTAnBgNVBAMTIFdJU2VLZXkgQ2VydGlmeUlEIEFkdmFuY2VkIEcxIENB", + "serialNumber": "WD1AyQAAAAAAJQ==", + "id": "8c984ecd-c61c-426a-97aa-3a808e4da482", + "last_modified": 1480349166308 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnI=", + "id": "333f6eb2-cefe-1a3b-3726-a8320b047847", + "last_modified": 1480349166286 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABJQcQRNU=", + "id": "2258e9bc-1c39-9db3-4fdb-c7eb12d0609c", + "last_modified": 1480349166264 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:15:54Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "Bg==", + "id": "b7fb6842-6407-8ae4-5e0f-e6daf112ed4f", + "last_modified": 1480349166240 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "IIxFSyNM6mWtCgTG0IL3Og==", + "id": "6457eeb8-d83a-3818-c416-0dce6d71d471", + "last_modified": 1480349166215 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABHkSl5AQ=", + "id": "de8e6484-fc97-6889-a1f9-dafd45786606", + "last_modified": 1480349166191 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByeQ9g==", + "id": "bb5a82a6-8da0-5390-a7d6-843bdb0c02c2", + "last_modified": 1480349166170 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:17:01Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "EQ==", + "id": "dcce309f-aa60-6484-eaed-aa8310440a5c", + "last_modified": 1480349166148 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "R4af5A==", + "id": "e60eeeb2-612e-ef08-d0b8-5e9f8e1a23a9", + "last_modified": 1480349166126 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:40:51Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAAA+X/GIyk=", + "id": "528cd047-ef3b-fc23-e37f-5d67fd3117e4", + "last_modified": 1480349166102 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "BYyEX2b5+K+myAIR7eXaRQ==", + "id": "9be08dd3-1922-fb30-77dc-5cfcf00164a0", + "last_modified": 1480349166080 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=", + "serialNumber": "RurwlgVMxeP6Zepun0LGZA==", + "id": "22a74468-602c-f4ac-0003-be4ce0167258", + "last_modified": 1480349166058 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBSb290IENB", + "serialNumber": "AJiWmg==", + "id": "1525b265-22d6-3253-079c-c4ffca58458f", + "last_modified": 1480349166036 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCLRVuhcUZaluIgIVlRJx+O", + "id": "a6299e39-84a4-2dce-ffbb-751107660f4f", + "last_modified": 1480349166012 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:43:20Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABCUVQ9No=", + "id": "a1d34c2f-4a03-0e35-ba5f-bc14138bcff5", + "last_modified": 1480349165990 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:38:26Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==", + "serialNumber": "BAAAAAABF2Tb8Bc=", + "id": "7e19f742-420e-dbe9-f691-2d19430d75b2", + "last_modified": 1480349165968 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9Ltm4=", + "id": "20732fc6-dd20-fe76-b6b5-b78388b64bdd", + "last_modified": 1480349165947 + }, + { + "schema": 1552493027652, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "fMTRbGCp280pnyE/u53zbA==", + "id": "44aa21e7-a92c-a0cc-6f6c-85b7ee52a87d", + "last_modified": 1480349165925 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "d8AtKymQwkOPDBj+hjPzFg==", + "id": "014a5b67-d566-0767-c9d7-48e54115a69a", + "last_modified": 1480349165591 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=", + "serialNumber": "Os2rnHWYhryvdOXfgan06A==", + "id": "dc94f688-044b-f8a0-79f9-5dc2d42e3edb", + "last_modified": 1480349165569 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:46:19Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "e/fIfg2Dj2tkYIWVu2r82Cc=", + "id": "9e8ec7bc-0f79-42c2-c9bc-32bfbbd3b591", + "last_modified": 1480349165547 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:16:27Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "EA==", + "id": "362a0532-ea75-9bc6-2e50-35d9566a6ad2", + "last_modified": 1480349165523 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:45:06Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "UU3AP1SMxmyhBFq7MRFZmf0=", + "id": "4f2e59ff-cdf1-48ee-1122-961833187e49", + "last_modified": 1480349165501 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESBqoILo90ntDW7OTK43MS2F", + "id": "0702b706-86e5-6a48-49fa-6c53b99009f3", + "last_modified": 1480349165479 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "GtXUVojhwOTkaQ4bTKblEQ==", + "id": "9475e2f6-7247-cbe1-5055-8af86f39a149", + "last_modified": 1480349165453 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "KjoVfZ3by6+pL8fssyfM6A==", + "id": "f4c8162a-d49b-1cbd-adb9-5e6223793aa4", + "last_modified": 1480349165429 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydr0Q==", + "id": "a85aef34-3bfe-2135-845d-466adadc414b", + "last_modified": 1480349165407 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Xbevr3ut3Z9m1GuXC9SonA==", + "id": "549710cf-bcaa-843c-df9d-5962bad88a9a", + "last_modified": 1480349165384 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CeagHQ==", + "id": "d69db231-b7b5-4d79-147b-49198f93fc10", + "last_modified": 1480349165355 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "EAdmaA==", + "id": "618009ee-0ef1-af4b-8841-349e6f82eacc", + "last_modified": 1480349165329 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:47:54Z" + }, + "enabled": true, + "issuerName": "MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB", + "serialNumber": "BAAAAAABHkSl7L4=", + "id": "636c65b9-2d52-8689-023f-7a23a0baec5b", + "last_modified": 1480349165306 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABFqoAZoI=", + "id": "2b0d58aa-9c96-748f-4fc0-b1f413ca8e20", + "last_modified": 1480349165285 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1250907", + "who": ".", + "why": ".", + "name": "IdenTrust cross certificate", + "created": "2016-02-24T20:04:54Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlJZGVuVHJ1c3QxIDAeBgNVBAsTF0lkZW5UcnVzdCBQdWJsaWMgU2VjdG9yMRwwGgYDVQQDExNJZGVuVHJ1c3QgQUNFUyBDQSAx", + "serialNumber": "fwAAAQAAAUrz/HmrAAAAAg==", + "id": "352c78aa-997c-bdbe-66ba-930d66fde011", + "last_modified": 1480349165264 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CeFU2w==", + "id": "e8e298f0-efa2-0d08-458f-c085ee9df8f9", + "last_modified": 1480349165242 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1149603", + "who": ".", + "why": ".", + "name": "MCSHOLDING intermediate certificate", + "created": "2015-03-31T14:53:16Z" + }, + "enabled": true, + "issuerName": "MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==", + "serialNumber": "STMAjg==", + "id": "c9897d2c-c68e-3c02-2f39-678954b0cf3e", + "last_modified": 1480349165209 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:46:35Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "bAOrKSMsmA0MLJyAJ5BRsUM=", + "id": "7d44cb3e-28a5-16dd-024c-796312f780bc", + "last_modified": 1480349165175 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F5BhE0zbgQ==", + "id": "29925947-91ab-16a8-a5af-65558cdb27c2", + "last_modified": 1480349165145 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABL07hUBg=", + "id": "1240480e-2ef8-8ac3-4314-4e8e494741b9", + "last_modified": 1480349165119 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCyHU+xOECnh9Rf2IvgR8zS", + "id": "3980401b-c0e2-0533-f0fb-0cc04685d248", + "last_modified": 1480349165097 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CdWFNw==", + "id": "f06ff510-954e-b917-fda1-2c3153788d7d", + "last_modified": 1480349165075 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "TrKEMhb2PKktH8lHg0AV5A==", + "id": "8b7985ab-ab8b-fcd2-cf88-cf8dad0f7a97", + "last_modified": 1480349165048 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:41:56Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABJQdAjik=", + "id": "a75f5980-9149-fff9-70d5-b24121c3eaff", + "last_modified": 1480349165026 + }, + { + "schema": 1552493029193, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cfk9lw==", + "id": "59b587cb-401b-a5d0-8128-86c3691c4be1", + "last_modified": 1480349165002 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDMgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAzIENB", + "serialNumber": "BAAAAAABHkSl6mw=", + "id": "3cc60c06-a870-951e-1d12-4b29ee13989e", + "last_modified": 1480349164725 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==", + "serialNumber": "SdegFrLaFTCsoMAW5ED+zA==", + "id": "9fbfe267-c715-8f7b-d9ad-166aad9f91af", + "last_modified": 1480349164704 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:42:11Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABMxvC9bk=", + "id": "923a5e98-11f7-cdae-b073-45b525fb2294", + "last_modified": 1480349164681 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Bydp0g==", + "id": "10e51569-072a-611a-c397-3050fdf22649", + "last_modified": 1480349164658 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QZCrvQ==", + "id": "6d791114-68b4-8c3c-ee3c-29ed83eced2e", + "last_modified": 1480349164636 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "AQAAAAI=", + "id": "bff9c953-6690-f618-cfea-7b936f3691a6", + "last_modified": 1480349164614 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Aw1SPC56593ZCZ9vCNHKwQ==", + "id": "c395381f-fe34-7b6c-f56a-f20b20bf0d0d", + "last_modified": 1480349164590 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:44:22Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABAJmPjfQ=", + "id": "24b096b4-987f-a21a-04d3-aedc9eaafc1e", + "last_modified": 1480349164559 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABK84yjs8=", + "id": "8078d5ff-c93b-15d1-ebcf-607bdbfc159f", + "last_modified": 1480349164533 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "FJl6tXgNpSk=", + "id": "7ab0a200-7ecf-576f-bff9-652fb14c3af6", + "last_modified": 1480349164510 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "M0VSOewW3WI=", + "id": "f3688c95-3934-e80a-e32f-0d5dcb2f0c4c", + "last_modified": 1480349164486 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "VN2yeFexyXjPf34fHGmbhg==", + "id": "bbcfc451-2fcc-b380-e579-bb6d11fc7d34", + "last_modified": 1480349164463 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==", + "serialNumber": "STMAeg==", + "id": "a46be506-1dbc-41a9-2775-95d67708fb5f", + "last_modified": 1480349164433 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1300977", + "who": ".", + "why": ".", + "name": "revoked.badssl.com certificate", + "created": "2016-09-09T16:26:08Z" + }, + "enabled": true, + "issuerName": "ME0xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIgU2VjdXJlIFNlcnZlciBDQQ==", + "serialNumber": "Aa8e+91erglSMgsk/mtVaA==", + "id": "4b778ec2-ef45-c5b2-dc44-b00c87b11741", + "last_modified": 1480349164410 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "TurPPI6eivtNeGYdM0ZWXQ==", + "id": "8192a2fa-165d-3759-fd20-4b7d8e2a0e84", + "last_modified": 1480349164388 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MEMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAMTFHRoYXd0ZSBTSEEyNTYgU1NMIENB", + "serialNumber": "UKKK5ol/rKBZchAAOnZjaA==", + "id": "ca0c5f15-2808-8c94-3f77-6a277d6738b2", + "last_modified": 1480349164365 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "UMUwXwT1Z4juyQ/CNTf4mw==", + "id": "c623e511-79c8-cbe6-6a6e-0d9896f07e71", + "last_modified": 1480349164342 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dD", + "serialNumber": "Ew1ee9Jq7Q/Dig3ACF4V6Q==", + "id": "c011d2b4-73bc-27e5-3d3a-ab00be2a4d05", + "last_modified": 1480349164320 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "XLhHIg7vP+tWfRqvuKeAxw==", + "id": "520c26f8-9a60-5949-0372-c9d93f9e95dd", + "last_modified": 1480349164297 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "E5I2y6sIonl4a+TmlXc7fw==", + "id": "ee842f50-9d56-8fdf-fa55-8b55b8519b81", + "last_modified": 1480349164275 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMUVVROLVVTRVJGaXJzdC1PYmplY3Q=", + "serialNumber": "Jq6jgeApiT9O4W2Tx/NTRQ==", + "id": "3b658f17-11f9-7550-d991-3e9a1397402d", + "last_modified": 1480349164253 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MG0xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRswGQYDVQQLExJQcmltYXJ5IENsYXNzIDIgQ0ExJjAkBgNVBAMTHUdsb2JhbFNpZ24gUHJpbWFyeSBDbGFzcyAyIENB", + "serialNumber": "BAAAAAABHkSl6Co=", + "id": "6eb0fa3b-c226-f411-0fab-df88962a5769", + "last_modified": 1480349164232 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESD9YhzIEOwiOT7Nwip+E1KI", + "id": "d6172148-c2ee-a904-db40-079b10436cca", + "last_modified": 1480349164209 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:17:24Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "Eg==", + "id": "34561e12-916b-083e-6fa6-181b5b89ec80", + "last_modified": 1480349164187 + }, + { + "schema": 1552493030646, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==", + "serialNumber": "NvEJoRYL2yvAZrAjbDIipQ==", + "id": "b1119d43-b3b8-1f41-6fbb-9cf2f67a521d", + "last_modified": 1480349164164 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCC9oPNcRdPOox+SjWm9dTX", + "id": "8858e9fc-cc55-54ea-fe45-c4e533c2e410", + "last_modified": 1480349163834 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1145157", + "who": ".", + "why": ".", + "name": "live.fi certificate", + "created": "2015-03-31T11:14:46Z" + }, + "enabled": true, + "issuerName": "MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "serialNumber": "D9UltDPl4XVfSSqQOvdiwQ==", + "id": "0a9323dd-982f-c62d-a9b4-b9d04617fc62", + "last_modified": 1480349163810 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:45:35Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "OYBKgxEHpW/8XGAGAlvJyMA=", + "id": "d6dfdc76-52ae-2843-3484-7fbff46f0100", + "last_modified": 1480349163788 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "EM8bDLBnnoYe4LnWpLIhS4esr3I=", + "id": "be09b295-68d1-9c01-97ee-10df36acd3ea", + "last_modified": 1480349163764 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:39:41Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABJ/ufRdg=", + "id": "5d64f82e-9ad8-fde1-a8c9-2d94552a8ad4", + "last_modified": 1480349163739 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:43:51Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABHJRKMpA=", + "id": "f5b2da3a-5176-b4e4-240a-181f39f6756b", + "last_modified": 1480349163715 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:40:18Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABBHYoIFs=", + "id": "f59ed73e-f3c5-eef3-4481-3ca8af0b0688", + "last_modified": 1480349163691 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:37:41Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==", + "serialNumber": "BAAAAAABHhw1vwc=", + "id": "1b1856c1-f4f5-82ca-ba57-d94739e74576", + "last_modified": 1480349163668 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "TqfXw+FkhxfVgE9GVMgjWQ==", + "id": "60f6a3be-ad83-a868-d645-7aad77914bc8", + "last_modified": 1480349163645 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:21:56Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "LdbnCbsA9sOgI4mkUpWXPw==", + "id": "d839b1ed-7d39-5e57-687c-2f4d6f0514e5", + "last_modified": 1480349163622 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MEgxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEXMBUGA1UEAxMOU2VjdXJlVHJ1c3QgQ0E=", + "serialNumber": "ANygrItIJ2rcKlyS3Lue07U=", + "id": "d55572d9-be60-5967-948a-7dae793ab30f", + "last_modified": 1480349163595 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydeGg==", + "id": "b7d8b0e0-9747-6a09-ab6b-051c57579fe9", + "last_modified": 1480349163572 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCVop+Q4/OBgtf4WJkr01Gh", + "id": "7602529c-c2ea-d18b-9b2a-fdb70ca936f9", + "last_modified": 1480349163548 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:42:31Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABGMG0Gmw=", + "id": "317aeda4-6de7-7b11-76cb-3b0afa9aaf86", + "last_modified": 1480349163514 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:47:04Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "O2S99lVUxErLSk56GvWRv+E=", + "id": "27847bcc-dbaf-196f-ed5e-c1c022798717", + "last_modified": 1480349163492 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:16:35Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "By7fBTreouRwX/qrpgSUsg==", + "id": "322de470-76d3-a45d-740f-342a6a8eb863", + "last_modified": 1480349163470 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "ezdAeCxKH7BFs7vn3byYaw==", + "id": "fc5bde6b-45b9-c141-7171-0a6f37e7938a", + "last_modified": 1480349163448 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "VLm3Xe60+1YgPpXCGtXLng==", + "id": "055d66b6-8fbd-93df-f2c4-dcdb41943212", + "last_modified": 1480349163426 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==", + "serialNumber": "cDggUYfwJ3A1YcdoeT6s4A==", + "id": "093f20b4-93b8-a171-cbe7-3e24a543c7e9", + "last_modified": 1480349163403 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "a9/VeyVWrzFD7rM2PEHwQA==", + "id": "c02a9772-b351-59dc-8633-1293ac9addee", + "last_modified": 1480349163377 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCis569omrbb20yySF39+aE", + "id": "aaa19866-32ce-e842-6431-6d357fafe8d8", + "last_modified": 1480349163350 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F5Bg6C237Q==", + "id": "57ddfa31-3f08-298a-d7bd-712e3aaea567", + "last_modified": 1480349163327 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "HZyLf+K70FKc+jomm8DiDw==", + "id": "452f4798-87d4-8df1-b275-177456d2f1c8", + "last_modified": 1480349163302 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CskruA==", + "id": "e92cad12-4098-0817-e317-3674d4242ba9", + "last_modified": 1480349163280 + }, + { + "schema": 1552493032094, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMCREU=", + "serialNumber": "a12RvBNhznU=", + "id": "33154d98-0f20-7e7d-d2d0-3244a7d1f971", + "last_modified": 1480349163257 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "CjM=", + "id": "283b292b-1212-a713-c8be-c976c0222410", + "last_modified": 1480349162955 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "NTgf4iwIfeyJPIomw2dwSXEwtxQ=", + "id": "542dbfc0-0faa-2398-9670-cd249525fd2c", + "last_modified": 1480349162930 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cd/dug==", + "id": "3cf54b7b-336d-7c80-4596-d5a7762329d9", + "last_modified": 1480349162902 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWU=", + "serialNumber": "M64Z5ufZzDRVTHkJR1uXzw==", + "id": "5d6d86a0-7e3e-b832-83e5-afc1eb2e294f", + "last_modified": 1480349162880 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABIBnBjWg=", + "id": "0713fba9-386c-888f-cbe5-5188ff2696a4", + "last_modified": 1480349162857 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "fWK0j/Vi8vNWg3VAGjc02w==", + "id": "a4428979-4be6-3ba2-f435-aa8e4574ffe0", + "last_modified": 1480349162835 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==", + "serialNumber": "JpUvYJyWjdGmeoH7YcYunw==", + "id": "929de7d6-0669-5601-0b8f-9a192bf1cb17", + "last_modified": 1480349162803 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "45KI4WIxyXfNrdtdj7C6", + "id": "7cb25ddb-9f7e-7297-e8b4-c50d019f0cf7", + "last_modified": 1480349162781 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:15:22Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "BQ==", + "id": "7c28e9e7-b9ce-f4ed-8d5a-e7b9e534fba5", + "last_modified": 1480349162758 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:45:19Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "F7PAjw2k0dTX5escPnyVOBo=", + "id": "3c7e3e8e-5c25-8fbf-0006-2c92256e0a4b", + "last_modified": 1480349162734 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "Sx51x7V8pYe8rp7PMP/3qg==", + "id": "4006917e-3260-59cd-eb3f-f4d1167cf888", + "last_modified": 1480349162710 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "DHmmaw==", + "id": "0abfb8ad-d1b7-5f0e-cbea-630f2424171d", + "last_modified": 1480349162686 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CcHC/g==", + "id": "cba8acd8-a291-1ad6-9581-ed647dd5d56d", + "last_modified": 1480349162661 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155114", + "who": ".", + "why": ".", + "name": "Intermediate CA's under Staat der Nederlanden Root CA", + "created": "2015-05-08T10:53:13Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "ATE0vw==", + "id": "fc71cbb8-4e2a-2835-d5be-4c48cd3650bb", + "last_modified": 1480349162632 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABLF5/HXY=", + "id": "ca59600c-e90a-e85a-43b8-22bc76bb0e1f", + "last_modified": 1480349162610 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDI=", + "serialNumber": "BXA=", + "id": "a44b5bf8-8158-1924-7626-e1ba2d2031f7", + "last_modified": 1480349162580 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:24:01Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "VOcIuNbTqkpOMUyI108FOg==", + "id": "43e72628-c1a5-2091-2dcd-41bbde768c73", + "last_modified": 1480349162557 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155114", + "who": ".", + "why": ".", + "name": "Intermediate CA's under Staat der Nederlanden Root CA", + "created": "2015-05-08T10:53:40Z" + }, + "enabled": true, + "issuerName": "MFkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKjAoBgNVBAMTIVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPdmVyaGVpZCBDQQ==", + "serialNumber": "ATFpsA==", + "id": "325bb598-839b-f7aa-8a22-08ab8c09e803", + "last_modified": 1480349162533 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:45:50Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "UV9aaDeNRNtQuXjRYk4Skhg=", + "id": "c11dd0af-83d5-61ec-63af-5b816a3ae557", + "last_modified": 1480349162506 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "bzTw0uq05TUYEGS98bh0Ww==", + "id": "6b743fc3-1ce6-8dfe-52f1-9f3e6a31f15a", + "last_modified": 1480349162483 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:21:30Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "Gd/pPu+qLnXUdvP9sW73CQ==", + "id": "2da11236-c2d8-7804-7de3-ffd711406b04", + "last_modified": 1480349162460 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:09Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABJ/v3ZwA=", + "id": "9f6615be-a0b9-519a-1e26-2bd7aaf5953b", + "last_modified": 1480349162438 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:39:05Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABGMGjftY=", + "id": "5f1ab732-8d48-0102-fb50-46ce74fc1a90", + "last_modified": 1480349162415 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "Rvm2CEw2IC2Mu/ax0A46QQ==", + "id": "82f97501-85ba-bb19-f68f-ca7ce68ca7b3", + "last_modified": 1480349162392 + }, + { + "schema": 1552493033534, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MDsxGDAWBgNVBAoTD0N5YmVydHJ1c3QsIEluYzEfMB0GA1UEAxMWQ3liZXJ0cnVzdCBHbG9iYWwgUm9vdA==", + "serialNumber": "BAAAAAABJpQ0AbA=", + "id": "520cee2c-e36a-936e-d551-a51ee9c659c8", + "last_modified": 1480349162369 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "ByfHkw==", + "id": "74d82589-0fde-91da-20cc-22fbd84cd0aa", + "last_modified": 1480349161997 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESC8DawWRiAyEMd38UXbfgPR", + "id": "6147b64f-da46-ba30-2b9e-30b515e8f6b9", + "last_modified": 1480349161969 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABK84ykc0=", + "id": "0a42070f-3149-2b91-1bbe-c54de4ed1be8", + "last_modified": 1480349161946 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnM=", + "id": "dc28966b-d7db-b55f-1606-c5bff610bf99", + "last_modified": 1480349161925 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "FJl6tXgNpSg=", + "id": "bab58b65-8ef7-f3c2-fc13-88f72a0840f7", + "last_modified": 1480349161902 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESDItX4ruWiLnrlz0rk4/bmz", + "id": "c12d5f3c-b722-948f-b367-857845ed5e8e", + "last_modified": 1480349161872 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==", + "serialNumber": "bx/XHJqcwxDOptxJ2lh5vw==", + "id": "a0da0bd1-8bec-4c82-fb9b-636ddcde057d", + "last_modified": 1480349161850 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:47:39Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "Mq0P6o03FDk0B2bnJ+mYPGo=", + "id": "54f096a5-3608-9b68-ddca-a200e799ba06", + "last_modified": 1480349161828 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:43:00Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABJ/ufQg8=", + "id": "61db13cb-74d9-e675-a1e8-db5f78eb3f4e", + "last_modified": 1480349161807 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABM6d3Z0s=", + "id": "b9c439ef-fcf3-2263-c14e-80727cbef898", + "last_modified": 1480349161785 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==", + "serialNumber": "FNISyWWTGi5Yco6fGh58/A==", + "id": "bab0ff94-c98e-7843-97b3-a4d1e044715b", + "last_modified": 1480349161763 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==", + "serialNumber": "VfTSum25nb65YPlpuhJAvg==", + "id": "91149dc9-6cc4-a670-1799-7bd05c159858", + "last_modified": 1480349161740 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH8xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEwMC4GA1UEAxMnU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBFViBDQSAtIEcy", + "serialNumber": "OhrtngFwotLcm4i+z00SjA==", + "id": "337e2f4b-02c4-c70d-55be-09c8ea41731c", + "last_modified": 1480349161718 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1181126", + "who": ".", + "why": ".", + "name": "RCS Certification Authority", + "created": "2015-07-13T09:05:40Z" + }, + "enabled": true, + "issuerName": "MDcxJDAiBgNVBAMTG1JDUyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEPMA0GA1UEChMGSFQgc3Js", + "serialNumber": "AN9bfYOvlR1t", + "id": "18c0d37b-739d-04c6-04f2-a615be5b9948", + "last_modified": 1480349161696 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F5BhENPfVw==", + "id": "72a65abf-9828-365f-4d4a-78457d7bdec4", + "last_modified": 1480349161666 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESISuBo/wdW2tBztKmHdFCFz", + "id": "2ad44cbf-32ca-5245-c881-6d6f290ac2c1", + "last_modified": 1480349161641 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1205651", + "who": ".", + "why": ".", + "name": "Misused certificate", + "created": "2015-09-21T13:21:25Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHjAcBgNVBAMTFXRoYXd0ZSBFViBTU0wgQ0EgLSBHMw==", + "serialNumber": "CrTHPEE6AZSfI3jysin2bA==", + "id": "13987e52-821e-1fe3-3743-393136b17167", + "last_modified": 1480349161619 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==", + "serialNumber": "WX89jn8yGZVvoKTD9jDfRQ==", + "id": "a15c9fa5-cd6e-362d-1341-1e95c11c38fb", + "last_modified": 1480349161596 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1252142", + "who": ".", + "why": ".", + "name": "exceptional SHA-1 Certificates", + "created": "2016-03-01T21:22:54Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "JV/LVzSKI/wsDgg3UuZHlA==", + "id": "ba7935ef-b624-3eef-5ae8-a78044ec3af0", + "last_modified": 1480349161575 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA2IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNQ==", + "serialNumber": "buROL/l2GuXISv+/JVLkdA==", + "id": "963548ca-bde2-1843-f140-1c26e8e40def", + "last_modified": 1480349161531 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cfk9oA==", + "id": "e85980d5-12f0-f624-7904-2d6cedeacd55", + "last_modified": 1480349161510 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "IyIVazG4RE9AERkb+ekH8w==", + "id": "be3014a7-3d3b-08c7-02d9-5a74f601a1b1", + "last_modified": 1480349161489 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "HNo1DR4XCe4mS1iUMsY6Wg==", + "id": "fd51f7d8-2006-663f-4779-3cf559ee5e74", + "last_modified": 1480349161466 + }, + { + "schema": 1552493034992, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "Ai7cBJYqBE0I9NdyoZfRrw==", + "id": "f7582bc8-dbdb-37dd-b24c-418242bbc4e0", + "last_modified": 1480349161443 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "UUFV3S2cUidOOv7ESN65Ng==", + "id": "24ccf065-b88b-0fc9-1d1f-6c7722ca4faa", + "last_modified": 1480349161103 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "EDQMI0tR4kSntv1O37N10g==", + "id": "e7509e16-1cd3-599e-2630-495ab52d3b71", + "last_modified": 1480349161077 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:48:11Z" + }, + "enabled": true, + "issuerName": "MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB", + "serialNumber": "BAAAAAABI54PryQ=", + "id": "e3bd531e-1ee4-7407-27ce-6fdc9cecbbdc", + "last_modified": 1480349161051 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byc68g==", + "id": "308f27b5-fc0e-da9a-2b7f-e65a1cb5cd47", + "last_modified": 1480349161021 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MHMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEkMCIGA1UEAxMbU3ltYW50ZWMgQ2xhc3MgMyBEU0EgU1NMIENB", + "serialNumber": "AuhvPsYZfVP6UDsuyjeZ4Q==", + "id": "a0482eed-1e3f-7b9f-a433-94e4a4da49a1", + "last_modified": 1480349160991 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:39:59Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABMrS7t2g=", + "id": "41a888b9-2e84-9782-69dc-d9153a3bd3aa", + "last_modified": 1480349160963 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1197885", + "who": ".", + "why": ".", + "name": "SECOM intermediate certificate", + "created": "2016-07-21T16:52:32Z" + }, + "enabled": true, + "issuerName": "MFAxCzAJBgNVBAYTAkpQMRgwFgYDVQQKEw9TRUNPTSBUcnVzdC5uZXQxJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmljYXRpb24gUm9vdENBMQ==", + "serialNumber": "Ermwxw==", + "id": "1220feb9-9e66-0b24-3409-c5d1a1f8d24f", + "last_modified": 1480349160941 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:36:53Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==", + "serialNumber": "BAAAAAABCfhiO+s=", + "id": "e17ded97-6669-eecc-54fa-81f9a81ed281", + "last_modified": 1480349160914 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1288354", + "who": ".", + "why": ".", + "name": "Symantec AATL ECC Intermediate CA cert", + "created": "2016-07-21T16:58:57Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA3IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHNA==", + "serialNumber": "cXXMzbWDHMIdCotb3h64yw==", + "id": "a9970f7e-bac4-3b91-eae3-b0335ce0d1c2", + "last_modified": 1480349160892 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==", + "serialNumber": "OqQ2rV0ISTc308Z/oQgzFw==", + "id": "781f8787-6508-d6aa-c508-13ac2bb0d930", + "last_modified": 1480349160870 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cyr1PA==", + "id": "ec93f86d-fea2-7eea-42d4-7cf7a397e097", + "last_modified": 1480349160848 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "F5Bg+EziQQ==", + "id": "64229466-b4da-c63f-e088-ab1b5ba37930", + "last_modified": 1480349160826 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:09Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIyMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABEAuMoRs=", + "id": "fe2dd507-9fc4-a6db-7ee0-255452bea28a", + "last_modified": 1480349160804 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:39:24Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABHkSHlSo=", + "id": "04460239-09d9-2a76-191e-f344a8e5d0bd", + "last_modified": 1480349160782 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:41:08Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABKB/OGqI=", + "id": "6b5475ec-8181-e7b8-4245-c7e2ffed213c", + "last_modified": 1480349160759 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESDu2nhlLPzfx+LYgjlYFP/k", + "id": "dc98c4eb-836f-6ca0-6673-6678fc45516a", + "last_modified": 1480349160736 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1289808", + "who": ".", + "why": ".", + "name": "FNMT revoked intermediate certificates", + "created": "2016-07-28T12:14:46Z" + }, + "enabled": true, + "issuerName": "MDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTQ==", + "serialNumber": "BA==", + "id": "765f426d-748a-5f3d-e409-35eac9be8240", + "last_modified": 1480349160714 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MIGFMQswCQYDVQQGEwJVUzEgMB4GA1UECgwXV2VsbHMgRmFyZ28gV2VsbHNTZWN1cmUxHDAaBgNVBAsME1dlbGxzIEZhcmdvIEJhbmsgTkExNjA0BgNVBAMMLVdlbGxzU2VjdXJlIFB1YmxpYyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eQ==", + "serialNumber": "Aw==", + "id": "644e3fde-7ab5-556a-85ef-c5fac8b8c7de", + "last_modified": 1480349160684 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABMYnGRuw=", + "id": "0a9335d8-3fa3-5d0b-7b27-72ddd14b5f74", + "last_modified": 1480349160660 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "Byd5cg==", + "id": "f046b001-5aa9-09b4-995d-23ad9f15db30", + "last_modified": 1480349160638 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESJJweWBPhoXAaB9c8SHwI4O", + "id": "1bdc6228-2f9d-ad26-30b2-408959ede857", + "last_modified": 1480349160614 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xMjAwBgNVBAMMKVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPcmdhbmlzYXRpZSBDQSAtIEcy", + "serialNumber": "LTRcDHabRHU=", + "id": "86e95439-a9f5-e04e-5241-268cf0186425", + "last_modified": 1480349160593 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "YNOos6YJoPC77qwSGCpb7w==", + "id": "3c4f4898-da76-0a38-a765-18d9436285f2", + "last_modified": 1480349160571 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABHkSHki0=", + "id": "e057bdab-4ad6-7e50-83ce-1099b84cce04", + "last_modified": 1480349160547 + }, + { + "schema": 1552493036467, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1286752", + "who": ".", + "why": ".", + "name": "Symantec erroneous SHA-1 certificates", + "created": "2016-07-14T14:40:23Z" + }, + "enabled": true, + "issuerName": "MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=", + "serialNumber": "A9GPKQ8jv9oIxfwiOy7qxQ==", + "id": "4bb8966b-1db9-844d-4904-d823539cdf7e", + "last_modified": 1480349160524 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cbssdw==", + "id": "661e65a5-ee0b-7a82-12c3-731cb560e112", + "last_modified": 1480349160216 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CcHC1w==", + "id": "26890617-93ad-e85f-550a-afb414cd110c", + "last_modified": 1480349160190 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "DjIvBkX+ECVbB/C3i6w2Gg==", + "id": "657b91b1-9447-8c4a-db57-e0f0e2b10439", + "last_modified": 1480349160165 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1150585", + "who": ".", + "why": ".", + "name": "XS4ALL certificate", + "created": "2015-04-07T11:04:11Z" + }, + "enabled": true, + "issuerName": "MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB", + "serialNumber": "UoRGnb96CUDTxIqVry6LBg==", + "id": "9084bc79-01cf-2ddc-b077-cd6c6251dd2b", + "last_modified": 1480349160143 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "PAdKZPiaac2CvPxbOrsHOw==", + "id": "b79e2eb5-754d-4c21-1d8a-8c363f70dadc", + "last_modified": 1480349160119 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIGQMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxOzA5BgNVBAMTMkFyaXN0b3RsZSBVbml2ZXJzaXR5IG9mIFRoZXNzYWxvbmlraSBDZW50cmFsIENBIFI0", + "serialNumber": "EqthLKdUgwI=", + "id": "5b9744a8-bd65-b926-2920-8d8e5e1acd7d", + "last_modified": 1480349160086 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==", + "serialNumber": "OE4/d+p3YRzzcSl+kmZ8Mw==", + "id": "fd2fe6f3-d095-5c3d-3c6b-afe00cb665c0", + "last_modified": 1480349160058 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MIGCMQswCQYDVQQGEwJVUzEeMBwGA1UECxMVd3d3LnhyYW1wc2VjdXJpdHkuY29tMSQwIgYDVQQKExtYUmFtcCBTZWN1cml0eSBTZXJ2aWNlcyBJbmMxLTArBgNVBAMTJFhSYW1wIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eQ==", + "serialNumber": "QZCrvA==", + "id": "32f968f5-4426-66ad-d1ab-9dda7b6f0c85", + "last_modified": 1480349160028 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "AygWP2Fgd2T+iLbmAlKT6g==", + "id": "e23bcca2-1fb1-abd2-5ed7-892c5f08c4ff", + "last_modified": 1480349159993 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "CqL7CA==", + "id": "1a0c71ac-7f34-eeb0-2f71-1c14ee8e6552", + "last_modified": 1480349159963 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=", + "serialNumber": "E77H6yvyFQjO0PcN3x0H+Q==", + "id": "be9e31eb-6a06-7699-708a-732a081a10c7", + "last_modified": 1480349159939 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:46:04Z" + }, + "enabled": true, + "issuerName": "MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==", + "serialNumber": "YRJNfMoc12IpmW+Enpv3Pdo=", + "id": "3bb8f2d9-511b-121d-da29-23041d3c15c1", + "last_modified": 1480349159914 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155114", + "who": ".", + "why": ".", + "name": "Intermediate CA's under Staat der Nederlanden Root CA", + "created": "2015-05-08T10:54:05Z" + }, + "enabled": true, + "issuerName": "MFkxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKjAoBgNVBAMTIVN0YWF0IGRlciBOZWRlcmxhbmRlbiBPdmVyaGVpZCBDQQ==", + "serialNumber": "ATFEdg==", + "id": "4a17f132-602c-2d30-a781-145087794075", + "last_modified": 1480349159890 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==", + "serialNumber": "e0bEFhI16xx9U1yvlI56rA==", + "id": "07b97387-d755-0bbb-6e4c-616a7cb69cb4", + "last_modified": 1480349159869 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "GN2Hrh9LtnA=", + "id": "b3fb394d-f951-b49e-d856-f93028850feb", + "last_modified": 1480349159846 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "AQAAAAU=", + "id": "2448ada0-833d-0d4d-3bcd-2c73cff02fb4", + "last_modified": 1480349159825 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:38:03Z" + }, + "enabled": true, + "issuerName": "MF8xCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRQwEgYDVQQLEwtQYXJ0bmVycyBDQTEfMB0GA1UEAxMWR2xvYmFsU2lnbiBQYXJ0bmVycyBDQQ==", + "serialNumber": "BAAAAAABCFiEp9s=", + "id": "5cef5dc2-95f2-0ac8-3273-153a09d3d227", + "last_modified": 1480349159804 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "CSY=", + "id": "b4c9041b-f5a5-dec1-b221-7286a32a6309", + "last_modified": 1480349159782 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:44:06Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABHJRKNmk=", + "id": "824e9b09-808e-e8cb-f2dd-ba669df011bc", + "last_modified": 1480349159760 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "DYifRdP6aQQ8MLbXZY2f5g==", + "id": "59a292db-d567-6485-167f-1cb5af33f437", + "last_modified": 1480349159739 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFYxCzAJBgNVBAYTAkpQMQ8wDQYDVQQKEwZKSVBERUMxGjAYBgNVBAsTEUpDQU4gU3ViIFJvb3QgQ0EwMRowGAYDVQQDExFKQ0FOIFN1YiBSb290IENBMA==", + "serialNumber": "BAAAAAABL07hTcY=", + "id": "0a5a0233-bb8a-e4cc-5bb6-03cee44a07c2", + "last_modified": 1480349159716 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "L7tgs/W85vnhV7I7qJ6N/g==", + "id": "e38e4e46-413a-eac8-9e85-1b9ee06f535d", + "last_modified": 1480349159694 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:40:36Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABLM/7qjk=", + "id": "62dfeb32-e132-09a3-e4ba-e48c21d027de", + "last_modified": 1480349159673 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:41:41Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABHkSHjz8=", + "id": "880134d0-617c-ec1b-3568-4c74af31bcc1", + "last_modified": 1480349159649 + }, + { + "schema": 1552493037901, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpERk4tVmVyZWluMRAwDgYDVQQLEwdERk4tUEtJMSQwIgYDVQQDExtERk4tVmVyZWluIFBDQSBHbG9iYWwgLSBHMDE=", + "serialNumber": "Cfk9qg==", + "id": "9debdd66-d615-073f-26f7-011a8c54b484", + "last_modified": 1480349159625 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "AQAAAAM=", + "id": "e4d16e1f-ac86-e9c0-2122-8a7d7d2dcdc0", + "last_modified": 1480349159267 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESAyW/JX3+hZIp44EAMlXU2b", + "id": "6b135741-bfc6-1b3f-eb4b-4569aae2ab34", + "last_modified": 1480349159244 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGVMQswCQYDVQQGEwJHUjFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTE=", + "serialNumber": "AQAAAAQ=", + "id": "263f03a7-53f0-2140-89a5-46e7740d755a", + "last_modified": 1480349159206 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESByYNtAIfizf2L3NMzCH8zZ", + "id": "853889e5-ad93-77ea-f9c0-a0907c9a3576", + "last_modified": 1480349159183 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "BYOGvG32ukb1Yxj2oKoFyw==", + "id": "b049208e-5f4c-60a6-ac91-45f093defc33", + "last_modified": 1480349159159 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=", + "serialNumber": "P6G7IYSL2RZxtzTh8I6qPA==", + "id": "222a4201-120e-64f4-5f6f-b2314470365c", + "last_modified": 1480349159136 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:12Z" + }, + "enabled": true, + "issuerName": "MIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UEAxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls", + "serialNumber": "D/wZ7+m1Mv8SONSEFcs73w==", + "id": "d7fdf5b8-f8a5-b105-297d-d0e3617e59fc", + "last_modified": 1480349159113 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MIG9MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5", + "serialNumber": "eR1nUEz8k+nDSBD+bb5uIQ==", + "id": "f6fb7b44-64cd-37b9-b9f6-fbe1210cb160", + "last_modified": 1480349159091 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:08Z" + }, + "enabled": true, + "issuerName": "MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==", + "serialNumber": "QZBvapTZFvmYktEPsBYLQQ==", + "id": "b6f5b3ee-6cac-57a7-b4e9-83e84f1020b8", + "last_modified": 1480349159069 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABKUXDqA8=", + "id": "e48f4383-6df1-0a9b-8063-a2add391a879", + "last_modified": 1480349159034 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWduIFJvb3QgQ0E=", + "serialNumber": "BAAAAAABLF5/Gog=", + "id": "bc0be7d5-b8d3-1ac2-8a63-f5bf8b00ba3c", + "last_modified": 1480349159011 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=", + "serialNumber": "UW3oKZKTDsrPy/rfwmGNaQ==", + "id": "2a14bce1-47bb-e067-74a6-46be30e576a7", + "last_modified": 1480349158980 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDM=", + "serialNumber": "CSU=", + "id": "77e152ae-89a4-53b1-62d4-fb7c2cd5037b", + "last_modified": 1480349158957 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:14Z" + }, + "enabled": true, + "issuerName": "MG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3Q=", + "serialNumber": "U3t2Vk8pfxTcaUPpIq0seQ==", + "id": "2c8f7191-fc5f-c2e0-af8d-852faefcdadf", + "last_modified": 1480349158934 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + "who": ".", + "why": ".", + "name": "GlobalSign certs", + "created": "2016-01-18T14:38:46Z" + }, + "enabled": true, + "issuerName": "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + "serialNumber": "BAAAAAABAPpuVh0=", + "id": "fce6cca1-707c-c4f2-6de2-26c4663cda01", + "last_modified": 1480349158899 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MIHKMQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMw==", + "serialNumber": "U4P1tUoxl/XkztlVHdtdgw==", + "id": "ed4d2530-eb3e-800f-41b2-e70023185673", + "last_modified": 1480349158876 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MHsxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEsMCoGA1UEAxMjU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBTU0wgQ0E=", + "serialNumber": "U3SgRR3J+D6575WuCxuXeQ==", + "id": "72e44e74-fb5d-fc97-abdd-02050d3ab727", + "last_modified": 1480349158854 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MIGXMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEfMB0GA1UEAxMWVVROLVVTRVJGaXJzdC1IYXJkd2FyZQ==", + "serialNumber": "EEpERSryZFMagbsNw/WoWQ==", + "id": "a4f11626-67e9-24e5-81a5-aca8126934c3", + "last_modified": 1480349158820 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MEwxIDAeBgNVBAsTF0dsb2JhbFNpZ24gUm9vdCBDQSAtIFIzMRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu", + "serialNumber": "BAAAAAABJQcQQN0=", + "id": "e7328f54-0f56-48a2-3bab-d4c9b41a2b13", + "last_modified": 1480349158794 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:11Z" + }, + "enabled": true, + "issuerName": "MFoxCzAJBgNVBAYTAklFMRIwEAYDVQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxIjAgBgNVBAMTGUJhbHRpbW9yZSBDeWJlclRydXN0IFJvb3Q=", + "serialNumber": "BydKkg==", + "id": "6b9bc0cc-c1fa-50eb-5ece-812ee9bac5b6", + "last_modified": 1480349158772 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:07Z" + }, + "enabled": true, + "issuerName": "MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==", + "serialNumber": "cpqpXVWPk5AXzGw+zNIcBw==", + "id": "ac8d05cd-d85d-8caa-ed13-261b83c183c2", + "last_modified": 1480349158750 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1267648", + "who": ".", + "why": ".", + "name": "Symantec test certificates", + "created": "2016-04-28T21:08:06Z" + }, + "enabled": true, + "issuerName": "MGExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEbMBkGA1UEAxMSR2VvVHJ1c3QgRFYgU1NMIENB", + "serialNumber": "CWhp", + "id": "ff01b36d-3bcc-2157-1f89-b1841120c97e", + "last_modified": 1480349158726 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:13Z" + }, + "enabled": true, + "issuerName": "MD0xCzAJBgNVBAYTAkZSMREwDwYDVQQKEwhDZXJ0cGx1czEbMBkGA1UEAxMSQ2xhc3MgMiBQcmltYXJ5IENB", + "serialNumber": "ESCEUbthDurBjJw0/h/FfuNY", + "id": "2c047b2c-43aa-d0be-e2c9-2acac789bd55", + "last_modified": 1480349158695 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1312150", + "who": ".", + "why": ".", + "name": "Revoked intermediates", + "created": "2016-10-27T17:52:10Z" + }, + "enabled": true, + "issuerName": "MEoxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdTZWN1cmVUcnVzdCBDb3Jwb3JhdGlvbjEZMBcGA1UEAxMQU2VjdXJlIEdsb2JhbCBDQQ==", + "serialNumber": "TXxtAQ==", + "id": "ea4847df-ca17-c476-11df-b14c1d6658a0", + "last_modified": 1480349158671 + }, + { + "schema": 1552493039345, + "details": { + "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1142137", + "who": ".", + "why": ".", + "name": "T-Systems intermediate cert", + "created": "2015-05-08T10:51:23Z" + }, + "enabled": true, + "issuerName": "MGcxCzAJBgNVBAYTAkRFMRMwEQYDVQQKEwpGcmF1bmhvZmVyMSEwHwYDVQQLExhGcmF1bmhvZmVyIENvcnBvcmF0ZSBQS0kxIDAeBgNVBAMTF0ZyYXVuaG9mZXIgUm9vdCBDQSAyMDA3", + "serialNumber": "YR3YYQAAAAAABA==", + "id": "ae8bec3c-3b92-822e-53f1-68394cbb1758", + "last_modified": 1480349158647 + } + ], + "timestamp": 1710189695302 +} diff --git a/services/settings/moz.build b/services/settings/moz.build new file mode 100644 index 0000000000..6c3f622d56 --- /dev/null +++ b/services/settings/moz.build @@ -0,0 +1,37 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Remote Settings Client") + +DIRS += [ + "dumps", + "static-dumps", +] + +EXTRA_COMPONENTS += [ + "servicesSettings.manifest", +] + +EXTRA_JS_MODULES["services-settings"] += [ + "Attachments.sys.mjs", + "Database.sys.mjs", + "IDBHelpers.sys.mjs", + "remote-settings.sys.mjs", + "RemoteSettings.worker.mjs", + "RemoteSettingsClient.sys.mjs", + "RemoteSettingsComponents.sys.mjs", + "RemoteSettingsWorker.sys.mjs", + "SharedUtils.sys.mjs", + "SyncHistory.sys.mjs", + "Utils.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.toml"] + +SPHINX_TREES["/services/settings"] = "docs" diff --git a/services/settings/remote-settings.sys.mjs b/services/settings/remote-settings.sys.mjs new file mode 100644 index 0000000000..c06e99eb2a --- /dev/null +++ b/services/settings/remote-settings.sys.mjs @@ -0,0 +1,610 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Database: "resource://services-settings/Database.sys.mjs", + FilterExpressions: + "resource://gre/modules/components-utils/FilterExpressions.sys.mjs", + pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs", + RemoteSettingsClient: + "resource://services-settings/RemoteSettingsClient.sys.mjs", + SyncHistory: "resource://services-settings/SyncHistory.sys.mjs", + UptakeTelemetry: "resource://services-common/uptake-telemetry.sys.mjs", + Utils: "resource://services-settings/Utils.sys.mjs", +}); + +const PREF_SETTINGS_BRANCH = "services.settings."; +const PREF_SETTINGS_SERVER_BACKOFF = "server.backoff"; +const PREF_SETTINGS_LAST_UPDATE = "last_update_seconds"; +const PREF_SETTINGS_LAST_ETAG = "last_etag"; +const PREF_SETTINGS_CLOCK_SKEW_SECONDS = "clock_skew_seconds"; +const PREF_SETTINGS_SYNC_HISTORY_SIZE = "sync_history_size"; +const PREF_SETTINGS_SYNC_HISTORY_ERROR_THRESHOLD = + "sync_history_error_threshold"; + +// Telemetry identifiers. +const TELEMETRY_COMPONENT = "remotesettings"; +const TELEMETRY_SOURCE_POLL = "settings-changes-monitoring"; +const TELEMETRY_SOURCE_SYNC = "settings-sync"; + +// Push broadcast id. +const BROADCAST_ID = "remote-settings/monitor_changes"; + +// Signer to be used when not specified (see Ci.nsIContentSignatureVerifier). +const DEFAULT_SIGNER = "remote-settings.content-signature.mozilla.org"; + +ChromeUtils.defineLazyGetter(lazy, "gPrefs", () => { + return Services.prefs.getBranch(PREF_SETTINGS_BRANCH); +}); +ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log); + +ChromeUtils.defineLazyGetter(lazy, "gSyncHistory", () => { + const prefSize = lazy.gPrefs.getIntPref(PREF_SETTINGS_SYNC_HISTORY_SIZE, 100); + const size = Math.min(Math.max(prefSize, 1000), 10); + return new lazy.SyncHistory(TELEMETRY_SOURCE_SYNC, { size }); +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gPrefBrokenSyncThreshold", + PREF_SETTINGS_BRANCH + PREF_SETTINGS_SYNC_HISTORY_ERROR_THRESHOLD, + 10 +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gPrefDestroyBrokenEnabled", + PREF_SETTINGS_BRANCH + "destroy_broken_db_enabled", + true +); + +/** + * Default entry filtering function, in charge of excluding remote settings entries + * where the JEXL expression evaluates into a falsy value. + * @param {Object} entry The Remote Settings entry to be excluded or kept. + * @param {ClientEnvironment} environment Information about version, language, platform etc. + * @returns {?Object} the entry or null if excluded. + */ +export async function jexlFilterFunc(entry, environment) { + const { filter_expression } = entry; + if (!filter_expression) { + return entry; + } + let result; + try { + const context = { + env: environment, + }; + result = await lazy.FilterExpressions.eval(filter_expression, context); + } catch (e) { + console.error(e); + } + return result ? entry : null; +} + +function remoteSettingsFunction() { + const _clients = new Map(); + let _invalidatePolling = false; + + // If not explicitly specified, use the default signer. + const defaultOptions = { + signerName: DEFAULT_SIGNER, + filterFunc: jexlFilterFunc, + }; + + /** + * RemoteSettings constructor. + * + * @param {String} collectionName The remote settings identifier + * @param {Object} options Advanced options + * @returns {RemoteSettingsClient} An instance of a Remote Settings client. + */ + const remoteSettings = function (collectionName, options) { + // Get or instantiate a remote settings client. + if (!_clients.has(collectionName)) { + // Register a new client! + const c = new lazy.RemoteSettingsClient(collectionName, { + ...defaultOptions, + ...options, + }); + // Store instance for later call. + _clients.set(collectionName, c); + // Invalidate the polling status, since we want the new collection to + // be taken into account. + _invalidatePolling = true; + lazy.console.debug(`Instantiated new client ${c.identifier}`); + } + return _clients.get(collectionName); + }; + + /** + * Internal helper to retrieve existing instances of clients or new instances + * with default options if possible, or `null` if bucket/collection are unknown. + */ + async function _client(bucketName, collectionName) { + // Check if a client was registered for this bucket/collection. Potentially + // with some specific options like signer, filter function etc. + const client = _clients.get(collectionName); + if (client && client.bucketName == bucketName) { + return client; + } + // There was no client registered for this collection, but it's the main bucket, + // therefore we can instantiate a client with the default options. + // So if we have a local database or if we ship a JSON dump, then it means that + // this client is known but it was not registered yet (eg. calling module not "imported" yet). + if ( + bucketName == + lazy.Utils.actualBucketName(AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET) + ) { + const c = new lazy.RemoteSettingsClient(collectionName, defaultOptions); + const [dbExists, localDump] = await Promise.all([ + lazy.Utils.hasLocalData(c), + lazy.Utils.hasLocalDump(bucketName, collectionName), + ]); + if (dbExists || localDump) { + return c; + } + } + // Else, we cannot return a client instance because we are not able to synchronize data in specific buckets. + // Mainly because we cannot guess which `signerName` has to be used for example. + // And we don't want to synchronize data for collections in the main bucket that are + // completely unknown (ie. no database and no JSON dump). + lazy.console.debug(`No known client for ${bucketName}/${collectionName}`); + return null; + } + + /** + * Helper to introspect the synchronization history and determine whether it is + * consistently failing and thus, broken. + * @returns {bool} true if broken. + */ + async function isSynchronizationBroken() { + // The minimum number of errors is customizable, but with a maximum. + const threshold = Math.min(lazy.gPrefBrokenSyncThreshold, 20); + // Read history of synchronization past statuses. + const pastEntries = await lazy.gSyncHistory.list(); + const lastSuccessIdx = pastEntries.findIndex( + e => e.status == lazy.UptakeTelemetry.STATUS.SUCCESS + ); + return ( + // Only errors since last success. + lastSuccessIdx >= threshold || + // Or only errors with a minimum number of history entries. + (lastSuccessIdx < 0 && pastEntries.length >= threshold) + ); + } + + /** + * Main polling method, called by the ping mechanism. + * + * @param {Object} options +. * @param {Object} options.expectedTimestamp (optional) The expected timestamp to be received — used by servers for cache busting. + * @param {string} options.trigger (optional) label to identify what triggered this sync (eg. ``"timer"``, default: `"manual"`) + * @param {bool} options.full (optional) Ignore last polling status and fetch all changes (default: `false`) + * @returns {Promise} or throws error if something goes wrong. + */ + remoteSettings.pollChanges = async ({ + expectedTimestamp, + trigger = "manual", + full = false, + } = {}) => { + if (lazy.Utils.shouldSkipRemoteActivityDueToTests) { + return; + } + // When running in full mode, we ignore last polling status. + if (full) { + lazy.gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF); + lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_UPDATE); + lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG); + } + + let pollTelemetryArgs = { + source: TELEMETRY_SOURCE_POLL, + trigger, + }; + + if (lazy.Utils.isOffline) { + lazy.console.info("Network is offline. Give up."); + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR, + pollTelemetryArgs + ); + return; + } + + const startedAt = new Date(); + + // Check if the server backoff time is elapsed. + if (lazy.gPrefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) { + const backoffReleaseTime = lazy.gPrefs.getStringPref( + PREF_SETTINGS_SERVER_BACKOFF + ); + const remainingMilliseconds = + parseInt(backoffReleaseTime, 10) - Date.now(); + if (remainingMilliseconds > 0) { + // Backoff time has not elapsed yet. + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + lazy.UptakeTelemetry.STATUS.BACKOFF, + pollTelemetryArgs + ); + throw new Error( + `Server is asking clients to back off; retry in ${Math.ceil( + remainingMilliseconds / 1000 + )}s.` + ); + } else { + lazy.gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF); + } + } + + // When triggered from the daily timer, we try to recover a broken + // sync state by destroying the local DB completely and retrying from scratch. + if ( + lazy.gPrefDestroyBrokenEnabled && + trigger == "timer" && + (await isSynchronizationBroken()) + ) { + // We don't want to destroy the local DB if the failures are related to + // network or server errors though. + const lastStatus = await lazy.gSyncHistory.last(); + const lastErrorClass = + lazy.RemoteSettingsClient[lastStatus?.infos?.errorName] || Error; + const isLocalError = !( + lastErrorClass.prototype instanceof lazy.RemoteSettingsClient.APIError + ); + if (isLocalError) { + console.warn( + "Synchronization has failed consistently. Destroy database." + ); + // Clear the last ETag to refetch everything. + lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG); + // Clear the history, to avoid re-destroying several times in a row. + await lazy.gSyncHistory.clear().catch(error => console.error(error)); + // Delete the whole IndexedDB database. + await lazy.Database.destroy().catch(error => console.error(error)); + } else { + console.warn( + `Synchronization is broken, but last error is ${lastStatus}` + ); + } + } + + lazy.console.info("Start polling for changes"); + Services.obs.notifyObservers( + null, + "remote-settings:changes-poll-start", + JSON.stringify({ expectedTimestamp }) + ); + + // Do we have the latest version already? + // Every time we register a new client, we have to fetch the whole list again. + const lastEtag = _invalidatePolling + ? "" + : lazy.gPrefs.getStringPref(PREF_SETTINGS_LAST_ETAG, ""); + + let pollResult; + try { + pollResult = await lazy.Utils.fetchLatestChanges(lazy.Utils.SERVER_URL, { + expectedTimestamp, + lastEtag, + }); + } catch (e) { + // Report polling error to Uptake Telemetry. + let reportStatus; + if (/JSON\.parse/.test(e.message)) { + reportStatus = lazy.UptakeTelemetry.STATUS.PARSE_ERROR; + } else if (/content-type/.test(e.message)) { + reportStatus = lazy.UptakeTelemetry.STATUS.CONTENT_ERROR; + } else if (/Server/.test(e.message)) { + reportStatus = lazy.UptakeTelemetry.STATUS.SERVER_ERROR; + // If the server replied with bad request, clear the last ETag + // value to unblock the next run of synchronization. + lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG); + } else if (/Timeout/.test(e.message)) { + reportStatus = lazy.UptakeTelemetry.STATUS.TIMEOUT_ERROR; + } else if (/NetworkError/.test(e.message)) { + reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR; + } else { + reportStatus = lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR; + } + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + reportStatus, + pollTelemetryArgs + ); + // No need to go further. + throw new Error(`Polling for changes failed: ${e.message}.`); + } + + const { + serverTimeMillis, + changes, + currentEtag, + backoffSeconds, + ageSeconds, + } = pollResult; + + // Report age of server data in Telemetry. + pollTelemetryArgs = { age: ageSeconds, ...pollTelemetryArgs }; + + // Report polling success to Uptake Telemetry. + const reportStatus = + changes.length === 0 + ? lazy.UptakeTelemetry.STATUS.UP_TO_DATE + : lazy.UptakeTelemetry.STATUS.SUCCESS; + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + reportStatus, + pollTelemetryArgs + ); + + // Check if the server asked the clients to back off (for next poll). + if (backoffSeconds) { + lazy.console.info( + "Server asks clients to backoff for ${backoffSeconds} seconds" + ); + const backoffReleaseTime = Date.now() + backoffSeconds * 1000; + lazy.gPrefs.setStringPref( + PREF_SETTINGS_SERVER_BACKOFF, + backoffReleaseTime + ); + } + + // Record new update time and the difference between local and server time. + // Negative clockDifference means local time is behind server time + // by the absolute of that value in seconds (positive means it's ahead) + const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000); + lazy.gPrefs.setIntPref(PREF_SETTINGS_CLOCK_SKEW_SECONDS, clockDifference); + const checkedServerTimeInSeconds = Math.round(serverTimeMillis / 1000); + lazy.gPrefs.setIntPref( + PREF_SETTINGS_LAST_UPDATE, + checkedServerTimeInSeconds + ); + + // Iterate through the collections version info and initiate a synchronization + // on the related remote settings clients. + let firstError; + for (const change of changes) { + const { bucket, collection, last_modified } = change; + + const client = await _client(bucket, collection); + if (!client) { + // This collection has no associated client (eg. preview, other platform...) + continue; + } + // Start synchronization! It will be a no-op if the specified `lastModified` equals + // the one in the local database. + try { + await client.maybeSync(last_modified, { trigger }); + + // Save last time this client was successfully synced. + Services.prefs.setIntPref( + client.lastCheckTimePref, + checkedServerTimeInSeconds + ); + } catch (e) { + lazy.console.error(e); + if (!firstError) { + firstError = e; + firstError.details = change; + } + } + } + + // Polling is done. + _invalidatePolling = false; + + // Report total synchronization duration to Telemetry. + const durationMilliseconds = new Date() - startedAt; + const syncTelemetryArgs = { + source: TELEMETRY_SOURCE_SYNC, + duration: durationMilliseconds, + timestamp: `${currentEtag}`, + trigger, + }; + + if (firstError) { + // Report the global synchronization failure. Individual uptake reports will also have been sent for each collection. + const status = lazy.UptakeTelemetry.STATUS.SYNC_ERROR; + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + status, + syncTelemetryArgs + ); + // Keep track of sync failure in history. + await lazy.gSyncHistory + .store(currentEtag, status, { + expectedTimestamp, + errorName: firstError.name, + }) + .catch(error => console.error(error)); + // Notify potential observers of the error. + Services.obs.notifyObservers( + { wrappedJSObject: { error: firstError } }, + "remote-settings:sync-error" + ); + + // If synchronization has been consistently failing, send a specific signal. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1729400 + // and https://bugzilla.mozilla.org/show_bug.cgi?id=1658597 + if (await isSynchronizationBroken()) { + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + lazy.UptakeTelemetry.STATUS.SYNC_BROKEN_ERROR, + syncTelemetryArgs + ); + + Services.obs.notifyObservers( + { wrappedJSObject: { error: firstError } }, + "remote-settings:broken-sync-error" + ); + } + + // Rethrow the first observed error + throw firstError; + } + + // Save current Etag for next poll. + lazy.gPrefs.setStringPref(PREF_SETTINGS_LAST_ETAG, currentEtag); + + // Report the global synchronization success. + const status = lazy.UptakeTelemetry.STATUS.SUCCESS; + await lazy.UptakeTelemetry.report( + TELEMETRY_COMPONENT, + status, + syncTelemetryArgs + ); + // Keep track of sync success in history. + await lazy.gSyncHistory + .store(currentEtag, status) + .catch(error => console.error(error)); + + lazy.console.info("Polling for changes done"); + Services.obs.notifyObservers(null, "remote-settings:changes-poll-end"); + }; + + /** + * Enables or disables preview mode. + * + * When enabled, all existing and future clients will pull data from + * the `*-preview` buckets. This allows developers and QA to test their + * changes before publishing them for all clients. + */ + remoteSettings.enablePreviewMode = enabled => { + // Set the flag for future clients. + lazy.Utils.enablePreviewMode(enabled); + // Enable it on existing clients. + for (const client of _clients.values()) { + client.refreshBucketName(); + } + }; + + /** + * Returns an object with polling status information and the list of + * known remote settings collections. + */ + remoteSettings.inspect = async () => { + // Make sure we fetch the latest server info, use a random cache bust value. + const randomCacheBust = 99990000 + Math.floor(Math.random() * 9999); + const { changes, currentEtag: serverTimestamp } = + await lazy.Utils.fetchLatestChanges(lazy.Utils.SERVER_URL, { + expected: randomCacheBust, + }); + + const collections = await Promise.all( + changes.map(async change => { + const { bucket, collection, last_modified: serverTimestamp } = change; + const client = await _client(bucket, collection); + if (!client) { + return null; + } + const localTimestamp = await client.getLastModified(); + const lastCheck = Services.prefs.getIntPref( + client.lastCheckTimePref, + 0 + ); + return { + bucket, + collection, + localTimestamp, + serverTimestamp, + lastCheck, + signerName: client.signerName, + }; + }) + ); + + return { + serverURL: lazy.Utils.SERVER_URL, + pollingEndpoint: lazy.Utils.SERVER_URL + lazy.Utils.CHANGES_PATH, + serverTimestamp, + localTimestamp: lazy.gPrefs.getStringPref(PREF_SETTINGS_LAST_ETAG, null), + lastCheck: lazy.gPrefs.getIntPref(PREF_SETTINGS_LAST_UPDATE, 0), + mainBucket: lazy.Utils.actualBucketName( + AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET + ), + defaultSigner: DEFAULT_SIGNER, + previewMode: lazy.Utils.PREVIEW_MODE, + collections: collections.filter(c => !!c), + history: { + [TELEMETRY_SOURCE_SYNC]: await lazy.gSyncHistory.list(), + }, + }; + }; + + /** + * Delete all local data, of every collection. + */ + remoteSettings.clearAll = async () => { + const { collections } = await remoteSettings.inspect(); + await Promise.all( + collections.map(async ({ collection }) => { + const client = RemoteSettings(collection); + // Delete all potential attachments. + await client.attachments.deleteAll(); + // Delete local data. + await client.db.clear(); + // Remove status pref. + Services.prefs.clearUserPref(client.lastCheckTimePref); + }) + ); + }; + + /** + * Startup function called from nsBrowserGlue. + */ + remoteSettings.init = () => { + lazy.console.info("Initialize Remote Settings"); + // Hook the Push broadcast and RemoteSettings polling. + // When we start on a new profile there will be no ETag stored. + // Use an arbitrary ETag that is guaranteed not to occur. + // This will trigger a broadcast message but that's fine because we + // will check the changes on each collection and retrieve only the + // changes (e.g. nothing if we have a dump with the same data). + const currentVersion = lazy.gPrefs.getStringPref( + PREF_SETTINGS_LAST_ETAG, + '"0"' + ); + + const moduleInfo = { + moduleURI: import.meta.url, + symbolName: "remoteSettingsBroadcastHandler", + }; + lazy.pushBroadcastService.addListener( + BROADCAST_ID, + currentVersion, + moduleInfo + ); + }; + + return remoteSettings; +} + +export var RemoteSettings = remoteSettingsFunction(); + +export var remoteSettingsBroadcastHandler = { + async receivedBroadcastMessage(version, broadcastID, context) { + const { phase } = context; + const isStartup = [ + lazy.pushBroadcastService.PHASES.HELLO, + lazy.pushBroadcastService.PHASES.REGISTER, + ].includes(phase); + + lazy.console.info( + `Push notification received (version=${version} phase=${phase})` + ); + + return RemoteSettings.pollChanges({ + expectedTimestamp: version, + trigger: isStartup ? "startup" : "broadcast", + }); + }, +}; diff --git a/services/settings/servicesSettings.manifest b/services/settings/servicesSettings.manifest new file mode 100644 index 0000000000..3bfed26ea4 --- /dev/null +++ b/services/settings/servicesSettings.manifest @@ -0,0 +1,7 @@ +# Register resource aliases +resource services-settings resource://gre/modules/services-settings/ + +# Schedule polling of remote settings changes +# (default 24H, max 72H) +# see syntax https://searchfox.org/mozilla-central/rev/cc280c4be94ff8cf64a27cc9b3d6831ffa49fa45/toolkit/components/timermanager/UpdateTimerManager.jsm#155 +category update-timer RemoteSettingsComponents @mozilla.org/services/settings;1,getService,services-settings-poll-changes,services.settings.poll_interval,86400,259200 diff --git a/services/settings/static-dumps/main/doh-config.json b/services/settings/static-dumps/main/doh-config.json new file mode 100644 index 0000000000..030fa75569 --- /dev/null +++ b/services/settings/static-dumps/main/doh-config.json @@ -0,0 +1,15 @@ +{ + "data": [ + { + "providers": "cloudflare-global, nextdns-global", + "rolloutEnabled": false, + "steeringEnabled": false, + "steeringProviders": "", + "autoDefaultEnabled": false, + "autoDefaultProviders": "", + "id": "global", + "last_modified": 1621943462970 + } + ], + "timestamp": 1621943462970 +} diff --git a/services/settings/static-dumps/main/doh-providers.json b/services/settings/static-dumps/main/doh-providers.json new file mode 100644 index 0000000000..b2e62b7b01 --- /dev/null +++ b/services/settings/static-dumps/main/doh-providers.json @@ -0,0 +1,23 @@ +{ + "data": [ + { + "uri": "https://firefox.dns.nextdns.io/", + "UIName": "NextDNS", + "schema": 1621819183640, + "autoDefault": false, + "canonicalName": "", + "id": "nextdns-global", + "last_modified": 1621943542621 + }, + { + "uri": "https://mozilla.cloudflare-dns.com/dns-query", + "UIName": "Cloudflare", + "schema": 1621819221428, + "autoDefault": true, + "canonicalName": "", + "id": "cloudflare-global", + "last_modified": 1621943542615 + } + ], + "timestamp": 1621943542621 +} diff --git a/services/settings/static-dumps/main/moz.build b/services/settings/static-dumps/main/moz.build new file mode 100644 index 0000000000..cc7658a22a --- /dev/null +++ b/services/settings/static-dumps/main/moz.build @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_TARGET_FILES.defaults.settings.main += [ + "doh-config.json", + "doh-providers.json", +] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DIST_SUBDIR = "browser" diff --git a/services/settings/static-dumps/moz.build b/services/settings/static-dumps/moz.build new file mode 100644 index 0000000000..557c1d80d9 --- /dev/null +++ b/services/settings/static-dumps/moz.build @@ -0,0 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + "main", +] diff --git a/services/settings/static-dumps/readme.md b/services/settings/static-dumps/readme.md new file mode 100644 index 0000000000..dca1096fe1 --- /dev/null +++ b/services/settings/static-dumps/readme.md @@ -0,0 +1,14 @@ +# Remote Settings Initial Data + +In order to reduce the amount of data to be downloaded on first synchronization, +a JSON dump from the records present on the remote server can be shipped with the +release. + +The dumps in this directory will NOT automaticaly be kept in sync with remote. +This is useful for collections that benefit from a default iniital state but +require dynamism beyond that - e.g. DoH regional configurations. For dumps +that should automatially be kept in sync with remote, use ../dumps/. + +Dumps from dumps/ and static-dumps/ are packaged into the same resource path, +thus looking the same to the client code and also implying that filenames must +be unique between the two directories. diff --git a/services/settings/test/unit/test_attachments_downloader.js b/services/settings/test/unit/test_attachments_downloader.js new file mode 100644 index 0000000000..86dd52b729 --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader.js @@ -0,0 +1,703 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); +const { UptakeTelemetry } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" +); +const { Downloader } = ChromeUtils.importESModule( + "resource://services-settings/Attachments.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const RECORD = { + id: "1f3a0802-648d-11ea-bd79-876a8b69c377", + attachment: { + hash: "f41ed47d0f43325c9f089d03415c972ce1d3f1ecab6e4d6260665baf3db3ccee", + size: 1597, + filename: "test_file.pem", + location: + "main-workspace/some-collection/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem", + mimetype: "application/x-pem-file", + }, +}; + +const RECORD_OF_DUMP = { + id: "filename-of-dump.txt", + attachment: { + filename: "filename-of-dump.txt", + hash: "4c46ef7e4f1951d210fe54c21e07c09bab265fd122580083ed1d6121547a8c6b", + size: 25, + }, + last_modified: 1234567, + some_key: "some metadata", +}; + +let downloader; +let server; + +function pathFromURL(url) { + const uri = Services.io.newURI(url); + const file = uri.QueryInterface(Ci.nsIFileURL).file; + return file.path; +} + +const PROFILE_URL = PathUtils.toFileURI(PathUtils.localProfileDir); + +function run_test() { + server = new HttpServer(); + server.start(-1); + registerCleanupFunction(() => server.stop(() => {})); + + server.registerDirectory( + "/cdn/main-workspace/some-collection/", + do_get_file("test_attachments_downloader") + ); + + run_next_test(); +} + +async function clear_state() { + Services.prefs.setStringPref( + "services.settings.server", + `http://localhost:${server.identity.primaryPort}/v1` + ); + + downloader = new Downloader("main", "some-collection"); + const dummyCacheImpl = { + get: async attachmentId => {}, + set: async (attachmentId, attachment) => {}, + delete: async attachmentId => {}, + }; + // The download() method requires a cacheImpl, but the Downloader + // class does not have one. Define a dummy no-op one. + Object.defineProperty(downloader, "cacheImpl", { + value: dummyCacheImpl, + // Writable to allow specific tests to override cacheImpl. + writable: true, + }); + await downloader.deleteDownloaded(RECORD); + + server.registerPathHandler("/v1/", (request, response) => { + response.write( + JSON.stringify({ + capabilities: { + attachments: { + base_url: `http://localhost:${server.identity.primaryPort}/cdn/`, + }, + }, + }) + ); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setStatusLine(null, 200, "OK"); + }); +} + +add_task(clear_state); + +add_task(async function test_base_attachment_url_depends_on_server() { + const before = await downloader._baseAttachmentsURL(); + + Services.prefs.setStringPref( + "services.settings.server", + `http://localhost:${server.identity.primaryPort}/v2` + ); + + server.registerPathHandler("/v2/", (request, response) => { + response.write( + JSON.stringify({ + capabilities: { + attachments: { + base_url: "http://some-cdn-url.org", + }, + }, + }) + ); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setStatusLine(null, 200, "OK"); + }); + + const after = await downloader._baseAttachmentsURL(); + + Assert.notEqual(before, after, "base URL was changed"); + Assert.equal(after, "http://some-cdn-url.org/", "A trailing slash is added"); +}); +add_task(clear_state); + +add_task( + async function test_download_throws_server_info_error_if_invalid_response() { + server.registerPathHandler("/v1/", (request, response) => { + response.write("{bad json content"); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setStatusLine(null, 200, "OK"); + }); + + let error; + try { + await downloader.download(RECORD); + } catch (e) { + error = e; + } + + Assert.ok(error instanceof Downloader.ServerInfoError); + } +); +add_task(clear_state); + +add_task(async function test_download_writes_file_in_profile() { + const fileURL = await downloader.downloadToDisk(RECORD); + const localFilePath = pathFromURL(fileURL); + + Assert.equal( + fileURL, + PROFILE_URL + "/settings/main/some-collection/test_file.pem" + ); + Assert.ok(await IOUtils.exists(localFilePath)); + const stat = await IOUtils.stat(localFilePath); + Assert.equal(stat.size, 1597); +}); +add_task(clear_state); + +add_task(async function test_download_as_bytes() { + const bytes = await downloader.downloadAsBytes(RECORD); + + // See *.pem file in tests data. + Assert.ok(bytes.byteLength > 1500, `Wrong bytes size: ${bytes.byteLength}`); +}); +add_task(clear_state); + +add_task(async function test_file_is_redownloaded_if_size_does_not_match() { + const fileURL = await downloader.downloadToDisk(RECORD); + const localFilePath = pathFromURL(fileURL); + await IOUtils.writeUTF8(localFilePath, "bad-content"); + let stat = await IOUtils.stat(localFilePath); + Assert.notEqual(stat.size, 1597); + + await downloader.downloadToDisk(RECORD); + + stat = await IOUtils.stat(localFilePath); + Assert.equal(stat.size, 1597); +}); +add_task(clear_state); + +add_task(async function test_file_is_redownloaded_if_corrupted() { + const fileURL = await downloader.downloadToDisk(RECORD); + const localFilePath = pathFromURL(fileURL); + const byteArray = await IOUtils.read(localFilePath); + byteArray[0] = 42; + await IOUtils.write(localFilePath, byteArray); + let content = await IOUtils.readUTF8(localFilePath); + Assert.notEqual(content.slice(0, 5), "-----"); + + await downloader.downloadToDisk(RECORD); + + content = await IOUtils.readUTF8(localFilePath); + Assert.equal(content.slice(0, 5), "-----"); +}); +add_task(clear_state); + +add_task(async function test_download_is_retried_3_times_if_download_fails() { + const record = { + id: "abc", + attachment: { + ...RECORD.attachment, + location: "404-error.pem", + }, + }; + + let called = 0; + const _fetchAttachment = downloader._fetchAttachment; + downloader._fetchAttachment = async url => { + called++; + return _fetchAttachment(url); + }; + + let error; + try { + await downloader.download(record); + } catch (e) { + error = e; + } + + Assert.equal(called, 4); // 1 + 3 retries + Assert.ok(error instanceof Downloader.DownloadError); +}); +add_task(clear_state); + +add_task(async function test_download_is_retried_3_times_if_content_fails() { + const record = { + id: "abc", + attachment: { + ...RECORD.attachment, + hash: "always-wrong", + }, + }; + let called = 0; + downloader._fetchAttachment = async () => { + called++; + return new ArrayBuffer(); + }; + + let error; + try { + await downloader.download(record); + } catch (e) { + error = e; + } + + Assert.equal(called, 4); // 1 + 3 retries + Assert.ok(error instanceof Downloader.BadContentError); +}); +add_task(clear_state); + +add_task(async function test_delete_removes_local_file() { + const fileURL = await downloader.downloadToDisk(RECORD); + const localFilePath = pathFromURL(fileURL); + Assert.ok(await IOUtils.exists(localFilePath)); + + await downloader.deleteFromDisk(RECORD); + + Assert.ok(!(await IOUtils.exists(localFilePath))); + // And removes parent folders. + const parentFolder = PathUtils.join( + PathUtils.localProfileDir, + ...downloader.folders + ); + Assert.ok(!(await IOUtils.exists(parentFolder))); +}); +add_task(clear_state); + +add_task(async function test_delete_all() { + const client = RemoteSettings("some-collection"); + await client.db.create(RECORD); + await downloader.download(RECORD); + const fileURL = await downloader.downloadToDisk(RECORD); + const localFilePath = pathFromURL(fileURL); + Assert.ok(await IOUtils.exists(localFilePath)); + + await client.attachments.deleteAll(); + + Assert.ok(!(await IOUtils.exists(localFilePath))); + Assert.ok(!(await client.attachments.cacheImpl.get(RECORD.id))); +}); +add_task(clear_state); + +add_task(async function test_downloader_is_accessible_via_client() { + const client = RemoteSettings("some-collection"); + + const fileURL = await client.attachments.downloadToDisk(RECORD); + + Assert.equal( + fileURL, + [ + PROFILE_URL, + "settings", + client.bucketName, + client.collectionName, + RECORD.attachment.filename, + ].join("/") + ); +}); +add_task(clear_state); + +add_task(async function test_downloader_reports_download_errors() { + await withFakeChannel("nightly", async () => { + const client = RemoteSettings("some-collection"); + + const record = { + attachment: { + ...RECORD.attachment, + location: "404-error.pem", + }, + }; + + try { + await client.attachments.download(record, { retry: 0 }); + } catch (e) {} + + TelemetryTestUtils.assertEvents([ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.DOWNLOAD_ERROR, + { + source: client.identifier, + }, + ], + ]); + }); +}); +add_task(clear_state); + +add_task(async function test_downloader_reports_offline_error() { + const backupOffline = Services.io.offline; + Services.io.offline = true; + + await withFakeChannel("nightly", async () => { + try { + const client = RemoteSettings("some-collection"); + const record = { + attachment: { + ...RECORD.attachment, + location: "will-try-and-fail.pem", + }, + }; + try { + await client.attachments.download(record, { retry: 0 }); + } catch (e) {} + + TelemetryTestUtils.assertEvents([ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR, + { + source: client.identifier, + }, + ], + ]); + } finally { + Services.io.offline = backupOffline; + } + }); +}); +add_task(clear_state); + +// Common code for test_download_cache_hit and test_download_cache_corruption. +async function doTestDownloadCacheImpl({ simulateCorruption }) { + let readCount = 0; + let writeCount = 0; + const cacheImpl = { + async get(attachmentId) { + Assert.equal(attachmentId, RECORD.id, "expected attachmentId"); + ++readCount; + if (simulateCorruption) { + throw new Error("Simulation of corrupted cache (read)"); + } + }, + async set(attachmentId, attachment) { + Assert.equal(attachmentId, RECORD.id, "expected attachmentId"); + Assert.deepEqual(attachment.record, RECORD, "expected record"); + ++writeCount; + if (simulateCorruption) { + throw new Error("Simulation of corrupted cache (write)"); + } + }, + async delete(attachmentId) {}, + }; + Object.defineProperty(downloader, "cacheImpl", { value: cacheImpl }); + + let downloadResult = await downloader.download(RECORD); + Assert.equal(downloadResult._source, "remote_match", "expected source"); + Assert.equal(downloadResult.buffer.byteLength, 1597, "expected result"); + Assert.equal(readCount, 1, "expected cache read attempts"); + Assert.equal(writeCount, 1, "expected cache write attempts"); +} + +add_task(async function test_download_cache_hit() { + await doTestDownloadCacheImpl({ simulateCorruption: false }); +}); +add_task(clear_state); + +// Verify that the downloader works despite a broken cache implementation. +add_task(async function test_download_cache_corruption() { + await doTestDownloadCacheImpl({ simulateCorruption: true }); +}); +add_task(clear_state); + +add_task(async function test_download_cached() { + const client = RemoteSettings("main", "some-collection"); + const attachmentId = "dummy filename"; + const badRecord = { + attachment: { + ...RECORD.attachment, + hash: "non-matching hash", + location: "non-existing-location-should-fail.bin", + }, + }; + async function downloadWithCache(record, options) { + options = { ...options, useCache: true }; + return client.attachments.download(record, options); + } + function checkInfo(downloadResult, expectedSource, msg) { + Assert.deepEqual( + downloadResult.record, + RECORD, + `${msg} : expected identical record` + ); + // Simple check: assume that content is identical if the size matches. + Assert.equal( + downloadResult.buffer.byteLength, + RECORD.attachment.size, + `${msg} : expected buffer` + ); + Assert.equal( + downloadResult._source, + expectedSource, + `${msg} : expected source of the result` + ); + } + + await Assert.rejects( + downloadWithCache(null, { attachmentId }), + /DownloadError: Could not download dummy filename/, + "Download without record or cache should fail." + ); + + // Populate cache. + const info1 = await downloadWithCache(RECORD, { attachmentId }); + checkInfo(info1, "remote_match", "first time download"); + + await Assert.rejects( + downloadWithCache(null, { attachmentId }), + /DownloadError: Could not download dummy filename/, + "Download without record still fails even if there is a cache." + ); + + await Assert.rejects( + downloadWithCache(badRecord, { attachmentId }), + /DownloadError: Could not download .*non-existing-location-should-fail.bin/, + "Download with non-matching record still fails even if there is a cache." + ); + + // Download from cache. + const info2 = await downloadWithCache(RECORD, { attachmentId }); + checkInfo(info2, "cache_match", "download matching record from cache"); + + const info3 = await downloadWithCache(RECORD, { + attachmentId, + fallbackToCache: true, + }); + checkInfo(info3, "cache_match", "fallbackToCache accepts matching record"); + + const info4 = await downloadWithCache(null, { + attachmentId, + fallbackToCache: true, + }); + checkInfo(info4, "cache_fallback", "fallbackToCache accepts null record"); + + const info5 = await downloadWithCache(badRecord, { + attachmentId, + fallbackToCache: true, + }); + checkInfo(info5, "cache_fallback", "fallbackToCache ignores bad record"); + + // Bye bye cache. + await client.attachments.deleteDownloaded({ id: attachmentId }); + await Assert.rejects( + downloadWithCache(null, { attachmentId, fallbackToCache: true }), + /DownloadError: Could not download dummy filename/, + "Download without cache should fail again." + ); + await Assert.rejects( + downloadWithCache(badRecord, { attachmentId, fallbackToCache: true }), + /DownloadError: Could not download .*non-existing-location-should-fail.bin/, + "Download should fail to fall back to a download of a non-existing record" + ); +}); +add_task(clear_state); + +add_task(async function test_download_from_dump() { + const client = RemoteSettings("dump-collection", { + bucketName: "dump-bucket", + }); + + // Temporarily replace the resource:-URL with another resource:-URL. + const orig_RESOURCE_BASE_URL = Downloader._RESOURCE_BASE_URL; + Downloader._RESOURCE_BASE_URL = "resource://rs-downloader-test"; + const resProto = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + resProto.setSubstitution( + "rs-downloader-test", + Services.io.newFileURI(do_get_file("test_attachments_downloader")) + ); + + function checkInfo(result, expectedSource, expectedRecord = RECORD_OF_DUMP) { + Assert.equal( + new TextDecoder().decode(new Uint8Array(result.buffer)), + "This would be a RS dump.\n", + "expected content from dump" + ); + Assert.deepEqual(result.record, expectedRecord, "expected record for dump"); + Assert.equal(result._source, expectedSource, "expected source of dump"); + } + + // If record matches, should happen before network request. + const dump1 = await client.attachments.download(RECORD_OF_DUMP, { + // Note: attachmentId not set, so should fall back to record.id. + fallbackToDump: true, + }); + checkInfo(dump1, "dump_match"); + + // If no record given, should try network first, but then fall back to dump. + const dump2 = await client.attachments.download(null, { + attachmentId: RECORD_OF_DUMP.id, + fallbackToDump: true, + }); + checkInfo(dump2, "dump_fallback"); + + // Fill the cache with the same data as the dump for the next part. + await client.db.saveAttachment(RECORD_OF_DUMP.id, { + record: RECORD_OF_DUMP, + blob: new Blob([dump1.buffer]), + }); + // The dump should take precedence over the cache. + const dump3 = await client.attachments.download(RECORD_OF_DUMP, { + fallbackToCache: true, + fallbackToDump: true, + }); + checkInfo(dump3, "dump_match"); + + // When the record is not given, the dump takes precedence over the cache + // as a fallback (when the cache and dump are identical). + const dump4 = await client.attachments.download(null, { + attachmentId: RECORD_OF_DUMP.id, + fallbackToCache: true, + fallbackToDump: true, + }); + checkInfo(dump4, "dump_fallback"); + + // Store a record in the cache that is newer than the dump. + const RECORD_NEWER_THAN_DUMP = { + ...RECORD_OF_DUMP, + last_modified: RECORD_OF_DUMP.last_modified + 1, + }; + await client.db.saveAttachment(RECORD_OF_DUMP.id, { + record: RECORD_NEWER_THAN_DUMP, + blob: new Blob([dump1.buffer]), + }); + + // When the record is not given, use the cache if it has a more recent record. + const dump5 = await client.attachments.download(null, { + attachmentId: RECORD_OF_DUMP.id, + fallbackToCache: true, + fallbackToDump: true, + }); + checkInfo(dump5, "cache_fallback", RECORD_NEWER_THAN_DUMP); + + // When a record is given, use whichever that has the matching last_modified. + const dump6 = await client.attachments.download(RECORD_OF_DUMP, { + fallbackToCache: true, + fallbackToDump: true, + }); + checkInfo(dump6, "dump_match"); + const dump7 = await client.attachments.download(RECORD_NEWER_THAN_DUMP, { + fallbackToCache: true, + fallbackToDump: true, + }); + checkInfo(dump7, "cache_match", RECORD_NEWER_THAN_DUMP); + + await client.attachments.deleteDownloaded(RECORD_OF_DUMP); + + await Assert.rejects( + client.attachments.download(null, { + attachmentId: "filename-without-meta.txt", + fallbackToDump: true, + }), + /DownloadError: Could not download filename-without-meta.txt/, + "Cannot download dump that lacks a .meta.json file" + ); + + await Assert.rejects( + client.attachments.download(null, { + attachmentId: "filename-without-content.txt", + fallbackToDump: true, + }), + /Could not download resource:\/\/rs-downloader-test\/settings\/dump-bucket\/dump-collection\/filename-without-content\.txt(?!\.meta\.json)/, + "Cannot download dump that is missing, despite the existing .meta.json" + ); + + // Restore, just in case. + Downloader._RESOURCE_BASE_URL = orig_RESOURCE_BASE_URL; + resProto.setSubstitution("rs-downloader-test", null); +}); +// Not really needed because the last test doesn't modify the main collection, +// but added for consistency with other tests tasks around here. +add_task(clear_state); + +add_task(async function test_obsolete_attachments_are_pruned() { + const RECORD2 = { + ...RECORD, + id: "another-id", + }; + const client = RemoteSettings("some-collection"); + // Store records and related attachments directly in the cache. + await client.db.importChanges({}, 42, [RECORD, RECORD2], { clear: true }); + await client.db.saveAttachment(RECORD.id, { + record: RECORD, + blob: new Blob(["123"]), + }); + await client.db.saveAttachment("custom-id", { + record: RECORD2, + blob: new Blob(["456"]), + }); + // Store an extraneous cached attachment. + await client.db.saveAttachment("bar", { + record: { id: "bar" }, + blob: new Blob(["789"]), + }); + + const recordAttachment = await client.attachments.cacheImpl.get(RECORD.id); + Assert.equal( + await recordAttachment.blob.text(), + "123", + "Record has a cached attachment" + ); + const record2Attachment = await client.attachments.cacheImpl.get("custom-id"); + Assert.equal( + await record2Attachment.blob.text(), + "456", + "Record 2 has a cached attachment" + ); + const { blob: cachedExtra } = await client.attachments.cacheImpl.get("bar"); + Assert.equal(await cachedExtra.text(), "789", "There is an extra attachment"); + + await client.attachments.prune([]); + + Assert.ok( + await client.attachments.cacheImpl.get(RECORD.id), + "Record attachment was kept" + ); + Assert.ok( + await client.attachments.cacheImpl.get("custom-id"), + "Record 2 attachment was kept" + ); + Assert.ok( + !(await client.attachments.cacheImpl.get("bar")), + "Extra was deleted" + ); +}); +add_task(clear_state); + +add_task( + async function test_obsolete_attachments_listed_as_excluded_are_not_pruned() { + const client = RemoteSettings("some-collection"); + // Store records and related attachments directly in the cache. + await client.db.importChanges({}, 42, [], { clear: true }); + await client.db.saveAttachment(RECORD.id, { + record: RECORD, + blob: new Blob(["123"]), + }); + + const recordAttachment = await client.attachments.cacheImpl.get(RECORD.id); + Assert.equal( + await recordAttachment.blob.text(), + "123", + "Record has a cached attachment" + ); + + await client.attachments.prune([RECORD.id]); + + Assert.ok( + await client.attachments.cacheImpl.get(RECORD.id), + "Record attachment was kept" + ); + } +); +add_task(clear_state); diff --git a/services/settings/test/unit/test_attachments_downloader/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem b/services/settings/test/unit/test_attachments_downloader/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem new file mode 100644 index 0000000000..502e8c9ce0 --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader/65650a0f-7c22-4c10-9744-2d67e301f5f4.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEbjCCA1agAwIBAgIQBg3WwdBnkBtUdfz/wp4xNzANBgkqhkiG9w0BAQsFADBa +MQswCQYDVQQGEwJJRTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJl +clRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTE1 +MTAxNDEyMDAwMFoXDTIwMTAxNDEyMDAwMFowbzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZEZs +YXJlLCBJbmMuMSAwHgYDVQQDExdDbG91ZEZsYXJlIEluYyBSU0EgQ0EtMTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJGiNOIE4s0M4wdhDeV9aMfAYY9l +yG9cfGQqt7a5UgrRA81bi4istCyhzfzRWUW+NAmf6X2HEnA3xLI1M+pH/xEbk9pw +jc8/1CPy9jUjBwb89zt5PWh2I1KxZVg/Bnx2yYdVcKTUMKt0GLDXfZXN+RYZHJQo +lDlzjH5xV0IpDMv/FsMEZWcfx1JorBf08bRnRVkl9RY00y2ujVr+492ze+zYQ9s7 +HcidpR+7ret3jzLSvojsaA5+fOaCG0ctVJcLfnkQ5lWR95ByBdO1NapfqZ1+kmCL +3baVSeUpYQriBwznxfLuGs8POo4QdviYVtSPBWjOEfb+o1c6Mbo8p4noFzUCAwEA +AaOCARkwggEVMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMDQG +CCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu +Y29tMDoGA1UdHwQzMDEwL6AtoCuGKWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9P +bW5pcm9vdDIwMjUuY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIB +FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSRBYrfTCLG +bYuUTBZFfu5vAvu3wDAfBgNVHSMEGDAWgBTlnVkwgkdYzKz6CFQ2hns6tQRN8DAN +BgkqhkiG9w0BAQsFAAOCAQEAVJle3ar9NSnTrLAhgfkcpClIY6/kabDIEa8cOnu1 +SOXf4vbtZakSmmIbFbmYDUGIU5XwwVdF/FKNzNBRf9G4EL/S0NXytBKj4A34UGQA +InaV+DgVLzCifN9cAHi8EFEAfbglUvPvLPFXF0bwffElYm7QBSiHYSZmfOKLCyiv +3zlQsf7ozNBAxfbmnRMRSUBcIhRwnaFoFgDs7yU6R1Yk4pO7eMgWpdPGhymDTIvv +RnauKStzKsAli9i5hQ4nTDITUpMAmeJoXodgwRkC3Civw32UR2rxObIyxPpbfODb +sZKNGO9K5Sjj6turB1zwbd2wI8MhtUCY9tGmSYhe7G6Bkw== +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt new file mode 100644 index 0000000000..77d7b4154f --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt @@ -0,0 +1 @@ +This would be a RS dump. diff --git a/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt.meta.json b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt.meta.json new file mode 100644 index 0000000000..de7681940d --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-of-dump.txt.meta.json @@ -0,0 +1,10 @@ +{ + "id": "filename-of-dump.txt", + "attachment": { + "filename": "filename-of-dump.txt", + "hash": "4c46ef7e4f1951d210fe54c21e07c09bab265fd122580083ed1d6121547a8c6b", + "size": 25 + }, + "last_modified": 1234567, + "some_key": "some metadata" +} diff --git a/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-content.txt.meta.json b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-content.txt.meta.json new file mode 100644 index 0000000000..5867247dce --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-content.txt.meta.json @@ -0,0 +1,8 @@ +{ + "fyi": "This .meta.json file describes an attachment, but that attachment is missing.", + "attachment": { + "filename": "filename-without-content.txt", + "hash": "...", + "size": "..." + } +} diff --git a/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-meta.txt b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-meta.txt new file mode 100644 index 0000000000..5fbfd11c9e --- /dev/null +++ b/services/settings/test/unit/test_attachments_downloader/settings/dump-bucket/dump-collection/filename-without-meta.txt @@ -0,0 +1 @@ +The filename-without-meta.txt.meta.json file is missing. diff --git a/services/settings/test/unit/test_remote_settings.js b/services/settings/test/unit/test_remote_settings.js new file mode 100644 index 0000000000..0937acb519 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings.js @@ -0,0 +1,1681 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { ObjectUtils } = ChromeUtils.importESModule( + "resource://gre/modules/ObjectUtils.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); +const { UptakeTelemetry, Policy } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const IS_ANDROID = AppConstants.platform == "android"; + +const TELEMETRY_COMPONENT = "remotesettings"; +const TELEMETRY_EVENTS_FILTERS = { + category: "uptake.remotecontent.result", + method: "uptake", +}; + +let server; +let client; +let clientWithDump; + +async function clear_state() { + // Reset preview mode. + RemoteSettings.enablePreviewMode(undefined); + Services.prefs.clearUserPref("services.settings.preview_enabled"); + + client.verifySignature = false; + clientWithDump.verifySignature = false; + + // Clear local DB. + await client.db.clear(); + // Reset event listeners. + client._listeners.set("sync", []); + + await clientWithDump.db.clear(); + + // Clear events snapshot. + TelemetryTestUtils.assertEvents([], {}, { process: "dummy" }); +} + +function run_test() { + // Set up an HTTP Server + server = new HttpServer(); + server.start(-1); + + // Pretend we are in nightly channel to make sure all telemetry events are sent. + let oldGetChannel = Policy.getChannel; + Policy.getChannel = () => "nightly"; + + // Point the blocklist clients to use this local HTTP server. + Services.prefs.setStringPref( + "services.settings.server", + `http://localhost:${server.identity.primaryPort}/v1` + ); + + Services.prefs.setStringPref("services.settings.loglevel", "debug"); + + client = RemoteSettings("password-fields"); + clientWithDump = RemoteSettings("language-dictionaries"); + + server.registerPathHandler("/v1/", handleResponse); + server.registerPathHandler( + "/v1/buckets/monitor/collections/changes/changeset", + handleResponse + ); + server.registerPathHandler( + "/v1/buckets/main/collections/password-fields/changeset", + handleResponse + ); + server.registerPathHandler( + "/v1/buckets/main/collections/language-dictionaries/changeset", + handleResponse + ); + server.registerPathHandler( + "/v1/buckets/main/collections/with-local-fields/changeset", + handleResponse + ); + server.registerPathHandler("/fake-x5u", handleResponse); + + run_next_test(); + + registerCleanupFunction(() => { + Policy.getChannel = oldGetChannel; + server.stop(() => {}); + }); +} +add_task(clear_state); + +add_task(async function test_records_obtained_from_server_are_stored_in_db() { + // Test an empty db populates + await client.maybeSync(2000); + + // Open the collection, verify it's been populated: + // Our test data has a single record; it should be in the local collection + const list = await client.get(); + equal(list.length, 1); + + const timestamp = await client.db.getLastModified(); + equal(timestamp, 3000, "timestamp was stored"); + + const { signature } = await client.db.getMetadata(); + equal(signature.signature, "abcdef", "metadata was stored"); +}); +add_task(clear_state); + +add_task( + async function test_records_from_dump_are_listed_as_created_in_event() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + let received; + clientWithDump.on("sync", ({ data }) => (received = data)); + // Use a timestamp superior to latest record in dump. + const timestamp = 5000000000000; // Fri Jun 11 2128 + + await clientWithDump.maybeSync(timestamp); + + const list = await clientWithDump.get(); + ok(list.length > 20, `The dump was loaded (${list.length} records)`); + equal(received.created[0].id, "xx", "Record from the sync come first."); + + const createdById = received.created.reduce((acc, r) => { + acc[r.id] = r; + return acc; + }, {}); + + ok( + !(received.deleted[0].id in createdById), + "Deleted records are not listed as created" + ); + equal( + createdById[received.updated[0].new.id], + received.updated[0].new, + "The records that were updated should appear as created in their newest form." + ); + + equal( + received.created.length, + list.length, + "The list of created records contains the dump" + ); + equal(received.current.length, received.created.length); + } +); +add_task(clear_state); + +add_task(async function test_throws_when_network_is_offline() { + const backupOffline = Services.io.offline; + try { + Services.io.offline = true; + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + clientWithDump.identifier + ); + let error; + try { + await clientWithDump.maybeSync(2000); + } catch (e) { + error = e; + } + equal(error.name, "NetworkOfflineError"); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + clientWithDump.identifier + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + } finally { + Services.io.offline = backupOffline; + } +}); +add_task(clear_state); + +add_task(async function test_sync_event_is_sent_even_if_up_to_date() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + // First, determine what is the dump timestamp. Sync will load it. + // Use a timestamp inferior to latest record in dump. + await clientWithDump._importJSONDump(); + const uptodateTimestamp = await clientWithDump.db.getLastModified(); + await clear_state(); + + // Now, simulate that server data wasn't changed since dump was released. + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + clientWithDump.identifier + ); + let received; + clientWithDump.on("sync", ({ data }) => (received = data)); + + await clientWithDump.maybeSync(uptodateTimestamp); + + ok(!!received.current.length, "Dump records are listed as created"); + equal(received.current.length, received.created.length); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + clientWithDump.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.UP_TO_DATE]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_records_can_have_local_fields() { + const c = RemoteSettings("with-local-fields", { localFields: ["accepted"] }); + c.verifySignature = false; + + await c.maybeSync(2000); + + await c.db.update({ + id: "c74279ce-fb0a-42a6-ae11-386b567a6119", + accepted: true, + }); + await c.maybeSync(3000); // Does not fail. +}); +add_task(clear_state); + +add_task( + async function test_records_changes_are_overwritten_by_server_changes() { + // Create some local conflicting data, and make sure it syncs without error. + await client.db.create({ + website: "", + id: "9d500963-d80e-3a91-6e74-66f3811b99cc", + }); + + await client.maybeSync(2000); + + const data = await client.get(); + equal(data[0].website, "https://some-website.com"); + } +); +add_task(clear_state); + +add_task( + async function test_get_loads_default_records_from_a_local_dump_when_database_is_empty() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + + // When collection has a dump in services/settings/dumps/{bucket}/{collection}.json + const data = await clientWithDump.get(); + notEqual(data.length, 0); + // No synchronization happened (responses are not mocked). + } +); +add_task(clear_state); + +add_task(async function test_get_loads_dump_only_once_if_called_in_parallel() { + const backup = clientWithDump._importJSONDump; + let callCount = 0; + clientWithDump._importJSONDump = async () => { + callCount++; + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 100)); + return 42; + }; + await Promise.all([clientWithDump.get(), clientWithDump.get()]); + equal(callCount, 1, "JSON dump was called more than once"); + clientWithDump._importJSONDump = backup; +}); +add_task(clear_state); + +add_task(async function test_get_falls_back_to_dump_if_db_fails() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + const backup = clientWithDump.db.getLastModified; + clientWithDump.db.getLastModified = () => { + throw new Error("Unknown error"); + }; + + const records = await clientWithDump.get({ dumpFallback: true }); + ok(!!records.length, "dump content is returned"); + + // If fallback is disabled, error is thrown. + let error; + try { + await clientWithDump.get({ dumpFallback: false }); + } catch (e) { + error = e; + } + equal(error.message, "Unknown error"); + + clientWithDump.db.getLastModified = backup; +}); +add_task(clear_state); + +add_task(async function test_get_sorts_results_if_specified() { + await client.db.importChanges( + {}, + 42, + [ + { + field: 12, + id: "9d500963-d80e-3a91-6e74-66f3811b99cc", + }, + { + field: 7, + id: "d83444a4-f348-4cd8-8228-842cb927db9f", + }, + ], + { clear: true } + ); + + const records = await client.get({ order: "field" }); + ok( + records[0].field < records[records.length - 1].field, + "records are sorted" + ); +}); +add_task(clear_state); + +add_task(async function test_get_falls_back_sorts_results() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + const backup = clientWithDump.db.getLastModified; + clientWithDump.db.getLastModified = () => { + throw new Error("Unknown error"); + }; + + const records = await clientWithDump.get({ + dumpFallback: true, + order: "-id", + }); + + ok(records[0].id > records[records.length - 1].id, "records are sorted"); + + clientWithDump.db.getLastModified = backup; +}); +add_task(clear_state); + +add_task(async function test_get_falls_back_to_dump_if_db_fails_later() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + const backup = clientWithDump.db.list; + clientWithDump.db.list = () => { + throw new Error("Unknown error"); + }; + + const records = await clientWithDump.get({ dumpFallback: true }); + ok(!!records.length, "dump content is returned"); + + // If fallback is disabled, error is thrown. + let error; + try { + await clientWithDump.get({ dumpFallback: false }); + } catch (e) { + error = e; + } + equal(error.message, "Unknown error"); + + clientWithDump.db.list = backup; +}); +add_task(clear_state); + +add_task(async function test_get_falls_back_to_dump_if_network_fails() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + const backup = clientWithDump.sync; + clientWithDump.sync = () => { + throw new Error("Sync error"); + }; + + const records = await clientWithDump.get(); + ok(!!records.length, "dump content is returned"); + + clientWithDump.sync = backup; +}); +add_task(clear_state); + +add_task(async function test_get_does_not_sync_if_empty_dump_is_provided() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + + const clientWithEmptyDump = RemoteSettings("example"); + Assert.ok(!(await Utils.hasLocalData(clientWithEmptyDump))); + + const data = await clientWithEmptyDump.get(); + + equal(data.length, 0); + Assert.ok(await Utils.hasLocalData(clientWithEmptyDump)); +}); +add_task(clear_state); + +add_task(async function test_get_synchronization_can_be_disabled() { + const data = await client.get({ syncIfEmpty: false }); + + equal(data.length, 0); +}); +add_task(clear_state); + +add_task( + async function test_get_triggers_synchronization_when_database_is_empty() { + // The "password-fields" collection has no local dump, and no local data. + // Therefore a synchronization will happen. + const data = await client.get(); + + // Data comes from mocked HTTP response (see below). + equal(data.length, 1); + equal(data[0].selector, "#webpage[field-pwd]"); + } +); +add_task(clear_state); + +add_task(async function test_get_ignores_synchronization_errors_by_default() { + // The monitor endpoint won't contain any information about this collection. + let data = await RemoteSettings("some-unknown-key").get(); + equal(data.length, 0); + // The sync endpoints are not mocked, this fails internally. + data = await RemoteSettings("no-mocked-responses").get(); + equal(data.length, 0); +}); +add_task(clear_state); + +add_task(async function test_get_throws_if_no_empty_fallback() { + // The monitor endpoint won't contain any information about this collection. + try { + await RemoteSettings("some-unknown-key").get({ + emptyListFallback: false, + }); + Assert.ok(false, ".get() should throw"); + } catch (error) { + Assert.ok( + error.message.includes("Response from server unparseable"), + "Server error was thrown" + ); + } +}); +add_task(clear_state); + +add_task(async function test_get_verify_signature_no_sync() { + // No signature in metadata, and no sync if empty. + let error; + try { + await client.get({ verifySignature: true, syncIfEmpty: false }); + } catch (e) { + error = e; + } + equal(error.message, "Missing signature (main/password-fields)"); +}); +add_task(clear_state); + +add_task(async function test_get_can_verify_signature_pulled() { + // Populate the local DB (only records, eg. loaded from dump previously) + await client._importJSONDump(); + + let calledSignature; + client._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + calledSignature = signature; + return true; + }, + }; + client.verifySignature = true; + + // No metadata in local DB, but gets pulled and then verifies. + ok(ObjectUtils.isEmpty(await client.db.getMetadata()), "Metadata is empty"); + + await client.get({ verifySignature: true }); + + ok( + !ObjectUtils.isEmpty(await client.db.getMetadata()), + "Metadata was pulled" + ); + ok(calledSignature.endsWith("some-sig"), "Signature was verified"); +}); +add_task(clear_state); + +add_task(async function test_get_can_verify_signature() { + // Populate the local DB (record and metadata) + await client.maybeSync(2000); + + // It validates signature that was stored in local DB. + let calledSignature; + client._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + calledSignature = signature; + return JSON.parse(serialized).data.length == 1; + }, + }; + ok(await Utils.hasLocalData(client), "Local data was populated"); + await client.get({ verifySignature: true }); + + ok(calledSignature.endsWith("abcdef"), "Signature was verified"); + + // It throws when signature does not verify. + await client.db.delete("9d500963-d80e-3a91-6e74-66f3811b99cc"); + let error = null; + try { + await client.get({ verifySignature: true }); + } catch (e) { + error = e; + } + equal( + error.message, + "Invalid content signature (main/password-fields) using 'fake-x5u'" + ); +}); +add_task(clear_state); + +add_task(async function test_get_does_not_verify_signature_if_load_dump() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + + let called; + clientWithDump._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + called = true; + return true; + }, + }; + + // When dump is loaded, signature is not verified. + const records = await clientWithDump.get({ verifySignature: true }); + ok(!!records.length, "dump is loaded"); + ok(!called, "signature is missing but not verified"); + + // If metadata is missing locally, it is not fetched if `syncIfEmpty` is disabled. + let error; + try { + await clientWithDump.get({ verifySignature: true, syncIfEmpty: false }); + } catch (e) { + error = e; + } + ok(!called, "signer was not called"); + equal( + error.message, + "Missing signature (main/language-dictionaries)", + "signature is missing locally" + ); + + // If metadata is missing locally, it is fetched by default (`syncIfEmpty: true`) + await clientWithDump.get({ verifySignature: true }); + const metadata = await clientWithDump.db.getMetadata(); + ok(!!Object.keys(metadata).length, "metadata was fetched"); + ok(called, "signature was verified for the data that was in dump"); +}); +add_task(clear_state); + +add_task( + async function test_get_does_verify_signature_if_json_loaded_in_parallel() { + const backup = clientWithDump._verifier; + let callCount = 0; + clientWithDump._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + callCount++; + return true; + }, + }; + await Promise.all([ + clientWithDump.get({ verifySignature: true }), + clientWithDump.get({ verifySignature: true }), + ]); + equal(callCount, 0, "No need to verify signatures if JSON dump is loaded"); + clientWithDump._verifier = backup; + } +); +add_task(clear_state); + +add_task(async function test_get_can_force_a_sync() { + const step0 = await client.db.getLastModified(); + await client.get({ forceSync: true }); + const step1 = await client.db.getLastModified(); + await client.get(); + const step2 = await client.db.getLastModified(); + await client.get({ forceSync: true }); + const step3 = await client.db.getLastModified(); + + equal(step0, null); + equal(step1, 3000); + equal(step2, 3000); + equal(step3, 3001); +}); +add_task(clear_state); + +add_task(async function test_sync_runs_once_only() { + const backup = Utils.log.warn; + const messages = []; + Utils.log.warn = m => { + messages.push(m); + }; + + await Promise.all([client.maybeSync(2000), client.maybeSync(2000)]); + + ok( + messages.includes("main/password-fields sync already running"), + "warning is shown about sync already running" + ); + Utils.log.warn = backup; +}); +add_task(clear_state); + +add_task( + async function test_sync_pulls_metadata_if_missing_with_dump_is_up_to_date() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + + let called; + clientWithDump._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + called = true; + return true; + }, + }; + // When dump is loaded, signature is not verified. + const records = await clientWithDump.get({ verifySignature: true }); + ok(!!records.length, "dump is loaded"); + ok(!called, "signature is missing but not verified"); + + // Synchronize the collection (local data is up-to-date). + // Signature verification is disabled (see `clear_state()`), so we don't bother with + // fetching metadata. + const uptodateTimestamp = await clientWithDump.db.getLastModified(); + await clientWithDump.maybeSync(uptodateTimestamp); + let metadata = await clientWithDump.db.getMetadata(); + ok(!metadata, "metadata was not fetched"); + + // Synchronize again the collection (up-to-date, since collection last modified still > 42) + clientWithDump.verifySignature = true; + await clientWithDump.maybeSync(42); + + // With signature verification, metadata was fetched. + metadata = await clientWithDump.db.getMetadata(); + ok(!!Object.keys(metadata).length, "metadata was fetched"); + ok(called, "signature was verified for the data that was in dump"); + + // Metadata is present, signature will now verified. + called = false; + await clientWithDump.get({ verifySignature: true }); + ok(called, "local signature is verified"); + } +); +add_task(clear_state); + +add_task(async function test_sync_event_provides_information_about_records() { + let eventData; + client.on("sync", ({ data }) => (eventData = data)); + + await client.maybeSync(2000); + equal(eventData.current.length, 1); + + await client.maybeSync(3001); + equal(eventData.current.length, 2); + equal(eventData.created.length, 1); + equal(eventData.created[0].website, "https://www.other.org/signin"); + equal(eventData.updated.length, 1); + equal(eventData.updated[0].old.website, "https://some-website.com"); + equal(eventData.updated[0].new.website, "https://some-website.com/login"); + equal(eventData.deleted.length, 0); + + await client.maybeSync(4001); + equal(eventData.current.length, 1); + equal(eventData.created.length, 0); + equal(eventData.updated.length, 0); + equal(eventData.deleted.length, 1); + equal(eventData.deleted[0].website, "https://www.other.org/signin"); +}); +add_task(clear_state); + +add_task(async function test_inspect_method() { + // Synchronize the `password-fields` collection in order to have + // some local data when .inspect() is called. + await client.maybeSync(2000); + + const inspected = await RemoteSettings.inspect(); + + // Assertion for global attributes. + const { mainBucket, serverURL, defaultSigner, collections, serverTimestamp } = + inspected; + const rsSigner = "remote-settings.content-signature.mozilla.org"; + equal(mainBucket, "main"); + equal(serverURL, `http://localhost:${server.identity.primaryPort}/v1`); + equal(defaultSigner, rsSigner); + equal(serverTimestamp, '"5000"'); + + // A collection is listed in .inspect() if it has local data or if there + // is a JSON dump for it. + // "password-fields" has no dump but was synchronized above and thus has local data. + let col = collections.pop(); + equal(col.collection, "password-fields"); + equal(col.serverTimestamp, 3000); + equal(col.localTimestamp, 3000); + + if (!IS_ANDROID) { + // "language-dictionaries" has a local dump (not on Android) + col = collections.pop(); + equal(col.collection, "language-dictionaries"); + equal(col.serverTimestamp, 4000); + ok(!col.localTimestamp); // not synchronized. + } +}); +add_task(clear_state); + +add_task(async function test_inspect_method_uses_a_random_cache_bust() { + const backup = Utils.fetchLatestChanges; + const cacheBusts = []; + Utils.fetchLatestChanges = (url, options) => { + cacheBusts.push(options.expected); + return { changes: [] }; + }; + + await RemoteSettings.inspect(); + await RemoteSettings.inspect(); + await RemoteSettings.inspect(); + + notEqual(cacheBusts[0], cacheBusts[1]); + notEqual(cacheBusts[1], cacheBusts[2]); + notEqual(cacheBusts[0], cacheBusts[2]); + Utils.fetchLatestChanges = backup; +}); + +add_task(async function test_clearAll_method() { + // Make sure we have some local data. + await client.maybeSync(2000); + await clientWithDump.maybeSync(2000); + + await RemoteSettings.clearAll(); + + ok(!(await Utils.hasLocalData(client)), "Local data was deleted"); + ok(!(await Utils.hasLocalData(clientWithDump)), "Local data was deleted"); + ok( + !Services.prefs.prefHasUserValue(client.lastCheckTimePref), + "Pref was cleaned" + ); + + // Synchronization is not broken after resuming. + await client.maybeSync(2000); + await clientWithDump.maybeSync(2000); + ok(await Utils.hasLocalData(client), "Local data was populated"); + ok(await Utils.hasLocalData(clientWithDump), "Local data was populated"); +}); +add_task(clear_state); + +add_task(async function test_listeners_are_not_deduplicated() { + let count = 0; + const plus1 = () => { + count += 1; + }; + + client.on("sync", plus1); + client.on("sync", plus1); + client.on("sync", plus1); + + await client.maybeSync(2000); + + equal(count, 3); +}); +add_task(clear_state); + +add_task(async function test_listeners_can_be_removed() { + let count = 0; + const onSync = () => { + count += 1; + }; + + client.on("sync", onSync); + client.off("sync", onSync); + + await client.maybeSync(2000); + + equal(count, 0); +}); +add_task(clear_state); + +add_task(async function test_all_listeners_are_executed_if_one_fails() { + let count = 0; + client.on("sync", () => { + count += 1; + }); + client.on("sync", () => { + throw new Error("boom"); + }); + client.on("sync", () => { + count += 2; + }); + + let error; + try { + await client.maybeSync(2000); + } catch (e) { + error = e; + } + + equal(count, 3); + equal(error.message, "boom"); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_up_to_date() { + await client.maybeSync(2000); + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + await client.maybeSync(3000); + + // No Telemetry was sent. + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.UP_TO_DATE]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_if_sync_succeeds() { + // We test each client because Telemetry requires preleminary declarations. + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + await client.maybeSync(2000); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.SUCCESS]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task( + async function test_synchronization_duration_is_reported_in_uptake_status() { + await client.maybeSync(2000); + + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.SUCCESS, + { + source: client.identifier, + duration: v => v > 0, + trigger: "manual", + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); + } +); +add_task(clear_state); + +add_task(async function test_telemetry_reports_if_application_fails() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + client.on("sync", () => { + throw new Error("boom"); + }); + + try { + await client.maybeSync(2000); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.APPLY_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_if_sync_fails() { + await client.db.importChanges({}, 9999); + + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + try { + await client.maybeSync(10000); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.SERVER_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_if_parsing_fails() { + await client.db.importChanges({}, 10000); + + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + try { + await client.maybeSync(10001); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.PARSE_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_if_fetching_signature_fails() { + await client.db.importChanges({}, 11000); + + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + try { + await client.maybeSync(11001); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.SERVER_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_unknown_errors() { + const backup = client.db.list; + client.db.list = () => { + throw new Error("Internal"); + }; + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + try { + await client.maybeSync(2000); + } catch (e) {} + + client.db.list = backup; + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.UNKNOWN_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_indexeddb_as_custom_1() { + const backup = client.db.getLastModified; + const msg = + "IndexedDB getLastModified() The operation failed for reasons unrelated to the database itself"; + client.db.getLastModified = () => { + throw new Error(msg); + }; + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + + try { + await client.maybeSync(2000); + } catch (e) {} + + client.db.getLastModified = backup; + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + client.identifier + ); + const expectedIncrements = { [UptakeTelemetry.STATUS.CUSTOM_1_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_telemetry_reports_error_name_as_event_nightly() { + const backup = client.db.list; + client.db.list = () => { + const e = new Error("Some unknown error"); + e.name = "ThrownError"; + throw e; + }; + + try { + await client.maybeSync(2000); + } catch (e) {} + + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.UNKNOWN_ERROR, + { + source: client.identifier, + trigger: "manual", + duration: v => v >= 0, + errorName: "ThrownError", + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); + + client.db.list = backup; +}); +add_task(clear_state); + +add_task(async function test_bucketname_changes_when_preview_mode_is_enabled() { + equal(client.bucketName, "main"); + + RemoteSettings.enablePreviewMode(true); + + equal(client.bucketName, "main-preview"); +}); +add_task(clear_state); + +add_task( + async function test_preview_mode_pref_affects_bucket_names_before_instantiated() { + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + + let clientWithDefaultBucket = RemoteSettings("other"); + let clientWithBucket = RemoteSettings("coll", { bucketName: "buck" }); + + equal(clientWithDefaultBucket.bucketName, "main-preview"); + equal(clientWithBucket.bucketName, "buck-preview"); + } +); +add_task(clear_state); + +add_task( + async function test_preview_enabled_pref_ignored_when_mode_is_set_explicitly() { + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + + let clientWithDefaultBucket = RemoteSettings("other"); + let clientWithBucket = RemoteSettings("coll", { bucketName: "buck" }); + + equal(clientWithDefaultBucket.bucketName, "main-preview"); + equal(clientWithBucket.bucketName, "buck-preview"); + + RemoteSettings.enablePreviewMode(false); + + equal(clientWithDefaultBucket.bucketName, "main"); + equal(clientWithBucket.bucketName, "buck"); + } +); +add_task(clear_state); + +add_task( + async function test_get_loads_default_records_from_a_local_dump_when_preview_mode_is_enabled() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + RemoteSettings.enablePreviewMode(true); + // When collection has a dump in services/settings/dumps/{bucket}/{collection}.json + const data = await clientWithDump.get(); + notEqual(data.length, 0); + // No synchronization happened (responses are not mocked). + } +); +add_task(clear_state); + +add_task(async function test_local_db_distinguishes_preview_records() { + RemoteSettings.enablePreviewMode(true); + client.db.importChanges({}, Date.now(), [{ id: "record-1" }], { + clear: true, + }); + + RemoteSettings.enablePreviewMode(false); + client.db.importChanges({}, Date.now(), [{ id: "record-2" }], { + clear: true, + }); + + deepEqual(await client.get(), [{ id: "record-2" }]); +}); +add_task(clear_state); + +add_task( + async function test_inspect_changes_the_list_when_preview_mode_is_enabled() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest), + // and this test relies on the fact that clients are instantiated if a dump is packaged. + return; + } + + // Register a client only listed in -preview... + RemoteSettings("crash-rate"); + + const { collections: before, previewMode: previewModeBefore } = + await RemoteSettings.inspect(); + + Assert.ok(!previewModeBefore, "preview is not enabled"); + + // These two collections are listed in the main bucket in monitor/changes (one with dump, one registered). + deepEqual(before.map(c => c.collection).sort(), [ + "language-dictionaries", + "password-fields", + ]); + + // Switch to preview mode. + RemoteSettings.enablePreviewMode(true); + + const { + collections: after, + mainBucket, + previewMode, + } = await RemoteSettings.inspect(); + + Assert.ok(previewMode, "preview is enabled"); + + // These two collections are listed in the main bucket in monitor/changes (both are registered). + deepEqual(after.map(c => c.collection).sort(), [ + "crash-rate", + "password-fields", + ]); + equal(mainBucket, "main-preview"); + } +); +add_task(clear_state); + +add_task(async function test_sync_event_is_not_sent_from_get_when_no_dump() { + let called = false; + client.on("sync", e => { + called = true; + }); + + await client.get(); + + Assert.ok(!called, "sync event is not sent from .get()"); +}); +add_task(clear_state); + +add_task(async function test_get_can_be_called_from_sync_event_callback() { + let fromGet; + let fromEvent; + + client.on("sync", async ({ data: { current } }) => { + // Before fixing Bug 1761953 this would result in a deadlock. + fromGet = await client.get(); + fromEvent = current; + }); + + await client.maybeSync(2000); + + Assert.ok(fromGet, "sync callback was called"); + Assert.deepEqual(fromGet, fromEvent, ".get() gives current records list"); +}); +add_task(clear_state); + +add_task(async function test_attachments_are_pruned_when_sync_from_timer() { + await client.db.saveAttachment("bar", { + record: { id: "bar" }, + blob: new Blob(["456"]), + }); + + await client.maybeSync(2000, { trigger: "broadcast" }); + + Assert.ok( + await client.attachments.cacheImpl.get("bar"), + "Extra attachment was not deleted on broadcast" + ); + + await client.maybeSync(3001, { trigger: "timer" }); + + Assert.ok( + !(await client.attachments.cacheImpl.get("bar")), + "Extra attachment was deleted on timer" + ); +}); +add_task(clear_state); + +function handleResponse(request, response) { + try { + const sample = getSampleResponse(request, server.identity.primaryPort); + if (!sample) { + do_throw( + `unexpected ${request.method} request for ${request.path}?${request.queryString}` + ); + } + + response.setStatusLine( + null, + sample.status.status, + sample.status.statusText + ); + // send the headers + for (let headerLine of sample.sampleHeaders) { + let headerElements = headerLine.split(":"); + response.setHeader(headerElements[0], headerElements[1].trimLeft()); + } + response.setHeader("Date", new Date().toUTCString()); + + const body = + typeof sample.responseBody == "string" + ? sample.responseBody + : JSON.stringify(sample.responseBody); + response.write(body); + response.finish(); + } catch (e) { + info(e); + } +} + +function getSampleResponse(req, port) { + const responses = { + OPTIONS: { + sampleHeaders: [ + "Access-Control-Allow-Headers: Content-Length,Expires,Backoff,Retry-After,Last-Modified,Total-Records,ETag,Pragma,Cache-Control,authorization,content-type,if-none-match,Alert,Next-Page", + "Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST,DELETE,OPTIONS", + "Access-Control-Allow-Origin: *", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 200, statusText: "OK" }, + responseBody: null, + }, + "GET:/v1/": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + settings: { + batch_max_requests: 25, + }, + url: `http://localhost:${port}/v1/`, + documentation: "https://kinto.readthedocs.org/", + version: "1.5.1", + commit: "cbc6f58", + hello: "kinto", + }, + }, + "GET:/v1/buckets/monitor/collections/changes/changeset": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + `Date: ${new Date().toUTCString()}`, + 'Etag: "5000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 5000, + changes: [ + { + id: "4676f0c7-9757-4796-a0e8-b40a5a37a9c9", + bucket: "main", + collection: "unknown-locally", + last_modified: 5000, + }, + { + id: "4676f0c7-9757-4796-a0e8-b40a5a37a9c9", + bucket: "main", + collection: "language-dictionaries", + last_modified: 4000, + }, + { + id: "0af8da0b-3e03-48fb-8d0d-2d8e4cb7514d", + bucket: "main", + collection: "password-fields", + last_modified: 3000, + }, + { + id: "4acda969-3bd3-4074-a678-ff311eeb076e", + bucket: "main-preview", + collection: "password-fields", + last_modified: 2000, + }, + { + id: "58697bd1-315f-4185-9bee-3371befc2585", + bucket: "main-preview", + collection: "crash-rate", + last_modified: 1000, + }, + ], + }, + }, + "GET:/fake-x5u": { + sampleHeaders: ["Content-Type: application/octet-stream"], + status: { status: 200, statusText: "OK" }, + responseBody: `-----BEGIN CERTIFICATE----- +MIIGYTCCBEmgAwIBAgIBATANBgkqhkiG9w0BAQwFADB9MQswCQYDVQQGEwJVU +ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL +26b24/tRam4SJjqpiq20lynhUrmTtt6hbG3E1Hpy3bmkt2DYnuMFwEx2gfXNcnbT +wNuvFqc= +-----END CERTIFICATE-----`, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=2000": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "3000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 3000, + metadata: { + id: "password-fields", + last_modified: 1234, + signature: { + signature: "abcdef", + x5u: `http://localhost:${port}/fake-x5u`, + }, + }, + changes: [ + { + id: "9d500963-d80e-3a91-6e74-66f3811b99cc", + last_modified: 3000, + website: "https://some-website.com", + selector: "#user[password]", + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=3001&_since=%223000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "4000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + metadata: { + signature: {}, + }, + timestamp: 4000, + changes: [ + { + id: "aabad965-e556-ffe7-4191-074f5dee3df3", + last_modified: 4000, + website: "https://www.other.org/signin", + selector: "#signinpassword", + }, + { + id: "9d500963-d80e-3a91-6e74-66f3811b99cc", + last_modified: 3500, + website: "https://some-website.com/login", + selector: "input#user[password]", + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=4001&_since=%224000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "5000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + metadata: { + signature: {}, + }, + timestamp: 5000, + changes: [ + { + id: "aabad965-e556-ffe7-4191-074f5dee3df3", + deleted: true, + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10000&_since=%229999%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 503, statusText: "Service Unavailable" }, + responseBody: { + code: 503, + errno: 999, + error: "Service Unavailable", + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10001&_since=%2210000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "10001"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: "<invalid json", + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=11001&_since=%2211000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 503, statusText: "Service Unavailable" }, + responseBody: { + changes: [ + { + id: "c4f021e3-f68c-4269-ad2a-d4ba87762b35", + last_modified: 4000, + website: "https://www.eff.org", + selector: "#pwd", + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields?_expected=11001": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 503, statusText: "Service Unavailable" }, + responseBody: { + code: 503, + errno: 999, + error: "Service Unavailable", + }, + }, + "GET:/v1/buckets/monitor/collections/changes/changeset?collection=password-fields&bucket=main&_expected=0": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + `Date: ${new Date().toUTCString()}`, + 'Etag: "1338"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 1338, + changes: [ + { + id: "fe5758d0-c67a-42d0-bb4f-8f2d75106b65", + bucket: "main", + collection: "password-fields", + last_modified: 1337, + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=1337": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "3000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + metadata: { + signature: { + signature: "some-sig", + x5u: `http://localhost:${port}/fake-x5u`, + }, + }, + timestamp: 3000, + changes: [ + { + id: "312cc78d-9c1f-4291-a4fa-a1be56f6cc69", + last_modified: 3000, + website: "https://some-website.com", + selector: "#webpage[field-pwd]", + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/password-fields/changeset?_expected=1337&_since=%223000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "3001"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + metadata: { + signature: { + signature: "some-sig", + x5u: `http://localhost:${port}/fake-x5u`, + }, + }, + timestamp: 3001, + changes: [ + { + id: "312cc78d-9c1f-4291-a4fa-a1be56f6cc69", + last_modified: 3001, + website: "https://some-website-2.com", + selector: "#webpage[field-pwd]", + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/language-dictionaries/changeset": { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "5000000000000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 5000000000000, + metadata: { + id: "language-dictionaries", + last_modified: 1234, + signature: { + signature: "xyz", + x5u: `http://localhost:${port}/fake-x5u`, + }, + }, + changes: [ + { + id: "xx", + last_modified: 5000000000000, + dictionaries: ["xx-XX@dictionaries.addons.mozilla.org"], + }, + { + id: "fr", + last_modified: 5000000000000 - 1, + deleted: true, + }, + { + id: "pt-BR", + last_modified: 5000000000000 - 2, + dictionaries: ["pt-BR@for-tests"], + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=2000": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "2000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 2000, + metadata: { + id: "with-local-fields", + last_modified: 1234, + signature: { + signature: "xyz", + x5u: `http://localhost:${port}/fake-x5u`, + }, + }, + changes: [ + { + id: "c74279ce-fb0a-42a6-ae11-386b567a6119", + last_modified: 2000, + }, + ], + }, + }, + "GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=3000&_since=%222000%22": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + 'Etag: "3000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + timestamp: 3000, + metadata: { + signature: {}, + }, + changes: [ + { + id: "1f5c98b9-6d93-4c13-aa26-978b38695096", + last_modified: 3000, + }, + ], + }, + }, + "GET:/v1/buckets/monitor/collections/changes/changeset?collection=no-mocked-responses&bucket=main&_expected=0": + { + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + `Date: ${new Date().toUTCString()}`, + 'Etag: "713705"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: { + data: [ + { + id: "07a98d1b-7c62-4344-ab18-76856b3facd8", + bucket: "main", + collection: "no-mocked-responses", + last_modified: 713705, + }, + ], + }, + }, + }; + return ( + responses[`${req.method}:${req.path}?${req.queryString}`] || + responses[`${req.method}:${req.path}`] || + responses[req.method] + ); +} diff --git a/services/settings/test/unit/test_remote_settings_dump_lastmodified.js b/services/settings/test/unit/test_remote_settings_dump_lastmodified.js new file mode 100644 index 0000000000..1cce089ff7 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_dump_lastmodified.js @@ -0,0 +1,55 @@ +"use strict"; + +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); + +async function getLocalDumpLastModified(bucket, collection) { + let res; + try { + res = await fetch( + `resource://app/defaults/settings/${bucket}/${collection}.json` + ); + } catch (e) { + return -1; + } + const { timestamp } = await res.json(); + ok(timestamp >= 0, `${bucket}/${collection} dump has timestamp`); + return timestamp; +} + +add_task(async function lastModified_of_non_existing_dump() { + ok(!Utils._dumpStats, "_dumpStats not initialized"); + equal( + await Utils.getLocalDumpLastModified("did not", "exist"), + -1, + "A non-existent dump has value -1" + ); + ok(Utils._dumpStats, "_dumpStats was initialized"); + + ok("did not/exist" in Utils._dumpStats, "cached non-existing dump result"); + delete Utils._dumpStats["did not/exist"]; +}); + +add_task(async function lastModified_summary_is_correct() { + ok(!!Object.keys(Utils._dumpStats).length, "Contains summary of dumps"); + + let checked = 0; + for (let [identifier, lastModified] of Object.entries(Utils._dumpStats)) { + let [bucket, collection] = identifier.split("/"); + let actual = await getLocalDumpLastModified(bucket, collection); + if (actual < 0) { + info(`${identifier} has no dump, skip.`); + continue; + } + info(`Checking correctness of ${identifier}`); + equal( + await Utils.getLocalDumpLastModified(bucket, collection), + lastModified, + `Expected last_modified value for ${identifier}` + ); + equal(lastModified, actual, `last_modified should match collection`); + checked++; + } + ok(checked > 0, "At least one dump was packaged and checked."); +}); diff --git a/services/settings/test/unit/test_remote_settings_jexl_filters.js b/services/settings/test/unit/test_remote_settings_jexl_filters.js new file mode 100644 index 0000000000..56d35bdd2b --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_jexl_filters.js @@ -0,0 +1,216 @@ +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); + +let client; + +async function createRecords(records) { + await client.db.importChanges( + {}, + 42, + records.map((record, i) => ({ + id: `record-${i}`, + ...record, + })), + { + clear: true, + } + ); +} + +function run_test() { + client = RemoteSettings("some-key"); + + run_next_test(); +} + +add_task(async function test_returns_all_without_target() { + await createRecords([ + { + passwordSelector: "#pass-signin", + }, + { + filter_expression: null, + }, + { + filter_expression: "", + }, + ]); + + const list = await client.get(); + equal(list.length, 3); +}); + +add_task(async function test_filters_can_be_disabled() { + const c = RemoteSettings("no-jexl", { filterFunc: null }); + await c.db.importChanges({}, 42, [ + { + id: "abc", + filter_expression: "1 == 2", + }, + ]); + + const list = await c.get(); + equal(list.length, 1); +}); + +add_task(async function test_returns_entries_where_jexl_is_true() { + await createRecords([ + { + willMatch: true, + filter_expression: "1", + }, + { + willMatch: true, + filter_expression: "[42]", + }, + { + willMatch: true, + filter_expression: "1 == 2 || 1 == 1", + }, + { + willMatch: true, + filter_expression: 'env.appinfo.ID == "xpcshell@tests.mozilla.org"', + }, + { + willMatch: false, + filter_expression: "env.version == undefined", + }, + { + willMatch: true, + filter_expression: "env.unknown == undefined", + }, + { + willMatch: false, + filter_expression: "1 == 2", + }, + ]); + + const list = await client.get(); + equal(list.length, 5); + ok(list.every(e => e.willMatch)); +}); + +add_task(async function test_ignores_entries_where_jexl_is_invalid() { + await createRecords([ + { + filter_expression: "true === true", // JavaScript Error: "Invalid expression token: =" + }, + { + filter_expression: "Objects.keys({}) == []", // Token ( (openParen) unexpected in expression + }, + ]); + + const list = await client.get(); + equal(list.length, 0); +}); + +add_task(async function test_support_of_date_filters() { + await createRecords([ + { + willMatch: true, + filter_expression: '"1982-05-08"|date < "2016-03-22"|date', + }, + { + willMatch: false, + filter_expression: '"2000-01-01"|date < "1970-01-01"|date', + }, + ]); + + const list = await client.get(); + equal(list.length, 1); + ok(list.every(e => e.willMatch)); +}); + +add_task(async function test_support_of_preferences_filters() { + await createRecords([ + { + willMatch: true, + filter_expression: '"services.settings.last_etag"|preferenceValue == 42', + }, + { + willMatch: true, + filter_expression: + '"services.settings.poll_interval"|preferenceExists == true', + }, + { + willMatch: true, + filter_expression: + '"services.settings.poll_interval"|preferenceIsUserSet == false', + }, + { + willMatch: true, + filter_expression: + '"services.settings.last_etag"|preferenceIsUserSet == true', + }, + ]); + + // Set a pref for the user. + Services.prefs.setIntPref("services.settings.last_etag", 42); + + const list = await client.get(); + equal(list.length, 4); + ok(list.every(e => e.willMatch)); +}); + +add_task(async function test_support_of_intersect_operator() { + await createRecords([ + { + willMatch: true, + filter_expression: '{foo: 1, bar: 2}|keys intersect ["foo"]', + }, + { + willMatch: true, + filter_expression: '(["a", "b"] intersect ["a", 1, 4]) == "a"', + }, + { + willMatch: false, + filter_expression: '(["a", "b"] intersect [3, 1, 4]) == "c"', + }, + { + willMatch: true, + filter_expression: ` + [1, 2, 3] + intersect + [3, 4, 5] + `, + }, + ]); + + const list = await client.get(); + equal(list.length, 3); + ok(list.every(e => e.willMatch)); +}); + +add_task(async function test_support_of_samples() { + await createRecords([ + { + willMatch: true, + filter_expression: '"always-true"|stableSample(1)', + }, + { + willMatch: false, + filter_expression: '"always-false"|stableSample(0)', + }, + { + willMatch: true, + filter_expression: '"turns-to-true-0"|stableSample(0.5)', + }, + { + willMatch: false, + filter_expression: '"turns-to-false-1"|stableSample(0.5)', + }, + { + willMatch: true, + filter_expression: '"turns-to-true-0"|bucketSample(0, 50, 100)', + }, + { + willMatch: false, + filter_expression: '"turns-to-false-1"|bucketSample(0, 50, 100)', + }, + ]); + + const list = await client.get(); + equal(list.length, 3); + ok(list.every(e => e.willMatch)); +}); diff --git a/services/settings/test/unit/test_remote_settings_offline.js b/services/settings/test/unit/test_remote_settings_offline.js new file mode 100644 index 0000000000..ffb810829d --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_offline.js @@ -0,0 +1,141 @@ +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); +const { RemoteSettingsWorker } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsWorker.sys.mjs" +); +const { SharedUtils } = ChromeUtils.importESModule( + "resource://services-settings/SharedUtils.sys.mjs" +); + +// A collection with a dump that's packaged on all builds where this test runs, +// including on Android at mobile/android/installer/package-manifest.in +const TEST_BUCKET = "main"; +const TEST_COLLECTION = "password-recipes"; + +let client; +let DUMP_RECORDS; +let DUMP_LAST_MODIFIED; + +add_task(async function setup() { + // "services.settings.server" pref is not set. + // Test defaults to an unreachable server, + // and will only load from the dump if any. + + client = new RemoteSettingsClient(TEST_COLLECTION, { + bucketName: TEST_BUCKET, + }); + + const dump = await SharedUtils.loadJSONDump(TEST_BUCKET, TEST_COLLECTION); + DUMP_RECORDS = dump.data; + DUMP_LAST_MODIFIED = dump.timestamp; + + // Dumps are fetched via the following, which sorts the records, newest first. + // https://searchfox.org/mozilla-central/rev/5b3444ad300e244b5af4214212e22bd9e4b7088a/taskcluster/docker/periodic-updates/scripts/periodic_file_updates.sh#304 + equal( + DUMP_LAST_MODIFIED, + DUMP_RECORDS[0].last_modified, + "records in dump ought to be sorted by last_modified" + ); +}); + +async function importData(records) { + await RemoteSettingsWorker._execute("_test_only_import", [ + TEST_BUCKET, + TEST_COLLECTION, + records, + records[0]?.last_modified || 0, + ]); +} + +async function clear_state() { + await client.db.clear(); +} + +add_task(async function test_load_from_dump_when_offline() { + // Baseline: verify that the collection is empty at first, + // but non-empty after loading from the dump. + const before = await client.get({ syncIfEmpty: false }); + equal(before.length, 0, "collection empty when offline"); + + // should import from dump since collection was not initialized. + const after = await client.get(); + equal(after.length, DUMP_RECORDS.length, "collection loaded from dump"); + equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp"); +}); +add_task(clear_state); + +add_task(async function test_optional_skip_dump_after_empty_import() { + // clear_state should have wiped the database. + const before = await client.get({ syncIfEmpty: false }); + equal(before.length, 0, "collection empty after clearing"); + + // Verify that the dump is not imported again by client.get() + // when the database is initialized with an empty dump + // with `loadDumpIfNewer` disabled. + await importData([]); // <-- Empty set of records. + + const after = await client.get({ loadDumpIfNewer: false }); + equal(after.length, 0, "collection still empty due to import"); + equal(await client.getLastModified(), 0, "Empty dump has no timestamp"); +}); +add_task(clear_state); + +add_task(async function test_optional_skip_dump_after_non_empty_import() { + await importData([{ last_modified: 1234, id: "dummy" }]); + + const after = await client.get({ loadDumpIfNewer: false }); + equal(after.length, 1, "Imported dummy data"); + equal(await client.getLastModified(), 1234, "Expected timestamp of import"); + + await importData([]); + const after2 = await client.get({ loadDumpIfNewer: false }); + equal(after2.length, 0, "Previous data wiped on duplicate import"); + equal(await client.getLastModified(), 0, "Timestamp of empty collection"); +}); +add_task(clear_state); + +add_task(async function test_load_dump_after_empty_import() { + await importData([]); // <-- Empty set of records, i.e. last_modified = 0. + + const after = await client.get(); + equal(after.length, DUMP_RECORDS.length, "Imported dump"); + equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp"); +}); +add_task(clear_state); + +add_task(async function test_load_dump_after_non_empty_import() { + // Dump is updated regularly, verify that the dump matches our expectations + // before running the test. + ok(DUMP_LAST_MODIFIED > 1234, "Assuming dump to be newer than dummy 1234"); + + await importData([{ last_modified: 1234, id: "dummy" }]); + + const after = await client.get(); + equal(after.length, DUMP_RECORDS.length, "Imported dump"); + equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp"); +}); +add_task(clear_state); + +add_task(async function test_load_dump_after_import_from_broken_distro() { + // Dump is updated regularly, verify that the dump matches our expectations + // before running the test. + ok(DUMP_LAST_MODIFIED > 1234, "Assuming dump to be newer than dummy 1234"); + + // No last_modified time. + await importData([{ id: "dummy" }]); + + const after = await client.get(); + equal(after.length, DUMP_RECORDS.length, "Imported dump"); + equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp"); +}); +add_task(clear_state); + +add_task(async function test_skip_dump_if_same_last_modified() { + await importData([{ last_modified: DUMP_LAST_MODIFIED, id: "dummy" }]); + + const after = await client.get(); + equal(after.length, 1, "Not importing dump when time matches"); + equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "Same timestamp"); +}); +add_task(clear_state); diff --git a/services/settings/test/unit/test_remote_settings_poll.js b/services/settings/test/unit/test_remote_settings_poll.js new file mode 100644 index 0000000000..3bf389ea34 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_poll.js @@ -0,0 +1,1386 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const { UptakeTelemetry, Policy } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" +); +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); +const { pushBroadcastService } = ChromeUtils.importESModule( + "resource://gre/modules/PushBroadcastService.sys.mjs" +); +const { SyncHistory } = ChromeUtils.importESModule( + "resource://services-settings/SyncHistory.sys.mjs" +); +const { RemoteSettings, remoteSettingsBroadcastHandler, BROADCAST_ID } = + ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" + ); +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const IS_ANDROID = AppConstants.platform == "android"; + +const PREF_SETTINGS_SERVER = "services.settings.server"; +const PREF_SETTINGS_SERVER_BACKOFF = "services.settings.server.backoff"; +const PREF_LAST_UPDATE = "services.settings.last_update_seconds"; +const PREF_LAST_ETAG = "services.settings.last_etag"; +const PREF_CLOCK_SKEW_SECONDS = "services.settings.clock_skew_seconds"; + +// Telemetry report result. +const TELEMETRY_COMPONENT = "remotesettings"; +const TELEMETRY_SOURCE_POLL = "settings-changes-monitoring"; +const TELEMETRY_SOURCE_SYNC = "settings-sync"; +const CHANGES_PATH = "/v1" + Utils.CHANGES_PATH; + +var server; + +async function clear_state() { + // set up prefs so the kinto updater talks to the test server + Services.prefs.setStringPref( + PREF_SETTINGS_SERVER, + `http://localhost:${server.identity.primaryPort}/v1` + ); + + // set some initial values so we can check these are updated appropriately + Services.prefs.setIntPref(PREF_LAST_UPDATE, 0); + Services.prefs.setIntPref(PREF_CLOCK_SKEW_SECONDS, 0); + Services.prefs.clearUserPref(PREF_LAST_ETAG); + + // Clear events snapshot. + TelemetryTestUtils.assertEvents([], {}, { process: "dummy" }); + + // Clear sync history. + await new SyncHistory("").clear(); +} + +function serveChangesEntries(serverTime, entriesOrFunc) { + return (request, response) => { + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date(serverTime).toUTCString()); + const entries = + typeof entriesOrFunc == "function" ? entriesOrFunc() : entriesOrFunc; + const latest = entries[0]?.last_modified ?? 42; + if (entries.length) { + response.setHeader("ETag", `"${latest}"`); + } + response.write(JSON.stringify({ timestamp: latest, changes: entries })); + }; +} + +function run_test() { + // Set up an HTTP Server + server = new HttpServer(); + server.start(-1); + + // Pretend we are in nightly channel to make sure all telemetry events are sent. + let oldGetChannel = Policy.getChannel; + Policy.getChannel = () => "nightly"; + + run_next_test(); + + registerCleanupFunction(() => { + Policy.getChannel = oldGetChannel; + server.stop(() => {}); + }); +} + +add_task(clear_state); + +add_task(async function test_an_event_is_sent_on_start() { + server.registerPathHandler(CHANGES_PATH, (request, response) => { + response.write(JSON.stringify({ timestamp: 42, changes: [] })); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("ETag", '"42"'); + response.setHeader("Date", new Date().toUTCString()); + response.setStatusLine(null, 200, "OK"); + }); + let notificationObserved = null; + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "remote-settings:changes-poll-start"); + notificationObserved = JSON.parse(aData); + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-start"); + + await RemoteSettings.pollChanges({ expectedTimestamp: 13 }); + + Assert.equal( + notificationObserved.expectedTimestamp, + 13, + "start notification should have been observed" + ); +}); +add_task(clear_state); + +add_task(async function test_offline_is_reported_if_relevant() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const offlineBackup = Services.io.offline; + try { + Services.io.offline = true; + + await RemoteSettings.pollChanges(); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + } finally { + Services.io.offline = offlineBackup; + } +}); +add_task(clear_state); + +add_task(async function test_check_success() { + const serverTime = 8000; + + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(serverTime, [ + { + id: "330a0c5f-fadf-ff0b-40c8-4eb0d924ff6a", + last_modified: 1100, + host: "localhost", + bucket: "some-other-bucket", + collection: "test-collection", + }, + { + id: "254cbb9e-6888-4d9f-8e60-58b74faa8778", + last_modified: 1000, + host: "localhost", + bucket: "test-bucket", + collection: "test-collection", + }, + ]) + ); + + // add a test kinto client that will respond to lastModified information + // for a collection called 'test-collection'. + // Let's use a bucket that is not the default one (`test-bucket`). + const c = RemoteSettings("test-collection", { + bucketName: "test-bucket", + }); + let maybeSyncCalled = false; + c.maybeSync = () => { + maybeSyncCalled = true; + }; + + // Ensure that the remote-settings:changes-poll-end notification works + let notificationObserved = false; + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "remote-settings:changes-poll-end"); + notificationObserved = true; + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-end"); + + await RemoteSettings.pollChanges(); + + // It didn't fail, hence we are sure that the unknown collection ``some-other-bucket/test-collection`` + // was ignored, otherwise it would have tried to reach the network. + + Assert.ok(maybeSyncCalled, "maybeSync was called"); + Assert.ok(notificationObserved, "a notification should have been observed"); + // Last timestamp was saved. An ETag header value is a quoted string. + Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), '"1100"'); + // check the last_update is updated + Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), serverTime / 1000); + + // ensure that we've accumulated the correct telemetry + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.SUCCESS, + { + source: TELEMETRY_SOURCE_POLL, + trigger: "manual", + }, + ], + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.SUCCESS, + { + source: TELEMETRY_SOURCE_SYNC, + trigger: "manual", + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); +}); +add_task(clear_state); + +add_task(async function test_update_timer_interface() { + const remoteSettings = Cc["@mozilla.org/services/settings;1"].getService( + Ci.nsITimerCallback + ); + + const serverTime = 8000; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(serverTime, [ + { + id: "028261ad-16d4-40c2-a96a-66f72914d125", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "whatever-collection", + }, + ]) + ); + + await new Promise(resolve => { + const e = "remote-settings:changes-poll-end"; + const changesPolledObserver = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, e); + resolve(); + }, + }; + Services.obs.addObserver(changesPolledObserver, e); + remoteSettings.notify(null); + }); + + // Everything went fine. + Assert.equal(Services.prefs.getStringPref(PREF_LAST_ETAG), '"42"'); + Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), serverTime / 1000); +}); +add_task(clear_state); + +add_task(async function test_check_up_to_date() { + // Simulate a poll with up-to-date collection. + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + const serverTime = 4000; + server.registerPathHandler(CHANGES_PATH, serveChangesEntries(serverTime, [])); + + Services.prefs.setStringPref(PREF_LAST_ETAG, '"1100"'); + + // Ensure that the remote-settings:changes-poll-end notification is sent. + let notificationObserved = false; + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "remote-settings:changes-poll-end"); + notificationObserved = true; + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-end"); + + // If server has no change, maybeSync() is not called. + let maybeSyncCalled = false; + const c = RemoteSettings("test-collection", { + bucketName: "test-bucket", + }); + c.maybeSync = () => { + maybeSyncCalled = true; + }; + + await RemoteSettings.pollChanges(); + + Assert.ok(notificationObserved, "a notification should have been observed"); + Assert.ok(!maybeSyncCalled, "maybeSync should not be called"); + // Last update is overwritten + Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), serverTime / 1000); + + // ensure that we've accumulated the correct telemetry + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.UP_TO_DATE]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_expected_timestamp() { + function withCacheBust(request, response) { + const entries = [ + { + id: "695c2407-de79-4408-91c7-70720dd59d78", + last_modified: 1100, + host: "localhost", + bucket: "main", + collection: "with-cache-busting", + }, + ]; + if ( + request.queryString.includes(`_expected=${encodeURIComponent('"42"')}`) + ) { + response.write( + JSON.stringify({ + timestamp: 1110, + changes: entries, + }) + ); + } + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("ETag", '"1100"'); + response.setHeader("Date", new Date().toUTCString()); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, withCacheBust); + + const c = RemoteSettings("with-cache-busting"); + let maybeSyncCalled = false; + c.maybeSync = () => { + maybeSyncCalled = true; + }; + + await RemoteSettings.pollChanges({ expectedTimestamp: '"42"' }); + + Assert.ok(maybeSyncCalled, "maybeSync was called"); +}); +add_task(clear_state); + +add_task(async function test_client_last_check_is_saved() { + server.registerPathHandler(CHANGES_PATH, (request, response) => { + response.write( + JSON.stringify({ + timestamp: 42, + changes: [ + { + id: "695c2407-de79-4408-91c7-70720dd59d78", + last_modified: 1100, + host: "localhost", + bucket: "main", + collection: "models-recipes", + }, + ], + }) + ); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("ETag", '"42"'); + response.setHeader("Date", new Date().toUTCString()); + response.setStatusLine(null, 200, "OK"); + }); + + const c = RemoteSettings("models-recipes"); + c.maybeSync = () => {}; + + equal( + c.lastCheckTimePref, + "services.settings.main.models-recipes.last_check" + ); + Services.prefs.setIntPref(c.lastCheckTimePref, 0); + + await RemoteSettings.pollChanges({ expectedTimestamp: '"42"' }); + + notEqual(Services.prefs.getIntPref(c.lastCheckTimePref), 0); +}); +add_task(clear_state); + +const TELEMETRY_EVENTS_FILTERS = { + category: "uptake.remotecontent.result", + method: "uptake", +}; +add_task(async function test_age_of_data_is_reported_in_uptake_status() { + const serverTime = 1552323900000; + const recordsTimestamp = serverTime - 3600 * 1000; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(serverTime, [ + { + id: "b6ba7fab-a40a-4d03-a4af-6b627f3c5b36", + last_modified: recordsTimestamp, + host: "localhost", + bucket: "main", + collection: "some-entry", + }, + ]) + ); + + await RemoteSettings.pollChanges(); + + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.SUCCESS, + { + source: TELEMETRY_SOURCE_POLL, + age: "3600", + trigger: "manual", + }, + ], + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.SUCCESS, + { + source: TELEMETRY_SOURCE_SYNC, + duration: () => true, + trigger: "manual", + timestamp: `"${recordsTimestamp}"`, + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); +}); +add_task(clear_state); + +add_task( + async function test_synchronization_duration_is_reported_in_uptake_status() { + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, [ + { + id: "b6ba7fab-a40a-4d03-a4af-6b627f3c5b36", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "some-entry", + }, + ]) + ); + const c = RemoteSettings("some-entry"); + // Simulate a synchronization that lasts 1 sec. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + c.maybeSync = () => new Promise(resolve => setTimeout(resolve, 1000)); + + await RemoteSettings.pollChanges(); + + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + "success", + { + source: TELEMETRY_SOURCE_POLL, + age: () => true, + trigger: "manual", + }, + ], + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + "success", + { + source: TELEMETRY_SOURCE_SYNC, + duration: v => v >= 1000, + trigger: "manual", + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); + } +); +add_task(clear_state); + +add_task(async function test_success_with_partial_list() { + function partialList(request, response) { + const entries = [ + { + id: "028261ad-16d4-40c2-a96a-66f72914d125", + last_modified: 43, + host: "localhost", + bucket: "main", + collection: "cid-1", + }, + { + id: "98a34576-bcd6-423f-abc2-1d290b776ed8", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "poll-test-collection", + }, + ]; + if (request.queryString.includes(`_since=${encodeURIComponent('"42"')}`)) { + response.write( + JSON.stringify({ + timestamp: 43, + changes: entries.slice(0, 1), + }) + ); + } else { + response.write( + JSON.stringify({ + timestamp: 42, + changes: entries, + }) + ); + } + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date().toUTCString()); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, partialList); + + const c = RemoteSettings("poll-test-collection"); + let maybeSyncCount = 0; + c.maybeSync = () => { + maybeSyncCount++; + }; + + await RemoteSettings.pollChanges(); + await RemoteSettings.pollChanges(); + + // On the second call, the server does not mention the poll-test-collection + // and maybeSync() is not called. + Assert.equal(maybeSyncCount, 1, "maybeSync should not be called twice"); +}); +add_task(clear_state); + +add_task(async function test_full_polling() { + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, [ + { + id: "b6ba7fab-a40a-4d03-a4af-6b627f3c5b36", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "poll-test-collection", + }, + ]) + ); + + const c = RemoteSettings("poll-test-collection"); + let maybeSyncCount = 0; + c.maybeSync = () => { + maybeSyncCount++; + }; + + await RemoteSettings.pollChanges(); + await RemoteSettings.pollChanges({ full: true }); + + // Since the second call is full, clients are called + Assert.equal(maybeSyncCount, 2, "maybeSync should be called twice"); +}); +add_task(clear_state); + +add_task(async function test_server_bad_json() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + function simulateBadJSON(request, response) { + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.write("<html></html>"); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, simulateBadJSON); + + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + Assert.ok(/JSON.parse: unexpected character/.test(error.message)); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.PARSE_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_server_bad_content_type() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + function simulateBadContentType(request, response) { + response.setHeader("Content-Type", "text/html"); + response.write("<html></html>"); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, simulateBadContentType); + + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + Assert.ok(/Unexpected content-type/.test(error.message)); + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.CONTENT_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_server_404_response() { + function simulateDummy404(request, response) { + response.setHeader("Content-Type", "text/html; charset=UTF-8"); + response.write("<html></html>"); + response.setStatusLine(null, 404, "OK"); + } + server.registerPathHandler(CHANGES_PATH, simulateDummy404); + + await RemoteSettings.pollChanges(); // Does not fail when running from tests. +}); +add_task(clear_state); + +add_task(async function test_server_error() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + // Simulate a server error. + function simulateErrorResponse(request, response) { + response.setHeader("Date", new Date(3000).toUTCString()); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.write( + JSON.stringify({ + code: 503, + errno: 999, + error: "Service Unavailable", + }) + ); + response.setStatusLine(null, 503, "Service Unavailable"); + } + server.registerPathHandler(CHANGES_PATH, simulateErrorResponse); + + let notificationObserved = false; + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, "remote-settings:changes-poll-end"); + notificationObserved = true; + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-end"); + Services.prefs.setIntPref(PREF_LAST_UPDATE, 42); + + // pollChanges() fails with adequate error and no notification. + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + + Assert.ok( + !notificationObserved, + "a notification should not have been observed" + ); + Assert.ok(/Polling for changes failed/.test(error.message)); + // When an error occurs, last update was not overwritten. + Assert.equal(Services.prefs.getIntPref(PREF_LAST_UPDATE), 42); + // ensure that we've accumulated the correct telemetry + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.SERVER_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_server_error_5xx() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + function simulateErrorResponse(request, response) { + response.setHeader("Date", new Date(3000).toUTCString()); + response.setHeader("Content-Type", "text/html; charset=UTF-8"); + response.write("<html></html>"); + response.setStatusLine(null, 504, "Gateway Timeout"); + } + server.registerPathHandler(CHANGES_PATH, simulateErrorResponse); + + try { + await RemoteSettings.pollChanges(); + } catch (e) {} + + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.SERVER_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_server_error_4xx() { + function simulateErrorResponse(request, response) { + response.setHeader("Date", new Date(3000).toUTCString()); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + if (request.queryString.includes(`_since=${encodeURIComponent('"abc"')}`)) { + response.setStatusLine(null, 400, "Bad Request"); + response.write(JSON.stringify({})); + } else { + response.setStatusLine(null, 200, "OK"); + response.write(JSON.stringify({ changes: [] })); + } + } + server.registerPathHandler(CHANGES_PATH, simulateErrorResponse); + + Services.prefs.setStringPref(PREF_LAST_ETAG, '"abc"'); + + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + + Assert.ok(error.message.includes("400 Bad Request"), "Polling failed"); + Assert.ok( + !Services.prefs.prefHasUserValue(PREF_LAST_ETAG), + "Last ETag pref was cleared" + ); + + await RemoteSettings.pollChanges(); // Does not raise. +}); +add_task(clear_state); + +add_task(async function test_client_error() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_SYNC + ); + + const collectionDetails = { + id: "b6ba7fab-a40a-4d03-a4af-6b627f3c5b36", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "some-entry", + }; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, [collectionDetails]) + ); + const c = RemoteSettings("some-entry"); + c.maybeSync = () => { + throw new RemoteSettingsClient.CorruptedDataError("main/some-entry"); + }; + + let notificationsObserved = []; + const observer = { + observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, aTopic); + notificationsObserved.push([aTopic, aSubject.wrappedJSObject]); + }, + }; + Services.obs.addObserver(observer, "remote-settings:changes-poll-end"); + Services.obs.addObserver(observer, "remote-settings:sync-error"); + Services.prefs.setIntPref(PREF_LAST_ETAG, 42); + + // pollChanges() fails with adequate error and a sync-error notification. + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + + Assert.equal( + notificationsObserved.length, + 1, + "only the error notification should not have been observed" + ); + console.log(notificationsObserved); + let [topicObserved, subjectObserved] = notificationsObserved[0]; + Assert.equal(topicObserved, "remote-settings:sync-error"); + Assert.ok( + subjectObserved.error instanceof RemoteSettingsClient.CorruptedDataError, + `original error is provided (got ${subjectObserved.error})` + ); + Assert.deepEqual( + subjectObserved.error.details, + collectionDetails, + "information about collection is provided" + ); + + Assert.ok(/Corrupted/.test(error.message), "original client error is thrown"); + // When an error occurs, last etag was not overwritten. + Assert.equal(Services.prefs.getIntPref(PREF_LAST_ETAG), 42); + // ensure that we've accumulated the correct telemetry + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_SYNC + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.SYNC_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_sync_success_is_stored_in_history() { + const collectionDetails = { + last_modified: 444, + bucket: "main", + collection: "desktop-manager", + }; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, [collectionDetails]) + ); + const c = RemoteSettings("desktop-manager"); + c.maybeSync = () => {}; + try { + await RemoteSettings.pollChanges({ expectedTimestamp: 555 }); + } catch (e) {} + + const { history } = await RemoteSettings.inspect(); + + Assert.deepEqual(history, { + [TELEMETRY_SOURCE_SYNC]: [ + { + timestamp: 444, + status: "success", + infos: {}, + datetime: new Date(444), + }, + ], + }); +}); +add_task(clear_state); + +add_task(async function test_sync_error_is_stored_in_history() { + const collectionDetails = { + last_modified: 1337, + bucket: "main", + collection: "desktop-manager", + }; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, [collectionDetails]) + ); + const c = RemoteSettings("desktop-manager"); + c.maybeSync = () => { + throw new RemoteSettingsClient.MissingSignatureError( + "main/desktop-manager" + ); + }; + try { + await RemoteSettings.pollChanges({ expectedTimestamp: 123456 }); + } catch (e) {} + + const { history } = await RemoteSettings.inspect(); + + Assert.deepEqual(history, { + [TELEMETRY_SOURCE_SYNC]: [ + { + timestamp: 1337, + status: "sync_error", + infos: { + expectedTimestamp: 123456, + errorName: "MissingSignatureError", + }, + datetime: new Date(1337), + }, + ], + }); +}); +add_task(clear_state); + +add_task( + async function test_sync_broken_signal_is_sent_on_consistent_failure() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + // Wait for the "sync-broken-error" notification. + let notificationObserved = false; + const observer = { + observe(aSubject, aTopic, aData) { + notificationObserved = true; + }, + }; + Services.obs.addObserver(observer, "remote-settings:broken-sync-error"); + // Register a client with a failing sync method. + const c = RemoteSettings("desktop-manager"); + c.maybeSync = () => { + throw new RemoteSettingsClient.InvalidSignatureError( + "main/desktop-manager" + ); + }; + // Simulate a response whose ETag gets incremented on each call + // (in order to generate several history entries, indexed by timestamp). + let timestamp = 1337; + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(10000, () => { + return [ + { + last_modified: ++timestamp, + bucket: "main", + collection: "desktop-manager", + }, + ]; + }) + ); + + // Now obtain several failures in a row (less than threshold). + for (var i = 0; i < 9; i++) { + try { + await RemoteSettings.pollChanges(); + } catch (e) {} + } + Assert.ok(!notificationObserved, "Not notified yet"); + + // Fail again once. Will now notify. + try { + await RemoteSettings.pollChanges(); + } catch (e) {} + Assert.ok(notificationObserved, "Broken sync notified"); + // Uptake event to notify broken sync is sent. + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_SYNC + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.SYNC_ERROR]: 10, + [UptakeTelemetry.STATUS.SYNC_BROKEN_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + + // Synchronize successfully. + notificationObserved = false; + const failingSync = c.maybeSync; + c.maybeSync = () => {}; + await RemoteSettings.pollChanges(); + + const { history } = await RemoteSettings.inspect(); + Assert.equal( + history[TELEMETRY_SOURCE_SYNC][0].status, + UptakeTelemetry.STATUS.SUCCESS, + "Last sync is success" + ); + Assert.ok(!notificationObserved, "Not notified after success"); + + // Now fail again. Broken sync isn't notified, we need several in a row. + c.maybeSync = failingSync; + try { + await RemoteSettings.pollChanges(); + } catch (e) {} + Assert.ok(!notificationObserved, "Not notified on single error"); + Services.obs.removeObserver(observer, "remote-settings:broken-sync-error"); + } +); +add_task(clear_state); + +add_task(async function test_check_clockskew_is_updated() { + const serverTime = 2000; + + function serverResponse(request, response) { + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date(serverTime).toUTCString()); + response.write(JSON.stringify({ timestamp: 42, changes: [] })); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, serverResponse); + + let startTime = Date.now(); + + await RemoteSettings.pollChanges(); + + // How does the clock difference look? + let endTime = Date.now(); + let clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS); + // we previously set the serverTime to 2 (seconds past epoch) + Assert.ok( + clockDifference <= endTime / 1000 && + clockDifference >= Math.floor(startTime / 1000) - serverTime / 1000 + ); + + // check negative clock skew times + // set to a time in the future + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(Date.now() + 10000, []) + ); + + await RemoteSettings.pollChanges(); + + clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS); + // we previously set the serverTime to Date.now() + 10000 ms past epoch + Assert.ok(clockDifference <= 0 && clockDifference >= -10); +}); +add_task(clear_state); + +add_task(async function test_check_clockskew_takes_age_into_account() { + const currentTime = Date.now(); + const skewSeconds = 5; + const ageCDNSeconds = 3600; + const serverTime = currentTime - skewSeconds * 1000 - ageCDNSeconds * 1000; + + function serverResponse(request, response) { + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date(serverTime).toUTCString()); + response.setHeader("Age", `${ageCDNSeconds}`); + response.write(JSON.stringify({ timestamp: 42, changes: [] })); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, serverResponse); + + await RemoteSettings.pollChanges(); + + const clockSkew = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS); + Assert.ok(clockSkew >= skewSeconds, `clockSkew is ${clockSkew}`); +}); +add_task(clear_state); + +add_task(async function test_backoff() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + function simulateBackoffResponse(request, response) { + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Backoff", "10"); + response.write(JSON.stringify({ timestamp: 42, changes: [] })); + response.setStatusLine(null, 200, "OK"); + } + server.registerPathHandler(CHANGES_PATH, simulateBackoffResponse); + + // First will work. + await RemoteSettings.pollChanges(); + // Second will fail because we haven't waited. + try { + await RemoteSettings.pollChanges(); + // The previous line should have thrown an error. + Assert.ok(false); + } catch (e) { + Assert.ok( + /Server is asking clients to back off; retry in \d+s./.test(e.message) + ); + } + + // Once backoff time has expired, polling for changes can start again. + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(12000, [ + { + id: "6a733d4a-601e-11e8-837a-0f85257529a1", + last_modified: 1300, + host: "localhost", + bucket: "some-bucket", + collection: "some-collection", + }, + ]) + ); + Services.prefs.setStringPref( + PREF_SETTINGS_SERVER_BACKOFF, + `${Date.now() - 1000}` + ); + + await RemoteSettings.pollChanges(); + + // Backoff tracking preference was cleared. + Assert.ok(!Services.prefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)); + + // Ensure that we've accumulated the correct telemetry + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.SUCCESS]: 1, + [UptakeTelemetry.STATUS.UP_TO_DATE]: 1, + [UptakeTelemetry.STATUS.BACKOFF]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_network_error() { + const startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + + // Simulate a network error (to check telemetry report). + Services.prefs.setStringPref(PREF_SETTINGS_SERVER, "http://localhost:42/v1"); + try { + await RemoteSettings.pollChanges(); + } catch (e) {} + + // ensure that we've accumulated the correct telemetry + const endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE_POLL + ); + const expectedIncrements = { + [UptakeTelemetry.STATUS.NETWORK_ERROR]: 1, + }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); +}); +add_task(clear_state); + +add_task(async function test_syncs_clients_with_local_database() { + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(42000, [ + { + id: "d4a14f44-601f-11e8-8b8a-030f3dc5b844", + last_modified: 10000, + host: "localhost", + bucket: "main", + collection: "some-unknown", + }, + { + id: "39f57e4e-6023-11e8-8b74-77c8dedfb389", + last_modified: 9000, + host: "localhost", + bucket: "blocklists", + collection: "addons", + }, + { + id: "9a594c1a-601f-11e8-9c8a-33b2239d9113", + last_modified: 8000, + host: "localhost", + bucket: "main", + collection: "recipes", + }, + ]) + ); + + // This simulates what remote-settings would do when initializing a local database. + // We don't want to instantiate a client using the RemoteSettings() API + // since we want to test «unknown» clients that have a local database. + new RemoteSettingsClient("addons", { + bucketName: "blocklists", + }).db.importChanges({}, 42); + new RemoteSettingsClient("recipes").db.importChanges({}, 43); + + let error; + try { + await RemoteSettings.pollChanges(); + Assert.ok(false, "pollChange() should throw when pulling recipes"); + } catch (e) { + error = e; + } + + // The `main/some-unknown` should be skipped because it has no local database. + // The `blocklists/addons` should be skipped because it is not the main bucket. + // The `recipes` has a local database, and should cause a network error because the test + // does not setup the server to receive the requests of `maybeSync()`. + Assert.ok(/HTTP 404/.test(error.message), "server will return 404 on sync"); + Assert.equal(error.details.collection, "recipes"); +}); +add_task(clear_state); + +add_task(async function test_syncs_clients_with_local_dump() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + server.registerPathHandler( + CHANGES_PATH, + serveChangesEntries(42000, [ + { + id: "d4a14f44-601f-11e8-8b8a-030f3dc5b844", + last_modified: 10000, + host: "localhost", + bucket: "main", + collection: "some-unknown", + }, + { + id: "39f57e4e-6023-11e8-8b74-77c8dedfb389", + last_modified: 9000, + host: "localhost", + bucket: "blocklists", + collection: "addons", + }, + { + id: "9a594c1a-601f-11e8-9c8a-33b2239d9113", + last_modified: 8000, + host: "localhost", + bucket: "main", + collection: "example", + }, + ]) + ); + + let error; + try { + await RemoteSettings.pollChanges(); + } catch (e) { + error = e; + } + + // The `main/some-unknown` should be skipped because it has no dump. + // The `blocklists/addons` should be skipped because it is not the main bucket. + // The `example` has a dump, and should cause a network error because the test + // does not setup the server to receive the requests of `maybeSync()`. + Assert.ok(/HTTP 404/.test(error.message), "server will return 404 on sync"); + Assert.equal(error.details.collection, "example"); +}); +add_task(clear_state); + +add_task(async function test_adding_client_resets_polling() { + function serve200(request, response) { + const entries = [ + { + id: "aa71e6cc-9f37-447a-b6e0-c025e8eabd03", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "a-collection", + }, + ]; + if (request.queryString.includes("_since")) { + response.write( + JSON.stringify({ + timestamp: 42, + changes: [], + }) + ); + } else { + response.write( + JSON.stringify({ + timestamp: 42, + changes: entries, + }) + ); + } + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date().toUTCString()); + } + server.registerPathHandler(CHANGES_PATH, serve200); + + // Poll once, without any client for "a-collection" + await RemoteSettings.pollChanges(); + + // Register a new client. + let maybeSyncCalled = false; + const c = RemoteSettings("a-collection"); + c.maybeSync = () => { + maybeSyncCalled = true; + }; + + // Poll again. + await RemoteSettings.pollChanges(); + + // The new client was called, even if the server data didn't change. + Assert.ok(maybeSyncCalled); + + // Poll again. This time maybeSync() won't be called. + maybeSyncCalled = false; + await RemoteSettings.pollChanges(); + Assert.ok(!maybeSyncCalled); +}); +add_task(clear_state); + +add_task( + async function test_broadcast_handler_passes_version_and_trigger_values() { + // The polling will use the broadcast version as cache busting query param. + let passedQueryString; + function serveCacheBusted(request, response) { + passedQueryString = request.queryString; + const entries = [ + { + id: "b6ba7fab-a40a-4d03-a4af-6b627f3c5b36", + last_modified: 42, + host: "localhost", + bucket: "main", + collection: "from-broadcast", + }, + ]; + response.write( + JSON.stringify({ + changes: entries, + timestamp: 42, + }) + ); + response.setHeader("ETag", '"42"'); + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date().toUTCString()); + } + server.registerPathHandler(CHANGES_PATH, serveCacheBusted); + + let passedTrigger; + const c = RemoteSettings("from-broadcast"); + c.maybeSync = (last_modified, { trigger }) => { + passedTrigger = trigger; + }; + + const version = "1337"; + + let context = { phase: pushBroadcastService.PHASES.HELLO }; + await remoteSettingsBroadcastHandler.receivedBroadcastMessage( + version, + BROADCAST_ID, + context + ); + Assert.equal(passedTrigger, "startup"); + Assert.equal(passedQueryString, `_expected=${version}`); + + clear_state(); + + context = { phase: pushBroadcastService.PHASES.REGISTER }; + await remoteSettingsBroadcastHandler.receivedBroadcastMessage( + version, + BROADCAST_ID, + context + ); + Assert.equal(passedTrigger, "startup"); + + clear_state(); + + context = { phase: pushBroadcastService.PHASES.BROADCAST }; + await remoteSettingsBroadcastHandler.receivedBroadcastMessage( + version, + BROADCAST_ID, + context + ); + Assert.equal(passedTrigger, "broadcast"); + } +); +add_task(clear_state); diff --git a/services/settings/test/unit/test_remote_settings_recover_broken.js b/services/settings/test/unit/test_remote_settings_recover_broken.js new file mode 100644 index 0000000000..c5f82d6949 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_recover_broken.js @@ -0,0 +1,153 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { SyncHistory } = ChromeUtils.importESModule( + "resource://services-settings/SyncHistory.sys.mjs" +); +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); + +const PREF_SETTINGS_SERVER = "services.settings.server"; +const CHANGES_PATH = "/v1" + Utils.CHANGES_PATH; +const BROKEN_SYNC_THRESHOLD = 10; // See default pref value + +let server; +let client; +let maybeSyncBackup; + +async function clear_state() { + // Disable logging output. + Services.prefs.setStringPref("services.settings.loglevel", "critical"); + // Pull data from the test server. + Services.prefs.setStringPref( + PREF_SETTINGS_SERVER, + `http://localhost:${server.identity.primaryPort}/v1` + ); + + // Clear sync history. + await new SyncHistory("").clear(); + + // Simulate a response whose ETag gets incremented on each call + // (in order to generate several history entries, indexed by timestamp). + let timestamp = 1337; + server.registerPathHandler(CHANGES_PATH, (request, response) => { + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "application/json; charset=UTF-8"); + response.setHeader("Date", new Date(1000000).toUTCString()); + response.setHeader("ETag", `"${timestamp}"`); + response.write( + JSON.stringify({ + timestamp, + changes: [ + { + last_modified: ++timestamp, + bucket: "main", + collection: "desktop-manager", + }, + ], + }) + ); + }); + + // Restore original maybeSync() method between each test. + client.maybeSync = maybeSyncBackup; +} + +function run_test() { + // Set up an HTTP Server + server = new HttpServer(); + server.start(-1); + + client = RemoteSettings("desktop-manager"); + maybeSyncBackup = client.maybeSync; + + run_next_test(); + + registerCleanupFunction(() => { + server.stop(() => {}); + // Restore original maybeSync() method when test suite is done. + client.maybeSync = maybeSyncBackup; + }); +} + +add_task(clear_state); + +add_task(async function test_db_is_destroyed_when_sync_is_broken() { + // Simulate a successful sync. + client.maybeSync = async () => { + // Store some data in local DB. + await client.db.importChanges({}, 1515, []); + }; + await RemoteSettings.pollChanges({ trigger: "timer" }); + + // Register a client with a failing sync method. + client.maybeSync = () => { + throw new RemoteSettingsClient.InvalidSignatureError( + "main/desktop-manager" + ); + }; + + // Now obtain several failures in a row. + for (var i = 0; i < BROKEN_SYNC_THRESHOLD; i++) { + try { + await RemoteSettings.pollChanges({ trigger: "timer" }); + } catch (e) {} + } + + // Synchronization is in broken state. + Assert.equal( + await client.db.getLastModified(), + 1515, + "Local DB was not destroyed yet" + ); + + // Synchronize again. Broken state will be detected. + try { + await RemoteSettings.pollChanges({ trigger: "timer" }); + } catch (e) {} + + // DB was destroyed. + Assert.equal( + await client.db.getLastModified(), + null, + "Local DB was destroyed" + ); +}); + +add_task(clear_state); + +add_task(async function test_db_is_not_destroyed_when_state_is_server_error() { + // Since we don't mock the server endpoints to obtain the changeset of this + // collection, the call to `maybeSync()` will fail with network errors. + + // Store some data in local DB. + await client.db.importChanges({}, 1515, []); + + // Now obtain several failures in a row. + let lastError; + for (var i = 0; i < BROKEN_SYNC_THRESHOLD + 1; i++) { + try { + await RemoteSettings.pollChanges({ trigger: "timer" }); + } catch (e) { + lastError = e; + } + } + Assert.ok( + /Cannot parse server content/.test(lastError.message), + "Error is about server" + ); + // DB was not destroyed. + Assert.equal( + await client.db.getLastModified(), + 1515, + "Local DB was not destroyed" + ); +}); + +add_task(clear_state); diff --git a/services/settings/test/unit/test_remote_settings_release_prefs.js b/services/settings/test/unit/test_remote_settings_release_prefs.js new file mode 100644 index 0000000000..bc3c3dd167 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_release_prefs.js @@ -0,0 +1,206 @@ +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +var nextUniqId = 0; +function getNewUtils() { + const { Utils } = ChromeUtils.importESModule( + `resource://services-settings/Utils.sys.mjs?_${++nextUniqId}` + ); + return Utils; +} + +function clear_state() { + Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "0"); + Services.prefs.clearUserPref("services.settings.server"); + Services.prefs.clearUserPref("services.settings.preview_enabled"); +} + +add_setup(async function () { + // Set this env vars in order to test the code path where the + // server URL can only be overridden from Dev Tools. + // See `isRunningTests` in `services/settings/Utils.jsm`. + const before = Services.env.get("MOZ_DISABLE_NONLOCAL_CONNECTIONS"); + Services.env.set("MOZ_DISABLE_NONLOCAL_CONNECTIONS", "0"); + + registerCleanupFunction(() => { + clear_state(); + Services.env.set("MOZ_DISABLE_NONLOCAL_CONNECTIONS", before); + }); +}); + +add_task(clear_state); + +add_task( + { + skip_if: () => !AppConstants.RELEASE_OR_BETA, + }, + async function test_server_url_cannot_be_toggled_in_release() { + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + + Assert.equal( + Utils.SERVER_URL, + AppConstants.REMOTE_SETTINGS_SERVER_URL, + "Server url pref was not read in release" + ); + } +); + +add_task( + { + skip_if: () => AppConstants.RELEASE_OR_BETA, + }, + async function test_server_url_cannot_be_toggled_in_dev_nightly() { + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + + Assert.notEqual( + Utils.SERVER_URL, + AppConstants.REMOTE_SETTINGS_SERVER_URL, + "Server url pref was read in nightly/dev" + ); + } +); +add_task(clear_state); + +add_task( + { + skip_if: () => !AppConstants.RELEASE_OR_BETA, + }, + async function test_preview_mode_cannot_be_toggled_in_release() { + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + + const Utils = getNewUtils(); + + Assert.ok(!Utils.PREVIEW_MODE, "Preview mode pref was not read in release"); + } +); +add_task(clear_state); + +add_task( + { + skip_if: () => AppConstants.RELEASE_OR_BETA, + }, + async function test_preview_mode_cannot_be_toggled_in_dev_nightly() { + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + + const Utils = getNewUtils(); + + Assert.ok(Utils.PREVIEW_MODE, "Preview mode pref is read in dev/nightly"); + } +); +add_task(clear_state); + +add_task( + { + skip_if: () => !AppConstants.RELEASE_OR_BETA, + }, + async function test_load_dumps_will_always_be_loaded_in_release() { + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + + Assert.equal( + Utils.SERVER_URL, + AppConstants.REMOTE_SETTINGS_SERVER_URL, + "Server url pref was not read" + ); + Assert.ok(Utils.LOAD_DUMPS, "Dumps will always be loaded"); + } +); + +add_task( + { + skip_if: () => AppConstants.RELEASE_OR_BETA, + }, + async function test_load_dumps_can_be_disabled_in_dev_nightly() { + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + + Assert.notEqual( + Utils.SERVER_URL, + AppConstants.REMOTE_SETTINGS_SERVER_URL, + "Server url pref was read" + ); + Assert.ok(!Utils.LOAD_DUMPS, "Dumps are not loaded if server is not prod"); + } +); +add_task(clear_state); + +add_task( + async function test_server_url_can_be_changed_in_all_versions_if_running_for_devtools() { + Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "1"); + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + + Assert.notEqual( + Utils.SERVER_URL, + AppConstants.REMOTE_SETTINGS_SERVER_URL, + "Server url pref was read" + ); + } +); +add_task(clear_state); + +add_task( + async function test_preview_mode_can_be_changed_in_all_versions_if_running_for_devtools() { + Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "1"); + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + + const Utils = getNewUtils(); + + Assert.ok(Utils.PREVIEW_MODE, "Preview mode pref was read"); + } +); +add_task(clear_state); + +add_task( + async function test_dumps_are_not_loaded_if_server_is_not_prod_if_running_for_devtools() { + Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "1"); + Services.prefs.setStringPref( + "services.settings.server", + "http://localhost:8888/v1" + ); + + const Utils = getNewUtils(); + Assert.ok(!Utils.LOAD_DUMPS, "Dumps won't be loaded"); + } +); +add_task(clear_state); + +add_task( + async function test_dumps_are_loaded_if_server_is_prod_if_running_for_devtools() { + Services.env.set("MOZ_REMOTE_SETTINGS_DEVTOOLS", "1"); + Services.prefs.setStringPref( + "services.settings.server", + AppConstants.REMOTE_SETTINGS_SERVER_URL + ); + + const Utils = getNewUtils(); + + Assert.ok(Utils.LOAD_DUMPS, "dumps are loaded if prod"); + } +); +add_task(clear_state); diff --git a/services/settings/test/unit/test_remote_settings_signatures.js b/services/settings/test/unit/test_remote_settings_signatures.js new file mode 100644 index 0000000000..840cc24dd8 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_signatures.js @@ -0,0 +1,830 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ +"use strict"; + +const { RemoteSettings } = ChromeUtils.importESModule( + "resource://services-settings/remote-settings.sys.mjs" +); +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); +const { UptakeTelemetry, Policy } = ChromeUtils.importESModule( + "resource://services-common/uptake-telemetry.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +const PREF_SETTINGS_SERVER = "services.settings.server"; +const SIGNER_NAME = "onecrl.content-signature.mozilla.org"; +const TELEMETRY_COMPONENT = "remotesettings"; + +const CERT_DIR = "test_remote_settings_signatures/"; +const CHAIN_FILES = ["collection_signing_ee.pem", "collection_signing_int.pem"]; + +function getFileData(file) { + const stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + stream.init(file, -1, 0, 0); + const data = NetUtil.readInputStreamToString(stream, stream.available()); + stream.close(); + return data; +} + +function getCertChain() { + const chain = []; + for (let file of CHAIN_FILES) { + chain.push(getFileData(do_get_file(CERT_DIR + file))); + } + return chain.join("\n"); +} + +let server; +let client; + +function run_test() { + // Signature verification is enabled by default. We use a custom signer + // because these tests were originally written for OneCRL. + client = RemoteSettings("signed", { signerName: SIGNER_NAME }); + + Services.prefs.setStringPref("services.settings.loglevel", "debug"); + + // Set up an HTTP Server + server = new HttpServer(); + server.start(-1); + + // Pretend we are in nightly channel to make sure all telemetry events are sent. + let oldGetChannel = Policy.getChannel; + Policy.getChannel = () => "nightly"; + + run_next_test(); + + registerCleanupFunction(() => { + Policy.getChannel = oldGetChannel; + server.stop(() => {}); + }); +} + +add_task(async function test_check_signatures() { + // First, perform a signature verification with known data and signature + // to ensure things are working correctly + let verifier = Cc[ + "@mozilla.org/security/contentsignatureverifier;1" + ].createInstance(Ci.nsIContentSignatureVerifier); + + const emptyData = "[]"; + const emptySignature = + "p384ecdsa=zbugm2FDitsHwk5-IWsas1PpWwY29f0Fg5ZHeqD8fzep7AVl2vfcaHA7LdmCZ28qZLOioGKvco3qT117Q4-HlqFTJM7COHzxGyU2MMJ0ZTnhJrPOC1fP3cVQjU1PTWi9"; + + ok( + await verifier.asyncVerifyContentSignature( + emptyData, + emptySignature, + getCertChain(), + SIGNER_NAME, + Ci.nsIX509CertDB.AppXPCShellRoot + ) + ); + + const collectionData = + '[{"details":{"bug":"https://bugzilla.mozilla.org/show_bug.cgi?id=1155145","created":"2016-01-18T14:43:37Z","name":"GlobalSign certs","who":".","why":"."},"enabled":true,"id":"97fbf7c4-3ef2-f54f-0029-1ba6540c63ea","issuerName":"MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==","last_modified":2000,"serialNumber":"BAAAAAABA/A35EU="},{"details":{"bug":"https://bugzilla.mozilla.org/show_bug.cgi?id=1155145","created":"2016-01-18T14:48:11Z","name":"GlobalSign certs","who":".","why":"."},"enabled":true,"id":"e3bd531e-1ee4-7407-27ce-6fdc9cecbbdc","issuerName":"MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB","last_modified":3000,"serialNumber":"BAAAAAABI54PryQ="}]'; + const collectionSignature = + "p384ecdsa=f4pA2tYM5jQgWY6YUmhUwQiBLj6QO5sHLD_5MqLePz95qv-7cNCuQoZnPQwxoptDtW8hcWH3kLb0quR7SB-r82gkpR9POVofsnWJRA-ETb0BcIz6VvI3pDT49ZLlNg3p"; + + ok( + await verifier.asyncVerifyContentSignature( + collectionData, + collectionSignature, + getCertChain(), + SIGNER_NAME, + Ci.nsIX509CertDB.AppXPCShellRoot + ) + ); +}); + +add_task(async function test_check_synchronization_with_signatures() { + const port = server.identity.primaryPort; + + const x5u = `http://localhost:${port}/test_remote_settings_signatures/test_cert_chain.pem`; + + // Telemetry reports. + const TELEMETRY_SOURCE = client.identifier; + + function registerHandlers(responses) { + function handleResponse(serverTimeMillis, request, response) { + const key = `${request.method}:${request.path}?${request.queryString}`; + const available = responses[key]; + const sampled = available.length > 1 ? available.shift() : available[0]; + if (!sampled) { + do_throw( + `unexpected ${request.method} request for ${request.path}?${request.queryString}` + ); + } + + response.setStatusLine( + null, + sampled.status.status, + sampled.status.statusText + ); + // send the headers + for (let headerLine of sampled.sampleHeaders) { + let headerElements = headerLine.split(":"); + response.setHeader(headerElements[0], headerElements[1].trimLeft()); + } + + // set the server date + response.setHeader("Date", new Date(serverTimeMillis).toUTCString()); + + response.write(sampled.responseBody); + } + + for (let key of Object.keys(responses)) { + const keyParts = key.split(":"); + const valueParts = keyParts[1].split("?"); + const path = valueParts[0]; + + server.registerPathHandler(path, handleResponse.bind(null, 2000)); + } + } + + // set up prefs so the kinto updater talks to the test server + Services.prefs.setStringPref( + PREF_SETTINGS_SERVER, + `http://localhost:${server.identity.primaryPort}/v1` + ); + + // These are records we'll use in the test collections + const RECORD1 = { + details: { + bug: "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + created: "2016-01-18T14:43:37Z", + name: "GlobalSign certs", + who: ".", + why: ".", + }, + enabled: true, + id: "97fbf7c4-3ef2-f54f-0029-1ba6540c63ea", + issuerName: + "MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==", + last_modified: 2000, + serialNumber: "BAAAAAABA/A35EU=", + }; + + const RECORD2 = { + details: { + bug: "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + created: "2016-01-18T14:48:11Z", + name: "GlobalSign certs", + who: ".", + why: ".", + }, + enabled: true, + id: "e3bd531e-1ee4-7407-27ce-6fdc9cecbbdc", + issuerName: + "MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB", + last_modified: 3000, + serialNumber: "BAAAAAABI54PryQ=", + }; + + const RECORD3 = { + details: { + bug: "https://bugzilla.mozilla.org/show_bug.cgi?id=1155145", + created: "2016-01-18T14:48:11Z", + name: "GlobalSign certs", + who: ".", + why: ".", + }, + enabled: true, + id: "c7c49b69-a4ab-418e-92a9-e1961459aa7f", + issuerName: + "MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB", + last_modified: 4000, + serialNumber: "BAAAAAABI54PryQ=", + }; + + const RECORD1_DELETION = { + deleted: true, + enabled: true, + id: "97fbf7c4-3ef2-f54f-0029-1ba6540c63ea", + last_modified: 3500, + }; + + // Check that a signature on an empty collection is OK + // We need to set up paths on the HTTP server to return specific data from + // specific paths for each test. Here we prepare data for each response. + + // A cert chain response (this the cert chain that contains the signing + // cert, the root and any intermediates in between). This is used in each + // sync. + const RESPONSE_CERT_CHAIN = { + comment: "RESPONSE_CERT_CHAIN", + sampleHeaders: ["Content-Type: text/plain; charset=UTF-8"], + status: { status: 200, statusText: "OK" }, + responseBody: getCertChain(), + }; + + // A server settings response. This is used in each sync. + const RESPONSE_SERVER_SETTINGS = { + comment: "RESPONSE_SERVER_SETTINGS", + sampleHeaders: [ + "Access-Control-Allow-Origin: *", + "Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff", + "Content-Type: application/json; charset=UTF-8", + "Server: waitress", + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + settings: { + batch_max_requests: 25, + }, + url: `http://localhost:${port}/v1/`, + documentation: "https://kinto.readthedocs.org/", + version: "1.5.1", + commit: "cbc6f58", + hello: "kinto", + }), + }; + + // This is the initial, empty state of the collection. This is only used + // for the first sync. + const RESPONSE_EMPTY_INITIAL = { + comment: "RESPONSE_EMPTY_INITIAL", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "1000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 1000, + metadata: { + signature: { + x5u, + signature: + "vxuAg5rDCB-1pul4a91vqSBQRXJG_j7WOYUTswxRSMltdYmbhLRH8R8brQ9YKuNDF56F-w6pn4HWxb076qgKPwgcEBtUeZAO_RtaHXRkRUUgVzAr86yQL4-aJTbv3D6u", + }, + }, + changes: [], + }), + }; + + // Here, we map request method and path to the available responses + const emptyCollectionResponses = { + "GET:/test_remote_settings_signatures/test_cert_chain.pem?": [ + RESPONSE_CERT_CHAIN, + ], + "GET:/v1/?": [RESPONSE_SERVER_SETTINGS], + "GET:/v1/buckets/main/collections/signed/changeset?_expected=1000": [ + RESPONSE_EMPTY_INITIAL, + ], + }; + + // + // 1. + // - collection: undefined -> [] + // - timestamp: undefined -> 1000 + // + + // .. and use this map to register handlers for each path + registerHandlers(emptyCollectionResponses); + + let startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + + // With all of this set up, we attempt a sync. This will resolve if all is + // well and throw if something goes wrong. + await client.maybeSync(1000); + + equal((await client.get()).length, 0); + + let endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + + // ensure that a success histogram is tracked when a succesful sync occurs. + let expectedIncrements = { [UptakeTelemetry.STATUS.SUCCESS]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + + // + // 2. + // - collection: [] -> [RECORD2, RECORD1] + // - timestamp: 1000 -> 3000 + // + // Check that some additions (2 records) to the collection have a valid + // signature. + + // This response adds two entries (RECORD1 and RECORD2) to the collection + const RESPONSE_TWO_ADDED = { + comment: "RESPONSE_TWO_ADDED", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "3000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 3000, + metadata: { + signature: { + x5u, + signature: + "dwhJeypadNIyzGj3QdI0KMRTPnHhFPF_j73mNrsPAHKMW46S2Ftf4BzsPMvPMB8h0TjDus13wo_R4l432DHe7tYyMIWXY0PBeMcoe5BREhFIxMxTsh9eGVXBD1e3UwRy", + }, + }, + changes: [RECORD2, RECORD1], + }), + }; + + const twoItemsResponses = { + "GET:/v1/buckets/main/collections/signed/changeset?_expected=3000&_since=%221000%22": + [RESPONSE_TWO_ADDED], + }; + registerHandlers(twoItemsResponses); + await client.maybeSync(3000); + + equal((await client.get()).length, 2); + + // + // 3. + // - collection: [RECORD2, RECORD1] -> [RECORD2, RECORD3] + // - timestamp: 3000 -> 4000 + // + // Check the collection with one addition and one removal has a valid + // signature + const THREE_ITEMS_SIG = + "MIEmNghKnkz12UodAAIc3q_Y4a3IJJ7GhHF4JYNYmm8avAGyPM9fYU7NzVo94pzjotG7vmtiYuHyIX2rTHTbT587w0LdRWxipgFd_PC1mHiwUyjFYNqBBG-kifYk7kEw"; + + // Remove RECORD1, add RECORD3 + const RESPONSE_ONE_ADDED_ONE_REMOVED = { + comment: "RESPONSE_ONE_ADDED_ONE_REMOVED ", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "4000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 4000, + metadata: { + signature: { + x5u, + signature: THREE_ITEMS_SIG, + }, + }, + changes: [RECORD3, RECORD1_DELETION], + }), + }; + + const oneAddedOneRemovedResponses = { + "GET:/v1/buckets/main/collections/signed/changeset?_expected=4000&_since=%223000%22": + [RESPONSE_ONE_ADDED_ONE_REMOVED], + }; + registerHandlers(oneAddedOneRemovedResponses); + await client.maybeSync(4000); + + equal((await client.get()).length, 2); + + // + // 4. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] + // - timestamp: 4000 -> 4100 + // + // Check the signature is still valid with no operation (no changes) + + // Leave the collection unchanged + const RESPONSE_EMPTY_NO_UPDATE = { + comment: "RESPONSE_EMPTY_NO_UPDATE ", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "4000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 4000, + metadata: { + signature: { + x5u, + signature: THREE_ITEMS_SIG, + }, + }, + changes: [], + }), + }; + + const noOpResponses = { + "GET:/v1/buckets/main/collections/signed/changeset?_expected=4100&_since=%224000%22": + [RESPONSE_EMPTY_NO_UPDATE], + }; + registerHandlers(noOpResponses); + await client.maybeSync(4100); + + equal((await client.get()).length, 2); + + console.info("---------------------------------------------------------"); + // + // 5. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] + // - timestamp: 4000 -> 5000 + // + // Check the collection is reset when the signature is invalid. + // Client will: + // - Fetch metadata (with bad signature) + // - Perform the sync (fetch empty changes) + // - Refetch the metadata and the whole collection + // - Validate signature successfully, but with no changes to emit. + + const RESPONSE_COMPLETE_INITIAL = { + comment: "RESPONSE_COMPLETE_INITIAL ", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "4000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 4000, + metadata: { + signature: { + x5u, + signature: THREE_ITEMS_SIG, + }, + }, + changes: [RECORD2, RECORD3], + }), + }; + + const RESPONSE_EMPTY_NO_UPDATE_BAD_SIG = { + ...RESPONSE_EMPTY_NO_UPDATE, + responseBody: JSON.stringify({ + timestamp: 4000, + metadata: { + signature: { + x5u, + signature: "aW52YWxpZCBzaWduYXR1cmUK", + }, + }, + changes: [], + }), + }; + + const badSigGoodSigResponses = { + // The first collection state is the three item collection (since + // there was sync with no updates before) - but, since the signature is wrong, + // another request will be made... + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=%224000%22": + [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG], + // Subsequent signature returned is a valid one for the three item + // collection. + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [ + RESPONSE_COMPLETE_INITIAL, + ], + }; + + registerHandlers(badSigGoodSigResponses); + + startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + + let syncEventSent = false; + client.on("sync", ({ data }) => { + syncEventSent = true; + }); + + await client.maybeSync(5000); + + equal((await client.get()).length, 2); + + endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + + // since we only fixed the signature, and no data was changed, the sync event + // was not sent. + equal(syncEventSent, false); + + // ensure that the failure count is incremented for a succesful sync with an + // (initial) bad signature - only SERVICES_SETTINGS_SYNC_SIG_FAIL should + // increment. + expectedIncrements = { [UptakeTelemetry.STATUS.SIGNATURE_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + + // + // 6. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] + // - timestamp: 4000 -> 5000 + // + // Check the collection is reset when the signature is invalid. + // Client will: + // - Fetch metadata (with bad signature) + // - Perform the sync (fetch empty changes) + // - Refetch the whole collection and metadata + // - Sync will be no-op since local is equal to server, no changes to emit. + + const badSigGoodOldResponses = { + // The first collection state is the current state (since there's no update + // - but, since the signature is wrong, another request will be made) + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=%224000%22": + [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG], + // The next request is for the full collection. This will be + // checked against the valid signature and last_modified times will be + // compared. Sync should be a no-op, even though the signature is good, + // because the local collection is newer. + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [ + RESPONSE_EMPTY_INITIAL, + ], + }; + + // ensure our collection hasn't been replaced with an older, empty one + equal((await client.get()).length, 2, "collection was restored"); + + registerHandlers(badSigGoodOldResponses); + + syncEventSent = false; + client.on("sync", ({ data }) => { + syncEventSent = true; + }); + + await client.maybeSync(5000); + + // Local data was unchanged, since it was never than the one returned by the server, + // thus the sync event is not sent. + equal(syncEventSent, false, "event was not sent"); + + // + // 7. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] + // - timestamp: 4000 -> 5000 + // + // Check that a tampered local DB will be overwritten and + // sync event contain the appropriate data. + + const RESPONSE_COMPLETE_BAD_SIG = { + ...RESPONSE_EMPTY_NO_UPDATE, + responseBody: JSON.stringify({ + timestamp: 5000, + metadata: { + signature: { + x5u, + signature: "aW52YWxpZCBzaWduYXR1cmUK", + }, + }, + changes: [RECORD2, RECORD3], + }), + }; + + const badLocalContentGoodSigResponses = { + "GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [ + RESPONSE_COMPLETE_BAD_SIG, + RESPONSE_COMPLETE_INITIAL, + ], + }; + + registerHandlers(badLocalContentGoodSigResponses); + + // we create a local state manually here, in order to test that the sync event data + // properly contains created, updated, and deleted records. + // the local DB contains same id as RECORD2 and a fake record. + // the final server collection contains RECORD2 and RECORD3 + const localId = "0602b1b2-12ab-4d3a-b6fb-593244e7b035"; + await client.db.importChanges( + { signature: { x5u, signature: "abc" } }, + null, + [ + { ...RECORD2, last_modified: 1234567890, serialNumber: "abc" }, + { id: localId }, + ], + { + clear: true, + } + ); + + let syncData = null; + client.on("sync", ({ data }) => { + syncData = data; + }); + + // Clear events snapshot. + TelemetryTestUtils.assertEvents([], {}, { process: "dummy" }); + + const TELEMETRY_EVENTS_FILTERS = { + category: "uptake.remotecontent.result", + method: "uptake", + }; + + // Events telemetry is sampled on released, use fake channel. + await client.maybeSync(5000); + + // We should report a corruption_error. + TelemetryTestUtils.assertEvents( + [ + [ + "uptake.remotecontent.result", + "uptake", + "remotesettings", + UptakeTelemetry.STATUS.CORRUPTION_ERROR, + { + source: client.identifier, + duration: v => v > 0, + trigger: "manual", + }, + ], + ], + TELEMETRY_EVENTS_FILTERS + ); + + // The local data was corrupted, and the Telemetry status reflects it. + // But the sync overwrote the bad data and was eventually a success. + // Since local data was replaced, we use records IDs to determine + // what was created and deleted. And bad local data will appear + // in the sync event as deleted. + equal(syncData.current.length, 2); + equal(syncData.created.length, 1); + equal(syncData.created[0].id, RECORD3.id); + equal(syncData.updated.length, 1); + equal(syncData.updated[0].old.serialNumber, "abc"); + equal(syncData.updated[0].new.serialNumber, RECORD2.serialNumber); + equal(syncData.deleted.length, 1); + equal(syncData.deleted[0].id, localId); + + // + // 8. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] (unchanged because of error) + // - timestamp: 4000 -> 6000 + // + // Check that a failing signature throws after retry, and that sync changes + // are not applied. + + const RESPONSE_ONLY_RECORD4_BAD_SIG = { + comment: "Create RECORD4", + sampleHeaders: [ + "Content-Type: application/json; charset=UTF-8", + 'ETag: "6000"', + ], + status: { status: 200, statusText: "OK" }, + responseBody: JSON.stringify({ + timestamp: 6000, + metadata: { + signature: { + x5u, + signature: "aaaaaaaaaaaaaaaaaaaaaaaa", // sig verifier wants proper length or will crash. + }, + }, + changes: [ + { + id: "f765df30-b2f1-42f6-9803-7bd5a07b5098", + last_modified: 6000, + }, + ], + }), + }; + const RESPONSE_EMPTY_NO_UPDATE_BAD_SIG_6000 = { + ...RESPONSE_EMPTY_NO_UPDATE, + responseBody: JSON.stringify({ + timestamp: 6000, + metadata: { + signature: { + x5u, + signature: "aW52YWxpZCBzaWduYXR1cmUK", + }, + }, + changes: [], + }), + }; + const allBadSigResponses = { + "GET:/v1/buckets/main/collections/signed/changeset?_expected=6000&_since=%224000%22": + [RESPONSE_EMPTY_NO_UPDATE_BAD_SIG_6000], + "GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [ + RESPONSE_ONLY_RECORD4_BAD_SIG, + ], + }; + + startSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + registerHandlers(allBadSigResponses); + await Assert.rejects( + client.maybeSync(6000), + RemoteSettingsClient.InvalidSignatureError, + "Sync failed as expected (bad signature after retry)" + ); + + // Ensure that the failure is reflected in the accumulated telemetry: + endSnapshot = getUptakeTelemetrySnapshot( + TELEMETRY_COMPONENT, + TELEMETRY_SOURCE + ); + expectedIncrements = { [UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR]: 1 }; + checkUptakeTelemetry(startSnapshot, endSnapshot, expectedIncrements); + + // When signature fails after retry, the local data present before sync + // should be maintained (if its signature is valid). + ok( + arrayEqual( + (await client.get()).map(r => r.id), + [RECORD3.id, RECORD2.id] + ), + "Local records were not changed" + ); + // And local data should still be valid. + await client.get({ verifySignature: true }); // Not raising. + + // + // 9. + // - collection: [RECORD2, RECORD3] -> [] (cleared) + // - timestamp: 4000 -> 6000 + // + // Check that local data is cleared during sync if signature is not valid. + + await client.db.create({ + id: "c6b19c67-2e0e-4a82-b7f7-1777b05f3e81", + last_modified: 42, + tampered: true, + }); + + await Assert.rejects( + client.maybeSync(6000), + RemoteSettingsClient.InvalidSignatureError, + "Sync failed as expected (bad signature after retry)" + ); + + // Since local data was tampered, it was cleared. + equal((await client.get()).length, 0, "Local database is now empty."); + + // + // 10. + // - collection: [RECORD2, RECORD3] -> [] (cleared) + // - timestamp: 4000 -> 6000 + // + // Check that local data is cleared during sync if signature is not valid. + + await client.db.create({ + id: "c6b19c67-2e0e-4a82-b7f7-1777b05f3e81", + last_modified: 42, + tampered: true, + }); + + await Assert.rejects( + client.maybeSync(6000), + RemoteSettingsClient.InvalidSignatureError, + "Sync failed as expected (bad signature after retry)" + ); + // Since local data was tampered, it was cleared. + equal((await client.get()).length, 0, "Local database is now empty."); + + // + // 11. + // - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3] + // - timestamp: 4000 -> 6000 + // + // Check that local data is restored if signature was valid before sync. + const sigCalls = []; + let i = 0; + client._verifier = { + async asyncVerifyContentSignature(serialized, signature) { + sigCalls.push(serialized); + console.log(`verify call ${i}`); + return [ + false, // After importing changes. + true, // When checking previous local data. + false, // Still fail after retry. + true, // When checking previous local data again. + ][i++]; + }, + }; + // Create an extra record. It will have a valid signature locally + // thanks to the verifier mock. + await client.db.importChanges( + { + signature: { x5u, signature: "aa" }, + }, + 4000, + [ + { + id: "extraId", + last_modified: 42, + }, + ] + ); + + equal((await client.get()).length, 1); + + // Now sync, but importing changes will have failing signature, + // and so will retry (see `sigResults`). + await Assert.rejects( + client.maybeSync(6000), + RemoteSettingsClient.InvalidSignatureError, + "Sync failed as expected (bad signature after retry)" + ); + equal(i, 4, "sync has retried as expected"); + + // Make sure that we retried on a blank DB. The extra record should + // have been deleted when we validated the signature the second time. + // Since local data was tampered, it was cleared. + ok(/extraId/.test(sigCalls[0]), "extra record when importing changes"); + ok(/extraId/.test(sigCalls[1]), "extra record when checking local"); + ok(!/extraId/.test(sigCalls[2]), "db was flushed before retry"); + ok(/extraId/.test(sigCalls[3]), "when checking local after retry"); +}); diff --git a/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem new file mode 100644 index 0000000000..7ffa37ea98 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICdTCCAV2gAwIBAgIUBSXZTtLSPDKor9Sfq1wT7jVpFHkwDQYJKoZIhvcNAQEL +BQAwIzEhMB8GA1UEAwwYY29sbGVjdGlvbi1zaWduZXItaW50LUNBMCIYDzIwMjIx +MTI3MDAwMDAwWhgPMjAyNTAyMDQwMDAwMDBaMCYxJDAiBgNVBAMMG2NvbGxlY3Rp +b24tc2lnbmVyLWVlLWludC1DQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABKFockM2 +K1x7GInzeRVGFaHHP7SN7oY+AikV22COJS3ktxMtqM6Y6DFTTmqcDAsJyNY5regy +BuW6gTRzoR+jMOBdqMluQ4P+J4c9qXEDviiIz/AC8Fr3Gh/dzIN0qm6pzqNIMEYw +EwYDVR0lBAwwCgYIKwYBBQUHAwMwLwYDVR0RBCgwJoIkb25lY3JsLmNvbnRlbnQt +c2lnbmF0dXJlLm1vemlsbGEub3JnMA0GCSqGSIb3DQEBCwUAA4IBAQAX1R1MDiqc +/wfvPd+Bj10cRatAZHVjlL/UOmJ3FnLB1JDhdNsbUBbnQGySQxeFtX7fHav2fV2Z +25xHnTEVh/Xe0gHJ/f5rZVY/lWlHpfD8Gm5YieGXUWHb5qaUoSIOkG6t31vfWs2W +VFTR4+E1yu073gUcJNcSNIUWv0Bih7qkb6GaEnz5nagGUsOAr4lvXm0gJDdxqO0/ +ODyveWYSCjBdIJYNN0vYVMz7oywpk9VSr2Tc0bkREQEXulb6gQbp0Jc4BREQ9XCQ +H6L5jyiCeQcBCoFigBgpjy8BN2UH3pS14qKbKOfnTgNJWDgkabf27XkzhyaqvAs2 +fastfud0EAre +-----END CERTIFICATE----- diff --git a/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem.certspec b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem.certspec new file mode 100644 index 0000000000..866c357c50 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_ee.pem.certspec @@ -0,0 +1,5 @@ +issuer:collection-signer-int-CA +subject:collection-signer-ee-int-CA +subjectKey:secp384r1 +extension:extKeyUsage:codeSigning +extension:subjectAlternativeName:onecrl.content-signature.mozilla.org diff --git a/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem new file mode 100644 index 0000000000..0c7941a72e --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDAzCCAeugAwIBAgIUNeLBp+Y0kXOPVq3thvrPmUeBIR4wDQYJKoZIhvcNAQEL +BQAwKTEnMCUGA1UEAwweeHBjc2hlbGwgc2lnbmVkIGFwcHMgdGVzdCByb290MCIY +DzIwMjIxMTI3MDAwMDAwWhgPMjAyNTAyMDQwMDAwMDBaMCMxITAfBgNVBAMMGGNv +bGxlY3Rpb24tc2lnbmVyLWludC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x +nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM +wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF +4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20 +yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx +j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMlMCMwDAYDVR0TBAUwAwEB/zAT +BgNVHSUEDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAEw0lySZX8x5B +mZVBIhs9oqsKjQX6Wi82MhDjAruCDjbB+jhnJw1sx0jFIYA6nALGplMRVdiyYtP5 +fAo/HTIKOQbh7wEnEsYr1rmvw/Huwim+FVL82VHwU2xhgl1TPIi07EmD2sXwaJYM +haF0QPicpQPILgTSbDp/jLuW/cGWguvPp/+jZPe6XkQCb004UfDQ4YE2pSEBMU/p +5ojqf81KSpkJf6s/tl5H9KC+bB8Ck5YMTLXP+fB0LpH1MqEZ2MGIQ+84UeMimCLH +7sHpU/ESUIHFtuZm21LpwuOY9+cQ2idz0WU0QLm3MKdDqSCtuyTnsxJhGm6dAP9G +LJBxb4T+lg== +-----END CERTIFICATE----- diff --git a/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem.certspec b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem.certspec new file mode 100644 index 0000000000..2b3eb2d78f --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_signatures/collection_signing_int.pem.certspec @@ -0,0 +1,4 @@ +issuer:xpcshell signed apps test root +subject:collection-signer-int-CA +extension:basicConstraints:cA, +extension:extKeyUsage:codeSigning diff --git a/services/settings/test/unit/test_remote_settings_sync_history.js b/services/settings/test/unit/test_remote_settings_sync_history.js new file mode 100644 index 0000000000..1fdc3d1adc --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_sync_history.js @@ -0,0 +1,69 @@ +"use strict"; + +const { SyncHistory } = ChromeUtils.importESModule( + "resource://services-settings/SyncHistory.sys.mjs" +); + +async function clear_state() { + await new SyncHistory("").clear(); +} +add_task(clear_state); + +add_task(async function test_entries_are_stored_by_source() { + const history = new SyncHistory(); + await history.store("42", "success", { pi: "3.14" }); + // Check that history is isolated by source. + await new SyncHistory("main/cfr").store("88", "error"); + + const l = await history.list(); + + Assert.deepEqual(l, [ + { + timestamp: 42, + status: "success", + infos: { pi: "3.14" }, + datetime: new Date(42), + }, + ]); +}); +add_task(clear_state); + +add_task( + async function test_old_entries_are_removed_keep_fixed_size_per_source() { + const history = new SyncHistory("settings-sync", { size: 3 }); + const anotherHistory = await new SyncHistory("main/cfr"); + + await history.store("42", "success"); + await history.store("41", "sync_error"); + await history.store("43", "up_to_date"); + + let l = await history.list(); + Assert.equal(l.length, 3); + + await history.store("44", "success"); + await anotherHistory.store("44", "success"); + + l = await history.list(); + Assert.equal(l.length, 3); + Assert.ok(!l.map(e => e.timestamp).includes(41)); + + l = await anotherHistory.list(); + Assert.equal(l.length, 1); + } +); +add_task(clear_state); + +add_task(async function test_entries_are_sorted_by_timestamp_desc() { + const history = new SyncHistory("settings-sync"); + await history.store("42", "success"); + await history.store("41", "sync_error"); + await history.store("44", "up_to_date"); + + const l = await history.list(); + + Assert.deepEqual( + l.map(e => e.timestamp), + [44, 42, 41] + ); +}); +add_task(clear_state); diff --git a/services/settings/test/unit/test_remote_settings_utils.js b/services/settings/test/unit/test_remote_settings_utils.js new file mode 100644 index 0000000000..de372b3e44 --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_utils.js @@ -0,0 +1,166 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); + +const BinaryOutputStream = Components.Constructor( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); + +const server = new HttpServer(); +server.start(-1); +registerCleanupFunction(() => server.stop(() => {})); +const SERVER_BASE_URL = `http://localhost:${server.identity.primaryPort}`; + +const proxyServer = new HttpServer(); +proxyServer.identity.add("http", "localhost", server.identity.primaryPort); +proxyServer.start(-1); +registerCleanupFunction(() => proxyServer.stop(() => {})); +const PROXY_PORT = proxyServer.identity.primaryPort; + +// A sequence of bytes that would become garbage if it were to be read as UTF-8: +// - 0xEF 0xBB 0xBF is a byte order mark. +// - 0xC0 on its own is invalid (it's the first byte of a 2-byte encoding). +const INVALID_UTF_8_BYTES = [0xef, 0xbb, 0xbf, 0xc0]; + +server.registerPathHandler("/binary.dat", (request, response) => { + response.setStatusLine(null, 201, "StatusLineHere"); + response.setHeader("headerName", "HeaderValue: HeaderValueEnd"); + let binaryOut = new BinaryOutputStream(response.bodyOutputStream); + binaryOut.writeByteArray([0xef, 0xbb, 0xbf, 0xc0]); +}); + +// HTTPS requests are proxied with CONNECT, but our test server is HTTP, +// which means that the proxy will receive GET http://localhost:port. +var proxiedCount = 0; +proxyServer.registerPrefixHandler("/", (request, response) => { + ++proxiedCount; + Assert.equal(request.path, "/binary.dat", `Proxy request ${proxiedCount}`); + // Close connection without sending any response. + response.seizePower(); + response.finish(); +}); + +add_task(async function test_utils_fetch_binary() { + let res = await Utils.fetch(`${SERVER_BASE_URL}/binary.dat`); + + Assert.equal(res.status, 201, "res.status"); + Assert.equal(res.statusText, "StatusLineHere", "res.statusText"); + Assert.equal( + res.headers.get("headerName"), + "HeaderValue: HeaderValueEnd", + "Utils.fetch should return the header" + ); + + Assert.deepEqual( + Array.from(new Uint8Array(await res.arrayBuffer())), + INVALID_UTF_8_BYTES, + "Binary response body should be returned as is" + ); +}); + +add_task(async function test_utils_fetch_binary_as_text() { + let res = await Utils.fetch(`${SERVER_BASE_URL}/binary.dat`); + Assert.deepEqual( + Array.from(await res.text(), c => c.charCodeAt(0)), + [65533], + "Interpreted as UTF-8, the response becomes garbage" + ); +}); + +add_task(async function test_utils_fetch_binary_as_json() { + let res = await Utils.fetch(`${SERVER_BASE_URL}/binary.dat`); + await Assert.rejects( + res.json(), + /SyntaxError: JSON.parse: unexpected character/, + "Binary data is invalid JSON" + ); +}); + +add_task(async function test_utils_fetch_has_conservative() { + let channelPromise = TestUtils.topicObserved("http-on-modify-request"); + await Utils.fetch(`${SERVER_BASE_URL}/binary.dat`); + + let channel = (await channelPromise)[0].QueryInterface(Ci.nsIHttpChannel); + + Assert.equal(channel.URI.spec, `${SERVER_BASE_URL}/binary.dat`, "URL OK"); + + let internalChannel = channel.QueryInterface(Ci.nsIHttpChannelInternal); + Assert.ok(internalChannel.beConservative, "beConservative flag is set"); +}); + +add_task(async function test_utils_fetch_has_conservative() { + let channelPromise = TestUtils.topicObserved("http-on-modify-request"); + await Utils.fetch(`${SERVER_BASE_URL}/binary.dat`); + + let channel = (await channelPromise)[0].QueryInterface(Ci.nsIHttpChannel); + + Assert.equal(channel.URI.spec, `${SERVER_BASE_URL}/binary.dat`, "URL OK"); + + let internalChannel = channel.QueryInterface(Ci.nsIHttpChannelInternal); + Assert.ok(internalChannel.beConservative, "beConservative flag is set"); +}); + +add_task(async function test_utils_fetch_with_bad_proxy() { + Services.prefs.setIntPref("network.proxy.type", 1); + Services.prefs.setStringPref("network.proxy.http", "127.0.0.1"); + Services.prefs.setIntPref("network.proxy.http_port", PROXY_PORT); + Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); + + // The URL that we're going to request. + const DESTINATION_URL = `${SERVER_BASE_URL}/binary.dat`; + + Assert.equal(proxiedCount, 0, "Proxy not used yet"); + { + info("Bad proxy, default prefs"); + let res = await Utils.fetch(DESTINATION_URL); + Assert.equal(res.status, 201, "Bypassed bad proxy"); + // 10 instead of 1 because of reconnect attempts after a dropped request. + Assert.equal(proxiedCount, 10, "Proxy was used by HttpChannel"); + } + + // Disables the failover logic from HttpChannel. + Services.prefs.setBoolPref("network.proxy.failover_direct", false); + proxiedCount = 0; + { + info("Bad proxy, disabled network.proxy.failover_direct"); + let res = await Utils.fetch(DESTINATION_URL); + Assert.equal(res.status, 201, "Bypassed bad proxy"); + // 10 instead of 1 because of reconnect attempts after a dropped request. + Assert.equal(proxiedCount, 10, "Proxy was used by ServiceRequest"); + } + + proxiedCount = 0; + { + info("Using internal option of Utils.fetch: bypassProxy=true"); + let res = await Utils.fetch(DESTINATION_URL, { bypassProxy: true }); + Assert.equal(res.status, 201, "Bypassed bad proxy"); + Assert.equal(proxiedCount, 0, "Not using proxy when bypassProxy=true"); + } + + // Disables the failover logic from ServiceRequest/Utils.fetch + Services.prefs.setBoolPref("network.proxy.allow_bypass", false); + proxiedCount = 0; + + info("Bad proxy, disabled network.proxy.allow_bypass"); + await Assert.rejects( + Utils.fetch(DESTINATION_URL), + /NetworkError/, + "Bad proxy request should fail without failover" + ); + // 10 instead of 1 because of reconnect attempts after a dropped request. + Assert.equal(proxiedCount, 10, "Attempted to use proxy again"); + + Services.prefs.clearUserPref("network.proxy.type"); + Services.prefs.clearUserPref("network.proxy.http"); + Services.prefs.clearUserPref("network.proxy.http_port"); + Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); + Services.prefs.clearUserPref("network.proxy.failover_direct"); + Services.prefs.clearUserPref("network.proxy.allow_bypass"); +}); diff --git a/services/settings/test/unit/test_remote_settings_utils_telemetry.js b/services/settings/test/unit/test_remote_settings_utils_telemetry.js new file mode 100644 index 0000000000..f12b0d7f9a --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_utils_telemetry.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); +const { Utils } = ChromeUtils.importESModule( + "resource://services-settings/Utils.sys.mjs" +); + +const server = new HttpServer(); +server.start(-1); +registerCleanupFunction(() => server.stop(() => {})); +const SERVER_BASE_URL = `http://localhost:${server.identity.primaryPort}`; + +const proxyServer = new HttpServer(); +proxyServer.start(-1); +const PROXY_PORT = proxyServer.identity.primaryPort; +proxyServer.stop(); + +server.registerPathHandler("/destination", (request, response) => { + response.setStatusLine(null, 412); +}); + +async function assertTelemetryEvents(expectedEvents) { + await TelemetryTestUtils.assertEvents(expectedEvents, { + category: "service_request", + method: "bypass", + }); +} + +add_task(async function setup() { + await TelemetryController.testSetup(); +}); + +add_task(async function test_telemetry() { + const DESTINATION_URL = `${SERVER_BASE_URL}/destination`; + + { + let res = await Utils.fetch(DESTINATION_URL); + Assert.equal(res.status, 412, "fetch without proxy succeeded"); + } + await assertTelemetryEvents([]); + + Services.prefs.setIntPref("network.proxy.type", 1); + Services.prefs.setStringPref("network.proxy.http", "127.0.0.1"); + Services.prefs.setIntPref("network.proxy.http_port", PROXY_PORT); + Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); + + { + let res = await Utils.fetch(DESTINATION_URL); + Assert.equal(res.status, 412, "fetch with broken proxy succeeded"); + } + // Note: failover handled by HttpChannel, hence no failover here. + await assertTelemetryEvents([]); + + // Disable HttpChannel failover in favor of Utils.fetch's implementation. + Services.prefs.setBoolPref("network.proxy.failover_direct", false); + { + let res = await Utils.fetch(DESTINATION_URL); + Assert.equal(res.status, 412, "fetch succeeded with bypassProxy feature"); + } + await assertTelemetryEvents([ + { + category: "service_request", + method: "bypass", + object: "proxy_info", + value: "remote-settings", + extra: { + source: "prefs", + type: "manual", + }, + }, + ]); + + Services.prefs.setBoolPref("network.proxy.allow_bypass", false); + await Assert.rejects( + Utils.fetch(DESTINATION_URL), + /NetworkError/, + "Request without failover fails" + ); + await assertTelemetryEvents([]); +}); diff --git a/services/settings/test/unit/test_remote_settings_worker.js b/services/settings/test/unit/test_remote_settings_worker.js new file mode 100644 index 0000000000..42b85bb92c --- /dev/null +++ b/services/settings/test/unit/test_remote_settings_worker.js @@ -0,0 +1,138 @@ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +const { RemoteSettingsWorker } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsWorker.sys.mjs" +); +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); +const { Database } = ChromeUtils.importESModule( + "resource://services-settings/Database.sys.mjs" +); + +const IS_ANDROID = AppConstants.platform == "android"; + +add_task(async function test_canonicaljson() { + const records = [ + { id: "1", title: "title 1" }, + { id: "2", title: "title 2" }, + ]; + const timestamp = 42; + + const serialized = await RemoteSettingsWorker.canonicalStringify( + records, + timestamp + ); + + Assert.equal( + serialized, + '{"data":[{"id":"1","title":"title 1"},{"id":"2","title":"title 2"}],"last_modified":"42"}' + ); +}); + +add_task(async function test_import_json_dump_into_idb() { + if (IS_ANDROID) { + // Skip test: we don't ship remote settings dumps on Android (see package-manifest). + return; + } + const client = new RemoteSettingsClient("language-dictionaries"); + const before = await client.get({ syncIfEmpty: false }); + Assert.equal(before.length, 0); + + await RemoteSettingsWorker.importJSONDump("main", "language-dictionaries"); + + const after = await client.get({ syncIfEmpty: false }); + Assert.ok(!!after.length); + let lastModifiedStamp = await client.getLastModified(); + + Assert.equal( + lastModifiedStamp, + Math.max(...after.map(record => record.last_modified)), + "Should have correct last modified timestamp" + ); + + // Force a DB close for shutdown so we can delete the DB later. + Database._shutdownHandler(); +}); + +add_task(async function test_throws_error_if_worker_fails() { + let error; + try { + await RemoteSettingsWorker.canonicalStringify(null, 42); + } catch (e) { + error = e; + } + Assert.equal(error.message.endsWith("records is null"), true); +}); + +add_task(async function test_throws_error_if_worker_fails_async() { + if (IS_ANDROID) { + // Skip test: we don't ship dump, so importJSONDump() is no-op. + return; + } + // Delete the Remote Settings database, and try to import a dump. + // This is not supported, and the error thrown asynchronously in the worker + // should be reported to the caller. + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase("remote-settings"); + request.onsuccess = event => resolve(); + request.onblocked = event => reject(new Error("Cannot delete DB")); + request.onerror = event => reject(event.target.error); + }); + let error; + try { + await RemoteSettingsWorker.importJSONDump("main", "language-dictionaries"); + } catch (e) { + error = e; + } + Assert.ok(/IndexedDB: Error accessing remote-settings/.test(error.message)); +}); + +add_task(async function test_throws_error_if_worker_crashes() { + // This simulates a crash at the worker level (not within a promise). + let error; + try { + await RemoteSettingsWorker._execute("unknown_method"); + } catch (e) { + error = e; + } + Assert.equal(error.message, "TypeError: Agent[method] is not a function"); +}); + +add_task(async function test_stops_worker_after_timeout() { + // Change the idle time. + Services.prefs.setIntPref( + "services.settings.worker_idle_max_milliseconds", + 1 + ); + // Run a task: + let serialized = await RemoteSettingsWorker.canonicalStringify([], 42); + Assert.equal(serialized, '{"data":[],"last_modified":"42"}', "API works."); + // Check that the worker gets stopped now the task is done: + await TestUtils.waitForCondition(() => !RemoteSettingsWorker.worker); + // Ensure the worker stays alive for 10 minutes instead: + Services.prefs.setIntPref( + "services.settings.worker_idle_max_milliseconds", + 600000 + ); + // Run another task: + serialized = await RemoteSettingsWorker.canonicalStringify([], 42); + Assert.equal( + serialized, + '{"data":[],"last_modified":"42"}', + "API still works." + ); + Assert.ok(RemoteSettingsWorker.worker, "Worker should stay alive a bit."); + + // Clear the pref. + Services.prefs.clearUserPref( + "services.settings.worker_idle_max_milliseconds" + ); +}); diff --git a/services/settings/test/unit/test_shutdown_handling.js b/services/settings/test/unit/test_shutdown_handling.js new file mode 100644 index 0000000000..a35ab6080a --- /dev/null +++ b/services/settings/test/unit/test_shutdown_handling.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +const { Database } = ChromeUtils.importESModule( + "resource://services-settings/Database.sys.mjs" +); +const { RemoteSettingsWorker } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsWorker.sys.mjs" +); +const { RemoteSettingsClient } = ChromeUtils.importESModule( + "resource://services-settings/RemoteSettingsClient.sys.mjs" +); + +add_task(async function test_shutdown_abort_after_start() { + // Start a forever transaction: + let counter = 0; + let transactionStarted; + let startedPromise = new Promise(r => { + transactionStarted = r; + }); + let promise = Database._executeIDB( + "records", + store => { + // Signal we've started. + transactionStarted(); + function makeRequest() { + if (++counter > 1000) { + Assert.ok( + false, + "We ran 1000 requests and didn't get aborted, what?" + ); + return; + } + dump("Making request " + counter + "\n"); + const request = store + .index("cid") + .openCursor(IDBKeyRange.only("foopydoo/foo")); + request.onsuccess = event => { + makeRequest(); + }; + } + makeRequest(); + }, + { mode: "readonly" } + ); + + // Wait for the transaction to start. + await startedPromise; + + Database._shutdownHandler(); // should abort the readonly transaction. + + let rejection; + await promise.catch(e => { + rejection = e; + }); + ok(rejection, "Promise should have rejected."); + + // Now clear the shutdown flag and rejection error: + Database._cancelShutdown(); + rejection = null; +}); + +add_task(async function test_shutdown_immediate_abort() { + // Now abort directly from the successful request. + let promise = Database._executeIDB( + "records", + store => { + let request = store + .index("cid") + .openCursor(IDBKeyRange.only("foopydoo/foo")); + request.onsuccess = event => { + // Abort immediately. + Database._shutdownHandler(); + request = store + .index("cid") + .openCursor(IDBKeyRange.only("foopydoo/foo")); + Assert.ok(false, "IndexedDB allowed opening a cursor after aborting?!"); + }; + }, + { mode: "readonly" } + ); + + let rejection; + // Wait for the abort + await promise.catch(e => { + rejection = e; + }); + ok(rejection, "Directly aborted promise should also have rejected."); + // Now clear the shutdown flag and rejection error: + Database._cancelShutdown(); +}); + +add_task(async function test_shutdown_worker() { + let client = new RemoteSettingsClient("language-dictionaries"); + const before = await client.get({ syncIfEmpty: false }); + Assert.equal(before.length, 0); + + let records = [{}]; + let importPromise = RemoteSettingsWorker._execute( + "_test_only_import", + ["main", "language-dictionaries", records, 0], + { mustComplete: true } + ); + let stringifyPromise = RemoteSettingsWorker.canonicalStringify( + [], + [], + Date.now() + ); + // Change the idle time so we shut the worker down even though we can't + // set gShutdown from outside of the worker management code. + Services.prefs.setIntPref( + "services.settings.worker_idle_max_milliseconds", + 1 + ); + RemoteSettingsWorker._abortCancelableRequests(); + await Assert.rejects( + stringifyPromise, + /Shutdown/, + "Should have aborted the stringify request at shutdown." + ); + await Assert.rejects( + importPromise, + /shutting down/, + "Ensure imports get aborted during shutdown" + ); + const after = await client.get({ syncIfEmpty: false }); + Assert.equal(after.length, 0); + await TestUtils.waitForCondition(() => !RemoteSettingsWorker.worker); + Assert.ok( + !RemoteSettingsWorker.worker, + "Worker should have been terminated." + ); +}); diff --git a/services/settings/test/unit/xpcshell.toml b/services/settings/test/unit/xpcshell.toml new file mode 100644 index 0000000000..b305ea39f3 --- /dev/null +++ b/services/settings/test/unit/xpcshell.toml @@ -0,0 +1,36 @@ +[DEFAULT] +head = "../../../common/tests/unit/head_global.js ../../../common/tests/unit/head_helpers.js" +firefox-appdir = "browser" +tags = "remote-settings" +support-files = ["test_remote_settings_signatures/**"] +skip-if = ["appname == 'thunderbird'"] # Bug 1662758 - these tests don't pass if default bucket isn't "main". + +["test_attachments_downloader.js"] +support-files = ["test_attachments_downloader/**"] + +["test_remote_settings.js"] + +["test_remote_settings_dump_lastmodified.js"] + +["test_remote_settings_jexl_filters.js"] + +["test_remote_settings_offline.js"] + +["test_remote_settings_poll.js"] + +["test_remote_settings_recover_broken.js"] + +["test_remote_settings_release_prefs.js"] + +["test_remote_settings_signatures.js"] + +["test_remote_settings_sync_history.js"] + +["test_remote_settings_utils.js"] + +["test_remote_settings_utils_telemetry.js"] +skip-if = ["os == 'android'"] # bug 1739463 + +["test_remote_settings_worker.js"] + +["test_shutdown_handling.js"] diff --git a/services/sync/SyncComponents.manifest b/services/sync/SyncComponents.manifest new file mode 100644 index 0000000000..68c36cd80f --- /dev/null +++ b/services/sync/SyncComponents.manifest @@ -0,0 +1,3 @@ +# Register resource aliases +# (Note, for tests these are also set up in addResourceAlias) +resource services-sync resource://gre/modules/services-sync/ diff --git a/services/sync/Weave.sys.mjs b/services/sync/Weave.sys.mjs new file mode 100644 index 0000000000..05a7031a73 --- /dev/null +++ b/services/sync/Weave.sys.mjs @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + CLIENT_NOT_CONFIGURED: "resource://services-sync/constants.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "syncUsername", + "services.sync.username" +); + +/** + * Sync's XPCOM service. + * + * It is named "Weave" for historical reasons. + * + * It's worth noting how Sync is lazily loaded. We register a timer that + * loads Sync a few seconds after app startup. This is so Sync does not + * adversely affect application start time. + * + * If Sync is not configured, no extra Sync code is loaded. If an + * external component (say the UI) needs to interact with Sync, it + * should use the promise-base function whenLoaded() - something like the + * following: + * + * // 1. Grab a handle to the Sync XPCOM service. + * let service = Cc["@mozilla.org/weave/service;1"] + * .getService(Components.interfaces.nsISupports) + * .wrappedJSObject; + * + * // 2. Use the .then method of the promise. + * service.whenLoaded().then(() => { + * // You are free to interact with "Weave." objects. + * return; + * }); + * + * And that's it! However, if you really want to avoid promises and do it + * old-school, then + * + * // 1. Get a reference to the service as done in (1) above. + * + * // 2. Check if the service has been initialized. + * if (service.ready) { + * // You are free to interact with "Weave." objects. + * return; + * } + * + * // 3. Install "ready" listener. + * Services.obs.addObserver(function onReady() { + * Services.obs.removeObserver(onReady, "weave:service:ready"); + * + * // You are free to interact with "Weave." objects. + * }, "weave:service:ready", false); + * + * // 4. Trigger loading of Sync. + * service.ensureLoaded(); + */ +export function WeaveService() { + this.wrappedJSObject = this; + this.ready = false; +} + +WeaveService.prototype = { + classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"), + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + + get Weave() { + const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" + ); + return Weave; + }, + + ensureLoaded() { + // Side-effect of accessing the service is that it is instantiated. + this.Weave.Service; + }, + + whenLoaded() { + if (this.ready) { + return Promise.resolve(); + } + let onReadyPromise = new Promise(resolve => { + Services.obs.addObserver(function onReady() { + Services.obs.removeObserver(onReady, "weave:service:ready"); + resolve(); + }, "weave:service:ready"); + }); + this.ensureLoaded(); + return onReadyPromise; + }, + + init() { + // Force Weave service to load if it hasn't triggered from overlays + this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this.timer.initWithCallback( + { + notify: () => { + let isConfigured = false; + // We only load more if it looks like Sync is configured. + if (this.enabled) { + // We have an associated FxAccount. So, do a more thorough check. + // This will import a number of modules and thus increase memory + // accordingly. We could potentially copy code performed by + // this check into this file if our above code is yielding too + // many false positives. + var { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" + ); + isConfigured = + Weave.Status.checkSetup() != lazy.CLIENT_NOT_CONFIGURED; + } + if (isConfigured) { + this.ensureLoaded(); + } + }, + }, + 10000, + Ci.nsITimer.TYPE_ONE_SHOT + ); + }, + + /** + * Whether Sync appears to be enabled. + * + * This returns true if we have an associated FxA account and Sync is enabled. + * + * It does *not* perform a robust check to see if the client is working. + * For that, you'll want to check Weave.Status.checkSetup(). + */ + get enabled() { + return ( + !!lazy.syncUsername && + Services.prefs.getBoolPref("identity.fxaccounts.enabled") + ); + }, +}; + +export function AboutWeaveLog() {} +AboutWeaveLog.prototype = { + classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"), + + QueryInterface: ChromeUtils.generateQI([ + "nsIAboutModule", + "nsISupportsWeakReference", + ]), + + getURIFlags(aURI) { + return 0; + }, + + newChannel(aURI, aLoadInfo) { + let dir = lazy.FileUtils.getDir("ProfD", ["weave", "logs"]); + try { + dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) { + throw ex; + } + // Ignore the exception due to a directory that already exists. + } + let uri = Services.io.newFileURI(dir); + let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo); + + channel.originalURI = aURI; + + // Ensure that the about page has the same privileges as a regular directory + // view. That way links to files can be opened. make sure we use the correct + // origin attributes when creating the principal for accessing the + // about:sync-log data. + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + aLoadInfo.originAttributes + ); + + channel.owner = principal; + return channel; + }, +}; diff --git a/services/sync/components.conf b/services/sync/components.conf new file mode 100644 index 0000000000..e4f82b35b1 --- /dev/null +++ b/services/sync/components.conf @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'cid': '{74b89fb0-f200-4ae8-a3ec-dd164117f6de}', + 'contract_ids': ['@mozilla.org/weave/service;1'], + 'esModule': 'resource://services-sync/Weave.sys.mjs', + 'constructor': 'WeaveService', + }, + { + 'cid': '{d28f8a0b-95da-48f4-b712-caf37097be41}', + 'contract_ids': ['@mozilla.org/network/protocol/about;1?what=sync-log'], + 'esModule': 'resource://services-sync/Weave.sys.mjs', + 'constructor': 'AboutWeaveLog', + }, +] diff --git a/services/sync/docs/engines.rst b/services/sync/docs/engines.rst new file mode 100644 index 0000000000..7a4fa721af --- /dev/null +++ b/services/sync/docs/engines.rst @@ -0,0 +1,133 @@ +============================ +The Sync engines in the tree +============================ + +Unless otherwise specified, the engine implementations can be found +`here <https://searchfox.org/mozilla-central/source/services/sync/modules/engines>`_ + +Please read the :doc:`overview`. + +Clients +======= + +The ``clients`` engine is a special engine in that it's invisible to the +user and can not be disabled - think of it as a "meta" engine. As such, it +doesn't really have a sensible concept of ``store`` or ``tracker``. + +The engine is mainly responsible for keeping its own record current in the +``clients`` collection. Some parts of Sync use this collection to know what +other clients exist and when they last synced (although alot of this is moving +to using the Firefox Accounts devices). + +Clients also has the ability to handle ``commands`` - in short, some other +client can write to this client's ``commands``, and when this client notices, +it will execute the command. Commands aren't arbitrary, so commands must be +understood by both sides for them to work. There are commands to "wipe" +collections etc. In practice, this is used only by ``bookmarks`` when a device +restores bookmarks - in that case, the restoring device will send a ``wipe`` +command to all other clients so that they take the new bookmarks instead of +merging them. + +If not for this somewhat limited ``commands`` functionality, this engine could +be considered deprecated and subsumed by FxA devices - but because we +can't just remove support for commands and also do not have a plan for +replacing them, the clients engine remains important. + +Bookmarks +========= + +The ``bookmarks`` engine has changed so that it's tightly integrated with the +``places`` database. Instead of an external ``tracker``, the tracking is +integrated into Places. Each bookmark has a `syncStatus` and a +`syncChangeCounter` and these are managed internally by places. Sync then just +queries for changed bookmarks by looking for these fields. + +Bookmarks is somewhat unique in that it needs to maintain a tree structure, +which makes merging a challenge. The `dogear <https://github.com/mozilla/dogear>`_ +component (written in Rust and also used by the +`application-services bookmarks component <https://github.com/mozilla/application-services/tree/main/components/places>`_) +performs this merging. + +Bookmarks also pioneered the concept of a "mirror" - this is a database table +which tracks exactly what is on the server. Because each sync only fetches +changes from the server since the last sync, each sync does not supply every +record on the server. However, the merging code does need to know what's on +the server - so the mirror tracks this. + +History +======= + +History is similar to bookmarks described above - it's closely integrated with +places - but is less complex because there's no tree structure involved. + +One unique characteristic of history is that the engine takes steps to *not* +upload everything - old profiles tend to have too much history to reasonably +store and upload, so typically uploads are limited to the last 5000 visits. + +Logins +====== + +Logins has also been upgraded to be closely integrated with `Services.logins` - +the logins component itself manages the metadata. + +Tabs +==== + +Tabs is a special engine in that there's no underlying storage at all - it +both saves the currently open tabs from this device (which are enumerated +every time it's updated) and also lets other parts of Firefox know which tabs +are open on other devices. There's no database - if we haven't synced yet we +don't know what other tabs are open, and when we do know, the list is just +stored in memory. + +The `SyncedTabs module <https://searchfox.org/mozilla-central/source/services/sync/modules/SyncedTabs.jsm>`_ +is the main interface the browser uses to get the list of tabs from other +devices. + +Add-ons +======= + +Addons is still an "old school" engine, with a tracker and store which aren't +closely integrated with the addon manager. As a result it's fairly complex and +error prone - eg, it persists the "last known" state so it can know what to +sync, where a better model would be for the addon manager to track the changes +on Sync's behalf. + +It also attempts to sync themes etc. The future of this engine isn't clear given +it doesn't work on mobile platforms. + +Addresses / Credit-Cards +======================== + +Addresses and Credit-cards have Sync functionality tightly bound with the +store. Unlike other engines above, this engine has always been tightly bound, +because it was written after we realized this tight-binding was a feature and +not a bug. + +Technically these are 2 separate engines and collections. However, because the +underlying storage uses a shared implementation, the syncing also uses a +shared implementation - ie, the same logic is used for both - so we tend to +treat them as a single engine in practice. + +As a result, only a shim is in the `services/sync/modules/engines/` directory, +while the actual logic is +`next to the storage implementation <https://searchfox.org/mozilla-central/source/toolkit/components/formautofill/FormAutofillSync.jsm>`_. + +This engine has a unique twist on the "mirror" concept described above - +whenever a change is made to a fields, the original value of the field is +stored directly in the storage. This means that on the next sync, the value +of the record on the server can be deduced, meaning a "3-way" merge can be +done, so it can better tell the difference between local only, remote only, or +conflicting changes. + +WebExt-Storage +============== + +webext-storage is implemented in Rust and lives in +`application services <https://github.com/mozilla/application-services/tree/main/components/webext-storage>`_ +and is vendored into the `addons code <https://searchfox.org/mozilla-central/source/toolkit/components/extensions/storage/webext_storage_bridge>`_ - +note that this includes the storage *and* Sync code. The Sync engine itself +is a shim in the sync directory. + +See the :doc:`rust-engines` document for more about how rust engines are +integrated. diff --git a/services/sync/docs/external.rst b/services/sync/docs/external.rst new file mode 100644 index 0000000000..f7cebde32d --- /dev/null +++ b/services/sync/docs/external.rst @@ -0,0 +1,8 @@ +============== +External Links +============== + +Some external links that might be of interest: + +* `Information about the server APIs <https://mozilla-services.readthedocs.io/en/latest/index.html>`_ +* `Some external Sync Client docs <https://mozilla-services.readthedocs.io/en/latest/sync/index.html>`_ diff --git a/services/sync/docs/index.rst b/services/sync/docs/index.rst new file mode 100644 index 0000000000..37ce3c19a0 --- /dev/null +++ b/services/sync/docs/index.rst @@ -0,0 +1,17 @@ +==== +Sync +==== + +This documents the sync implementation inside mozilla-central. It assumes +a general understanding of what Sync is and how it works at a high level - you +can find `some external docs <https://mozilla-services.readthedocs.io/en/latest/sync/>`_ +which can help with this. + +.. toctree:: + :maxdepth: 1 + + overview + engines + rust-engines + payload-evolution + external diff --git a/services/sync/docs/overview.rst b/services/sync/docs/overview.rst new file mode 100644 index 0000000000..e956090d70 --- /dev/null +++ b/services/sync/docs/overview.rst @@ -0,0 +1,81 @@ +==================== +Introduction to Sync +==================== + +This document is a brief introduction to how Sync is implemented in desktop Firefox. + +General, Historical, Anatomy of a Sync Engine +============================================= + +This section describes how Sync used to work - and indeed, how much of it still +does. While we discuss how this is slowly changing, this context is valuable. + +For any datatype which syncs, there tends to be 3 parts: + +Store +----- + +The sync ``store`` interfaces with the actual Firefox desktop store. For example, +in the ``passwords`` engine, the "store" is that layer that talks to +``Services.logins`` + +Tracker +------- + +The ``tracker`` is what knows that something should be synced. For example, +when the user creates or updates a password, it is the tracker that knows +we should sync now, and what particular password(s) should be updated. + +This is typically done via "observer" notifications - ``Services.logins``, +``places`` etc all send specific notifications when certain events happen +(and indeed, some of these were added for Sync's benefit) + +Engine +------ + +The ``engine`` ties it all together. It works with the ``store`` and +``tracker`` and tracks its own metadata (eg, the timestamp of the passwords on +the server, so it knows how to grab just changed records and how to pass them +off to the ``store`` so the actual underlying storage can be updated. + +All of the above parts were typically in the +`services/sync/modules/engines directory <https://searchfox.org/mozilla-central/source/services/sync/modules/engines>`_ +directory and decoupled from the data they were syncing. + + +The Future of Desktop-Specific Sync Engines +=========================================== + +The system described above reflects the fact that Sync was "bolted on" to +Desktop Firefox relatively late - eg, the Sync ``store`` is decoupled from the +actual ``store``. This has causes a number of problems - particularly around +the ``tracker`` and the metadata used by the engine, and the fact that changes +to the backing store would often forget that Sync existed. + +Over the last few years, the Sync team has come to the conclusion that Sync +support must be integrated much closer to the store itself. For example, +``Services.logins`` should track when something has changed that would cause +an item to be synced. It should also track the metadata for the store so that +if (say) a corrupt database is recovered by creating a new, empty one, the +metadata should also vanish so Sync knows something bad has happened and can +recover. + +However, this is a slow process - currently the ``bookmarks``, ``history`` and +``passwords`` legacy engines have been improved so more responsibility is taken +by the stores. In all cases, for implementation reasons, the Sync +implementation still has a ``store``, but it tends to be a thin wrapper around +the actual underlying store. + +The Future of Cross-Platform Sync Engines +========================================= + +There are a number of Sync engines implemented in Rust and which live in the +application-services repository. While these were often done for mobile +platforms, the longer term hope is that they can be reused on Desktop. +:doc:`engines` has more details on these. + +While no existing engines have been replaced with Rust implemented engines, +the webext-storage engine is implemented in Rust via application-services, so +doesn't tend to use any of the infrastructure described above. + +Hopefully over time we will find more Rust-implemented engines in Desktop. diff --git a/services/sync/docs/payload-evolution.md b/services/sync/docs/payload-evolution.md new file mode 100644 index 0000000000..e195ee545d --- /dev/null +++ b/services/sync/docs/payload-evolution.md @@ -0,0 +1,168 @@ +# Handling the evolution of Sync payloads + +(Note that this document has been written in the format of an [application-services ADR](https://github.com/mozilla/application-services/blob/main/docs/adr/0000-use-markdown-architectural-decision-records.md) +but the relelvant teams decided that ultimately the best home for this doc is in mozilla-central) + +* Status: Accepted +* Deciders: sync team, credentials management team +* Date: 2023-03-15 + +Technical Story: +* https://github.com/mozilla/application-services/pull/5434 +* https://docs.google.com/document/d/1ToLOERA5HKzEzRVZNv6Ohv_2wZaujW69pVb1Kef2jNY + +## Context and Problem Statement + +Sync exists on all platforms (Desktop, Android, iOS), all channels (Nightly, Beta, Release, ESR) and is heavily used across all Firefox features. +Whenever there are feature changes or requests that potentially involve schema changes, there are not a lot of good options to ensure sync doesn’t break for any specific client. +Since sync data is synced from all channels, we need to make sure each client can handle the new data and that all channels can support the new schema. +Issues like [credit card failing on android and desktop release channels due to schema change on desktop Nightly](https://bugzilla.mozilla.org/show_bug.cgi?id=1812235) +are examples of such cases we can run into. +This document describes our decision on how we will support payload evolution over time. + +Note that even though this document exists in the application-services repository, it should +be considered to apply to all sync implementations, whether in this repository, in mozilla-central, +or anywhere else. + +## Definitions + +* A "new" Firefox installation is a version of Firefox which has a change to a Sync payload which + is not yet understood by "recent" versions. The most common example would be a Nightly version + of Firefox with a new feature not yet on the release channel. + +* A "recent" Firefox installation is a version older than a "new" version, which does not understand + or have support for new features in "new" versions, but which we still want to support without + breakage and without the user perceiving data-loss. This is typically accepted to mean the + current ESR version or later, but taking into account the slow update when new ESRs are released. + +* An "old" version is any version before what we consider "recent". + + +## Decision Drivers + +* It must be possible to change what data is carried by Sync to meet future product requirements. +* Both desktop and mobile platforms must be considered. +* We must not break "recent" Firefox installations when a "new" Firefox installation syncs, and vice-versa. +* Round-tripping data from a "new" Firefox installation through a "recent" Firefox installation must not discard any of the new data, and vice-versa. +* Some degree of breakage for "old" Firefox installations when "new" or "recent" firefoxes sync + might be considered acceptable if absolutely necessary. +* However, breakage of "new" or "recent" Firefoxes when an "old" version syncs is *not* acceptable. +* Because such evolution should be rare, we do not want to set an up-front policy about locking out + "old" versions just because they might have a problem in the future. That is, we want to avoid + a policy that dictates versions more than (say) 2 years old will break when syncing "just in case" +* Any solution to this must be achievable in a relatively short timeframe as we know of product + asks coming down the line which require this capability. + +## Considered Options + +* A backwards compatible schema policy, consisting of (a) having engines "round trip" data they + do not know about and (b) never changing the semantics of existing data. +* A policy which prevents "recent" clients from syncing, or editing data, or other restrictions. +* A formal schema-driven process. +* Consider the sync payloads frozen and never change them. +* Use separate collections for new data + +## Decision Outcome + +Chosen option: A backwards compatible schema policy because it is very flexible and the only option +meeting the decision drivers. + +## Pros and Cons of the Options + +### A backwards compatible schema policy + +A summary of this option is a policy by which: + +* Every sync engine must arrange to persist any fields from the payload which it + does not understand. The next time that engine needs to upload that record to the storage server, + it must arrange to add all such "unknown" fields back into the payload. + +* Different engines must identify different locations where this might happen. For example, the + `passwords` engine would identify the "root" of the payload, `addresses` and `creditcards` would + identify the `entry` sub-object in the payload, while the history engine would probably identify + *both* the root of the payload and the `visits` array. + +* Fields can not change type, nor be removed for a significant amount of time. This might mean + that "new" clients must support both new fields *and* fields which are considered deprecated + by these "new" clients because they are still used by "recent" versions. + +The pros and cons: + +* Good, because it meets the requirements. + +* Good, because the initial set of work identified is relatively simple to implement (that work + specifically is to support the round-tripping of "unknown" fields, in the hope that by the + time actual schema changes are proposed, this round-trip capability will then be on all "recent" + versions) + +* Bad, because the inability to deprecate or change existing fields means that + some evolution tasks become complicated. For example, consider a hypothetical change where + we wanted to change from "street/city/state" fields into a free-form "address" field. New + Firefox versions would need to populate *both* new and old fields when writing to the server, + and handle the fact that only the old versions might be updated when it sees an incoming + record written by a "recent" or "old" versions of Firefox. However, this should be rare. + +* Bad, because it's not possible to prove a proposed change meets the requirements - the policy + is informal and requires good judgement as changes are proposed. + +### A policy which prevents "recent" clients from syncing, or editing data + +Proposals which fit into this category might have been implemented by (say) adding +a version number to the schema, and if clients did not fully understand the schema it would +either prevent syncing the record, or sync it but not allow editing it, or similar. + +This was rejected because: + +* The user would certainly perceive data-loss if we ignored the incoming data entirely. +* If we still wanted older versions to "partially" see the record (eg, but disallow editing) we'd + still need most of the chosen option anyway - specifically, we could still never + deprecate fields etc. +* The UI/UX of trying to explain to the user why they can't edit a record was deemed impossible + to do in a satisfactory way. +* This would effectively penalize users who chose to use Nightly Firefoxes in any way. Simply + allowing a Nightly to sync would effectively break Release/Mobile Firefox versions. + +### A formal schema-driven process. + +Ideally we could formally describe schemas, but we can't come up with anything here which +works with the constraints of supporting older clients - we simply can't update older released +Firefoxes so they know how to work with the new schemas. We also couldn't come up with a solution +where a schema is downloaded dynamically which also allowed the *semantics* (as opposed to simply +validity) of new fields to be described. + +### Consider the sync payloads frozen and never change them. + +A process where payloads are frozen was rejected because: + +* The most naive approach here would not meet the needs of Firefox in the future. + +* A complicated system where we started creating new payload and new collections + (ie, freezing "old" schemas but then creating "new" schemas only understood by + newer clients) could not be conceived in a way that still met the requirements, + particularly around data-loss for older clients. For example, adding a credit-card + on a Nightly version but having it be completely unavailable on a release firefox + isn't acceptable. + +### Use separate collections for new data + +We could store the new data in a separate collection. For example define a +bookmarks2 collection where each record has the same guid as one in bookmarks alongside any new fields. +Newer clients use both collections to sync. + +The pros and cons: + +* Good, because it allows newer clients to sync new data without affecting recent or older clients +* Bad, because sync writes would lose atomicity without server changes. + We can currently write to a single collection in an atomic way, but don't have a way to write to multiple collections. +* Bad because this number of collections grows each time we want to add fields. +* Bad because it potentially leaks extra information to an attacker that gets access to the encrypted server records. + For example if we added a new collection for a single field, then the attacker could guess if that + field was set or not based on the size of the encrypted record. +* Bad because it's difficult to handle nested data with this approach, + for example adding a field to a history record visit. +* Bad because it has the same issue with dependent data as the chosen solution. + +## Links <!-- optional --> + +* This document was originally [brain-stormed in this google docs document](https://docs.google.com/document/d/1ToLOERA5HKzEzRVZNv6Ohv_2wZaujW69pVb1Kef2jNY), + which may be of interest for historical context, but should not be considered part of this ADR. diff --git a/services/sync/docs/rust-engines.rst b/services/sync/docs/rust-engines.rst new file mode 100644 index 0000000000..af00fd6619 --- /dev/null +++ b/services/sync/docs/rust-engines.rst @@ -0,0 +1,37 @@ +================================ +How Rust Engines are implemented +================================ + +There are 2 main components to engines implemented in Rust + +The bridged-engine +================== + +Because Rust engines still need to work with the existing Sync infrastructure, +there's the concept of a `bridged-engine <https://searchfox.org/mozilla-central/source/services/sync/modules/bridged_engine.js>`_. +In short, this is just a shim between the existing +`Sync Service <https://searchfox.org/mozilla-central/source/services/sync/modules/service.js>`_ +and the Rust code. + +The bridge +========== + +`"Golden Gate" <https://searchfox.org/mozilla-central/source/services/sync/golden_gate>`_ +is a utility to help bridge any Rust implemented Sync engines with desktop. In +other words, it's a "rusty bridge" - get it? Get it? Yet another of Lina's puns +that live on! + +One of the key challenges with integrating a Rust Sync component with desktop +is the different threading models. The Rust code tends to be synchronous - +most functions block the calling thread to do the disk or network IO necessary +to work - it assumes that the consumer will delegate this to some other thread. + +So golden_gate is this background thread delegation for a Rust Sync engine - +gecko calls golden-gate on the main thread, it marshalls the call to a worker +thread, and the result is marshalled back to the main thread. + +It's worth noting that golden_gate is just for the Sync engine part - other +parts of the component (ie, the part that provides the functionality that's not +sync related) will have its own mechanism for this. For example, the +`webext-storage bridge <https://searchfox.org/mozilla-central/source/toolkit/components/extensions/storage/webext_storage_bridge/src>`_ +uses a similar technique `which has some in-depth documentation <../../toolkit/components/extensions/webextensions/webext-storage.html>`_. diff --git a/services/sync/golden_gate/Cargo.toml b/services/sync/golden_gate/Cargo.toml new file mode 100644 index 0000000000..3f94e1a1e9 --- /dev/null +++ b/services/sync/golden_gate/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "golden_gate" +description = "A bridge for wiring up Sync engines implemented in Rust" +version = "0.1.0" +authors = ["The Firefox Sync Developers <sync-team@mozilla.com>"] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +anyhow = "1" +atomic_refcell = "0.1" +cstr = "0.2" +interrupt-support = "0.1" +log = "0.4" +moz_task = { path = "../../../xpcom/rust/moz_task" } +nserror = { path = "../../../xpcom/rust/nserror" } +nsstring = { path = "../../../xpcom/rust/nsstring" } +serde_json = "1" +storage_variant = { path = "../../../storage/variant" } +sync15 = "0.1" +xpcom = { path = "../../../xpcom/rust/xpcom" } + +[dependencies.thin-vec] +version = "0.2.1" +features = ["gecko-ffi"] diff --git a/services/sync/golden_gate/src/error.rs b/services/sync/golden_gate/src/error.rs new file mode 100644 index 0000000000..373d20756e --- /dev/null +++ b/services/sync/golden_gate/src/error.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{error, fmt, result, str::Utf8Error}; + +use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_ERROR_UNEXPECTED}; +use serde_json::Error as JsonError; + +/// A specialized `Result` type for Golden Gate. +pub type Result<T> = result::Result<T, Error>; + +/// The error type for Golden Gate errors. +#[derive(Debug)] +pub enum Error { + /// A wrapped XPCOM error. + Nsresult(nsresult), + + /// A ferry didn't run on the background task queue. + DidNotRun(&'static str), + + /// A string contains invalid UTF-8 or JSON. + MalformedString(Box<dyn error::Error + Send + Sync + 'static>), +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::MalformedString(error) => Some(error.as_ref()), + _ => None, + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Nsresult(result) => write!(f, "Operation failed with {}", result.error_name()), + Error::DidNotRun(what) => write!(f, "Failed to run `{what}` on background thread"), + Error::MalformedString(error) => error.fmt(f), + } + } +} + +impl From<nsresult> for Error { + fn from(result: nsresult) -> Error { + Error::Nsresult(result) + } +} + +impl From<Utf8Error> for Error { + fn from(error: Utf8Error) -> Error { + Error::MalformedString(error.into()) + } +} + +impl From<JsonError> for Error { + fn from(error: JsonError) -> Error { + Error::MalformedString(error.into()) + } +} + +impl From<Error> for nsresult { + fn from(error: Error) -> nsresult { + match error { + Error::DidNotRun(_) => NS_ERROR_UNEXPECTED, + Error::Nsresult(result) => result, + Error::MalformedString(_) => NS_ERROR_INVALID_ARG, + } + } +} diff --git a/services/sync/golden_gate/src/ferry.rs b/services/sync/golden_gate/src/ferry.rs new file mode 100644 index 0000000000..99994811ab --- /dev/null +++ b/services/sync/golden_gate/src/ferry.rs @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use nsstring::nsCString; +use storage_variant::VariantType; +use sync15::Guid; +use xpcom::{interfaces::nsIVariant, RefPtr}; + +/// An operation that runs on the background thread, and optionally passes a +/// result to its callback. +pub enum Ferry { + LastSync, + SetLastSync(i64), + SyncId, + ResetSyncId, + EnsureCurrentSyncId(String), + SyncStarted, + StoreIncoming(Vec<nsCString>), + SetUploaded(i64, Vec<Guid>), + SyncFinished, + Reset, + Wipe, +} + +impl Ferry { + /// Returns the operation name for debugging and labeling the task + /// runnable. + pub fn name(&self) -> &'static str { + match self { + Ferry::LastSync => concat!(module_path!(), "getLastSync"), + Ferry::SetLastSync(_) => concat!(module_path!(), "setLastSync"), + Ferry::SyncId => concat!(module_path!(), "getSyncId"), + Ferry::ResetSyncId => concat!(module_path!(), "resetSyncId"), + Ferry::EnsureCurrentSyncId(_) => concat!(module_path!(), "ensureCurrentSyncId"), + Ferry::SyncStarted => concat!(module_path!(), "syncStarted"), + Ferry::StoreIncoming { .. } => concat!(module_path!(), "storeIncoming"), + Ferry::SetUploaded { .. } => concat!(module_path!(), "setUploaded"), + Ferry::SyncFinished => concat!(module_path!(), "syncFinished"), + Ferry::Reset => concat!(module_path!(), "reset"), + Ferry::Wipe => concat!(module_path!(), "wipe"), + } + } +} + +/// The result of a ferry task, sent from the background thread back to the +/// main thread. Results are converted to variants, and passed as arguments to +/// `mozIBridgedSyncEngineCallback`s. +pub enum FerryResult { + LastSync(i64), + SyncId(Option<String>), + AssignedSyncId(String), + Null, +} + +impl Default for FerryResult { + fn default() -> Self { + FerryResult::Null + } +} + +impl FerryResult { + /// Converts the result to an `nsIVariant` that can be passed as an + /// argument to `callback.handleResult()`. + pub fn into_variant(self) -> RefPtr<nsIVariant> { + match self { + FerryResult::LastSync(v) => v.into_variant(), + FerryResult::SyncId(Some(v)) => nsCString::from(v).into_variant(), + FerryResult::SyncId(None) => ().into_variant(), + FerryResult::AssignedSyncId(v) => nsCString::from(v).into_variant(), + FerryResult::Null => ().into_variant(), + } + } +} diff --git a/services/sync/golden_gate/src/lib.rs b/services/sync/golden_gate/src/lib.rs new file mode 100644 index 0000000000..8da6524bd7 --- /dev/null +++ b/services/sync/golden_gate/src/lib.rs @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! **Golden Gate** 🌉 is a crate for bridging Desktop Sync to our suite of +//! Rust sync and storage components. It connects Sync's `BridgedEngine` class +//! to the Rust `BridgedEngine` trait via the `mozIBridgedSyncEngine` XPCOM +//! interface. +//! +//! Due to limitations in implementing XPCOM interfaces for generic types, +//! Golden Gate doesn't implement `mozIBridgedSyncEngine` directly. Instead, +//! it provides helpers, called "ferries", for passing Sync records between +//! JavaScript and Rust. The ferries also handle threading and type +//! conversions. +//! +//! Here's a step-by-step guide for adding a new Rust Sync engine to Firefox. +//! +//! ## Step 1: Create your (XPCOM) bridge +//! +//! In your consuming crate, define a type for your `mozIBridgedSyncEngine` +//! implementation. We'll call this type the **brige**. The bridge is +//! responsible for exposing your Sync engine to XPIDL [^1], in a way that lets +//! JavaScript call it. +//! +//! For your bridge type, you'll need to implement an xpcom interface with the +//! `#[xpcom(implement(mozIBridgedSyncEngine), nonatomic)]` attribute then +//! define `xpcom_method!()` stubs for the `mozIBridgedSyncEngine` methods. For +//! more details about implementing XPCOM methods in Rust, check out the docs in +//! `xpcom/rust/xpcom/src/method.rs`. +//! +//! You'll also need to add an entry for your bridge type to `components.conf`, +//! and define C++ and Rust constructors for it, so that JavaScript code can +//! create instances of it. Check out `NS_NewWebExtStorage` (and, in C++, +//! `mozilla::extensions::storageapi::NewWebExtStorage`) and +//! `NS_NewSyncedBookmarksMerger` (`mozilla::places::NewSyncedBookmarksMerger` +//! in C++) for how to do this. +//! +//! [^1]: You can think of XPIDL as a souped-up C FFI, with richer types and a +//! degree of type safety. +//! +//! ## Step 2: Add a background task queue to your bridge +//! +//! A task queue lets your engine do I/O, merging, and other syncing tasks on a +//! background thread pool. This is important because database reads and writes +//! can take an unpredictable amount of time. Doing these on the main thread can +//! cause jank, and, in the worst case, lock up the browser UI for seconds at a +//! time. +//! +//! The `moz_task` crate provides a `create_background_task_queue` function to +//! do this. Once you have a queue, you can use it to call into your Rust +//! engine. Golden Gate takes care of ferrying arguments back and forth across +//! the thread boundary. +//! +//! Since it's a queue, ferries arrive in the order they're scheduled, so +//! your engine's `store_incoming` method will always be called before `apply`, +//! which is likewise called before `set_uploaded`. The thread manager scales +//! the pool for you; you don't need to create or manage your own threads. +//! +//! ## Step 3: Create your Rust engine +//! +//! Next, you'll need to implement the Rust side of the bridge. This is a type +//! that implements the `BridgedEngine` trait. +//! +//! Bridged engines handle storing incoming Sync records, merging changes, +//! resolving conflicts, and fetching outgoing records for upload. Under the +//! hood, your engine will hold either a database connection directly, or +//! another object that does. +//! +//! Although outside the scope of Golden Gate, your engine will also likely +//! expose a data storage API, for fetching, updating, and deleting items +//! locally. Golden Gate provides the syncing layer on top of this local store. +//! +//! A `BridgedEngine` itself doesn't need to be `Send` or `Sync`, but the +//! ferries require both, since they're calling into your bridge on the +//! background task queue. +//! +//! In practice, this means your bridge will need to hold a thread-safe owned +//! reference to the engine, via `Arc<Mutex<BridgedEngine>>`. In fact, this +//! pattern is so common that Golden Gate implements `BridgedEngine` for any +//! `Mutex<BridgedEngine>`, which automatically locks the mutex before calling +//! into the engine. +//! +//! ## Step 4: Connect the bridge to the JavaScript and Rust sides +//! +//! On the JavaScript side, you'll need to subclass Sync's `BridgedEngine` +//! class, and give it a handle to your XPCOM bridge. The base class has all the +//! machinery for hooking up any `mozIBridgedSyncEngine` implementation so that +//! Sync can drive it. +//! +//! On the Rust side, each `mozIBridgedSyncEngine` method should create a +//! Golden Gate ferry, and dispatch it to the background task queue. The +//! ferries correspond to the method names. For example, `ensureCurrentSyncId` +//! should create a `Ferry::ensure_current_sync_id(...)`; `storeIncoming`, a +//! `Ferry::store_incoming(...)`; and so on. This is mostly boilerplate. +//! +//! And that's it! Each ferry will, in turn, call into your Rust +//! `BridgedEngine`, and send the results back to JavaScript. +//! +//! For an example of how all this works, including exposing a storage (not +//! just syncing!) API to JS via XPIDL, check out `webext_storage::Bridge` for +//! the `storage.sync` API! + +#[macro_use] +extern crate cstr; + +pub mod error; +mod ferry; +pub mod log; +pub mod task; + +pub use crate::log::LogSink; +pub use error::{Error, Result}; +// Re-export items from `interrupt-support` and `sync15`, so that +// consumers of `golden_gate` don't have to depend on them. +pub use interrupt_support::{Interrupted, Interruptee}; +pub use sync15::bso::{IncomingBso, OutgoingBso}; +pub use sync15::engine::{ApplyResults, BridgedEngine}; +pub use sync15::Guid; +pub use task::{ApplyTask, FerryTask}; diff --git a/services/sync/golden_gate/src/log.rs b/services/sync/golden_gate/src/log.rs new file mode 100644 index 0000000000..de7fd0dfc3 --- /dev/null +++ b/services/sync/golden_gate/src/log.rs @@ -0,0 +1,161 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::fmt::{self, Write}; + +use log::{Level, LevelFilter, Log, Metadata, Record}; +use moz_task::{Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder}; +use nserror::nsresult; +use nsstring::nsString; +use xpcom::{interfaces::mozIServicesLogSink, RefPtr}; + +pub struct LogSink { + pub max_level: LevelFilter, + logger: Option<ThreadPtrHandle<mozIServicesLogSink>>, +} + +impl Default for LogSink { + fn default() -> Self { + LogSink { + max_level: LevelFilter::Off, + logger: None, + } + } +} + +impl LogSink { + /// Creates a log sink that adapts the Rust `log` crate to the Sync + /// `Log.sys.mjs` logger. + /// + /// This is copied from `bookmark_sync::Logger`. It would be nice to share + /// these, but, for now, we've just duplicated it to make prototyping + /// easier. + #[inline] + pub fn new(max_level: LevelFilter, logger: ThreadPtrHandle<mozIServicesLogSink>) -> LogSink { + LogSink { + max_level, + logger: Some(logger), + } + } + + /// Creates a log sink using the given Services `logger` as the + /// underlying implementation. The `logger` will always be called + /// asynchronously on its owning thread; it doesn't need to be + /// thread-safe. + pub fn with_logger(logger: Option<&mozIServicesLogSink>) -> Result<LogSink, nsresult> { + Ok(if let Some(logger) = logger { + // Fetch the maximum log level while we're on the main thread, so + // that `LogSink::enabled()` can check it while on the background + // thread. Otherwise, we'd need to dispatch a `LogTask` for every + // log message, only to discard most of them when the task calls + // into the logger on the main thread. + let mut raw_max_level = 0i16; + let rv = unsafe { logger.GetMaxLevel(&mut raw_max_level) }; + let max_level = if rv.succeeded() { + match raw_max_level { + mozIServicesLogSink::LEVEL_ERROR => LevelFilter::Error, + mozIServicesLogSink::LEVEL_WARN => LevelFilter::Warn, + mozIServicesLogSink::LEVEL_DEBUG => LevelFilter::Debug, + mozIServicesLogSink::LEVEL_TRACE => LevelFilter::Trace, + mozIServicesLogSink::LEVEL_INFO => LevelFilter::Info, + _ => LevelFilter::Off, + } + } else { + LevelFilter::Off + }; + LogSink::new( + max_level, + ThreadPtrHolder::new(cstr!("mozIServicesLogSink"), RefPtr::new(logger))?, + ) + } else { + LogSink::default() + }) + } + + /// Returns a reference to the underlying `mozIServicesLogSink`. + pub fn logger(&self) -> Option<&mozIServicesLogSink> { + self.logger.as_ref().and_then(|l| l.get()) + } + + /// Logs a message to the Sync logger, if one is set. This would be better + /// implemented as a macro, as Dogear does, so that we can pass variadic + /// arguments without manually invoking `fmt_args!()` every time we want + /// to log a message. + /// + /// The `log` crate's macros aren't suitable here, because those log to the + /// global logger. However, we don't want to set the global logger in our + /// crate, because that will log _everything_ that uses the Rust `log` crate + /// to the Sync logs, including WebRender and audio logging. + pub fn debug(&self, args: fmt::Arguments) { + let meta = Metadata::builder() + .level(Level::Debug) + .target(module_path!()) + .build(); + if self.enabled(&meta) { + self.log(&Record::builder().args(args).metadata(meta).build()); + } + } +} + +impl Log for LogSink { + #[inline] + fn enabled(&self, meta: &Metadata) -> bool { + self.logger.is_some() && meta.level() <= self.max_level + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + if let Some(logger) = &self.logger { + let mut message = nsString::new(); + if write!(message, "{}", record.args()).is_ok() { + let task = LogTask { + logger: logger.clone(), + level: record.metadata().level(), + message, + }; + let _ = TaskRunnable::new("extension_storage_sync::Logger::log", Box::new(task)) + .and_then(|r| TaskRunnable::dispatch(r, logger.owning_thread())); + } + } + } + + fn flush(&self) {} +} + +/// Logs a message to the mirror logger. This task is created on the background +/// thread queue, and dispatched to the main thread. +struct LogTask { + logger: ThreadPtrHandle<mozIServicesLogSink>, + level: Level, + message: nsString, +} + +impl Task for LogTask { + fn run(&self) { + let logger = self.logger.get().unwrap(); + match self.level { + Level::Error => unsafe { + logger.Error(&*self.message); + }, + Level::Warn => unsafe { + logger.Warn(&*self.message); + }, + Level::Debug => unsafe { + logger.Debug(&*self.message); + }, + Level::Trace => unsafe { + logger.Trace(&*self.message); + }, + Level::Info => unsafe { + logger.Info(&*self.message); + }, + } + } + + fn done(&self) -> Result<(), nsresult> { + Ok(()) + } +} diff --git a/services/sync/golden_gate/src/task.rs b/services/sync/golden_gate/src/task.rs new file mode 100644 index 0000000000..8cab21830b --- /dev/null +++ b/services/sync/golden_gate/src/task.rs @@ -0,0 +1,355 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::{fmt::Write, mem, result}; + +use atomic_refcell::AtomicRefCell; +use moz_task::{DispatchOptions, Task, TaskRunnable, ThreadPtrHandle, ThreadPtrHolder}; +use nserror::{nsresult, NS_ERROR_FAILURE}; +use nsstring::{nsACString, nsCString}; +use sync15::engine::{ApplyResults, BridgedEngine}; +use sync15::Guid; +use thin_vec::ThinVec; +use xpcom::{ + interfaces::{ + mozIBridgedSyncEngineApplyCallback, mozIBridgedSyncEngineCallback, nsIEventTarget, + }, + RefPtr, +}; + +use crate::error::{Error, Result}; +use crate::ferry::{Ferry, FerryResult}; + +/// A ferry task sends (or ferries) an operation to a bridged engine on a +/// background thread or task queue, and ferries back an optional result to +/// a callback. +pub struct FerryTask { + /// We want to ensure scheduled ferries can't block finalization of the underlying + /// store - we want a degree of confidence that closing the database will happen when + /// we want even if tasks are queued up to run on another thread. + /// We rely on the semantics of our BridgedEngines to help here: + /// * A bridged engine is expected to hold a weak reference to its store. + /// * Our LazyStore is the only thing holding a reference to the "real" store. + /// Thus, when our LazyStore asks our "real" store to close, we can be confident + /// a close will happen (ie, we assume that the real store will be able to unwrapp + /// the underlying sqlite `Connection` (using `Arc::try_unwrap`) and close it. + /// However, note that if an operation on the bridged engine is currently running, + /// we will block waiting for that operation to complete, so while this isn't + /// guaranteed to happen immediately, it should happen "soon enough". + engine: Box<dyn BridgedEngine>, + ferry: Ferry, + callback: ThreadPtrHandle<mozIBridgedSyncEngineCallback>, + result: AtomicRefCell<anyhow::Result<FerryResult>>, +} + +impl FerryTask { + /// Creates a task to fetch the engine's last sync time, in milliseconds. + #[inline] + pub fn for_last_sync( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::LastSync, callback) + } + + /// Creates a task to set the engine's last sync time, in milliseconds. + #[inline] + pub fn for_set_last_sync( + engine: Box<dyn BridgedEngine>, + last_sync_millis: i64, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::SetLastSync(last_sync_millis), callback) + } + + /// Creates a task to fetch the engine's sync ID. + #[inline] + pub fn for_sync_id( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::SyncId, callback) + } + + /// Creates a task to reset the engine's sync ID and all its local Sync + /// metadata. + #[inline] + pub fn for_reset_sync_id( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::ResetSyncId, callback) + } + + /// Creates a task to compare the bridged engine's local sync ID with + /// the `new_sync_id` from `meta/global`, and ferry back the final sync ID + /// to use. + #[inline] + pub fn for_ensure_current_sync_id( + engine: Box<dyn BridgedEngine>, + new_sync_id: &nsACString, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry( + engine, + Ferry::EnsureCurrentSyncId(std::str::from_utf8(new_sync_id)?.into()), + callback, + ) + } + + /// Creates a task to signal that the engine is about to sync. + #[inline] + pub fn for_sync_started( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::SyncStarted, callback) + } + + /// Creates a task to store incoming records. + pub fn for_store_incoming( + engine: Box<dyn BridgedEngine>, + incoming_envelopes_json: &[nsCString], + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry( + engine, + Ferry::StoreIncoming(incoming_envelopes_json.to_vec()), + callback, + ) + } + + /// Creates a task to mark a subset of outgoing records as uploaded. This + /// may be called multiple times per sync, or not at all if there are no + /// records to upload. + pub fn for_set_uploaded( + engine: Box<dyn BridgedEngine>, + server_modified_millis: i64, + uploaded_ids: &[nsCString], + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + let uploaded_ids = uploaded_ids.iter().map(|id| Guid::from_slice(id)).collect(); + Self::with_ferry( + engine, + Ferry::SetUploaded(server_modified_millis, uploaded_ids), + callback, + ) + } + + /// Creates a task to signal that all records have been uploaded, and + /// the engine has been synced. This is called even if there were no + /// records uploaded. + #[inline] + pub fn for_sync_finished( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::SyncFinished, callback) + } + + /// Creates a task to reset all local Sync state for the engine, without + /// erasing user data. + #[inline] + pub fn for_reset( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::Reset, callback) + } + + /// Creates a task to erase all local user data for the engine. + #[inline] + pub fn for_wipe( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + Self::with_ferry(engine, Ferry::Wipe, callback) + } + + /// Creates a task for a ferry. The `callback` is bound to the current + /// thread, and will be called once, after the ferry returns from the + /// background thread. + fn with_ferry( + engine: Box<dyn BridgedEngine>, + ferry: Ferry, + callback: &mozIBridgedSyncEngineCallback, + ) -> Result<FerryTask> { + let name = ferry.name(); + Ok(FerryTask { + engine, + ferry, + callback: ThreadPtrHolder::new( + cstr!("mozIBridgedSyncEngineCallback"), + RefPtr::new(callback), + )?, + result: AtomicRefCell::new(Err(Error::DidNotRun(name).into())), + }) + } + + /// Dispatches the task to the given thread `target`. + pub fn dispatch(self, target: &nsIEventTarget) -> Result<()> { + let runnable = TaskRunnable::new(self.ferry.name(), Box::new(self))?; + // `may_block` schedules the task on the I/O thread pool, since we + // expect most operations to wait on I/O. + TaskRunnable::dispatch_with_options( + runnable, + target, + DispatchOptions::default().may_block(true), + )?; + Ok(()) + } + + /// Runs the task on the background thread. This is split out into its own + /// method to make error handling easier. + fn inner_run(&self) -> anyhow::Result<FerryResult> { + let engine = &self.engine; + Ok(match &self.ferry { + Ferry::LastSync => FerryResult::LastSync(engine.last_sync()?), + Ferry::SetLastSync(last_sync_millis) => { + engine.set_last_sync(*last_sync_millis)?; + FerryResult::default() + } + Ferry::SyncId => FerryResult::SyncId(engine.sync_id()?), + Ferry::ResetSyncId => FerryResult::AssignedSyncId(engine.reset_sync_id()?), + Ferry::EnsureCurrentSyncId(new_sync_id) => { + FerryResult::AssignedSyncId(engine.ensure_current_sync_id(new_sync_id)?) + } + Ferry::SyncStarted => { + engine.sync_started()?; + FerryResult::default() + } + Ferry::StoreIncoming(incoming_envelopes_json) => { + let incoming_envelopes = incoming_envelopes_json + .iter() + .map(|envelope| Ok(serde_json::from_slice(envelope)?)) + .collect::<Result<_>>()?; + + engine.store_incoming(incoming_envelopes)?; + FerryResult::default() + } + Ferry::SetUploaded(server_modified_millis, uploaded_ids) => { + engine.set_uploaded(*server_modified_millis, uploaded_ids.as_slice())?; + FerryResult::default() + } + Ferry::SyncFinished => { + engine.sync_finished()?; + FerryResult::default() + } + Ferry::Reset => { + engine.reset()?; + FerryResult::default() + } + Ferry::Wipe => { + engine.wipe()?; + FerryResult::default() + } + }) + } +} + +impl Task for FerryTask { + fn run(&self) { + *self.result.borrow_mut() = self.inner_run(); + } + + fn done(&self) -> result::Result<(), nsresult> { + let callback = self.callback.get().unwrap(); + match mem::replace( + &mut *self.result.borrow_mut(), + Err(Error::DidNotRun(self.ferry.name()).into()), + ) { + Ok(result) => unsafe { callback.HandleSuccess(result.into_variant().coerce()) }, + Err(err) => { + let mut message = nsCString::new(); + write!(message, "{err}").unwrap(); + unsafe { callback.HandleError(NS_ERROR_FAILURE, &*message) } + } + } + .to_result() + } +} + +/// An apply task ferries incoming records to an engine on a background +/// thread, and ferries back records to upload. It's separate from +/// `FerryTask` because its callback type is different. +pub struct ApplyTask { + engine: Box<dyn BridgedEngine>, + callback: ThreadPtrHandle<mozIBridgedSyncEngineApplyCallback>, + result: AtomicRefCell<anyhow::Result<Vec<String>>>, +} + +impl ApplyTask { + /// Returns the task name for debugging. + pub fn name() -> &'static str { + concat!(module_path!(), "apply") + } + + /// Runs the task on the background thread. + fn inner_run(&self) -> anyhow::Result<Vec<String>> { + let ApplyResults { + records: outgoing_records, + .. + } = self.engine.apply()?; + let outgoing_records_json = outgoing_records + .iter() + .map(|record| Ok(serde_json::to_string(record)?)) + .collect::<Result<_>>()?; + Ok(outgoing_records_json) + } + + /// Creates a task. The `callback` is bound to the current thread, and will + /// be called once, after the records are applied on the background thread. + pub fn new( + engine: Box<dyn BridgedEngine>, + callback: &mozIBridgedSyncEngineApplyCallback, + ) -> Result<ApplyTask> { + Ok(ApplyTask { + engine, + callback: ThreadPtrHolder::new( + cstr!("mozIBridgedSyncEngineApplyCallback"), + RefPtr::new(callback), + )?, + result: AtomicRefCell::new(Err(Error::DidNotRun(Self::name()).into())), + }) + } + + /// Dispatches the task to the given thread `target`. + pub fn dispatch(self, target: &nsIEventTarget) -> Result<()> { + let runnable = TaskRunnable::new(Self::name(), Box::new(self))?; + TaskRunnable::dispatch_with_options( + runnable, + target, + DispatchOptions::default().may_block(true), + )?; + Ok(()) + } +} + +impl Task for ApplyTask { + fn run(&self) { + *self.result.borrow_mut() = self.inner_run(); + } + + fn done(&self) -> result::Result<(), nsresult> { + let callback = self.callback.get().unwrap(); + match mem::replace( + &mut *self.result.borrow_mut(), + Err(Error::DidNotRun(Self::name()).into()), + ) { + Ok(envelopes) => { + let result = envelopes + .into_iter() + .map(nsCString::from) + .collect::<ThinVec<_>>(); + unsafe { callback.HandleSuccess(&result) } + } + Err(err) => { + let mut message = nsCString::new(); + write!(message, "{err}").unwrap(); + unsafe { callback.HandleError(NS_ERROR_FAILURE, &*message) } + } + } + .to_result() + } +} diff --git a/services/sync/modules-testing/fakeservices.sys.mjs b/services/sync/modules-testing/fakeservices.sys.mjs new file mode 100644 index 0000000000..4fd7534bf1 --- /dev/null +++ b/services/sync/modules-testing/fakeservices.sys.mjs @@ -0,0 +1,114 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Weave } from "resource://services-sync/main.sys.mjs"; +import { RawCryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Utils } from "resource://services-sync/util.sys.mjs"; + +export function FakeFilesystemService(contents) { + this.fakeContents = contents; + let self = this; + + // Save away the unmocked versions of the functions we replace here for tests + // that really want the originals. As this may be called many times per test, + // we must be careful to not replace them with ones we previously replaced. + // (And why are we bothering with these mocks in the first place? Is the + // performance of the filesystem *really* such that it outweighs the downside + // of not running our real JSON functions in the tests? Eg, these mocks don't + // always throw exceptions when the real ones do. Anyway...) + for (let name of ["jsonSave", "jsonLoad", "jsonMove", "jsonRemove"]) { + let origName = "_real_" + name; + if (!Utils[origName]) { + Utils[origName] = Utils[name]; + } + } + + Utils.jsonSave = async function jsonSave(filePath, that, obj) { + let json = typeof obj == "function" ? obj.call(that) : obj; + self.fakeContents["weave/" + filePath + ".json"] = JSON.stringify(json); + }; + + Utils.jsonLoad = async function jsonLoad(filePath, that) { + let obj; + let json = self.fakeContents["weave/" + filePath + ".json"]; + if (json) { + obj = JSON.parse(json); + } + return obj; + }; + + Utils.jsonMove = function jsonMove(aFrom, aTo, that) { + const fromPath = "weave/" + aFrom + ".json"; + self.fakeContents["weave/" + aTo + ".json"] = self.fakeContents[fromPath]; + delete self.fakeContents[fromPath]; + return Promise.resolve(); + }; + + Utils.jsonRemove = function jsonRemove(filePath, that) { + delete self.fakeContents["weave/" + filePath + ".json"]; + return Promise.resolve(); + }; +} + +export function fakeSHA256HMAC(message) { + message = message.substr(0, 64); + while (message.length < 64) { + message += " "; + } + return message; +} + +export function FakeGUIDService() { + let latestGUID = 0; + + Utils.makeGUID = function makeGUID() { + // ensure that this always returns a unique 12 character string + let nextGUID = "fake-guid-" + String(latestGUID++).padStart(2, "0"); + return nextGUID.slice(nextGUID.length - 12, nextGUID.length); + }; +} + +/* + * Mock implementation of WeaveCrypto. It does not encrypt or + * decrypt, merely returning the input verbatim. + */ +export function FakeCryptoService() { + this.counter = 0; + + delete Weave.Crypto; // get rid of the getter first + Weave.Crypto = this; + + RawCryptoWrapper.prototype.ciphertextHMAC = function ciphertextHMAC( + keyBundle + ) { + return fakeSHA256HMAC(this.ciphertext); + }; +} + +FakeCryptoService.prototype = { + async encrypt(clearText, symmetricKey, iv) { + return clearText; + }, + + async decrypt(cipherText, symmetricKey, iv) { + return cipherText; + }, + + async generateRandomKey() { + return btoa("fake-symmetric-key-" + this.counter++); + }, + + generateRandomIV: function generateRandomIV() { + // A base64-encoded IV is 24 characters long + return btoa("fake-fake-fake-random-iv"); + }, + + expandData: function expandData(data, len) { + return data; + }, + + generateRandomBytes: function generateRandomBytes(byteCount) { + return "not-so-random-now-are-we-HA-HA-HA! >:)".slice(byteCount); + }, +}; diff --git a/services/sync/modules-testing/fxa_utils.sys.mjs b/services/sync/modules-testing/fxa_utils.sys.mjs new file mode 100644 index 0000000000..c953f0eaa3 --- /dev/null +++ b/services/sync/modules-testing/fxa_utils.sys.mjs @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Weave } from "resource://services-sync/main.sys.mjs"; +import { SyncAuthManager } from "resource://services-sync/sync_auth.sys.mjs"; + +import { TokenServerClient } from "resource://services-common/tokenserverclient.sys.mjs"; +import { configureFxAccountIdentity } from "resource://testing-common/services/sync/utils.sys.mjs"; + +// Create a new sync_auth object and initialize it with a +// mocked TokenServerClient which always receives the specified response. +export var initializeIdentityWithTokenServerResponse = function (response) { + // First create a mock "request" object that well' hack into the token server. + // A log for it + let requestLog = Log.repository.getLogger("testing.mock-rest"); + if (!requestLog.appenders.length) { + // might as well see what it says :) + requestLog.addAppender(new Log.DumpAppender()); + requestLog.level = Log.Level.Trace; + } + + // A mock request object. + function MockRESTRequest(url) {} + MockRESTRequest.prototype = { + _log: requestLog, + setHeader() {}, + async get() { + this.response = response; + return response; + }, + }; + // The mocked TokenServer client which will get the response. + function MockTSC() {} + MockTSC.prototype = new TokenServerClient(); + MockTSC.prototype.constructor = MockTSC; + MockTSC.prototype.newRESTRequest = function (url) { + return new MockRESTRequest(url); + }; + // Arrange for the same observerPrefix as sync_auth uses. + MockTSC.prototype.observerPrefix = "weave:service"; + + // tie it all together. + Weave.Status.__authManager = Weave.Service.identity = new SyncAuthManager(); + let syncAuthManager = Weave.Service.identity; + // a sanity check + if (!(syncAuthManager instanceof SyncAuthManager)) { + throw new Error("sync isn't configured to use sync_auth"); + } + let mockTSC = new MockTSC(); + configureFxAccountIdentity(syncAuthManager); + syncAuthManager._tokenServerClient = mockTSC; +}; diff --git a/services/sync/modules-testing/rotaryengine.sys.mjs b/services/sync/modules-testing/rotaryengine.sys.mjs new file mode 100644 index 0000000000..d7f2165e4d --- /dev/null +++ b/services/sync/modules-testing/rotaryengine.sys.mjs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + Store, + SyncEngine, + LegacyTracker, +} from "resource://services-sync/engines.sys.mjs"; + +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { SerializableSet, Utils } from "resource://services-sync/util.sys.mjs"; + +/* + * A fake engine implementation. + * This is used all over the place. + * + * Complete with record, store, and tracker implementations. + */ + +export function RotaryRecord(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +RotaryRecord.prototype = {}; +Object.setPrototypeOf(RotaryRecord.prototype, CryptoWrapper.prototype); +Utils.deferGetSet(RotaryRecord, "cleartext", ["denomination"]); + +export function RotaryStore(name, engine) { + Store.call(this, name, engine); + this.items = {}; +} + +RotaryStore.prototype = { + async create(record) { + this.items[record.id] = record.denomination; + }, + + async remove(record) { + delete this.items[record.id]; + }, + + async update(record) { + this.items[record.id] = record.denomination; + }, + + async itemExists(id) { + return id in this.items; + }, + + async createRecord(id, collection) { + let record = new RotaryRecord(collection, id); + + if (!(id in this.items)) { + record.deleted = true; + return record; + } + + record.denomination = this.items[id] || "Data for new record: " + id; + return record; + }, + + async changeItemID(oldID, newID) { + if (oldID in this.items) { + this.items[newID] = this.items[oldID]; + } + + delete this.items[oldID]; + }, + + async getAllIDs() { + let ids = {}; + for (let id in this.items) { + ids[id] = true; + } + return ids; + }, + + async wipe() { + this.items = {}; + }, +}; + +Object.setPrototypeOf(RotaryStore.prototype, Store.prototype); + +export function RotaryTracker(name, engine) { + LegacyTracker.call(this, name, engine); +} + +RotaryTracker.prototype = {}; +Object.setPrototypeOf(RotaryTracker.prototype, LegacyTracker.prototype); + +export function RotaryEngine(service) { + SyncEngine.call(this, "Rotary", service); + // Ensure that the engine starts with a clean slate. + this.toFetch = new SerializableSet(); + this.previousFailed = new SerializableSet(); +} + +RotaryEngine.prototype = { + _storeObj: RotaryStore, + _trackerObj: RotaryTracker, + _recordObj: RotaryRecord, + + async _findDupe(item) { + // This is a Special Value® used for testing proper reconciling on dupe + // detection. + if (item.id == "DUPE_INCOMING") { + return "DUPE_LOCAL"; + } + + for (let [id, value] of Object.entries(this._store.items)) { + if (item.denomination == value) { + return id; + } + } + return null; + }, +}; +Object.setPrototypeOf(RotaryEngine.prototype, SyncEngine.prototype); diff --git a/services/sync/modules-testing/utils.sys.mjs b/services/sync/modules-testing/utils.sys.mjs new file mode 100644 index 0000000000..498bf9872a --- /dev/null +++ b/services/sync/modules-testing/utils.sys.mjs @@ -0,0 +1,319 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { Assert } from "resource://testing-common/Assert.sys.mjs"; + +import { initTestLogging } from "resource://testing-common/services/common/logging.sys.mjs"; +import { + FakeCryptoService, + FakeFilesystemService, + FakeGUIDService, + fakeSHA256HMAC, +} from "resource://testing-common/services/sync/fakeservices.sys.mjs"; + +import { + FxAccounts, + AccountState, +} from "resource://gre/modules/FxAccounts.sys.mjs"; +import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs"; + +import { SCOPE_OLD_SYNC } from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +// A mock "storage manager" for FxAccounts that doesn't actually write anywhere. +export function MockFxaStorageManager() {} + +MockFxaStorageManager.prototype = { + promiseInitialized: Promise.resolve(), + + initialize(accountData) { + this.accountData = accountData; + }, + + finalize() { + return Promise.resolve(); + }, + + getAccountData(fields = null) { + let result; + if (!this.accountData) { + result = null; + } else if (fields == null) { + // can't use cloneInto as the keys get upset... + result = {}; + for (let field of Object.keys(this.accountData)) { + result[field] = this.accountData[field]; + } + } else { + if (!Array.isArray(fields)) { + fields = [fields]; + } + result = {}; + for (let field of fields) { + result[field] = this.accountData[field]; + } + } + return Promise.resolve(result); + }, + + updateAccountData(updatedFields) { + for (let [name, value] of Object.entries(updatedFields)) { + if (value == null) { + delete this.accountData[name]; + } else { + this.accountData[name] = value; + } + } + return Promise.resolve(); + }, + + deleteAccountData() { + this.accountData = null; + return Promise.resolve(); + }, +}; + +/** + * First wait >100ms (nsITimers can take up to that much time to fire, so + * we can account for the timer in delayedAutoconnect) and then two event + * loop ticks (to account for the CommonUtils.nextTick() in autoConnect). + */ +export function waitForZeroTimer(callback) { + let ticks = 2; + function wait() { + if (ticks) { + ticks -= 1; + CommonUtils.nextTick(wait); + return; + } + callback(); + } + CommonUtils.namedTimer(wait, 150, {}, "timer"); +} + +export var promiseZeroTimer = function () { + return new Promise(resolve => { + waitForZeroTimer(resolve); + }); +}; + +export var promiseNamedTimer = function (wait, thisObj, name) { + return new Promise(resolve => { + CommonUtils.namedTimer(resolve, wait, thisObj, name); + }); +}; + +// Return an identity configuration suitable for testing with our identity +// providers. |overrides| can specify overrides for any default values. +// |server| is optional, but if specified, will be used to form the cluster +// URL for the FxA identity. +export var makeIdentityConfig = function (overrides) { + // first setup the defaults. + let result = { + // Username used in both fxaccount and sync identity configs. + username: "foo", + // fxaccount specific credentials. + fxaccount: { + user: { + email: "foo", + scopedKeys: { + [SCOPE_OLD_SYNC]: { + kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw", + k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg", + kty: "oct", + }, + }, + sessionToken: "sessionToken", + uid: "a".repeat(32), + verified: true, + }, + token: { + endpoint: null, + duration: 300, + id: "id", + key: "key", + hashed_fxa_uid: "f".repeat(32), // used during telemetry validation + // uid will be set to the username. + }, + }, + }; + + // Now handle any specified overrides. + if (overrides) { + if (overrides.username) { + result.username = overrides.username; + } + if (overrides.fxaccount) { + // TODO: allow just some attributes to be specified + result.fxaccount = overrides.fxaccount; + } + if (overrides.node_type) { + result.fxaccount.token.node_type = overrides.node_type; + } + } + return result; +}; + +export var makeFxAccountsInternalMock = function (config) { + return { + newAccountState(credentials) { + // We only expect this to be called with null indicating the (mock) + // storage should be read. + if (credentials) { + throw new Error("Not expecting to have credentials passed"); + } + let storageManager = new MockFxaStorageManager(); + storageManager.initialize(config.fxaccount.user); + let accountState = new AccountState(storageManager); + return accountState; + }, + getOAuthToken: () => Promise.resolve("some-access-token"), + destroyOAuthToken: () => Promise.resolve(), + keys: { + getScopedKeys: () => + Promise.resolve({ + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + keyRotationTimestamp: 1510726317123, + }, + }), + }, + profile: { + getProfile() { + return null; + }, + }, + }; +}; + +// Configure an instance of an FxAccount identity provider with the specified +// config (or the default config if not specified). +export var configureFxAccountIdentity = function ( + authService, + config = makeIdentityConfig(), + fxaInternal = makeFxAccountsInternalMock(config) +) { + // until we get better test infrastructure for bid_identity, we set the + // signedin user's "email" to the username, simply as many tests rely on this. + config.fxaccount.user.email = config.username; + + let fxa = new FxAccounts(fxaInternal); + + let MockFxAccountsClient = function () { + FxAccountsClient.apply(this); + }; + MockFxAccountsClient.prototype = { + accountStatus() { + return Promise.resolve(true); + }, + }; + Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype + ); + let mockFxAClient = new MockFxAccountsClient(); + fxa._internal._fxAccountsClient = mockFxAClient; + + let mockTSC = { + // TokenServerClient + async getTokenUsingOAuth(url, oauthToken) { + Assert.equal( + url, + Services.prefs.getStringPref("identity.sync.tokenserver.uri") + ); + Assert.ok(oauthToken, "oauth token present"); + config.fxaccount.token.uid = config.username; + return config.fxaccount.token; + }, + }; + authService._fxaService = fxa; + authService._tokenServerClient = mockTSC; + // Set the "account" of the sync auth manager to be the "email" of the + // logged in user of the mockFXA service. + authService._signedInUser = config.fxaccount.user; + authService._account = config.fxaccount.user.email; +}; + +export var configureIdentity = async function (identityOverrides, server) { + let config = makeIdentityConfig(identityOverrides, server); + // Must be imported after the identity configuration is set up. + let { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" + ); + + // If a server was specified, ensure FxA has a correct cluster URL available. + if (server && !config.fxaccount.token.endpoint) { + let ep = server.baseURI; + if (!ep.endsWith("/")) { + ep += "/"; + } + ep += "1.1/" + config.username + "/"; + config.fxaccount.token.endpoint = ep; + } + + configureFxAccountIdentity(Service.identity, config); + Services.prefs.setStringPref("services.sync.username", config.username); + // many of these tests assume all the auth stuff is setup and don't hit + // a path which causes that auth to magically happen - so do it now. + await Service.identity._ensureValidToken(); + + // and cheat to avoid requiring each test do an explicit login - give it + // a cluster URL. + if (config.fxaccount.token.endpoint) { + Service.clusterURL = config.fxaccount.token.endpoint; + } +}; + +export function syncTestLogging(level = "Trace") { + let logStats = initTestLogging(level); + Services.prefs.setStringPref("services.sync.log.logger", level); + Services.prefs.setStringPref("services.sync.log.logger.engine", ""); + return logStats; +} + +export var SyncTestingInfrastructure = async function (server, username) { + let config = makeIdentityConfig({ username }); + await configureIdentity(config, server); + return { + logStats: syncTestLogging(), + fakeFilesystem: new FakeFilesystemService({}), + fakeGUIDService: new FakeGUIDService(), + fakeCryptoService: new FakeCryptoService(), + }; +}; + +/** + * Turn WBO cleartext into fake "encrypted" payload as it goes over the wire. + */ +export function encryptPayload(cleartext) { + if (typeof cleartext == "object") { + cleartext = JSON.stringify(cleartext); + } + + return { + ciphertext: cleartext, // ciphertext == cleartext with fake crypto + IV: "irrelevant", + hmac: fakeSHA256HMAC(cleartext), + }; +} + +export var sumHistogram = function (name, options = {}) { + let histogram = options.key + ? Services.telemetry.getKeyedHistogramById(name) + : Services.telemetry.getHistogramById(name); + let snapshot = histogram.snapshot(); + let sum = -Infinity; + if (snapshot) { + if (options.key && snapshot[options.key]) { + sum = snapshot[options.key].sum; + } else { + sum = snapshot.sum; + } + } + histogram.clear(); + return sum; +}; diff --git a/services/sync/modules/SyncDisconnect.sys.mjs b/services/sync/modules/SyncDisconnect.sys.mjs new file mode 100644 index 0000000000..2206a462ac --- /dev/null +++ b/services/sync/modules/SyncDisconnect.sys.mjs @@ -0,0 +1,235 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// This module provides a facility for disconnecting Sync and FxA, optionally +// sanitizing profile data as part of the process. + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs", + Log: "resource://gre/modules/Log.sys.mjs", + PREF_LAST_FXA_USER: "resource://gre/modules/FxAccountsCommon.sys.mjs", + Sanitizer: "resource:///modules/Sanitizer.sys.mjs", + Utils: "resource://services-sync/util.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +export const SyncDisconnectInternal = { + lockRetryInterval: 1000, // wait 1 seconds before trying for the lock again. + lockRetryCount: 120, // Try 120 times (==2 mins) before giving up in disgust. + promiseDisconnectFinished: null, // If we are sanitizing, a promise for completion. + + // mocked by tests. + getWeave() { + return ChromeUtils.importESModule("resource://services-sync/main.sys.mjs") + .Weave; + }, + + // Returns a promise that resolves when we are not syncing, waiting until + // a current Sync completes if necessary. Resolves with true if we + // successfully waited, in which case the sync lock will have been taken to + // ensure future syncs don't state, or resolves with false if we gave up + // waiting for the sync to complete (in which case we didn't take a lock - + // but note that Sync probably remains locked in this case regardless.) + async promiseNotSyncing(abortController) { + let weave = this.getWeave(); + let log = lazy.Log.repository.getLogger("Sync.Service"); + // We might be syncing - poll for up to 2 minutes waiting for the lock. + // (2 minutes seems extreme, but should be very rare.) + return new Promise(resolve => { + abortController.signal.onabort = () => { + resolve(false); + }; + + let attempts = 0; + let checkLock = () => { + if (abortController.signal.aborted) { + // We've already resolved, so don't want a new timer to ever start. + return; + } + if (weave.Service.lock()) { + resolve(true); + return; + } + attempts += 1; + if (attempts >= this.lockRetryCount) { + log.error( + "Gave up waiting for the sync lock - going ahead with sanitize anyway" + ); + resolve(false); + return; + } + log.debug("Waiting a couple of seconds to get the sync lock"); + lazy.setTimeout(checkLock, this.lockRetryInterval); + }; + checkLock(); + }); + }, + + // Sanitize Sync-related data. + async doSanitizeSyncData() { + let weave = this.getWeave(); + // Get the sync logger - if stuff goes wrong it can be useful to have that + // recorded in the sync logs. + let log = lazy.Log.repository.getLogger("Sync.Service"); + log.info("Starting santitize of Sync data"); + try { + // We clobber data for all Sync engines that are enabled. + await weave.Service.promiseInitialized; + weave.Service.enabled = false; + + log.info("starting actual sanitization"); + for (let engine of weave.Service.engineManager.getAll()) { + if (engine.enabled) { + try { + log.info("Wiping engine", engine.name); + await engine.wipeClient(); + } catch (ex) { + log.error("Failed to wipe engine", ex); + } + } + } + // Reset the pref which is used to show a warning when a different user + // signs in - this is no longer a concern now that we've removed the + // data from the profile. + Services.prefs.clearUserPref(lazy.PREF_LAST_FXA_USER); + + log.info("Finished wiping sync data"); + } catch (ex) { + log.error("Failed to sanitize Sync data", ex); + console.error("Failed to sanitize Sync data", ex); + } + try { + // ensure any logs we wrote are flushed to disk. + await weave.Service.errorHandler.resetFileLog(); + } catch (ex) { + console.log("Failed to flush the Sync log", ex); + } + }, + + // Sanitize all Browser data. + async doSanitizeBrowserData() { + try { + // sanitize everything other than "open windows" (and we don't do that + // because it may confuse the user - they probably want to see + // about:prefs with the disconnection reflected. + let itemsToClear = Object.keys(lazy.Sanitizer.items).filter( + k => k != "openWindows" + ); + await lazy.Sanitizer.sanitize(itemsToClear); + } catch (ex) { + console.error("Failed to sanitize other data", ex); + } + }, + + async doSyncAndAccountDisconnect(shouldUnlock) { + // We do a startOver of Sync first - if we do the account first we end + // up with Sync configured but FxA not configured, which causes the browser + // UI to briefly enter a "needs reauth" state. + let Weave = this.getWeave(); + await Weave.Service.promiseInitialized; + await Weave.Service.startOver(); + await lazy.fxAccounts.signOut(); + // Sync may have been disabled if we santized, so re-enable it now or + // else the user will be unable to resync should they sign in before a + // restart. + Weave.Service.enabled = true; + + // and finally, if we managed to get the lock before, we should unlock it + // now. + if (shouldUnlock) { + Weave.Service.unlock(); + } + }, + + // Start the sanitization process. Returns a promise that resolves when + // the sanitize is complete, and an AbortController which can be used to + // abort the process of waiting for a sync to complete. + async _startDisconnect(abortController, sanitizeData = false) { + // This is a bit convoluted - we want to wait for a sync to finish before + // sanitizing, but want to abort that wait if the browser shuts down while + // we are waiting (in which case we'll charge ahead anyway). + // So we do this by using an AbortController and passing that to the + // function that waits for the sync lock - it will immediately resolve + // if the abort controller is aborted. + let log = lazy.Log.repository.getLogger("Sync.Service"); + + // If the master-password is locked then we will fail to fully sanitize, + // so prompt for that now. If canceled, we just abort now. + log.info("checking master-password state"); + if (!lazy.Utils.ensureMPUnlocked()) { + log.warn( + "The master-password needs to be unlocked to fully disconnect from sync" + ); + return; + } + + log.info("waiting for any existing syncs to complete"); + let locked = await this.promiseNotSyncing(abortController); + + if (sanitizeData) { + await this.doSanitizeSyncData(); + + // We disconnect before sanitizing the browser data - in a worst-case + // scenario where the sanitize takes so long that even the shutdown + // blocker doesn't allow it to finish, we should still at least be in + // a disconnected state on the next startup. + log.info("disconnecting account"); + await this.doSyncAndAccountDisconnect(locked); + + await this.doSanitizeBrowserData(); + } else { + log.info("disconnecting account"); + await this.doSyncAndAccountDisconnect(locked); + } + }, + + async disconnect(sanitizeData) { + if (this.promiseDisconnectFinished) { + throw new Error("A disconnect is already in progress"); + } + let abortController = new AbortController(); + let promiseDisconnectFinished = this._startDisconnect( + abortController, + sanitizeData + ); + this.promiseDisconnectFinished = promiseDisconnectFinished; + let shutdownBlocker = () => { + // oh dear - we are sanitizing (probably stuck waiting for a sync to + // complete) and the browser is shutting down. Let's avoid the wait + // for sync to complete and continue the process anyway. + abortController.abort(); + return promiseDisconnectFinished; + }; + lazy.AsyncShutdown.quitApplicationGranted.addBlocker( + "SyncDisconnect: removing requested data", + shutdownBlocker + ); + + // wait for it to finish - hopefully without the blocker being called. + await promiseDisconnectFinished; + this.promiseDisconnectFinished = null; + + // sanitize worked so remove our blocker - it's a noop if the blocker + // did call us. + lazy.AsyncShutdown.quitApplicationGranted.removeBlocker(shutdownBlocker); + }, +}; + +export const SyncDisconnect = { + get promiseDisconnectFinished() { + return SyncDisconnectInternal.promiseDisconnectFinished; + }, + + disconnect(sanitizeData) { + return SyncDisconnectInternal.disconnect(sanitizeData); + }, +}; diff --git a/services/sync/modules/SyncedTabs.sys.mjs b/services/sync/modules/SyncedTabs.sys.mjs new file mode 100644 index 0000000000..410244413e --- /dev/null +++ b/services/sync/modules/SyncedTabs.sys.mjs @@ -0,0 +1,348 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CLIENT_NOT_CONFIGURED: "resource://services-sync/constants.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", +}); + +// The Sync XPCOM service +ChromeUtils.defineLazyGetter(lazy, "weaveXPCService", function () { + return Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; +}); + +// from MDN... +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +// A topic we fire whenever we have new tabs available. This might be due +// to a request made by this module to refresh the tab list, or as the result +// of a regularly scheduled sync. The intent is that consumers just listen +// for this notification and update their UI in response. +const TOPIC_TABS_CHANGED = "services.sync.tabs.changed"; + +// The interval, in seconds, before which we consider the existing list +// of tabs "fresh enough" and don't force a new sync. +const TABS_FRESH_ENOUGH_INTERVAL_SECONDS = 30; + +ChromeUtils.defineLazyGetter(lazy, "log", () => { + const { Log } = ChromeUtils.importESModule( + "resource://gre/modules/Log.sys.mjs" + ); + let log = Log.repository.getLogger("Sync.RemoteTabs"); + log.manageLevelFromPref("services.sync.log.logger.tabs"); + return log; +}); + +// A private singleton that does the work. +let SyncedTabsInternal = { + /* Make a "tab" record. Returns a promise */ + async _makeTab(client, tab, url, showRemoteIcons) { + let icon; + if (showRemoteIcons) { + icon = tab.icon; + } + if (!icon) { + // By not specifying a size the favicon service will pick the default, + // that is usually set through setDefaultIconURIPreferredSize by the + // first browser window. Commonly it's 16px at current dpi. + icon = "page-icon:" + url; + } + return { + type: "tab", + title: tab.title || url, + url, + icon, + client: client.id, + lastUsed: tab.lastUsed, + inactive: tab.inactive, + }; + }, + + /* Make a "client" record. Returns a promise for consistency with _makeTab */ + async _makeClient(client) { + return { + id: client.id, + type: "client", + name: lazy.Weave.Service.clientsEngine.getClientName(client.id), + clientType: lazy.Weave.Service.clientsEngine.getClientType(client.id), + lastModified: client.lastModified * 1000, // sec to ms + tabs: [], + }; + }, + + _tabMatchesFilter(tab, filter) { + let reFilter = new RegExp(escapeRegExp(filter), "i"); + return reFilter.test(tab.url) || reFilter.test(tab.title); + }, + + _createRecentTabsList( + clients, + maxCount, + extraParams = { removeAllDupes: true, removeDeviceDupes: false } + ) { + let tabs = []; + + for (let client of clients) { + if (extraParams.removeDeviceDupes) { + client.tabs = this._filterRecentTabsDupes(client.tabs); + } + for (let tab of client.tabs) { + tab.device = client.name; + tab.deviceType = client.clientType; + } + tabs = [...tabs, ...client.tabs.reverse()]; + } + if (extraParams.removeAllDupes) { + tabs = this._filterRecentTabsDupes(tabs); + } + tabs = tabs.sort((a, b) => b.lastUsed - a.lastUsed).slice(0, maxCount); + return tabs; + }, + + _filterRecentTabsDupes(tabs) { + // Filter out any tabs with duplicate URLs preserving + // the duplicate with the most recent lastUsed value + return tabs.filter(tab => { + return !tabs.some(t => { + return t.url === tab.url && tab.lastUsed < t.lastUsed; + }); + }); + }, + + async getTabClients(filter) { + lazy.log.info("Generating tab list with filter", filter); + let result = []; + + // If Sync isn't ready, don't try and get anything. + if (!lazy.weaveXPCService.ready) { + lazy.log.debug("Sync isn't yet ready, so returning an empty tab list"); + return result; + } + + // A boolean that controls whether we should show the icon from the remote tab. + const showRemoteIcons = Services.prefs.getBoolPref( + "services.sync.syncedTabs.showRemoteIcons", + true + ); + + let engine = lazy.Weave.Service.engineManager.get("tabs"); + + let ntabs = 0; + let clientTabList = await engine.getAllClients(); + for (let client of clientTabList) { + if (!lazy.Weave.Service.clientsEngine.remoteClientExists(client.id)) { + continue; + } + let clientRepr = await this._makeClient(client); + lazy.log.debug("Processing client", clientRepr); + + for (let tab of client.tabs) { + let url = tab.urlHistory[0]; + lazy.log.trace("remote tab", url); + + if (!url) { + continue; + } + let tabRepr = await this._makeTab(client, tab, url, showRemoteIcons); + if (filter && !this._tabMatchesFilter(tabRepr, filter)) { + continue; + } + clientRepr.tabs.push(tabRepr); + } + // We return all clients, even those without tabs - the consumer should + // filter it if they care. + ntabs += clientRepr.tabs.length; + result.push(clientRepr); + } + lazy.log.info( + `Final tab list has ${result.length} clients with ${ntabs} tabs.` + ); + return result; + }, + + async syncTabs(force) { + if (!force) { + // Don't bother refetching tabs if we already did so recently + let lastFetch = Services.prefs.getIntPref( + "services.sync.lastTabFetch", + 0 + ); + let now = Math.floor(Date.now() / 1000); + if (now - lastFetch < TABS_FRESH_ENOUGH_INTERVAL_SECONDS) { + lazy.log.info("_refetchTabs was done recently, do not doing it again"); + return false; + } + } + + // If Sync isn't configured don't try and sync, else we will get reports + // of a login failure. + if (lazy.Weave.Status.checkSetup() === lazy.CLIENT_NOT_CONFIGURED) { + lazy.log.info( + "Sync client is not configured, so not attempting a tab sync" + ); + return false; + } + // If the primary pass is locked, we should not try to sync + if (lazy.Weave.Utils.mpLocked()) { + lazy.log.info( + "Can't sync tabs due to the primary password being locked", + lazy.Weave.Status.login + ); + return false; + } + // Ask Sync to just do the tabs engine if it can. + try { + lazy.log.info("Doing a tab sync."); + await lazy.Weave.Service.sync({ why: "tabs", engines: ["tabs"] }); + return true; + } catch (ex) { + lazy.log.error("Sync failed", ex); + throw ex; + } + }, + + observe(subject, topic, data) { + lazy.log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`); + switch (topic) { + case "weave:engine:sync:finish": + if (data != "tabs") { + return; + } + // The tabs engine just finished syncing + // Set our lastTabFetch pref here so it tracks both explicit sync calls + // and normally scheduled ones. + Services.prefs.setIntPref( + "services.sync.lastTabFetch", + Math.floor(Date.now() / 1000) + ); + Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED); + break; + case "weave:service:start-over": + // start-over needs to notify so consumers find no tabs. + Services.prefs.clearUserPref("services.sync.lastTabFetch"); + Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED); + break; + case "nsPref:changed": + Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED); + break; + default: + break; + } + }, + + // Returns true if Sync is configured to Sync tabs, false otherwise + get isConfiguredToSyncTabs() { + if (!lazy.weaveXPCService.ready) { + lazy.log.debug("Sync isn't yet ready; assuming tab engine is enabled"); + return true; + } + + let engine = lazy.Weave.Service.engineManager.get("tabs"); + return engine && engine.enabled; + }, + + get hasSyncedThisSession() { + let engine = lazy.Weave.Service.engineManager.get("tabs"); + return engine && engine.hasSyncedThisSession; + }, +}; + +Services.obs.addObserver(SyncedTabsInternal, "weave:engine:sync:finish"); +Services.obs.addObserver(SyncedTabsInternal, "weave:service:start-over"); +// Observe the pref the indicates the state of the tabs engine has changed. +// This will force consumers to re-evaluate the state of sync and update +// accordingly. +Services.prefs.addObserver("services.sync.engine.tabs", SyncedTabsInternal); + +// The public interface. +export var SyncedTabs = { + // A mock-point for tests. + _internal: SyncedTabsInternal, + + // We make the topic for the observer notification public. + TOPIC_TABS_CHANGED, + + // Expose the interval used to determine if synced tabs data needs a new sync + TABS_FRESH_ENOUGH_INTERVAL_SECONDS, + + // Returns true if Sync is configured to Sync tabs, false otherwise + get isConfiguredToSyncTabs() { + return this._internal.isConfiguredToSyncTabs; + }, + + // Returns true if a tab sync has completed once this session. If this + // returns false, then getting back no clients/tabs possibly just means we + // are waiting for that first sync to complete. + get hasSyncedThisSession() { + return this._internal.hasSyncedThisSession; + }, + + // Return a promise that resolves with an array of client records, each with + // a .tabs array. Note that part of the contract for this module is that the + // returned objects are not shared between invocations, so callers are free + // to mutate the returned objects (eg, sort, truncate) however they see fit. + getTabClients(query) { + return this._internal.getTabClients(query); + }, + + // Starts a background request to start syncing tabs. Returns a promise that + // resolves when the sync is complete, but there's no resolved value - + // callers should be listening for TOPIC_TABS_CHANGED. + // If |force| is true we always sync. If false, we only sync if the most + // recent sync wasn't "recently". + syncTabs(force) { + return this._internal.syncTabs(force); + }, + + createRecentTabsList(clients, maxCount, extraParams) { + return this._internal._createRecentTabsList(clients, maxCount, extraParams); + }, + + sortTabClientsByLastUsed(clients) { + // First sort the list of tabs for each client. Note that + // this module promises that the objects it returns are never + // shared, so we are free to mutate those objects directly. + for (let client of clients) { + let tabs = client.tabs; + tabs.sort((a, b) => b.lastUsed - a.lastUsed); + } + // Now sort the clients - the clients are sorted in the order of the + // most recent tab for that client (ie, it is important the tabs for + // each client are already sorted.) + clients.sort((a, b) => { + if (!a.tabs.length) { + return 1; // b comes first. + } + if (!b.tabs.length) { + return -1; // a comes first. + } + return b.tabs[0].lastUsed - a.tabs[0].lastUsed; + }); + }, + + recordSyncedTabsTelemetry(object, tabEvent, extraOptions) { + Services.telemetry.setEventRecordingEnabled("synced_tabs", true); + Services.telemetry.recordEvent( + "synced_tabs", + tabEvent, + object, + null, + extraOptions + ); + }, + + // Get list of synced tabs across all devices/clients + // truncated by value of maxCount param, sorted by + // lastUsed value, and filtered for duplicate URLs + async getRecentTabs(maxCount, extraParams) { + let clients = await this.getTabClients(); + return this._internal._createRecentTabsList(clients, maxCount, extraParams); + }, +}; diff --git a/services/sync/modules/UIState.sys.mjs b/services/sync/modules/UIState.sys.mjs new file mode 100644 index 0000000000..8981d81f7d --- /dev/null +++ b/services/sync/modules/UIState.sys.mjs @@ -0,0 +1,285 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * @typedef {Object} UIState + * @property {string} status The Sync/FxA status, see STATUS_* constants. + * @property {string} [email] The FxA email configured to log-in with Sync. + * @property {string} [displayName] The user's FxA display name. + * @property {string} [avatarURL] The user's FxA avatar URL. + * @property {Date} [lastSync] The last sync time. + * @property {boolean} [syncing] Whether or not we are currently syncing. + */ + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + LOGIN_FAILED_LOGIN_REJECTED: "resource://services-sync/constants.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", +}); + +const TOPICS = [ + "weave:connected", + "weave:service:login:got-hashed-id", + "weave:service:login:error", + "weave:service:ready", + "weave:service:sync:start", + "weave:service:sync:finish", + "weave:service:sync:error", + "weave:service:start-over:finish", + "fxaccounts:onverified", + "fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive. + "fxaccounts:onlogout", + "fxaccounts:profilechange", + "fxaccounts:statechange", +]; + +const ON_UPDATE = "sync-ui-state:update"; + +const STATUS_NOT_CONFIGURED = "not_configured"; +const STATUS_LOGIN_FAILED = "login_failed"; +const STATUS_NOT_VERIFIED = "not_verified"; +const STATUS_SIGNED_IN = "signed_in"; + +const DEFAULT_STATE = { + status: STATUS_NOT_CONFIGURED, +}; + +const UIStateInternal = { + _initialized: false, + _state: null, + + // We keep _syncing out of the state object because we can only track it + // using sync events and we can't determine it at any point in time. + _syncing: false, + + get state() { + if (!this._state) { + return DEFAULT_STATE; + } + return Object.assign({}, this._state, { syncing: this._syncing }); + }, + + isReady() { + if (!this._initialized) { + this.init(); + return false; + } + return true; + }, + + init() { + this._initialized = true; + // Because the FxA toolbar is usually visible, this module gets loaded at + // browser startup, and we want to avoid pulling in all of FxA or Sync at + // that time, so we refresh the state after the browser has settled. + Services.tm.idleDispatchToMainThread(() => { + this.refreshState().catch(e => { + console.error(e); + }); + }, 2000); + }, + + // Used for testing. + reset() { + this._state = null; + this._syncing = false; + this._initialized = false; + }, + + observe(subject, topic, data) { + switch (topic) { + case "weave:service:sync:start": + this.toggleSyncActivity(true); + break; + case "weave:service:sync:finish": + case "weave:service:sync:error": + this.toggleSyncActivity(false); + break; + default: + this.refreshState().catch(e => { + console.error(e); + }); + break; + } + }, + + // Builds a new state from scratch. + async refreshState() { + const newState = {}; + await this._refreshFxAState(newState); + // Optimize the "not signed in" case to avoid refreshing twice just after + // startup - if there's currently no _state, and we still aren't configured, + // just early exit. + if (this._state == null && newState.status == DEFAULT_STATE.status) { + return this.state; + } + if (newState.syncEnabled) { + this._setLastSyncTime(newState); // We want this in case we change accounts. + } + this._state = newState; + + this.notifyStateUpdated(); + return this.state; + }, + + // Update the current state with the last sync time/currently syncing status. + toggleSyncActivity(syncing) { + this._syncing = syncing; + this._setLastSyncTime(this._state); + + this.notifyStateUpdated(); + }, + + notifyStateUpdated() { + Services.obs.notifyObservers(null, ON_UPDATE); + }, + + async _refreshFxAState(newState) { + let userData = await this._getUserData(); + await this._populateWithUserData(newState, userData); + }, + + async _populateWithUserData(state, userData) { + let status; + let syncUserName = Services.prefs.getStringPref( + "services.sync.username", + "" + ); + if (!userData) { + // If Sync thinks it is configured but there's no FxA user, then we + // want to enter the "login failed" state so the user can get + // reconfigured. + if (syncUserName) { + state.email = syncUserName; + status = STATUS_LOGIN_FAILED; + } else { + // everyone agrees nothing is configured. + status = STATUS_NOT_CONFIGURED; + } + } else { + let loginFailed = await this._loginFailed(); + if (loginFailed) { + status = STATUS_LOGIN_FAILED; + } else if (!userData.verified) { + status = STATUS_NOT_VERIFIED; + } else { + status = STATUS_SIGNED_IN; + } + state.uid = userData.uid; + state.email = userData.email; + state.displayName = userData.displayName; + // for better or worse, this module renames these attribues. + state.avatarURL = userData.avatar; + state.avatarIsDefault = userData.avatarDefault; + state.syncEnabled = !!syncUserName; + } + state.status = status; + }, + + async _getUserData() { + try { + return await this.fxAccounts.getSignedInUser(); + } catch (e) { + // This is most likely in tests, where we quickly log users in and out. + // The most likely scenario is a user logged out, so reflect that. + // Bug 995134 calls for better errors so we could retry if we were + // sure this was the failure reason. + console.error("Error updating FxA account info:", e); + return null; + } + }, + + _setLastSyncTime(state) { + if (state?.status == UIState.STATUS_SIGNED_IN) { + const lastSync = Services.prefs.getStringPref( + "services.sync.lastSync", + null + ); + state.lastSync = lastSync ? new Date(lastSync) : null; + } + }, + + async _loginFailed() { + // First ask FxA if it thinks the user needs re-authentication. In practice, + // this check is probably canonical (ie, we probably don't really need + // the check below at all as we drop local session info on the first sign + // of a problem) - but we keep it for now to keep the risk down. + let hasLocalSession = await this.fxAccounts.hasLocalSession(); + if (!hasLocalSession) { + return true; + } + + // Referencing Weave.Service will implicitly initialize sync, and we don't + // want to force that - so first check if it is ready. + let service = Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + if (!service.ready) { + return false; + } + // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in". + // All other login failures are assumed to be transient and should go + // away by themselves, so aren't reflected here. + return lazy.Weave.Status.login == lazy.LOGIN_FAILED_LOGIN_REJECTED; + }, + + set fxAccounts(mockFxAccounts) { + delete this.fxAccounts; + this.fxAccounts = mockFxAccounts; + }, +}; + +ChromeUtils.defineLazyGetter(UIStateInternal, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +for (let topic of TOPICS) { + Services.obs.addObserver(UIStateInternal, topic); +} + +export var UIState = { + _internal: UIStateInternal, + + ON_UPDATE, + + STATUS_NOT_CONFIGURED, + STATUS_LOGIN_FAILED, + STATUS_NOT_VERIFIED, + STATUS_SIGNED_IN, + + /** + * Returns true if the module has been initialized and the state set. + * If not, return false and trigger an init in the background. + */ + isReady() { + return this._internal.isReady(); + }, + + /** + * @returns {UIState} The current Sync/FxA UI State. + */ + get() { + return this._internal.state; + }, + + /** + * Refresh the state. Used for testing, don't call this directly since + * UIState already listens to Sync/FxA notifications to determine if the state + * needs to be refreshed. ON_UPDATE will be fired once the state is refreshed. + * + * @returns {Promise<UIState>} Resolved once the state is refreshed. + */ + refresh() { + return this._internal.refreshState(); + }, + + /** + * Reset the state of the whole module. Used for testing. + */ + reset() { + this._internal.reset(); + }, +}; diff --git a/services/sync/modules/addonsreconciler.sys.mjs b/services/sync/modules/addonsreconciler.sys.mjs new file mode 100644 index 0000000000..902f57348e --- /dev/null +++ b/services/sync/modules/addonsreconciler.sys.mjs @@ -0,0 +1,584 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains middleware to reconcile state of AddonManager for + * purposes of tracking events for Sync. The content in this file exists + * because AddonManager does not have a getChangesSinceX() API and adding + * that functionality properly was deemed too time-consuming at the time + * add-on sync was originally written. If/when AddonManager adds this API, + * this file can go away and the add-ons engine can be rewritten to use it. + * + * It was decided to have this tracking functionality exist in a separate + * standalone file so it could be more easily understood, tested, and + * hopefully ported. + */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs"; + +const DEFAULT_STATE_FILE = "addonsreconciler"; + +export var CHANGE_INSTALLED = 1; +export var CHANGE_UNINSTALLED = 2; +export var CHANGE_ENABLED = 3; +export var CHANGE_DISABLED = 4; + +/** + * Maintains state of add-ons. + * + * State is maintained in 2 data structures, an object mapping add-on IDs + * to metadata and an array of changes over time. The object mapping can be + * thought of as a minimal copy of data from AddonManager which is needed for + * Sync. The array is effectively a log of changes over time. + * + * The data structures are persisted to disk by serializing to a JSON file in + * the current profile. The data structures are updated by 2 mechanisms. First, + * they can be refreshed from the global state of the AddonManager. This is a + * sure-fire way of ensuring the reconciler is up to date. Second, the + * reconciler adds itself as an AddonManager listener. When it receives change + * notifications, it updates its internal state incrementally. + * + * The internal state is persisted to a JSON file in the profile directory. + * + * An instance of this is bound to an AddonsEngine instance. In reality, it + * likely exists as a singleton. To AddonsEngine, it functions as a store and + * an entity which emits events for tracking. + * + * The usage pattern for instances of this class is: + * + * let reconciler = new AddonsReconciler(...); + * await reconciler.ensureStateLoaded(); + * + * // At this point, your instance should be ready to use. + * + * When you are finished with the instance, please call: + * + * reconciler.stopListening(); + * await reconciler.saveState(...); + * + * This class uses the AddonManager AddonListener interface. + * When an add-on is installed, listeners are called in the following order: + * AL.onInstalling, AL.onInstalled + * + * For uninstalls, we see AL.onUninstalling then AL.onUninstalled. + * + * Enabling and disabling work by sending: + * + * AL.onEnabling, AL.onEnabled + * AL.onDisabling, AL.onDisabled + * + * Actions can be undone. All undoable actions notify the same + * AL.onOperationCancelled event. We treat this event like any other. + * + * When an add-on is uninstalled from about:addons, the user is offered an + * "Undo" option, which leads to the following sequence of events as + * observed by an AddonListener: + * Add-ons are first disabled then they are actually uninstalled. So, we will + * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled + * events only come after the Addon Manager is closed or another view is + * switched to. In the case of Sync performing the uninstall, the uninstall + * events will occur immediately. However, we still see disabling events and + * heed them like they were normal. In the end, the state is proper. + */ +export function AddonsReconciler(queueCaller) { + this._log = Log.repository.getLogger("Sync.AddonsReconciler"); + this._log.manageLevelFromPref("services.sync.log.logger.addonsreconciler"); + this.queueCaller = queueCaller; + + Svc.Obs.add("xpcom-shutdown", this.stopListening, this); +} + +AddonsReconciler.prototype = { + /** Flag indicating whether we are listening to AddonManager events. */ + _listening: false, + + /** + * Define this as false if the reconciler should not persist state + * to disk when handling events. + * + * This allows test code to avoid spinning to write during observer + * notifications and xpcom shutdown, which appears to cause hangs on WinXP + * (Bug 873861). + */ + _shouldPersist: true, + + /** Log logger instance */ + _log: null, + + /** + * Container for add-on metadata. + * + * Keys are add-on IDs. Values are objects which describe the state of the + * add-on. This is a minimal mirror of data that can be queried from + * AddonManager. In some cases, we retain data longer than AddonManager. + */ + _addons: {}, + + /** + * List of add-on changes over time. + * + * Each element is an array of [time, change, id]. + */ + _changes: [], + + /** + * Objects subscribed to changes made to this instance. + */ + _listeners: [], + + /** + * Accessor for add-ons in this object. + * + * Returns an object mapping add-on IDs to objects containing metadata. + */ + get addons() { + return this._addons; + }, + + async ensureStateLoaded() { + if (!this._promiseStateLoaded) { + this._promiseStateLoaded = this.loadState(); + } + return this._promiseStateLoaded; + }, + + /** + * Load reconciler state from a file. + * + * The path is relative to the weave directory in the profile. If no + * path is given, the default one is used. + * + * If the file does not exist or there was an error parsing the file, the + * state will be transparently defined as empty. + * + * @param file + * Path to load. ".json" is appended automatically. If not defined, + * a default path will be consulted. + */ + async loadState(file = DEFAULT_STATE_FILE) { + let json = await Utils.jsonLoad(file, this); + this._addons = {}; + this._changes = []; + + if (!json) { + this._log.debug("No data seen in loaded file: " + file); + return false; + } + + let version = json.version; + if (!version || version != 1) { + this._log.error( + "Could not load JSON file because version not " + + "supported: " + + version + ); + return false; + } + + this._addons = json.addons; + for (let id in this._addons) { + let record = this._addons[id]; + record.modified = new Date(record.modified); + } + + for (let [time, change, id] of json.changes) { + this._changes.push([new Date(time), change, id]); + } + + return true; + }, + + /** + * Saves the current state to a file in the local profile. + * + * @param file + * String path in profile to save to. If not defined, the default + * will be used. + */ + async saveState(file = DEFAULT_STATE_FILE) { + let state = { version: 1, addons: {}, changes: [] }; + + for (let [id, record] of Object.entries(this._addons)) { + state.addons[id] = {}; + for (let [k, v] of Object.entries(record)) { + if (k == "modified") { + state.addons[id][k] = v.getTime(); + } else { + state.addons[id][k] = v; + } + } + } + + for (let [time, change, id] of this._changes) { + state.changes.push([time.getTime(), change, id]); + } + + this._log.info("Saving reconciler state to file: " + file); + await Utils.jsonSave(file, this, state); + }, + + /** + * Registers a change listener with this instance. + * + * Change listeners are called every time a change is recorded. The listener + * is an object with the function "changeListener" that takes 3 arguments, + * the Date at which the change happened, the type of change (a CHANGE_* + * constant), and the add-on state object reflecting the current state of + * the add-on at the time of the change. + * + * @param listener + * Object containing changeListener function. + */ + addChangeListener: function addChangeListener(listener) { + if (!this._listeners.includes(listener)) { + this._log.debug("Adding change listener."); + this._listeners.push(listener); + } + }, + + /** + * Removes a previously-installed change listener from the instance. + * + * @param listener + * Listener instance to remove. + */ + removeChangeListener: function removeChangeListener(listener) { + this._listeners = this._listeners.filter(element => { + if (element == listener) { + this._log.debug("Removing change listener."); + return false; + } + return true; + }); + }, + + /** + * Tells the instance to start listening for AddonManager changes. + * + * This is typically called automatically when Sync is loaded. + */ + startListening: function startListening() { + if (this._listening) { + return; + } + + this._log.info("Registering as Add-on Manager listener."); + AddonManager.addAddonListener(this); + this._listening = true; + }, + + /** + * Tells the instance to stop listening for AddonManager changes. + * + * The reconciler should always be listening. This should only be called when + * the instance is being destroyed. + * + * This function will get called automatically on XPCOM shutdown. However, it + * is a best practice to call it yourself. + */ + stopListening: function stopListening() { + if (!this._listening) { + return; + } + + this._log.debug("Stopping listening and removing AddonManager listener."); + AddonManager.removeAddonListener(this); + this._listening = false; + }, + + /** + * Refreshes the global state of add-ons by querying the AddonManager. + */ + async refreshGlobalState() { + this._log.info("Refreshing global state from AddonManager."); + + let installs; + let addons = await AddonManager.getAllAddons(); + + let ids = {}; + + for (let addon of addons) { + ids[addon.id] = true; + await this.rectifyStateFromAddon(addon); + } + + // Look for locally-defined add-ons that no longer exist and update their + // record. + for (let [id, addon] of Object.entries(this._addons)) { + if (id in ids) { + continue; + } + + // If the id isn't in ids, it means that the add-on has been deleted or + // the add-on is in the process of being installed. We detect the + // latter by seeing if an AddonInstall is found for this add-on. + + if (!installs) { + installs = await AddonManager.getAllInstalls(); + } + + let installFound = false; + for (let install of installs) { + if ( + install.addon && + install.addon.id == id && + install.state == AddonManager.STATE_INSTALLED + ) { + installFound = true; + break; + } + } + + if (installFound) { + continue; + } + + if (addon.installed) { + addon.installed = false; + this._log.debug( + "Adding change because add-on not present in " + + "Add-on Manager: " + + id + ); + await this._addChange(new Date(), CHANGE_UNINSTALLED, addon); + } + } + + // See note for _shouldPersist. + if (this._shouldPersist) { + await this.saveState(); + } + }, + + /** + * Rectifies the state of an add-on from an Addon instance. + * + * This basically says "given an Addon instance, assume it is truth and + * apply changes to the local state to reflect it." + * + * This function could result in change listeners being called if the local + * state differs from the passed add-on's state. + * + * @param addon + * Addon instance being updated. + */ + async rectifyStateFromAddon(addon) { + this._log.debug( + `Rectifying state for addon ${addon.name} (version=${addon.version}, id=${addon.id})` + ); + + let id = addon.id; + let enabled = !addon.userDisabled; + let guid = addon.syncGUID; + let now = new Date(); + + if (!(id in this._addons)) { + let record = { + id, + guid, + enabled, + installed: true, + modified: now, + type: addon.type, + scope: addon.scope, + foreignInstall: addon.foreignInstall, + isSyncable: addon.isSyncable, + }; + this._addons[id] = record; + this._log.debug( + "Adding change because add-on not present locally: " + id + ); + await this._addChange(now, CHANGE_INSTALLED, record); + return; + } + + let record = this._addons[id]; + record.isSyncable = addon.isSyncable; + + if (!record.installed) { + // It is possible the record is marked as uninstalled because an + // uninstall is pending. + if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) { + record.installed = true; + record.modified = now; + } + } + + if (record.enabled != enabled) { + record.enabled = enabled; + record.modified = now; + let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED; + this._log.debug("Adding change because enabled state changed: " + id); + await this._addChange(new Date(), change, record); + } + + if (record.guid != guid) { + record.guid = guid; + // We don't record a change because the Sync engine rectifies this on its + // own. This is tightly coupled with Sync. If this code is ever lifted + // outside of Sync, this exception should likely be removed. + } + }, + + /** + * Record a change in add-on state. + * + * @param date + * Date at which the change occurred. + * @param change + * The type of the change. A CHANGE_* constant. + * @param state + * The new state of the add-on. From this.addons. + */ + async _addChange(date, change, state) { + this._log.info("Change recorded for " + state.id); + this._changes.push([date, change, state.id]); + + for (let listener of this._listeners) { + try { + await listener.changeListener(date, change, state); + } catch (ex) { + this._log.error("Exception calling change listener", ex); + } + } + }, + + /** + * Obtain the set of changes to add-ons since the date passed. + * + * This will return an array of arrays. Each entry in the array has the + * elements [date, change_type, id], where + * + * date - Date instance representing when the change occurred. + * change_type - One of CHANGE_* constants. + * id - ID of add-on that changed. + */ + getChangesSinceDate(date) { + let length = this._changes.length; + for (let i = 0; i < length; i++) { + if (this._changes[i][0] >= date) { + return this._changes.slice(i); + } + } + + return []; + }, + + /** + * Prunes all recorded changes from before the specified Date. + * + * @param date + * Entries older than this Date will be removed. + */ + pruneChangesBeforeDate(date) { + this._changes = this._changes.filter(function test_age(change) { + return change[0] >= date; + }); + }, + + /** + * Obtains the set of all known Sync GUIDs for add-ons. + */ + getAllSyncGUIDs() { + let result = {}; + for (let id in this.addons) { + result[id] = true; + } + + return result; + }, + + /** + * Obtain the add-on state record for an add-on by Sync GUID. + * + * If the add-on could not be found, returns null. + * + * @param guid + * Sync GUID of add-on to retrieve. + */ + getAddonStateFromSyncGUID(guid) { + for (let id in this.addons) { + let addon = this.addons[id]; + if (addon.guid == guid) { + return addon; + } + } + + return null; + }, + + /** + * Handler that is invoked as part of the AddonManager listeners. + */ + async _handleListener(action, addon) { + // Since this is called as an observer, we explicitly trap errors and + // log them to ourselves so we don't see errors reported elsewhere. + try { + let id = addon.id; + this._log.debug("Add-on change: " + action + " to " + id); + + switch (action) { + case "onEnabled": + case "onDisabled": + case "onInstalled": + case "onInstallEnded": + case "onOperationCancelled": + await this.rectifyStateFromAddon(addon); + break; + + case "onUninstalled": + let id = addon.id; + let addons = this.addons; + if (id in addons) { + let now = new Date(); + let record = addons[id]; + record.installed = false; + record.modified = now; + this._log.debug( + "Adding change because of uninstall listener: " + id + ); + await this._addChange(now, CHANGE_UNINSTALLED, record); + } + } + + // See note for _shouldPersist. + if (this._shouldPersist) { + await this.saveState(); + } + } catch (ex) { + this._log.warn("Exception", ex); + } + }, + + // AddonListeners + onEnabled: function onEnabled(addon) { + this.queueCaller.enqueueCall(() => + this._handleListener("onEnabled", addon) + ); + }, + onDisabled: function onDisabled(addon) { + this.queueCaller.enqueueCall(() => + this._handleListener("onDisabled", addon) + ); + }, + onInstalled: function onInstalled(addon) { + this.queueCaller.enqueueCall(() => + this._handleListener("onInstalled", addon) + ); + }, + onUninstalled: function onUninstalled(addon) { + this.queueCaller.enqueueCall(() => + this._handleListener("onUninstalled", addon) + ); + }, + onOperationCancelled: function onOperationCancelled(addon) { + this.queueCaller.enqueueCall(() => + this._handleListener("onOperationCancelled", addon) + ); + }, +}; diff --git a/services/sync/modules/addonutils.sys.mjs b/services/sync/modules/addonutils.sys.mjs new file mode 100644 index 0000000000..08a8c5b5f0 --- /dev/null +++ b/services/sync/modules/addonutils.sys.mjs @@ -0,0 +1,391 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Svc } from "resource://services-sync/util.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs", +}); + +function AddonUtilsInternal() { + this._log = Log.repository.getLogger("Sync.AddonUtils"); + this._log.Level = + Log.Level[Svc.PrefBranch.getStringPref("log.logger.addonutils", null)]; +} +AddonUtilsInternal.prototype = { + /** + * Obtain an AddonInstall object from an AddonSearchResult instance. + * + * The returned promise will be an AddonInstall on success or null (failure or + * addon not found) + * + * @param addon + * AddonSearchResult to obtain install from. + */ + getInstallFromSearchResult(addon) { + this._log.debug("Obtaining install for " + addon.id); + + // We should theoretically be able to obtain (and use) addon.install if + // it is available. However, the addon.sourceURI rewriting won't be + // reflected in the AddonInstall, so we can't use it. If we ever get rid + // of sourceURI rewriting, we can avoid having to reconstruct the + // AddonInstall. + return lazy.AddonManager.getInstallForURL(addon.sourceURI.spec, { + name: addon.name, + icons: addon.iconURL, + version: addon.version, + telemetryInfo: { source: "sync" }, + }); + }, + + /** + * Installs an add-on from an AddonSearchResult instance. + * + * The options argument defines extra options to control the install. + * Recognized keys in this map are: + * + * syncGUID - Sync GUID to use for the new add-on. + * enabled - Boolean indicating whether the add-on should be enabled upon + * install. + * + * The result object has the following keys: + * + * id ID of add-on that was installed. + * install AddonInstall that was installed. + * addon Addon that was installed. + * + * @param addon + * AddonSearchResult to install add-on from. + * @param options + * Object with additional metadata describing how to install add-on. + */ + async installAddonFromSearchResult(addon, options) { + this._log.info("Trying to install add-on from search result: " + addon.id); + + const install = await this.getInstallFromSearchResult(addon); + if (!install) { + throw new Error("AddonInstall not available: " + addon.id); + } + + try { + this._log.info("Installing " + addon.id); + let log = this._log; + + return new Promise((res, rej) => { + let listener = { + onInstallStarted: function onInstallStarted(install) { + if (!options) { + return; + } + + if (options.syncGUID) { + log.info( + "Setting syncGUID of " + install.name + ": " + options.syncGUID + ); + install.addon.syncGUID = options.syncGUID; + } + + // We only need to change userDisabled if it is disabled because + // enabled is the default. + if ("enabled" in options && !options.enabled) { + log.info( + "Marking add-on as disabled for install: " + install.name + ); + install.addon.disable(); + } + }, + onInstallEnded(install, addon) { + install.removeListener(listener); + + res({ id: addon.id, install, addon }); + }, + onInstallFailed(install) { + install.removeListener(listener); + + rej(new Error("Install failed: " + install.error)); + }, + onDownloadFailed(install) { + install.removeListener(listener); + + rej(new Error("Download failed: " + install.error)); + }, + }; + install.addListener(listener); + install.install(); + }); + } catch (ex) { + this._log.error("Error installing add-on", ex); + throw ex; + } + }, + + /** + * Uninstalls the addon instance. + * + * @param addon + * Addon instance to uninstall. + */ + async uninstallAddon(addon) { + return new Promise(res => { + let listener = { + onUninstalling(uninstalling, needsRestart) { + if (addon.id != uninstalling.id) { + return; + } + + // We assume restartless add-ons will send the onUninstalled event + // soon. + if (!needsRestart) { + return; + } + + // For non-restartless add-ons, we issue the callback on uninstalling + // because we will likely never see the uninstalled event. + lazy.AddonManager.removeAddonListener(listener); + res(addon); + }, + onUninstalled(uninstalled) { + if (addon.id != uninstalled.id) { + return; + } + + lazy.AddonManager.removeAddonListener(listener); + res(addon); + }, + }; + lazy.AddonManager.addAddonListener(listener); + addon.uninstall(); + }); + }, + + /** + * Installs multiple add-ons specified by metadata. + * + * The first argument is an array of objects. Each object must have the + * following keys: + * + * id - public ID of the add-on to install. + * syncGUID - syncGUID for new add-on. + * enabled - boolean indicating whether the add-on should be enabled. + * requireSecureURI - Boolean indicating whether to require a secure + * URI when installing from a remote location. This defaults to + * true. + * + * The callback will be called when activity on all add-ons is complete. The + * callback receives 2 arguments, error and result. + * + * If error is truthy, it contains a string describing the overall error. + * + * The 2nd argument to the callback is always an object with details on the + * overall execution state. It contains the following keys: + * + * installedIDs Array of add-on IDs that were installed. + * installs Array of AddonInstall instances that were installed. + * addons Array of Addon instances that were installed. + * errors Array of errors encountered. Only has elements if error is + * truthy. + * + * @param installs + * Array of objects describing add-ons to install. + */ + async installAddons(installs) { + let ids = []; + for (let addon of installs) { + ids.push(addon.id); + } + + let addons = await lazy.AddonRepository.getAddonsByIDs(ids); + this._log.info( + `Found ${addons.length} / ${ids.length}` + + " add-ons during repository search." + ); + + let ourResult = { + installedIDs: [], + installs: [], + addons: [], + skipped: [], + errors: [], + }; + + let toInstall = []; + + // Rewrite the "src" query string parameter of the source URI to note + // that the add-on was installed by Sync and not something else so + // server-side metrics aren't skewed (bug 708134). The server should + // ideally send proper URLs, but this solution was deemed too + // complicated at the time the functionality was implemented. + for (let addon of addons) { + // Find the specified options for this addon. + let options; + for (let install of installs) { + if (install.id == addon.id) { + options = install; + break; + } + } + if (!this.canInstallAddon(addon, options)) { + ourResult.skipped.push(addon.id); + continue; + } + + // We can go ahead and attempt to install it. + toInstall.push(addon); + + // We should always be able to QI the nsIURI to nsIURL. If not, we + // still try to install the add-on, but we don't rewrite the URL, + // potentially skewing metrics. + try { + addon.sourceURI.QueryInterface(Ci.nsIURL); + } catch (ex) { + this._log.warn( + "Unable to QI sourceURI to nsIURL: " + addon.sourceURI.spec + ); + continue; + } + + let params = addon.sourceURI.query + .split("&") + .map(function rewrite(param) { + if (param.indexOf("src=") == 0) { + return "src=sync"; + } + return param; + }); + + addon.sourceURI = addon.sourceURI + .mutate() + .setQuery(params.join("&")) + .finalize(); + } + + if (!toInstall.length) { + return ourResult; + } + + const installPromises = []; + // Start all the installs asynchronously. They will report back to us + // as they finish, eventually triggering the global callback. + for (let addon of toInstall) { + let options = {}; + for (let install of installs) { + if (install.id == addon.id) { + options = install; + break; + } + } + + installPromises.push( + (async () => { + try { + const result = await this.installAddonFromSearchResult( + addon, + options + ); + ourResult.installedIDs.push(result.id); + ourResult.installs.push(result.install); + ourResult.addons.push(result.addon); + } catch (error) { + ourResult.errors.push(error); + } + })() + ); + } + + await Promise.all(installPromises); + + if (ourResult.errors.length) { + throw new Error("1 or more add-ons failed to install"); + } + return ourResult; + }, + + /** + * Returns true if we are able to install the specified addon, false + * otherwise. It is expected that this will log the reason if it returns + * false. + * + * @param addon + * (Addon) Add-on instance to check. + * @param options + * (object) The options specified for this addon. See installAddons() + * for the valid elements. + */ + canInstallAddon(addon, options) { + // sourceURI presence isn't enforced by AddonRepository. So, we skip + // add-ons without a sourceURI. + if (!addon.sourceURI) { + this._log.info( + "Skipping install of add-on because missing sourceURI: " + addon.id + ); + return false; + } + // Verify that the source URI uses TLS. We don't allow installs from + // insecure sources for security reasons. The Addon Manager ensures + // that cert validation etc is performed. + // (We should also consider just dropping this entirely and calling + // XPIProvider.isInstallAllowed, but that has additional semantics we might + // need to think through...) + let requireSecureURI = true; + if (options && options.requireSecureURI !== undefined) { + requireSecureURI = options.requireSecureURI; + } + + if (requireSecureURI) { + let scheme = addon.sourceURI.scheme; + if (scheme != "https") { + this._log.info( + `Skipping install of add-on "${addon.id}" because sourceURI's scheme of "${scheme}" is not trusted` + ); + return false; + } + } + + // Policy prevents either installing this addon or any addon + if ( + Services.policies && + (!Services.policies.mayInstallAddon(addon) || + !Services.policies.isAllowed("xpinstall")) + ) { + this._log.info( + `Skipping install of "${addon.id}" due to enterprise policy` + ); + return false; + } + + this._log.info(`Add-on "${addon.id}" is able to be installed`); + return true; + }, + + /** + * Update the user disabled flag for an add-on. + * + * If the new flag matches the existing or if the add-on + * isn't currently active, the function will return immediately. + * + * @param addon + * (Addon) Add-on instance to operate on. + * @param value + * (bool) New value for add-on's userDisabled property. + */ + updateUserDisabled(addon, value) { + if (addon.userDisabled == value) { + return; + } + + this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value); + if (value) { + addon.disable(); + } else { + addon.enable(); + } + }, +}; + +export const AddonUtils = new AddonUtilsInternal(); diff --git a/services/sync/modules/bridged_engine.sys.mjs b/services/sync/modules/bridged_engine.sys.mjs new file mode 100644 index 0000000000..45e5f685cd --- /dev/null +++ b/services/sync/modules/bridged_engine.sys.mjs @@ -0,0 +1,499 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file has all the machinery for hooking up bridged engines implemented + * in Rust. It's the JavaScript side of the Golden Gate bridge that connects + * Desktop Sync to a Rust `BridgedEngine`, via the `mozIBridgedSyncEngine` + * XPCOM interface. + * + * Creating a bridged engine only takes a few lines of code, since most of the + * hard work is done on the Rust side. On the JS side, you'll need to subclass + * `BridgedEngine` (instead of `SyncEngine`), supply a `mozIBridgedSyncEngine` + * for your subclass to wrap, and optionally implement and override the tracker. + */ + +import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs"; +import { RawCryptoWrapper } from "resource://services-sync/record.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Log: "resource://gre/modules/Log.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +/** + * A stub store that converts between raw decrypted incoming records and + * envelopes. Since the interface we need is so minimal, this class doesn't + * inherit from the base `Store` implementation...it would take more code to + * override all those behaviors! + * + * This class isn't meant to be subclassed, because bridged engines shouldn't + * override their store classes in `_storeObj`. + */ +class BridgedStore { + constructor(name, engine) { + if (!engine) { + throw new Error("Store must be associated with an Engine instance."); + } + this.engine = engine; + this._log = lazy.Log.repository.getLogger(`Sync.Engine.${name}.Store`); + this._batchChunkSize = 500; + } + + async applyIncomingBatch(records, countTelemetry) { + for (let chunk of lazy.PlacesUtils.chunkArray( + records, + this._batchChunkSize + )) { + let incomingEnvelopesAsJSON = chunk.map(record => + JSON.stringify(record.toIncomingBso()) + ); + this._log.trace("incoming envelopes", incomingEnvelopesAsJSON); + await this.engine._bridge.storeIncoming(incomingEnvelopesAsJSON); + } + // Array of failed records. + return []; + } + + async wipe() { + await this.engine._bridge.wipe(); + } +} + +/** + * A wrapper class to convert between BSOs on the JS side, and envelopes on the + * Rust side. This class intentionally subclasses `RawCryptoWrapper`, because we + * don't want the stringification and parsing machinery in `CryptoWrapper`. + * + * This class isn't meant to be subclassed, because bridged engines shouldn't + * override their record classes in `_recordObj`. + */ +class BridgedRecord extends RawCryptoWrapper { + /** + * Creates an outgoing record from a BSO returned by a bridged engine. + * + * @param {String} collection The collection name. + * @param {Object} bso The outgoing bso (ie, a sync15::bso::OutgoingBso) returned from + * `mozIBridgedSyncEngine::apply`. + * @return {BridgedRecord} A Sync record ready to encrypt and upload. + */ + static fromOutgoingBso(collection, bso) { + // The BSO has already been JSON serialized coming out of Rust, so the + // envelope has been flattened. + if (typeof bso.id != "string") { + throw new TypeError("Outgoing BSO missing ID"); + } + if (typeof bso.payload != "string") { + throw new TypeError("Outgoing BSO missing payload"); + } + let record = new BridgedRecord(collection, bso.id); + record.cleartext = bso.payload; + return record; + } + + transformBeforeEncrypt(cleartext) { + if (typeof cleartext != "string") { + throw new TypeError("Outgoing bridged engine records must be strings"); + } + return cleartext; + } + + transformAfterDecrypt(cleartext) { + if (typeof cleartext != "string") { + throw new TypeError("Incoming bridged engine records must be strings"); + } + return cleartext; + } + + /* + * Converts this incoming record into an envelope to pass to a bridged engine. + * This object must be kept in sync with `sync15::IncomingBso`. + * + * @return {Object} The incoming envelope, to pass to + * `mozIBridgedSyncEngine::storeIncoming`. + */ + toIncomingBso() { + return { + id: this.data.id, + modified: this.data.modified, + payload: this.cleartext, + }; + } +} + +class BridgeError extends Error { + constructor(code, message) { + super(message); + this.name = "BridgeError"; + // TODO: We may want to use a different name for this, since errors with + // a `result` property are treated specially by telemetry, discarding the + // message...but, unlike other `nserror`s, the message is actually useful, + // and we still want to capture it. + this.result = code; + } +} + +class InterruptedError extends Error { + constructor(message) { + super(message); + this.name = "InterruptedError"; + } +} + +/** + * Adapts a `Log.sys.mjs` logger to a `mozIServicesLogSink`. This class is copied + * from `SyncedBookmarksMirror.jsm`. + */ +export class LogAdapter { + constructor(log) { + this.log = log; + } + + get maxLevel() { + let level = this.log.level; + if (level <= lazy.Log.Level.All) { + return Ci.mozIServicesLogSink.LEVEL_TRACE; + } + if (level <= lazy.Log.Level.Info) { + return Ci.mozIServicesLogSink.LEVEL_DEBUG; + } + if (level <= lazy.Log.Level.Warn) { + return Ci.mozIServicesLogSink.LEVEL_WARN; + } + if (level <= lazy.Log.Level.Error) { + return Ci.mozIServicesLogSink.LEVEL_ERROR; + } + return Ci.mozIServicesLogSink.LEVEL_OFF; + } + + trace(message) { + this.log.trace(message); + } + + debug(message) { + this.log.debug(message); + } + + warn(message) { + this.log.warn(message); + } + + error(message) { + this.log.error(message); + } +} + +// This converts the XPCOM-defined, callback-based mozIBridgedSyncEngine to +// a promise-based implementation. +export class BridgeWrapperXPCOM { + constructor(component) { + this.comp = component; + } + + // A few sync, non-callback based attributes. + get storageVersion() { + return this.comp.storageVersion; + } + + get allowSkippedRecord() { + return this.comp.allowSkippedRecord; + } + + get logger() { + return this.comp.logger; + } + + // And the async functions we promisify. + // Note this is `lastSync` via uniffi but `getLastSync` via xpcom + lastSync() { + return BridgeWrapperXPCOM.#promisify(this.comp.getLastSync); + } + + setLastSync(lastSyncMillis) { + return BridgeWrapperXPCOM.#promisify(this.comp.setLastSync, lastSyncMillis); + } + + getSyncId() { + return BridgeWrapperXPCOM.#promisify(this.comp.getSyncId); + } + + resetSyncId() { + return BridgeWrapperXPCOM.#promisify(this.comp.resetSyncId); + } + + ensureCurrentSyncId(newSyncId) { + return BridgeWrapperXPCOM.#promisify( + this.comp.ensureCurrentSyncId, + newSyncId + ); + } + + syncStarted() { + return BridgeWrapperXPCOM.#promisify(this.comp.syncStarted); + } + + storeIncoming(incomingEnvelopesAsJSON) { + return BridgeWrapperXPCOM.#promisify( + this.comp.storeIncoming, + incomingEnvelopesAsJSON + ); + } + + apply() { + return BridgeWrapperXPCOM.#promisify(this.comp.apply); + } + + setUploaded(newTimestampMillis, uploadedIds) { + return BridgeWrapperXPCOM.#promisify( + this.comp.setUploaded, + newTimestampMillis, + uploadedIds + ); + } + + syncFinished() { + return BridgeWrapperXPCOM.#promisify(this.comp.syncFinished); + } + + reset() { + return BridgeWrapperXPCOM.#promisify(this.comp.reset); + } + + wipe() { + return BridgeWrapperXPCOM.#promisify(this.comp.wipe); + } + + // Converts a XPCOM bridged function that takes a callback into one that returns a + // promise. + static #promisify(func, ...params) { + return new Promise((resolve, reject) => { + func(...params, { + // This object implicitly implements all three callback interfaces + // (`mozIBridgedSyncEngine{Apply, Result}Callback`), because they have + // the same methods. The only difference is the type of the argument + // passed to `handleSuccess`, which doesn't matter in JS. + handleSuccess: resolve, + handleError(code, message) { + reject(transformError(code, message)); + }, + }); + }); + } +} + +/** + * A base class used to plug a Rust engine into Sync, and have it work like any + * other engine. The constructor takes a bridge as its first argument, which is + * a "bridged sync engine", as defined by UniFFI in the application-services + * crate. + * For backwards compatibility, this can also be an instance of an XPCOM + * component class that implements `mozIBridgedSyncEngine`, wrapped in + * a `BridgeWrapperXPCOM` wrapper. + * (Note that at time of writing, the above is slightly aspirational; the + * actual definition of the UniFFI shared bridged engine is still in flux.) + * + * This class inherits from `SyncEngine`, which has a lot of machinery that we + * don't need, but that's fairly easy to override. It would be harder to + * reimplement the machinery that we _do_ need here. However, because of that, + * this class has lots of methods that do nothing, or return empty data. The + * docs above each method explain what it's overriding, and why. + * + * This class is designed to be subclassed, but the only part that your engine + * may want to override is `_trackerObj`. Even then, using the default (no-op) + * tracker is fine, because the shape of the `Tracker` interface may not make + * sense for all engines. + */ +export function BridgedEngine(name, service) { + SyncEngine.call(this, name, service); +} + +BridgedEngine.prototype = { + /** + * The Rust implemented bridge. Must be set by the engine which subclasses us. + */ + _bridge: null, + /** + * The tracker class for this engine. Subclasses may want to override this + * with their own tracker, though using the default `Tracker` is fine. + */ + _trackerObj: Tracker, + + /** Returns the record class for all bridged engines. */ + get _recordObj() { + return BridgedRecord; + }, + + set _recordObj(obj) { + throw new TypeError("Don't override the record class for bridged engines"); + }, + + /** Returns the store class for all bridged engines. */ + get _storeObj() { + return BridgedStore; + }, + + set _storeObj(obj) { + throw new TypeError("Don't override the store class for bridged engines"); + }, + + /** Returns the storage version for this engine. */ + get version() { + return this._bridge.storageVersion; + }, + + // Legacy engines allow sync to proceed if some records are too large to + // upload (eg, a payload that's bigger than the server's published limits). + // If this returns true, we will just skip the record without even attempting + // to upload. If this is false, we'll abort the entire batch. + // If the engine allows this, it will need to detect this scenario by noticing + // the ID is not in the 'success' records reported to `setUploaded`. + // (Note that this is not to be confused with the fact server's can currently + // reject records as part of a POST - but we hope to remove this ability from + // the server API. Note also that this is not bullet-proof - if the count of + // records is high, it's possible that we will have committed a previous + // batch before we hit the relevant limits, so things might have been written. + // We hope to fix this by ensuring batch limits are such that this is + // impossible) + get allowSkippedRecord() { + return this._bridge.allowSkippedRecord; + }, + + /** + * Returns the sync ID for this engine. This is exposed for tests, but + * Sync code always calls `resetSyncID()` and `ensureCurrentSyncID()`, + * not this. + * + * @returns {String?} The sync ID, or `null` if one isn't set. + */ + async getSyncID() { + // Note that all methods on an XPCOM class instance are automatically bound, + // so we don't need to write `this._bridge.getSyncId.bind(this._bridge)`. + let syncID = await this._bridge.getSyncId(); + return syncID; + }, + + async resetSyncID() { + await this._deleteServerCollection(); + let newSyncID = await this.resetLocalSyncID(); + return newSyncID; + }, + + async resetLocalSyncID() { + let newSyncID = await this._bridge.resetSyncId(); + return newSyncID; + }, + + async ensureCurrentSyncID(newSyncID) { + let assignedSyncID = await this._bridge.ensureCurrentSyncId(newSyncID); + return assignedSyncID; + }, + + async getLastSync() { + // The bridge defines lastSync as integer ms, but sync itself wants to work + // in a float seconds with 2 decimal places. + let lastSyncMS = await this._bridge.lastSync(); + return Math.round(lastSyncMS / 10) / 100; + }, + + async setLastSync(lastSyncSeconds) { + await this._bridge.setLastSync(Math.round(lastSyncSeconds * 1000)); + }, + + /** + * Returns the initial changeset for the sync. Bridged engines handle + * reconciliation internally, so we don't know what changed until after we've + * stored and applied all incoming records. So we return an empty changeset + * here, and replace it with the real one in `_processIncoming`. + */ + async pullChanges() { + return {}; + }, + + async trackRemainingChanges() { + await this._bridge.syncFinished(); + }, + + /** + * Marks a record for a hard-`DELETE` at the end of the sync. The base method + * also removes it from the tracker, but we don't use the tracker for that, + * so we override the method to just mark. + */ + _deleteId(id) { + this._noteDeletedId(id); + }, + + /** + * Always stage incoming records, bypassing the base engine's reconciliation + * machinery. + */ + async _reconcile() { + return true; + }, + + async _syncStartup() { + await super._syncStartup(); + await this._bridge.syncStarted(); + }, + + async _processIncoming(newitems) { + await super._processIncoming(newitems); + + let outgoingBsosAsJSON = await this._bridge.apply(); + let changeset = {}; + for (let bsoAsJSON of outgoingBsosAsJSON) { + this._log.trace("outgoing bso", bsoAsJSON); + let record = BridgedRecord.fromOutgoingBso( + this.name, + JSON.parse(bsoAsJSON) + ); + changeset[record.id] = { + synced: false, + record, + }; + } + this._modified.replace(changeset); + }, + + /** + * Notify the bridged engine that we've successfully uploaded a batch, so + * that it can update its local state. For example, if the engine uses a + * mirror and a temp table for outgoing records, it can write the uploaded + * records from the outgoing table back to the mirror. + */ + async _onRecordsWritten(succeeded, failed, serverModifiedTime) { + // JS uses seconds but Rust uses milliseconds so we'll need to convert + let serverModifiedMS = Math.round(serverModifiedTime * 1000); + await this._bridge.setUploaded(Math.floor(serverModifiedMS), succeeded); + }, + + async _createTombstone() { + throw new Error("Bridged engines don't support weak uploads"); + }, + + async _createRecord(id) { + let change = this._modified.changes[id]; + if (!change) { + throw new TypeError("Can't create record for unchanged item"); + } + return change.record; + }, + + async _resetClient() { + await super._resetClient(); + await this._bridge.reset(); + }, +}; +Object.setPrototypeOf(BridgedEngine.prototype, SyncEngine.prototype); + +function transformError(code, message) { + switch (code) { + case Cr.NS_ERROR_ABORT: + return new InterruptedError(message); + + default: + return new BridgeError(code, message); + } +} diff --git a/services/sync/modules/collection_validator.sys.mjs b/services/sync/modules/collection_validator.sys.mjs new file mode 100644 index 0000000000..a64ede10e9 --- /dev/null +++ b/services/sync/modules/collection_validator.sys.mjs @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Async: "resource://services-common/async.sys.mjs", +}); + +export class CollectionProblemData { + constructor() { + this.missingIDs = 0; + this.clientDuplicates = []; + this.duplicates = []; + this.clientMissing = []; + this.serverMissing = []; + this.serverDeleted = []; + this.serverUnexpected = []; + this.differences = []; + } + + /** + * Produce a list summarizing problems found. Each entry contains {name, count}, + * where name is the field name for the problem, and count is the number of times + * the problem was encountered. + * + * Validation has failed if all counts are not 0. + */ + getSummary() { + return [ + { name: "clientMissing", count: this.clientMissing.length }, + { name: "serverMissing", count: this.serverMissing.length }, + { name: "serverDeleted", count: this.serverDeleted.length }, + { name: "serverUnexpected", count: this.serverUnexpected.length }, + { name: "differences", count: this.differences.length }, + { name: "missingIDs", count: this.missingIDs }, + { name: "clientDuplicates", count: this.clientDuplicates.length }, + { name: "duplicates", count: this.duplicates.length }, + ]; + } +} + +export class CollectionValidator { + // Construct a generic collection validator. This is intended to be called by + // subclasses. + // - name: Name of the engine + // - idProp: Property that identifies a record. That is, if a client and server + // record have the same value for the idProp property, they should be + // compared against eachother. + // - props: Array of properties that should be compared + constructor(name, idProp, props) { + this.name = name; + this.props = props; + this.idProp = idProp; + + // This property deals with the fact that form history records are never + // deleted from the server. The FormValidator subclass needs to ignore the + // client missing records, and it uses this property to achieve it - + // (Bug 1354016). + this.ignoresMissingClients = false; + } + + // Should a custom ProblemData type be needed, return it here. + emptyProblemData() { + return new CollectionProblemData(); + } + + async getServerItems(engine) { + let collection = engine.itemSource(); + let collectionKey = engine.service.collectionKeys.keyForCollection( + engine.name + ); + collection.full = true; + let result = await collection.getBatched(); + if (!result.response.success) { + throw result.response; + } + let cleartexts = []; + + await lazy.Async.yieldingForEach(result.records, async record => { + await record.decrypt(collectionKey); + cleartexts.push(record.cleartext); + }); + + return cleartexts; + } + + // Should return a promise that resolves to an array of client items. + getClientItems() { + return Promise.reject("Must implement"); + } + + /** + * Can we guarantee validation will fail with a reason that isn't actually a + * problem? For example, if we know there are pending changes left over from + * the last sync, this should resolve to false. By default resolves to true. + */ + async canValidate() { + return true; + } + + // Turn the client item into something that can be compared with the server item, + // and is also safe to mutate. + normalizeClientItem(item) { + return Cu.cloneInto(item, {}); + } + + // Turn the server item into something that can be easily compared with the client + // items. + async normalizeServerItem(item) { + return item; + } + + // Return whether or not a server item should be present on the client. Expected + // to be overridden. + clientUnderstands(item) { + return true; + } + + // Return whether or not a client item should be present on the server. Expected + // to be overridden + async syncedByClient(item) { + return true; + } + + // Compare the server item and the client item, and return a list of property + // names that are different. Can be overridden if needed. + getDifferences(client, server) { + let differences = []; + for (let prop of this.props) { + let clientProp = client[prop]; + let serverProp = server[prop]; + if ((clientProp || "") !== (serverProp || "")) { + differences.push(prop); + } + } + return differences; + } + + // Returns an object containing + // problemData: an instance of the class returned by emptyProblemData(), + // clientRecords: Normalized client records + // records: Normalized server records, + // deletedRecords: Array of ids that were marked as deleted by the server. + async compareClientWithServer(clientItems, serverItems) { + const yieldState = lazy.Async.yieldState(); + + const clientRecords = []; + + await lazy.Async.yieldingForEach( + clientItems, + item => { + clientRecords.push(this.normalizeClientItem(item)); + }, + yieldState + ); + + const serverRecords = []; + await lazy.Async.yieldingForEach( + serverItems, + async item => { + serverRecords.push(await this.normalizeServerItem(item)); + }, + yieldState + ); + + let problems = this.emptyProblemData(); + let seenServer = new Map(); + let serverDeleted = new Set(); + let allRecords = new Map(); + + for (let record of serverRecords) { + let id = record[this.idProp]; + if (!id) { + ++problems.missingIDs; + continue; + } + if (record.deleted) { + serverDeleted.add(record); + } else { + let serverHasPossibleDupe = seenServer.has(id); + if (serverHasPossibleDupe) { + problems.duplicates.push(id); + } else { + seenServer.set(id, record); + allRecords.set(id, { server: record, client: null }); + } + record.understood = this.clientUnderstands(record); + } + } + + let seenClient = new Map(); + for (let record of clientRecords) { + let id = record[this.idProp]; + record.shouldSync = await this.syncedByClient(record); + let clientHasPossibleDupe = seenClient.has(id); + if (clientHasPossibleDupe && record.shouldSync) { + // Only report duplicate client IDs for syncable records. + problems.clientDuplicates.push(id); + continue; + } + seenClient.set(id, record); + let combined = allRecords.get(id); + if (combined) { + combined.client = record; + } else { + allRecords.set(id, { client: record, server: null }); + } + } + + for (let [id, { server, client }] of allRecords) { + if (!client && !server) { + throw new Error("Impossible: no client or server record for " + id); + } else if (server && !client) { + if (!this.ignoresMissingClients && server.understood) { + problems.clientMissing.push(id); + } + } else if (client && !server) { + if (client.shouldSync) { + problems.serverMissing.push(id); + } + } else { + if (!client.shouldSync) { + if (!problems.serverUnexpected.includes(id)) { + problems.serverUnexpected.push(id); + } + continue; + } + let differences = this.getDifferences(client, server); + if (differences && differences.length) { + problems.differences.push({ id, differences }); + } + } + } + return { + problemData: problems, + clientRecords, + records: serverRecords, + deletedRecords: [...serverDeleted], + }; + } + + async validate(engine) { + let start = Cu.now(); + let clientItems = await this.getClientItems(); + let serverItems = await this.getServerItems(engine); + let serverRecordCount = serverItems.length; + let result = await this.compareClientWithServer(clientItems, serverItems); + let end = Cu.now(); + let duration = end - start; + engine._log.debug(`Validated ${this.name} in ${duration}ms`); + engine._log.debug(`Problem summary`); + for (let { name, count } of result.problemData.getSummary()) { + engine._log.debug(` ${name}: ${count}`); + } + return { + duration, + version: this.version, + problems: result.problemData, + recordCount: serverRecordCount, + }; + } +} + +// Default to 0, some engines may override. +CollectionValidator.prototype.version = 0; diff --git a/services/sync/modules/constants.sys.mjs b/services/sync/modules/constants.sys.mjs new file mode 100644 index 0000000000..35c0ac2f0b --- /dev/null +++ b/services/sync/modules/constants.sys.mjs @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Don't manually modify this line, as it is automatically replaced on merge day +// by the gecko_migration.py script. +export const WEAVE_VERSION = "1.126.0"; + +// Sync Server API version that the client supports. +export const SYNC_API_VERSION = "1.5"; + +// Version of the data format this client supports. The data format describes +// how records are packaged; this is separate from the Server API version and +// the per-engine cleartext formats. +export const STORAGE_VERSION = 5; +export const PREFS_BRANCH = "services.sync."; + +// Put in [] because those aren't allowed in a collection name. +export const DEFAULT_KEYBUNDLE_NAME = "[default]"; + +// Key dimensions. +export const SYNC_KEY_ENCODED_LENGTH = 26; +export const SYNC_KEY_DECODED_LENGTH = 16; + +export const NO_SYNC_NODE_INTERVAL = 10 * 60 * 1000; // 10 minutes + +export const MAX_ERROR_COUNT_BEFORE_BACKOFF = 3; + +// Backoff intervals +export const MINIMUM_BACKOFF_INTERVAL = 15 * 60 * 1000; // 15 minutes +export const MAXIMUM_BACKOFF_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours + +// HMAC event handling timeout. +// 10 minutes = a compromise between the multi-desktop sync interval +// and the mobile sync interval. +export const HMAC_EVENT_INTERVAL = 600000; + +// How long to wait between sync attempts if the Master Password is locked. +export const MASTER_PASSWORD_LOCKED_RETRY_INTERVAL = 15 * 60 * 1000; // 15 minutes + +// 50 is hardcoded here because of URL length restrictions. +// (GUIDs can be up to 64 chars long.) +// Individual engines can set different values for their limit if their +// identifiers are shorter. +export const DEFAULT_GUID_FETCH_BATCH_SIZE = 50; + +// Default batch size for download batching +// (how many records are fetched at a time from the server when batching is used). +export const DEFAULT_DOWNLOAD_BATCH_SIZE = 1000; + +// score thresholds for early syncs +export const SINGLE_USER_THRESHOLD = 1000; +export const MULTI_DEVICE_THRESHOLD = 300; + +// Other score increment constants +export const SCORE_INCREMENT_SMALL = 1; +export const SCORE_INCREMENT_MEDIUM = 10; + +// Instant sync score increment +export const SCORE_INCREMENT_XLARGE = 300 + 1; //MULTI_DEVICE_THRESHOLD + 1 + +// Delay before incrementing global score +export const SCORE_UPDATE_DELAY = 100; + +// Delay for the back observer debouncer. This is chosen to be longer than any +// observed spurious idle/back events and short enough to pre-empt user activity. +export const IDLE_OBSERVER_BACK_DELAY = 100; + +// Duplicate URI_LENGTH_MAX from Places (from nsNavHistory.h), used to discard +// tabs with huge uris during tab sync. +export const URI_LENGTH_MAX = 65536; + +export const MAX_HISTORY_UPLOAD = 5000; +export const MAX_HISTORY_DOWNLOAD = 5000; + +// Top-level statuses +export const STATUS_OK = "success.status_ok"; +export const SYNC_FAILED = "error.sync.failed"; +export const LOGIN_FAILED = "error.login.failed"; +export const SYNC_FAILED_PARTIAL = "error.sync.failed_partial"; +export const CLIENT_NOT_CONFIGURED = "service.client_not_configured"; +export const STATUS_DISABLED = "service.disabled"; +export const MASTER_PASSWORD_LOCKED = "service.master_password_locked"; + +// success states +export const LOGIN_SUCCEEDED = "success.login"; +export const SYNC_SUCCEEDED = "success.sync"; +export const ENGINE_SUCCEEDED = "success.engine"; + +// login failure status codes +export const LOGIN_FAILED_NO_USERNAME = "error.login.reason.no_username"; +export const LOGIN_FAILED_NO_PASSPHRASE = "error.login.reason.no_recoverykey"; +export const LOGIN_FAILED_NETWORK_ERROR = "error.login.reason.network"; +export const LOGIN_FAILED_SERVER_ERROR = "error.login.reason.server"; +export const LOGIN_FAILED_INVALID_PASSPHRASE = "error.login.reason.recoverykey"; +export const LOGIN_FAILED_LOGIN_REJECTED = "error.login.reason.account"; + +// sync failure status codes +export const METARECORD_DOWNLOAD_FAIL = + "error.sync.reason.metarecord_download_fail"; +export const VERSION_OUT_OF_DATE = "error.sync.reason.version_out_of_date"; +export const CREDENTIALS_CHANGED = "error.sync.reason.credentials_changed"; +export const ABORT_SYNC_COMMAND = "aborting sync, process commands said so"; +export const NO_SYNC_NODE_FOUND = "error.sync.reason.no_node_found"; +export const OVER_QUOTA = "error.sync.reason.over_quota"; +export const SERVER_MAINTENANCE = "error.sync.reason.serverMaintenance"; + +export const RESPONSE_OVER_QUOTA = "14"; + +// engine failure status codes +export const ENGINE_UPLOAD_FAIL = "error.engine.reason.record_upload_fail"; +export const ENGINE_DOWNLOAD_FAIL = "error.engine.reason.record_download_fail"; +export const ENGINE_UNKNOWN_FAIL = "error.engine.reason.unknown_fail"; +export const ENGINE_APPLY_FAIL = "error.engine.reason.apply_fail"; +// an upload failure where the batch was interrupted with a 412 +export const ENGINE_BATCH_INTERRUPTED = "error.engine.reason.batch_interrupted"; + +// Ways that a sync can be disabled (messages only to be printed in debug log) +export const kSyncMasterPasswordLocked = + "User elected to leave Primary Password locked"; +export const kSyncWeaveDisabled = "Weave is disabled"; +export const kSyncNetworkOffline = "Network is offline"; +export const kSyncBackoffNotMet = + "Trying to sync before the server said it's okay"; +export const kFirstSyncChoiceNotMade = + "User has not selected an action for first sync"; +export const kSyncNotConfigured = "Sync is not configured"; +export const kFirefoxShuttingDown = "Firefox is about to shut down"; + +export const DEVICE_TYPE_DESKTOP = "desktop"; +export const DEVICE_TYPE_MOBILE = "mobile"; + +export const SQLITE_MAX_VARIABLE_NUMBER = 999; diff --git a/services/sync/modules/doctor.sys.mjs b/services/sync/modules/doctor.sys.mjs new file mode 100644 index 0000000000..ebcf38e2a2 --- /dev/null +++ b/services/sync/modules/doctor.sys.mjs @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// A doctor for our collections. She can be asked to make a consultation, and +// may just diagnose an issue without attempting to cure it, may diagnose and +// attempt to cure, or may decide she is overworked and underpaid. +// Or something - naming is hard :) + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { Observers } from "resource://services-common/observers.sys.mjs"; +import { Service } from "resource://services-sync/service.sys.mjs"; +import { Resource } from "resource://services-sync/resource.sys.mjs"; +import { Svc } from "resource://services-sync/util.sys.mjs"; + +const log = Log.repository.getLogger("Sync.Doctor"); + +export var Doctor = { + async consult(recentlySyncedEngines) { + if (!Services.telemetry.canRecordBase) { + log.info("Skipping consultation: telemetry reporting is disabled"); + return; + } + + let engineInfos = this._getEnginesToValidate(recentlySyncedEngines); + + await this._runValidators(engineInfos); + }, + + _getEnginesToValidate(recentlySyncedEngines) { + let result = {}; + for (let e of recentlySyncedEngines) { + let prefPrefix = `engine.${e.name}.`; + if ( + !Svc.PrefBranch.getBoolPref(prefPrefix + "validation.enabled", false) + ) { + log.info(`Skipping check of ${e.name} - disabled via preferences`); + continue; + } + // Check the last validation time for the engine. + let lastValidation = Svc.PrefBranch.getIntPref( + prefPrefix + "validation.lastTime", + 0 + ); + let validationInterval = Svc.PrefBranch.getIntPref( + prefPrefix + "validation.interval" + ); + let nowSeconds = this._now(); + + if (nowSeconds - lastValidation < validationInterval) { + log.info( + `Skipping validation of ${e.name}: too recent since last validation attempt` + ); + continue; + } + // Update the time now, even if we decline to actually perform a + // validation. We don't want to check the rest of these more frequently + // than once a day. + Svc.PrefBranch.setIntPref( + prefPrefix + "validation.lastTime", + Math.floor(nowSeconds) + ); + + // Validation only occurs a certain percentage of the time. + let validationProbability = + Svc.PrefBranch.getIntPref( + prefPrefix + "validation.percentageChance", + 0 + ) / 100.0; + if (validationProbability < Math.random()) { + log.info( + `Skipping validation of ${e.name}: Probability threshold not met` + ); + continue; + } + + let maxRecords = Svc.PrefBranch.getIntPref( + prefPrefix + "validation.maxRecords" + ); + if (!maxRecords) { + log.info(`Skipping validation of ${e.name}: No maxRecords specified`); + continue; + } + // OK, so this is a candidate - the final decision will be based on the + // number of records actually found. + result[e.name] = { engine: e, maxRecords }; + } + return result; + }, + + async _runValidators(engineInfos) { + if (!Object.keys(engineInfos).length) { + log.info("Skipping validation: no engines qualify"); + return; + } + + if (Object.values(engineInfos).filter(i => i.maxRecords != -1).length) { + // at least some of the engines have maxRecord restrictions which require + // us to ask the server for the counts. + let countInfo = await this._fetchCollectionCounts(); + for (let [engineName, recordCount] of Object.entries(countInfo)) { + if (engineName in engineInfos) { + engineInfos[engineName].recordCount = recordCount; + } + } + } + + for (let [ + engineName, + { engine, maxRecords, recordCount }, + ] of Object.entries(engineInfos)) { + // maxRecords of -1 means "any number", so we can skip asking the server. + // Used for tests. + if (maxRecords >= 0 && recordCount > maxRecords) { + log.debug( + `Skipping validation for ${engineName} because ` + + `the number of records (${recordCount}) is greater ` + + `than the maximum allowed (${maxRecords}).` + ); + continue; + } + let validator = engine.getValidator(); + if (!validator) { + // This is probably only possible in profile downgrade cases. + log.warn( + `engine.getValidator returned null for ${engineName} but the pref that controls validation is enabled.` + ); + continue; + } + + if (!(await validator.canValidate())) { + log.debug( + `Skipping validation for ${engineName} because validator.canValidate() is false` + ); + continue; + } + + // Let's do it! + Services.console.logStringMessage( + `Sync is about to run a consistency check of ${engine.name}. This may be slow, and ` + + `can be controlled using the pref "services.sync.${engine.name}.validation.enabled".\n` + + `If you encounter any problems because of this, please file a bug.` + ); + + try { + log.info(`Running validator for ${engine.name}`); + let result = await validator.validate(engine); + let { problems, version, duration, recordCount } = result; + Observers.notify( + "weave:engine:validate:finish", + { + version, + checked: recordCount, + took: duration, + problems: problems ? problems.getSummary(true) : null, + }, + engine.name + ); + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + log.error(`Failed to run validation on ${engine.name}!`, ex); + Observers.notify("weave:engine:validate:error", ex, engine.name); + // Keep validating -- there's no reason to think that a failure for one + // validator would mean the others will fail. + } + } + }, + + // mainly for mocking. + async _fetchCollectionCounts() { + let collectionCountsURL = Service.userBaseURL + "info/collection_counts"; + try { + let infoResp = await Service._fetchInfo(collectionCountsURL); + if (!infoResp.success) { + log.error( + "Can't fetch collection counts: request to info/collection_counts responded with " + + infoResp.status + ); + return {}; + } + return infoResp.obj; // might throw because obj is a getter which parses json. + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + // Not running validation is totally fine, so we just write an error log and return. + log.error("Caught error when fetching counts", ex); + return {}; + } + }, + + // functions used so tests can mock them + _now() { + // We use the server time, which is SECONDS + return Resource.serverTime; + }, +}; diff --git a/services/sync/modules/engines.sys.mjs b/services/sync/modules/engines.sys.mjs new file mode 100644 index 0000000000..0d490ac4b3 --- /dev/null +++ b/services/sync/modules/engines.sys.mjs @@ -0,0 +1,2274 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { Observers } from "resource://services-common/observers.sys.mjs"; + +import { + DEFAULT_DOWNLOAD_BATCH_SIZE, + DEFAULT_GUID_FETCH_BATCH_SIZE, + ENGINE_BATCH_INTERRUPTED, + ENGINE_DOWNLOAD_FAIL, + ENGINE_UPLOAD_FAIL, + VERSION_OUT_OF_DATE, + PREFS_BRANCH, +} from "resource://services-sync/constants.sys.mjs"; + +import { + Collection, + CryptoWrapper, +} from "resource://services-sync/record.sys.mjs"; +import { Resource } from "resource://services-sync/resource.sys.mjs"; +import { + SerializableSet, + Svc, + Utils, +} from "resource://services-sync/util.sys.mjs"; +import { SyncedRecordsTelemetry } from "resource://services-sync/telemetry.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +function ensureDirectory(path) { + return IOUtils.makeDirectory(PathUtils.parent(path), { + createAncestors: true, + }); +} + +/** + * Trackers are associated with a single engine and deal with + * listening for changes to their particular data type. + * + * The base `Tracker` only supports listening for changes, and bumping the score + * to indicate how urgently the engine wants to sync. It does not persist any + * data. Engines that track changes directly in the storage layer (like + * bookmarks, bridged engines, addresses, and credit cards) or only upload a + * single record (tabs and preferences) should subclass `Tracker`. + */ +export function Tracker(name, engine) { + if (!engine) { + throw new Error("Tracker must be associated with an Engine instance."); + } + + name = name || "Unnamed"; + this.name = name.toLowerCase(); + this.engine = engine; + + this._log = Log.repository.getLogger(`Sync.Engine.${name}.Tracker`); + + this._score = 0; + + this.asyncObserver = Async.asyncObserver(this, this._log); +} + +Tracker.prototype = { + // New-style trackers use change sources to filter out changes made by Sync in + // observer notifications, so we don't want to let the engine ignore all + // changes during a sync. + get ignoreAll() { + return false; + }, + + // Define an empty setter so that the engine doesn't throw a `TypeError` + // setting a read-only property. + set ignoreAll(value) {}, + + /* + * Score can be called as often as desired to decide which engines to sync + * + * Valid values for score: + * -1: Do not sync unless the user specifically requests it (almost disabled) + * 0: Nothing has changed + * 100: Please sync me ASAP! + * + * Setting it to other values should (but doesn't currently) throw an exception + */ + get score() { + return this._score; + }, + + set score(value) { + this._score = value; + Observers.notify("weave:engine:score:updated", this.name); + }, + + // Should be called by service everytime a sync has been done for an engine + resetScore() { + this._score = 0; + }, + + // Unsupported, and throws a more descriptive error to ensure callers aren't + // accidentally using persistence. + async getChangedIDs() { + throw new TypeError("This tracker doesn't store changed IDs"); + }, + + // Also unsupported. + async addChangedID(id, when) { + throw new TypeError("Can't add changed ID to this tracker"); + }, + + // Ditto. + async removeChangedID(...ids) { + throw new TypeError("Can't remove changed IDs from this tracker"); + }, + + // This method is called at various times, so we override with a no-op + // instead of throwing. + clearChangedIDs() {}, + + _now() { + return Date.now() / 1000; + }, + + _isTracking: false, + + start() { + if (!this.engineIsEnabled()) { + return; + } + this._log.trace("start()."); + if (!this._isTracking) { + this.onStart(); + this._isTracking = true; + } + }, + + async stop() { + this._log.trace("stop()."); + if (this._isTracking) { + await this.asyncObserver.promiseObserversComplete(); + this.onStop(); + this._isTracking = false; + } + }, + + // Override these in your subclasses. + onStart() {}, + onStop() {}, + async observe(subject, topic, data) {}, + + engineIsEnabled() { + if (!this.engine) { + // Can't tell -- we must be running in a test! + return true; + } + return this.engine.enabled; + }, + + /** + * Starts or stops listening for changes depending on the associated engine's + * enabled state. + * + * @param {Boolean} engineEnabled Whether the engine was enabled. + */ + async onEngineEnabledChanged(engineEnabled) { + if (engineEnabled == this._isTracking) { + return; + } + + if (engineEnabled) { + this.start(); + } else { + await this.stop(); + this.clearChangedIDs(); + } + }, + + async finalize() { + await this.stop(); + }, +}; + +/* + * A tracker that persists a list of IDs for all changed items that need to be + * synced. This is 🚨 _extremely deprecated_ 🚨 and only kept around for current + * engines. ⚠️ Please **don't use it** for new engines! ⚠️ + * + * Why is this kind of external change tracking deprecated? Because it causes + * consistency issues due to missed notifications, interrupted syncs, and the + * tracker's view of what changed diverging from the data store's. + */ +export function LegacyTracker(name, engine) { + Tracker.call(this, name, engine); + + this._ignored = []; + this.file = this.name; + this._storage = new JSONFile({ + path: Utils.jsonFilePath("changes", this.file), + dataPostProcessor: json => this._dataPostProcessor(json), + beforeSave: () => this._beforeSave(), + }); + this._ignoreAll = false; +} + +LegacyTracker.prototype = { + get ignoreAll() { + return this._ignoreAll; + }, + + set ignoreAll(value) { + this._ignoreAll = value; + }, + + // Default to an empty object if the file doesn't exist. + _dataPostProcessor(json) { + return (typeof json == "object" && json) || {}; + }, + + // Ensure the Weave storage directory exists before writing the file. + _beforeSave() { + return ensureDirectory(this._storage.path); + }, + + async getChangedIDs() { + await this._storage.load(); + return this._storage.data; + }, + + _saveChangedIDs() { + this._storage.saveSoon(); + }, + + // ignore/unignore specific IDs. Useful for ignoring items that are + // being processed, or that shouldn't be synced. + // But note: not persisted to disk + + ignoreID(id) { + this.unignoreID(id); + this._ignored.push(id); + }, + + unignoreID(id) { + let index = this._ignored.indexOf(id); + if (index != -1) { + this._ignored.splice(index, 1); + } + }, + + async _saveChangedID(id, when) { + this._log.trace(`Adding changed ID: ${id}, ${JSON.stringify(when)}`); + const changedIDs = await this.getChangedIDs(); + changedIDs[id] = when; + this._saveChangedIDs(); + }, + + async addChangedID(id, when) { + if (!id) { + this._log.warn("Attempted to add undefined ID to tracker"); + return false; + } + + if (this.ignoreAll || this._ignored.includes(id)) { + return false; + } + + // Default to the current time in seconds if no time is provided. + if (when == null) { + when = this._now(); + } + + const changedIDs = await this.getChangedIDs(); + // Add/update the entry if we have a newer time. + if ((changedIDs[id] || -Infinity) < when) { + await this._saveChangedID(id, when); + } + + return true; + }, + + async removeChangedID(...ids) { + if (!ids.length || this.ignoreAll) { + return false; + } + for (let id of ids) { + if (!id) { + this._log.warn("Attempted to remove undefined ID from tracker"); + continue; + } + if (this._ignored.includes(id)) { + this._log.debug(`Not removing ignored ID ${id} from tracker`); + continue; + } + const changedIDs = await this.getChangedIDs(); + if (changedIDs[id] != null) { + this._log.trace("Removing changed ID " + id); + delete changedIDs[id]; + } + } + this._saveChangedIDs(); + return true; + }, + + clearChangedIDs() { + this._log.trace("Clearing changed ID list"); + this._storage.data = {}; + this._saveChangedIDs(); + }, + + async finalize() { + // Persist all pending tracked changes to disk, and wait for the final write + // to finish. + await super.finalize(); + this._saveChangedIDs(); + await this._storage.finalize(); + }, +}; +Object.setPrototypeOf(LegacyTracker.prototype, Tracker.prototype); + +/** + * The Store serves as the interface between Sync and stored data. + * + * The name "store" is slightly a misnomer because it doesn't actually "store" + * anything. Instead, it serves as a gateway to something that actually does + * the "storing." + * + * The store is responsible for record management inside an engine. It tells + * Sync what items are available for Sync, converts items to and from Sync's + * record format, and applies records from Sync into changes on the underlying + * store. + * + * Store implementations require a number of functions to be implemented. These + * are all documented below. + * + * For stores that deal with many records or which have expensive store access + * routines, it is highly recommended to implement a custom applyIncomingBatch + * and/or applyIncoming function on top of the basic APIs. + */ + +export function Store(name, engine) { + if (!engine) { + throw new Error("Store must be associated with an Engine instance."); + } + + name = name || "Unnamed"; + this.name = name.toLowerCase(); + this.engine = engine; + + this._log = Log.repository.getLogger(`Sync.Engine.${name}.Store`); + + ChromeUtils.defineLazyGetter(this, "_timer", function () { + return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + }); +} + +Store.prototype = { + /** + * Apply multiple incoming records against the store. + * + * This is called with a set of incoming records to process. The function + * should look at each record, reconcile with the current local state, and + * make the local changes required to bring its state in alignment with the + * record. + * + * The default implementation simply iterates over all records and calls + * applyIncoming(). Store implementations may overwrite this function + * if desired. + * + * @param records Array of records to apply + * @param a SyncedRecordsTelemetry obj that will keep track of failed reasons + * @return Array of record IDs which did not apply cleanly + */ + async applyIncomingBatch(records, countTelemetry) { + let failed = []; + + await Async.yieldingForEach(records, async record => { + try { + await this.applyIncoming(record); + } catch (ex) { + if (ex.code == SyncEngine.prototype.eEngineAbortApplyIncoming) { + // This kind of exception should have a 'cause' attribute, which is an + // originating exception. + // ex.cause will carry its stack with it when rethrown. + throw ex.cause; + } + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.warn("Failed to apply incoming record " + record.id, ex); + failed.push(record.id); + countTelemetry.addIncomingFailedReason(ex.message); + } + }); + + return failed; + }, + + /** + * Apply a single record against the store. + * + * This takes a single record and makes the local changes required so the + * local state matches what's in the record. + * + * The default implementation calls one of remove(), create(), or update() + * depending on the state obtained from the store itself. Store + * implementations may overwrite this function if desired. + * + * @param record + * Record to apply + */ + async applyIncoming(record) { + if (record.deleted) { + await this.remove(record); + } else if (!(await this.itemExists(record.id))) { + await this.create(record); + } else { + await this.update(record); + } + }, + + // override these in derived objects + + /** + * Create an item in the store from a record. + * + * This is called by the default implementation of applyIncoming(). If using + * applyIncomingBatch(), this won't be called unless your store calls it. + * + * @param record + * The store record to create an item from + */ + async create(record) { + throw new Error("override create in a subclass"); + }, + + /** + * Remove an item in the store from a record. + * + * This is called by the default implementation of applyIncoming(). If using + * applyIncomingBatch(), this won't be called unless your store calls it. + * + * @param record + * The store record to delete an item from + */ + async remove(record) { + throw new Error("override remove in a subclass"); + }, + + /** + * Update an item from a record. + * + * This is called by the default implementation of applyIncoming(). If using + * applyIncomingBatch(), this won't be called unless your store calls it. + * + * @param record + * The record to use to update an item from + */ + async update(record) { + throw new Error("override update in a subclass"); + }, + + /** + * Determine whether a record with the specified ID exists. + * + * Takes a string record ID and returns a booleans saying whether the record + * exists. + * + * @param id + * string record ID + * @return boolean indicating whether record exists locally + */ + async itemExists(id) { + throw new Error("override itemExists in a subclass"); + }, + + /** + * Create a record from the specified ID. + * + * If the ID is known, the record should be populated with metadata from + * the store. If the ID is not known, the record should be created with the + * delete field set to true. + * + * @param id + * string record ID + * @param collection + * Collection to add record to. This is typically passed into the + * constructor for the newly-created record. + * @return record type for this engine + */ + async createRecord(id, collection) { + throw new Error("override createRecord in a subclass"); + }, + + /** + * Change the ID of a record. + * + * @param oldID + * string old/current record ID + * @param newID + * string new record ID + */ + async changeItemID(oldID, newID) { + throw new Error("override changeItemID in a subclass"); + }, + + /** + * Obtain the set of all known record IDs. + * + * @return Object with ID strings as keys and values of true. The values + * are ignored. + */ + async getAllIDs() { + throw new Error("override getAllIDs in a subclass"); + }, + + /** + * Wipe all data in the store. + * + * This function is called during remote wipes or when replacing local data + * with remote data. + * + * This function should delete all local data that the store is managing. It + * can be thought of as clearing out all state and restoring the "new + * browser" state. + */ + async wipe() { + throw new Error("override wipe in a subclass"); + }, +}; + +export function EngineManager(service) { + this.service = service; + + this._engines = {}; + + this._altEngineInfo = {}; + + // This will be populated by Service on startup. + this._declined = new Set(); + this._log = Log.repository.getLogger("Sync.EngineManager"); + this._log.manageLevelFromPref("services.sync.log.logger.service.engines"); + // define the default level for all engine logs here (although each engine + // allows its level to be controlled via a specific, non-default pref) + Log.repository + .getLogger(`Sync.Engine`) + .manageLevelFromPref("services.sync.log.logger.engine"); +} + +EngineManager.prototype = { + get(name) { + // Return an array of engines if we have an array of names + if (Array.isArray(name)) { + let engines = []; + name.forEach(function (name) { + let engine = this.get(name); + if (engine) { + engines.push(engine); + } + }, this); + return engines; + } + + return this._engines[name]; // Silently returns undefined for unknown names. + }, + + getAll() { + let engines = []; + for (let [, engine] of Object.entries(this._engines)) { + engines.push(engine); + } + return engines; + }, + + /** + * If a user has changed a pref that controls which variant of a sync engine + * for a given collection we use, unregister the old engine and register the + * new one. + * + * This is called by EngineSynchronizer before every sync. + */ + async switchAlternatives() { + for (let [name, info] of Object.entries(this._altEngineInfo)) { + let prefValue = info.prefValue; + if (prefValue === info.lastValue) { + this._log.trace( + `No change for engine ${name} (${info.pref} is still ${prefValue})` + ); + continue; + } + // Unregister the old engine, register the new one. + this._log.info( + `Switching ${name} engine ("${info.pref}" went from ${info.lastValue} => ${prefValue})` + ); + try { + await this._removeAndFinalize(name); + } catch (e) { + this._log.warn(`Failed to remove previous ${name} engine...`, e); + } + let engineType = prefValue ? info.whenTrue : info.whenFalse; + try { + // If register throws, we'll try again next sync, but until then there + // won't be an engine registered for this collection. + await this.register(engineType); + info.lastValue = prefValue; + // Note: engineType.name is using Function.prototype.name. + this._log.info(`Switched the ${name} engine to use ${engineType.name}`); + } catch (e) { + this._log.warn( + `Switching the ${name} engine to use ${engineType.name} failed (couldn't register)`, + e + ); + } + } + }, + + async registerAlternatives(name, pref, whenTrue, whenFalse) { + let info = { name, pref, whenTrue, whenFalse }; + + XPCOMUtils.defineLazyPreferenceGetter(info, "prefValue", pref, false); + + let chosen = info.prefValue ? info.whenTrue : info.whenFalse; + info.lastValue = info.prefValue; + this._altEngineInfo[name] = info; + + await this.register(chosen); + }, + + /** + * N.B., does not pay attention to the declined list. + */ + getEnabled() { + return this.getAll() + .filter(engine => engine.enabled) + .sort((a, b) => a.syncPriority - b.syncPriority); + }, + + get enabledEngineNames() { + return this.getEnabled().map(e => e.name); + }, + + persistDeclined() { + Svc.PrefBranch.setStringPref( + "declinedEngines", + [...this._declined].join(",") + ); + }, + + /** + * Returns an array. + */ + getDeclined() { + return [...this._declined]; + }, + + setDeclined(engines) { + this._declined = new Set(engines); + this.persistDeclined(); + }, + + isDeclined(engineName) { + return this._declined.has(engineName); + }, + + /** + * Accepts a Set or an array. + */ + decline(engines) { + for (let e of engines) { + this._declined.add(e); + } + this.persistDeclined(); + }, + + undecline(engines) { + for (let e of engines) { + this._declined.delete(e); + } + this.persistDeclined(); + }, + + /** + * Register an Engine to the service. Alternatively, give an array of engine + * objects to register. + * + * @param engineObject + * Engine object used to get an instance of the engine + * @return The engine object if anything failed + */ + async register(engineObject) { + if (Array.isArray(engineObject)) { + for (const e of engineObject) { + await this.register(e); + } + return; + } + + try { + let engine = new engineObject(this.service); + let name = engine.name; + if (name in this._engines) { + this._log.error("Engine '" + name + "' is already registered!"); + } else { + if (engine.initialize) { + await engine.initialize(); + } + this._engines[name] = engine; + } + } catch (ex) { + let name = engineObject || ""; + name = name.prototype || ""; + name = name.name || ""; + + this._log.error(`Could not initialize engine ${name}`, ex); + } + }, + + async unregister(val) { + let name = val; + if (val instanceof SyncEngine) { + name = val.name; + } + await this._removeAndFinalize(name); + delete this._altEngineInfo[name]; + }, + + // Common code for disabling an engine by name, that doesn't complain if the + // engine doesn't exist. Doesn't touch the engine's alternative info (if any + // exists). + async _removeAndFinalize(name) { + if (name in this._engines) { + let engine = this._engines[name]; + delete this._engines[name]; + await engine.finalize(); + } + }, + + async clear() { + for (let name in this._engines) { + let engine = this._engines[name]; + delete this._engines[name]; + await engine.finalize(); + } + this._altEngineInfo = {}; + }, +}; + +export function SyncEngine(name, service) { + if (!service) { + throw new Error("SyncEngine must be associated with a Service instance."); + } + + this.Name = name || "Unnamed"; + this.name = name.toLowerCase(); + this.service = service; + + this._notify = Utils.notify("weave:engine:"); + this._log = Log.repository.getLogger("Sync.Engine." + this.Name); + this._log.manageLevelFromPref(`services.sync.log.logger.engine.${this.name}`); + + this._modified = this.emptyChangeset(); + this._tracker; // initialize tracker to load previously changed IDs + this._log.debug("Engine constructed"); + + this._toFetchStorage = new JSONFile({ + path: Utils.jsonFilePath("toFetch", this.name), + dataPostProcessor: json => this._metadataPostProcessor(json), + beforeSave: () => this._beforeSaveMetadata(), + }); + + this._previousFailedStorage = new JSONFile({ + path: Utils.jsonFilePath("failed", this.name), + dataPostProcessor: json => this._metadataPostProcessor(json), + beforeSave: () => this._beforeSaveMetadata(), + }); + + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_enabled", + `services.sync.engine.${this.prefName}`, + false + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_syncID", + `services.sync.${this.name}.syncID`, + "" + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_lastSync", + `services.sync.${this.name}.lastSync`, + "0", + null, + v => parseFloat(v) + ); + // Async initializations can be made in the initialize() method. + + this.asyncObserver = Async.asyncObserver(this, this._log); +} + +// Enumeration to define approaches to handling bad records. +// Attached to the constructor to allow use as a kind of static enumeration. +SyncEngine.kRecoveryStrategy = { + ignore: "ignore", + retry: "retry", + error: "error", +}; + +SyncEngine.prototype = { + _recordObj: CryptoWrapper, + // _storeObj, and _trackerObj should to be overridden in subclasses + _storeObj: Store, + _trackerObj: Tracker, + version: 1, + + // Local 'constant'. + // Signal to the engine that processing further records is pointless. + eEngineAbortApplyIncoming: "error.engine.abort.applyincoming", + + // Should we keep syncing if we find a record that cannot be uploaded (ever)? + // If this is false, we'll throw, otherwise, we'll ignore the record and + // continue. This currently can only happen due to the record being larger + // than the record upload limit. + allowSkippedRecord: true, + + // Which sortindex to use when retrieving records for this engine. + _defaultSort: undefined, + + _hasSyncedThisSession: false, + + _metadataPostProcessor(json) { + if (Array.isArray(json)) { + // Pre-`JSONFile` storage stored an array, but `JSONFile` defaults to + // an object, so we wrap the array for consistency. + json = { ids: json }; + } + if (!json.ids) { + json.ids = []; + } + // The set serializes the same way as an array, but offers more efficient + // methods of manipulation. + json.ids = new SerializableSet(json.ids); + return json; + }, + + async _beforeSaveMetadata() { + await ensureDirectory(this._toFetchStorage.path); + await ensureDirectory(this._previousFailedStorage.path); + }, + + // A relative priority to use when computing an order + // for engines to be synced. Higher-priority engines + // (lower numbers) are synced first. + // It is recommended that a unique value be used for each engine, + // in order to guarantee a stable sequence. + syncPriority: 0, + + // How many records to pull in a single sync. This is primarily to avoid very + // long first syncs against profiles with many history records. + downloadLimit: null, + + // How many records to pull at one time when specifying IDs. This is to avoid + // URI length limitations. + guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE, + + downloadBatchSize: DEFAULT_DOWNLOAD_BATCH_SIZE, + + async initialize() { + await this._toFetchStorage.load(); + await this._previousFailedStorage.load(); + Services.prefs.addObserver( + `${PREFS_BRANCH}engine.${this.prefName}`, + this.asyncObserver, + true + ); + this._log.debug("SyncEngine initialized", this.name); + }, + + get prefName() { + return this.name; + }, + + get enabled() { + return this._enabled; + }, + + set enabled(val) { + if (!!val != this._enabled) { + Svc.PrefBranch.setBoolPref("engine." + this.prefName, !!val); + } + }, + + get score() { + return this._tracker.score; + }, + + get _store() { + let store = new this._storeObj(this.Name, this); + this.__defineGetter__("_store", () => store); + return store; + }, + + get _tracker() { + let tracker = new this._trackerObj(this.Name, this); + this.__defineGetter__("_tracker", () => tracker); + return tracker; + }, + + get storageURL() { + return this.service.storageURL; + }, + + get engineURL() { + return this.storageURL + this.name; + }, + + get cryptoKeysURL() { + return this.storageURL + "crypto/keys"; + }, + + get metaURL() { + return this.storageURL + "meta/global"; + }, + + startTracking() { + this._tracker.start(); + }, + + // Returns a promise + stopTracking() { + return this._tracker.stop(); + }, + + // Listens for engine enabled state changes, and updates the tracker's state. + // This is an async observer because the tracker waits on all its async + // observers to finish when it's stopped. + async observe(subject, topic, data) { + if ( + topic == "nsPref:changed" && + data == `services.sync.engine.${this.prefName}` + ) { + await this._tracker.onEngineEnabledChanged(this._enabled); + } + }, + + async sync() { + if (!this.enabled) { + return false; + } + + if (!this._sync) { + throw new Error("engine does not implement _sync method"); + } + + return this._notify("sync", this.name, this._sync)(); + }, + + // Override this method to return a new changeset type. + emptyChangeset() { + return new Changeset(); + }, + + /** + * Returns the local sync ID for this engine, or `""` if the engine hasn't + * synced for the first time. This is exposed for tests. + * + * @return the current sync ID. + */ + async getSyncID() { + return this._syncID; + }, + + /** + * Ensures that the local sync ID for the engine matches the sync ID for the + * collection on the server. A mismatch indicates that another client wiped + * the collection; we're syncing after a node reassignment, and another + * client synced before us; or the store was replaced since the last sync. + * In case of a mismatch, we need to reset all local Sync state and start + * over as a first sync. + * + * In most cases, this method should return the new sync ID as-is. However, an + * engine may ignore the given ID and assign a different one, if it determines + * that the sync ID on the server is out of date. The bookmarks engine uses + * this to wipe the server and other clients on the first sync after the user + * restores from a backup. + * + * @param newSyncID + * The new sync ID for the collection from `meta/global`. + * @return The assigned sync ID. If this doesn't match `newSyncID`, we'll + * replace the sync ID in `meta/global` with the assigned ID. + */ + async ensureCurrentSyncID(newSyncID) { + let existingSyncID = this._syncID; + if (existingSyncID == newSyncID) { + return existingSyncID; + } + this._log.debug( + `Engine syncIDs differ (old="${existingSyncID}", new="${newSyncID}") - resetting the engine` + ); + await this.resetClient(); + Svc.PrefBranch.setStringPref(this.name + ".syncID", newSyncID); + Svc.PrefBranch.setStringPref(this.name + ".lastSync", "0"); + return newSyncID; + }, + + /** + * Resets the local sync ID for the engine, wipes the server, and resets all + * local Sync state to start over as a first sync. + * + * @return the new sync ID. + */ + async resetSyncID() { + let newSyncID = await this.resetLocalSyncID(); + await this.wipeServer(); + return newSyncID; + }, + + /** + * Resets the local sync ID for the engine, signaling that we're starting over + * as a first sync. + * + * @return the new sync ID. + */ + async resetLocalSyncID() { + return this.ensureCurrentSyncID(Utils.makeGUID()); + }, + + /** + * Allows overriding scheduler logic -- added to help reduce kinto server + * getting hammered because our scheduler never got tuned for it. + * + * Note: Overriding engines must take resyncs into account -- score will not + * be cleared. + */ + shouldSkipSync(syncReason) { + return false; + }, + + /* + * lastSync is a timestamp in server time. + */ + async getLastSync() { + return this._lastSync; + }, + async setLastSync(lastSync) { + // Store the value as a string to keep floating point precision + Svc.PrefBranch.setStringPref(this.name + ".lastSync", lastSync.toString()); + }, + async resetLastSync() { + this._log.debug("Resetting " + this.name + " last sync time"); + await this.setLastSync(0); + }, + + get hasSyncedThisSession() { + return this._hasSyncedThisSession; + }, + + set hasSyncedThisSession(hasSynced) { + this._hasSyncedThisSession = hasSynced; + }, + + get toFetch() { + this._toFetchStorage.ensureDataReady(); + return this._toFetchStorage.data.ids; + }, + + set toFetch(ids) { + if (ids.constructor.name != "SerializableSet") { + throw new Error( + "Bug: Attempted to set toFetch to something that isn't a SerializableSet" + ); + } + this._toFetchStorage.data = { ids }; + this._toFetchStorage.saveSoon(); + }, + + get previousFailed() { + this._previousFailedStorage.ensureDataReady(); + return this._previousFailedStorage.data.ids; + }, + + set previousFailed(ids) { + if (ids.constructor.name != "SerializableSet") { + throw new Error( + "Bug: Attempted to set previousFailed to something that isn't a SerializableSet" + ); + } + this._previousFailedStorage.data = { ids }; + this._previousFailedStorage.saveSoon(); + }, + + /* + * Returns a changeset for this sync. Engine implementations can override this + * method to bypass the tracker for certain or all changed items. + */ + async getChangedIDs() { + return this._tracker.getChangedIDs(); + }, + + // Create a new record using the store and add in metadata. + async _createRecord(id) { + let record = await this._store.createRecord(id, this.name); + record.id = id; + record.collection = this.name; + return record; + }, + + // Creates a tombstone Sync record with additional metadata. + _createTombstone(id) { + let tombstone = new this._recordObj(this.name, id); + tombstone.id = id; + tombstone.collection = this.name; + tombstone.deleted = true; + return tombstone; + }, + + // Any setup that needs to happen at the beginning of each sync. + async _syncStartup() { + // Determine if we need to wipe on outdated versions + let metaGlobal = await this.service.recordManager.get(this.metaURL); + let engines = metaGlobal.payload.engines || {}; + let engineData = engines[this.name] || {}; + + // Assume missing versions are 0 and wipe the server + if ((engineData.version || 0) < this.version) { + this._log.debug("Old engine data: " + [engineData.version, this.version]); + + // Clear the server and reupload everything on bad version or missing + // meta. Note that we don't regenerate per-collection keys here. + let newSyncID = await this.resetSyncID(); + + // Set the newer version and newly generated syncID + engineData.version = this.version; + engineData.syncID = newSyncID; + + // Put the new data back into meta/global and mark for upload + engines[this.name] = engineData; + metaGlobal.payload.engines = engines; + metaGlobal.changed = true; + } else if (engineData.version > this.version) { + // Don't sync this engine if the server has newer data + + let error = new Error("New data: " + [engineData.version, this.version]); + error.failureCode = VERSION_OUT_OF_DATE; + throw error; + } else { + // Changes to syncID mean we'll need to upload everything + let assignedSyncID = await this.ensureCurrentSyncID(engineData.syncID); + if (assignedSyncID != engineData.syncID) { + engineData.syncID = assignedSyncID; + metaGlobal.changed = true; + } + } + + // Save objects that need to be uploaded in this._modified. As we + // successfully upload objects we remove them from this._modified. If an + // error occurs or any objects fail to upload, they will remain in + // this._modified. At the end of a sync, or after an error, we add all + // objects remaining in this._modified to the tracker. + let initialChanges = await this.pullChanges(); + this._modified.replace(initialChanges); + // Clear the tracker now. If the sync fails we'll add the ones we failed + // to upload back. + this._tracker.clearChangedIDs(); + this._tracker.resetScore(); + + // Keep track of what to delete at the end of sync + this._delete = {}; + }, + + async pullChanges() { + let lastSync = await this.getLastSync(); + if (lastSync) { + return this.pullNewChanges(); + } + this._log.debug("First sync, uploading all items"); + return this.pullAllChanges(); + }, + + /** + * A tiny abstraction to make it easier to test incoming record + * application. + */ + itemSource() { + return new Collection(this.engineURL, this._recordObj, this.service); + }, + + /** + * Download and apply remote records changed since the last sync. This + * happens in three stages. + * + * In the first stage, we fetch full records for all changed items, newest + * first, up to the download limit. The limit lets us make progress for large + * collections, where the sync is likely to be interrupted before we + * can fetch everything. + * + * In the second stage, we fetch the IDs of any remaining records changed + * since the last sync, add them to our backlog, and fast-forward our last + * sync time. + * + * In the third stage, we fetch and apply records for all backlogged IDs, + * as well as any records that failed to apply during the last sync. We + * request records for the IDs in chunks, to avoid exceeding URL length + * limits, then remove successfully applied records from the backlog, and + * record IDs of any records that failed to apply to retry on the next sync. + */ + async _processIncoming() { + this._log.trace("Downloading & applying server changes"); + + let newitems = this.itemSource(); + let lastSync = await this.getLastSync(); + + newitems.newer = lastSync; + newitems.full = true; + + let downloadLimit = Infinity; + if (this.downloadLimit) { + // Fetch new records up to the download limit. Currently, only the history + // engine sets a limit, since the history collection has the highest volume + // of changed records between syncs. The other engines fetch all records + // changed since the last sync. + if (this._defaultSort) { + // A download limit with a sort order doesn't make sense: we won't know + // which records to backfill. + throw new Error("Can't specify download limit with default sort order"); + } + newitems.sort = "newest"; + downloadLimit = newitems.limit = this.downloadLimit; + } else if (this._defaultSort) { + // The bookmarks engine fetches records by sort index; other engines leave + // the order unspecified. We can remove `_defaultSort` entirely after bug + // 1305563: the sort index won't matter because we'll buffer all bookmarks + // before applying. + newitems.sort = this._defaultSort; + } + + // applied => number of items that should be applied. + // failed => number of items that failed in this sync. + // newFailed => number of items that failed for the first time in this sync. + // reconciled => number of items that were reconciled. + // failedReasons => {name, count} of reasons a record failed + let countTelemetry = new SyncedRecordsTelemetry(); + let count = countTelemetry.incomingCounts; + let recordsToApply = []; + let failedInCurrentSync = new SerializableSet(); + + let oldestModified = this.lastModified; + let downloadedIDs = new Set(); + + // Stage 1: Fetch new records from the server, up to the download limit. + if (this.lastModified == null || this.lastModified > lastSync) { + let { response, records } = await newitems.getBatched( + this.downloadBatchSize + ); + if (!response.success) { + response.failureCode = ENGINE_DOWNLOAD_FAIL; + throw response; + } + + await Async.yieldingForEach(records, async record => { + downloadedIDs.add(record.id); + + if (record.modified < oldestModified) { + oldestModified = record.modified; + } + + let { shouldApply, error } = await this._maybeReconcile(record); + if (error) { + failedInCurrentSync.add(record.id); + count.failed++; + countTelemetry.addIncomingFailedReason(error.message); + return; + } + if (!shouldApply) { + count.reconciled++; + return; + } + recordsToApply.push(record); + }); + + let failedToApply = await this._applyRecords( + recordsToApply, + countTelemetry + ); + Utils.setAddAll(failedInCurrentSync, failedToApply); + + // `applied` is a bit of a misnomer: it counts records that *should* be + // applied, so it also includes records that we tried to apply and failed. + // `recordsToApply.length - failedToApply.length` is the number of records + // that we *successfully* applied. + count.failed += failedToApply.length; + count.applied += recordsToApply.length; + } + + // Stage 2: If we reached our download limit, we might still have records + // on the server that changed since the last sync. Fetch the IDs for the + // remaining records, and add them to the backlog. Note that this stage + // only runs for engines that set a download limit. + if (downloadedIDs.size == downloadLimit) { + let guidColl = this.itemSource(); + + guidColl.newer = lastSync; + guidColl.older = oldestModified; + guidColl.sort = "oldest"; + + let guids = await guidColl.get(); + if (!guids.success) { + throw guids; + } + + // Filtering out already downloaded IDs here isn't necessary. We only do + // that in case the Sync server doesn't support `older` (bug 1316110). + let remainingIDs = guids.obj.filter(id => !downloadedIDs.has(id)); + if (remainingIDs.length) { + this.toFetch = Utils.setAddAll(this.toFetch, remainingIDs); + } + } + + // Fast-foward the lastSync timestamp since we have backlogged the + // remaining items. + if (lastSync < this.lastModified) { + lastSync = this.lastModified; + await this.setLastSync(lastSync); + } + + // Stage 3: Backfill records from the backlog, and those that failed to + // decrypt or apply during the last sync. We only backfill up to the + // download limit, to prevent a large backlog for one engine from blocking + // the others. We'll keep processing the backlog on subsequent engine syncs. + let failedInPreviousSync = this.previousFailed; + let idsToBackfill = Array.from( + Utils.setAddAll( + Utils.subsetOfSize(this.toFetch, downloadLimit), + failedInPreviousSync + ) + ); + + // Note that we intentionally overwrite the previously failed list here. + // Records that fail to decrypt or apply in two consecutive syncs are likely + // corrupt; we remove them from the list because retrying and failing on + // every subsequent sync just adds noise. + this.previousFailed = failedInCurrentSync; + + let backfilledItems = this.itemSource(); + + backfilledItems.sort = "newest"; + backfilledItems.full = true; + + // `getBatched` includes the list of IDs as a query parameter, so we need to fetch + // records in chunks to avoid exceeding URI length limits. + if (this.guidFetchBatchSize) { + for (let ids of lazy.PlacesUtils.chunkArray( + idsToBackfill, + this.guidFetchBatchSize + )) { + backfilledItems.ids = ids; + + let { response, records } = await backfilledItems.getBatched( + this.downloadBatchSize + ); + if (!response.success) { + response.failureCode = ENGINE_DOWNLOAD_FAIL; + throw response; + } + + let backfilledRecordsToApply = []; + let failedInBackfill = []; + + await Async.yieldingForEach(records, async record => { + let { shouldApply, error } = await this._maybeReconcile(record); + if (error) { + failedInBackfill.push(record.id); + count.failed++; + countTelemetry.addIncomingFailedReason(error.message); + return; + } + if (!shouldApply) { + count.reconciled++; + return; + } + backfilledRecordsToApply.push(record); + }); + + let failedToApply = await this._applyRecords( + backfilledRecordsToApply, + countTelemetry + ); + failedInBackfill.push(...failedToApply); + + count.failed += failedToApply.length; + count.applied += backfilledRecordsToApply.length; + + this.toFetch = Utils.setDeleteAll(this.toFetch, ids); + this.previousFailed = Utils.setAddAll( + this.previousFailed, + failedInBackfill + ); + + if (lastSync < this.lastModified) { + lastSync = this.lastModified; + await this.setLastSync(lastSync); + } + } + } + + count.newFailed = 0; + for (let item of this.previousFailed) { + // Anything that failed in the current sync that also failed in + // the previous sync means there is likely something wrong with + // the record, we remove it from trying again to prevent + // infinitely syncing corrupted records + if (failedInPreviousSync.has(item)) { + this.previousFailed.delete(item); + } else { + // otherwise it's a new failed and we count it as so + ++count.newFailed; + } + } + + count.succeeded = Math.max(0, count.applied - count.failed); + this._log.info( + [ + "Records:", + count.applied, + "applied,", + count.succeeded, + "successfully,", + count.failed, + "failed to apply,", + count.newFailed, + "newly failed to apply,", + count.reconciled, + "reconciled.", + ].join(" ") + ); + Observers.notify("weave:engine:sync:applied", count, this.name); + }, + + async _maybeReconcile(item) { + let key = this.service.collectionKeys.keyForCollection(this.name); + + // Grab a later last modified if possible + if (this.lastModified == null || item.modified > this.lastModified) { + this.lastModified = item.modified; + } + + try { + try { + await item.decrypt(key); + } catch (ex) { + if (!Utils.isHMACMismatch(ex)) { + throw ex; + } + let strategy = await this.handleHMACMismatch(item, true); + if (strategy == SyncEngine.kRecoveryStrategy.retry) { + // You only get one retry. + try { + // Try decrypting again, typically because we've got new keys. + this._log.info("Trying decrypt again..."); + key = this.service.collectionKeys.keyForCollection(this.name); + await item.decrypt(key); + strategy = null; + } catch (ex) { + if (!Utils.isHMACMismatch(ex)) { + throw ex; + } + strategy = await this.handleHMACMismatch(item, false); + } + } + + switch (strategy) { + case null: + // Retry succeeded! No further handling. + break; + case SyncEngine.kRecoveryStrategy.retry: + this._log.debug("Ignoring second retry suggestion."); + // Fall through to error case. + case SyncEngine.kRecoveryStrategy.error: + this._log.warn("Error decrypting record", ex); + return { shouldApply: false, error: ex }; + case SyncEngine.kRecoveryStrategy.ignore: + this._log.debug( + "Ignoring record " + item.id + " with bad HMAC: already handled." + ); + return { shouldApply: false, error: null }; + } + } + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.warn("Error decrypting record", ex); + return { shouldApply: false, error: ex }; + } + + if (this._shouldDeleteRemotely(item)) { + this._log.trace("Deleting item from server without applying", item); + await this._deleteId(item.id); + return { shouldApply: false, error: null }; + } + + let shouldApply; + try { + shouldApply = await this._reconcile(item); + } catch (ex) { + if (ex.code == SyncEngine.prototype.eEngineAbortApplyIncoming) { + this._log.warn("Reconciliation failed: aborting incoming processing."); + throw ex.cause; + } else if (!Async.isShutdownException(ex)) { + this._log.warn("Failed to reconcile incoming record " + item.id, ex); + return { shouldApply: false, error: ex }; + } else { + throw ex; + } + } + + if (!shouldApply) { + this._log.trace("Skipping reconciled incoming item " + item.id); + } + + return { shouldApply, error: null }; + }, + + async _applyRecords(records, countTelemetry) { + this._tracker.ignoreAll = true; + try { + let failedIDs = await this._store.applyIncomingBatch( + records, + countTelemetry + ); + return failedIDs; + } catch (ex) { + // Catch any error that escapes from applyIncomingBatch. At present + // those will all be abort events. + this._log.warn("Got exception, aborting processIncoming", ex); + throw ex; + } finally { + this._tracker.ignoreAll = false; + } + }, + + // Indicates whether an incoming item should be deleted from the server at + // the end of the sync. Engines can override this method to clean up records + // that shouldn't be on the server. + _shouldDeleteRemotely(remoteItem) { + return false; + }, + + /** + * Find a GUID of an item that is a duplicate of the incoming item but happens + * to have a different GUID + * + * @return GUID of the similar item; falsy otherwise + */ + async _findDupe(item) { + // By default, assume there's no dupe items for the engine + }, + + /** + * Called before a remote record is discarded due to failed reconciliation. + * Used by bookmark sync to merge folder child orders. + */ + beforeRecordDiscard(localRecord, remoteRecord, remoteIsNewer) {}, + + // Called when the server has a record marked as deleted, but locally we've + // changed it more recently than the deletion. If we return false, the + // record will be deleted locally. If we return true, we'll reupload the + // record to the server -- any extra work that's needed as part of this + // process should be done at this point (such as mark the record's parent + // for reuploading in the case of bookmarks). + async _shouldReviveRemotelyDeletedRecord(remoteItem) { + return true; + }, + + async _deleteId(id) { + await this._tracker.removeChangedID(id); + this._noteDeletedId(id); + }, + + // Marks an ID for deletion at the end of the sync. + _noteDeletedId(id) { + if (this._delete.ids == null) { + this._delete.ids = [id]; + } else { + this._delete.ids.push(id); + } + }, + + async _switchItemToDupe(localDupeGUID, incomingItem) { + // The local, duplicate ID is always deleted on the server. + await this._deleteId(localDupeGUID); + + // We unconditionally change the item's ID in case the engine knows of + // an item but doesn't expose it through itemExists. If the API + // contract were stronger, this could be changed. + this._log.debug( + "Switching local ID to incoming: " + + localDupeGUID + + " -> " + + incomingItem.id + ); + return this._store.changeItemID(localDupeGUID, incomingItem.id); + }, + + /** + * Reconcile incoming record with local state. + * + * This function essentially determines whether to apply an incoming record. + * + * @param item + * Record from server to be tested for application. + * @return boolean + * Truthy if incoming record should be applied. False if not. + */ + async _reconcile(item) { + if (this._log.level <= Log.Level.Trace) { + this._log.trace("Incoming: " + item); + } + + // We start reconciling by collecting a bunch of state. We do this here + // because some state may change during the course of this function and we + // need to operate on the original values. + let existsLocally = await this._store.itemExists(item.id); + let locallyModified = this._modified.has(item.id); + + // TODO Handle clock drift better. Tracked in bug 721181. + let remoteAge = Resource.serverTime - item.modified; + let localAge = locallyModified + ? Date.now() / 1000 - this._modified.getModifiedTimestamp(item.id) + : null; + let remoteIsNewer = remoteAge < localAge; + + this._log.trace( + "Reconciling " + + item.id + + ". exists=" + + existsLocally + + "; modified=" + + locallyModified + + "; local age=" + + localAge + + "; incoming age=" + + remoteAge + ); + + // We handle deletions first so subsequent logic doesn't have to check + // deleted flags. + if (item.deleted) { + // If the item doesn't exist locally, there is nothing for us to do. We + // can't check for duplicates because the incoming record has no data + // which can be used for duplicate detection. + if (!existsLocally) { + this._log.trace( + "Ignoring incoming item because it was deleted and " + + "the item does not exist locally." + ); + return false; + } + + // We decide whether to process the deletion by comparing the record + // ages. If the item is not modified locally, the remote side wins and + // the deletion is processed. If it is modified locally, we take the + // newer record. + if (!locallyModified) { + this._log.trace( + "Applying incoming delete because the local item " + + "exists and isn't modified." + ); + return true; + } + this._log.trace("Incoming record is deleted but we had local changes."); + + if (remoteIsNewer) { + this._log.trace("Remote record is newer -- deleting local record."); + return true; + } + // If the local record is newer, we defer to individual engines for + // how to handle this. By default, we revive the record. + let willRevive = await this._shouldReviveRemotelyDeletedRecord(item); + this._log.trace("Local record is newer -- reviving? " + willRevive); + + return !willRevive; + } + + // At this point the incoming record is not for a deletion and must have + // data. If the incoming record does not exist locally, we check for a local + // duplicate existing under a different ID. The default implementation of + // _findDupe() is empty, so engines have to opt in to this functionality. + // + // If we find a duplicate, we change the local ID to the incoming ID and we + // refresh the metadata collected above. See bug 710448 for the history + // of this logic. + if (!existsLocally) { + let localDupeGUID = await this._findDupe(item); + if (localDupeGUID) { + this._log.trace( + "Local item " + + localDupeGUID + + " is a duplicate for " + + "incoming item " + + item.id + ); + + // The current API contract does not mandate that the ID returned by + // _findDupe() actually exists. Therefore, we have to perform this + // check. + existsLocally = await this._store.itemExists(localDupeGUID); + + // If the local item was modified, we carry its metadata forward so + // appropriate reconciling can be performed. + if (this._modified.has(localDupeGUID)) { + locallyModified = true; + localAge = + this._tracker._now() - + this._modified.getModifiedTimestamp(localDupeGUID); + remoteIsNewer = remoteAge < localAge; + + this._modified.changeID(localDupeGUID, item.id); + } else { + locallyModified = false; + localAge = null; + } + + // Tell the engine to do whatever it needs to switch the items. + await this._switchItemToDupe(localDupeGUID, item); + + this._log.debug( + "Local item after duplication: age=" + + localAge + + "; modified=" + + locallyModified + + "; exists=" + + existsLocally + ); + } else { + this._log.trace("No duplicate found for incoming item: " + item.id); + } + } + + // At this point we've performed duplicate detection. But, nothing here + // should depend on duplicate detection as the above should have updated + // state seamlessly. + + if (!existsLocally) { + // If the item doesn't exist locally and we have no local modifications + // to the item (implying that it was not deleted), always apply the remote + // item. + if (!locallyModified) { + this._log.trace( + "Applying incoming because local item does not exist " + + "and was not deleted." + ); + return true; + } + + // If the item was modified locally but isn't present, it must have + // been deleted. If the incoming record is younger, we restore from + // that record. + if (remoteIsNewer) { + this._log.trace( + "Applying incoming because local item was deleted " + + "before the incoming item was changed." + ); + this._modified.delete(item.id); + return true; + } + + this._log.trace( + "Ignoring incoming item because the local item's " + + "deletion is newer." + ); + return false; + } + + // If the remote and local records are the same, there is nothing to be + // done, so we don't do anything. In the ideal world, this logic wouldn't + // be here and the engine would take a record and apply it. The reason we + // want to defer this logic is because it would avoid a redundant and + // possibly expensive dip into the storage layer to query item state. + // This should get addressed in the async rewrite, so we ignore it for now. + let localRecord = await this._createRecord(item.id); + let recordsEqual = Utils.deepEquals(item.cleartext, localRecord.cleartext); + + // If the records are the same, we don't need to do anything. This does + // potentially throw away a local modification time. But, if the records + // are the same, does it matter? + if (recordsEqual) { + this._log.trace( + "Ignoring incoming item because the local item is identical." + ); + + this._modified.delete(item.id); + return false; + } + + // At this point the records are different. + + // If we have no local modifications, always take the server record. + if (!locallyModified) { + this._log.trace("Applying incoming record because no local conflicts."); + return true; + } + + // At this point, records are different and the local record is modified. + // We resolve conflicts by record age, where the newest one wins. This does + // result in data loss and should be handled by giving the engine an + // opportunity to merge the records. Bug 720592 tracks this feature. + this._log.warn( + "DATA LOSS: Both local and remote changes to record: " + item.id + ); + if (!remoteIsNewer) { + this.beforeRecordDiscard(localRecord, item, remoteIsNewer); + } + return remoteIsNewer; + }, + + // Upload outgoing records. + async _uploadOutgoing() { + this._log.trace("Uploading local changes to server."); + + // collection we'll upload + let up = new Collection(this.engineURL, null, this.service); + let modifiedIDs = new Set(this._modified.ids()); + let countTelemetry = new SyncedRecordsTelemetry(); + let counts = countTelemetry.outgoingCounts; + this._log.info(`Uploading ${modifiedIDs.size} outgoing records`); + if (modifiedIDs.size) { + counts.sent = modifiedIDs.size; + + let failed = []; + let successful = []; + let lastSync = await this.getLastSync(); + let handleResponse = async (postQueue, resp, batchOngoing) => { + // Note: We don't want to update this.lastSync, or this._modified until + // the batch is complete, however we want to remember success/failure + // indicators for when that happens. + if (!resp.success) { + this._log.debug(`Uploading records failed: ${resp.status}`); + resp.failureCode = + resp.status == 412 ? ENGINE_BATCH_INTERRUPTED : ENGINE_UPLOAD_FAIL; + throw resp; + } + + // Update server timestamp from the upload. + failed = failed.concat(Object.keys(resp.obj.failed)); + successful = successful.concat(resp.obj.success); + + if (batchOngoing) { + // Nothing to do yet + return; + } + + if (failed.length && this._log.level <= Log.Level.Debug) { + this._log.debug( + "Records that will be uploaded again because " + + "the server couldn't store them: " + + failed.join(", ") + ); + } + + counts.failed += failed.length; + Object.values(failed).forEach(message => { + countTelemetry.addOutgoingFailedReason(message); + }); + + for (let id of successful) { + this._modified.delete(id); + } + + await this._onRecordsWritten( + successful, + failed, + postQueue.lastModified + ); + + // Advance lastSync since we've finished the batch. + if (postQueue.lastModified > lastSync) { + lastSync = postQueue.lastModified; + await this.setLastSync(lastSync); + } + + // clear for next batch + failed.length = 0; + successful.length = 0; + }; + + let postQueue = up.newPostQueue(this._log, lastSync, handleResponse); + + for (let id of modifiedIDs) { + let out; + let ok = false; + try { + out = await this._createRecord(id); + if (this._log.level <= Log.Level.Trace) { + this._log.trace("Outgoing: " + out); + } + await out.encrypt( + this.service.collectionKeys.keyForCollection(this.name) + ); + ok = true; + } catch (ex) { + this._log.warn("Error creating record", ex); + ++counts.failed; + countTelemetry.addOutgoingFailedReason(ex.message); + if (Async.isShutdownException(ex) || !this.allowSkippedRecord) { + if (!this.allowSkippedRecord) { + // Don't bother for shutdown errors + Observers.notify("weave:engine:sync:uploaded", counts, this.name); + } + throw ex; + } + } + if (ok) { + let { enqueued, error } = await postQueue.enqueue(out); + if (!enqueued) { + ++counts.failed; + countTelemetry.addOutgoingFailedReason(error.message); + if (!this.allowSkippedRecord) { + Observers.notify("weave:engine:sync:uploaded", counts, this.name); + this._log.warn( + `Failed to enqueue record "${id}" (aborting)`, + error + ); + throw error; + } + this._modified.delete(id); + this._log.warn( + `Failed to enqueue record "${id}" (skipping)`, + error + ); + } + } + await Async.promiseYield(); + } + await postQueue.flush(true); + } + + if (counts.sent || counts.failed) { + Observers.notify("weave:engine:sync:uploaded", counts, this.name); + } + }, + + async _onRecordsWritten(succeeded, failed, serverModifiedTime) { + // Implement this method to take specific actions against successfully + // uploaded records and failed records. + }, + + // Any cleanup necessary. + // Save the current snapshot so as to calculate changes at next sync + async _syncFinish() { + this._log.trace("Finishing up sync"); + + let doDelete = async (key, val) => { + let coll = new Collection(this.engineURL, this._recordObj, this.service); + coll[key] = val; + await coll.delete(); + }; + + for (let [key, val] of Object.entries(this._delete)) { + // Remove the key for future uses + delete this._delete[key]; + + this._log.trace("doing post-sync deletions", { key, val }); + // Send a simple delete for the property + if (key != "ids" || val.length <= 100) { + await doDelete(key, val); + } else { + // For many ids, split into chunks of at most 100 + while (val.length) { + await doDelete(key, val.slice(0, 100)); + val = val.slice(100); + } + } + } + this.hasSyncedThisSession = true; + await this._tracker.asyncObserver.promiseObserversComplete(); + }, + + async _syncCleanup() { + try { + // Mark failed WBOs as changed again so they are reuploaded next time. + await this.trackRemainingChanges(); + } finally { + this._modified.clear(); + } + }, + + async _sync() { + try { + Async.checkAppReady(); + await this._syncStartup(); + Async.checkAppReady(); + Observers.notify("weave:engine:sync:status", "process-incoming"); + await this._processIncoming(); + Async.checkAppReady(); + Observers.notify("weave:engine:sync:status", "upload-outgoing"); + try { + await this._uploadOutgoing(); + Async.checkAppReady(); + await this._syncFinish(); + } catch (ex) { + if (!ex.status || ex.status != 412) { + throw ex; + } + // a 412 posting just means another client raced - but we don't want + // to treat that as a sync error - the next sync is almost certain + // to work. + this._log.warn("412 error during sync - will retry."); + } + } finally { + await this._syncCleanup(); + } + }, + + async canDecrypt() { + // Report failure even if there's nothing to decrypt + let canDecrypt = false; + + // Fetch the most recently uploaded record and try to decrypt it + let test = new Collection(this.engineURL, this._recordObj, this.service); + test.limit = 1; + test.sort = "newest"; + test.full = true; + + let key = this.service.collectionKeys.keyForCollection(this.name); + + // Any failure fetching/decrypting will just result in false + try { + this._log.trace("Trying to decrypt a record from the server.."); + let json = (await test.get()).obj[0]; + let record = new this._recordObj(); + record.deserialize(json); + await record.decrypt(key); + canDecrypt = true; + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.debug("Failed test decrypt", ex); + } + + return canDecrypt; + }, + + /** + * Deletes the collection for this engine on the server, and removes all local + * Sync metadata for this engine. This does *not* remove any existing data on + * other clients. This is called when we reset the sync ID. + */ + async wipeServer() { + await this._deleteServerCollection(); + await this._resetClient(); + }, + + /** + * Deletes the collection for this engine on the server, without removing + * any local Sync metadata or user data. Deleting the collection will not + * remove any user data on other clients, but will force other clients to + * start over as a first sync. + */ + async _deleteServerCollection() { + let response = await this.service.resource(this.engineURL).delete(); + if (response.status != 200 && response.status != 404) { + throw response; + } + }, + + async removeClientData() { + // Implement this method in engines that store client specific data + // on the server. + }, + + /* + * Decide on (and partially effect) an error-handling strategy. + * + * Asks the Service to respond to an HMAC error, which might result in keys + * being downloaded. That call returns true if an action which might allow a + * retry to occur. + * + * If `mayRetry` is truthy, and the Service suggests a retry, + * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns + * kRecoveryStrategy.error. + * + * Subclasses of SyncEngine can override this method to allow for different + * behavior -- e.g., to delete and ignore erroneous entries. + * + * All return values will be part of the kRecoveryStrategy enumeration. + */ + async handleHMACMismatch(item, mayRetry) { + // By default we either try again, or bail out noisily. + return (await this.service.handleHMACEvent()) && mayRetry + ? SyncEngine.kRecoveryStrategy.retry + : SyncEngine.kRecoveryStrategy.error; + }, + + /** + * Returns a changeset containing all items in the store. The default + * implementation returns a changeset with timestamps from long ago, to + * ensure we always use the remote version if one exists. + * + * This function is only called for the first sync. Subsequent syncs call + * `pullNewChanges`. + * + * @return A `Changeset` object. + */ + async pullAllChanges() { + let changes = {}; + let ids = await this._store.getAllIDs(); + for (let id in ids) { + changes[id] = 0; + } + return changes; + }, + + /* + * Returns a changeset containing entries for all currently tracked items. + * The default implementation returns a changeset with timestamps indicating + * when the item was added to the tracker. + * + * @return A `Changeset` object. + */ + async pullNewChanges() { + await this._tracker.asyncObserver.promiseObserversComplete(); + return this.getChangedIDs(); + }, + + /** + * Adds all remaining changeset entries back to the tracker, typically for + * items that failed to upload. This method is called at the end of each sync. + * + */ + async trackRemainingChanges() { + for (let [id, change] of this._modified.entries()) { + await this._tracker.addChangedID(id, change); + } + }, + + /** + * Removes all local Sync metadata for this engine, but keeps all existing + * local user data. + */ + async resetClient() { + return this._notify("reset-client", this.name, this._resetClient)(); + }, + + async _resetClient() { + await this.resetLastSync(); + this.hasSyncedThisSession = false; + this.previousFailed = new SerializableSet(); + this.toFetch = new SerializableSet(); + }, + + /** + * Removes all local Sync metadata and user data for this engine. + */ + async wipeClient() { + return this._notify("wipe-client", this.name, this._wipeClient)(); + }, + + async _wipeClient() { + await this.resetClient(); + this._log.debug("Deleting all local data"); + this._tracker.ignoreAll = true; + await this._store.wipe(); + this._tracker.ignoreAll = false; + this._tracker.clearChangedIDs(); + }, + + /** + * If one exists, initialize and return a validator for this engine (which + * must have a `validate(engine)` method that returns a promise to an object + * with a getSummary method). Otherwise return null. + */ + getValidator() { + return null; + }, + + async finalize() { + Services.prefs.removeObserver( + `${PREFS_BRANCH}engine.${this.prefName}`, + this.asyncObserver + ); + await this.asyncObserver.promiseObserversComplete(); + await this._tracker.finalize(); + await this._toFetchStorage.finalize(); + await this._previousFailedStorage.finalize(); + }, + + // Returns a new watchdog. Exposed for tests. + _newWatchdog() { + return Async.watchdog(); + }, +}; + +/** + * A changeset is created for each sync in `Engine::get{Changed, All}IDs`, + * and stores opaque change data for tracked IDs. The default implementation + * only records timestamps, though engines can extend this to store additional + * data for each entry. + */ +export class Changeset { + // Creates an empty changeset. + constructor() { + this.changes = {}; + } + + // Returns the last modified time, in seconds, for an entry in the changeset. + // `id` is guaranteed to be in the set. + getModifiedTimestamp(id) { + return this.changes[id]; + } + + // Adds a change for a tracked ID to the changeset. + set(id, change) { + this.changes[id] = change; + } + + // Adds multiple entries to the changeset, preserving existing entries. + insert(changes) { + Object.assign(this.changes, changes); + } + + // Overwrites the existing set of tracked changes with new entries. + replace(changes) { + this.changes = changes; + } + + // Indicates whether an entry is in the changeset. + has(id) { + return id in this.changes; + } + + // Deletes an entry from the changeset. Used to clean up entries for + // reconciled and successfully uploaded records. + delete(id) { + delete this.changes[id]; + } + + // Changes the ID of an entry in the changeset. Used when reconciling + // duplicates that have local changes. + changeID(oldID, newID) { + this.changes[newID] = this.changes[oldID]; + delete this.changes[oldID]; + } + + // Returns an array of all tracked IDs in this changeset. + ids() { + return Object.keys(this.changes); + } + + // Returns an array of `[id, change]` tuples. Used to repopulate the tracker + // with entries for failed uploads at the end of a sync. + entries() { + return Object.entries(this.changes); + } + + // Returns the number of entries in this changeset. + count() { + return this.ids().length; + } + + // Clears the changeset. + clear() { + this.changes = {}; + } +} diff --git a/services/sync/modules/engines/addons.sys.mjs b/services/sync/modules/engines/addons.sys.mjs new file mode 100644 index 0000000000..782d23239e --- /dev/null +++ b/services/sync/modules/engines/addons.sys.mjs @@ -0,0 +1,818 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file defines the add-on sync functionality. + * + * There are currently a number of known limitations: + * - We only sync XPI extensions and themes available from addons.mozilla.org. + * We hope to expand support for other add-ons eventually. + * - We only attempt syncing of add-ons between applications of the same type. + * This means add-ons will not synchronize between Firefox desktop and + * Firefox mobile, for example. This is because of significant add-on + * incompatibility between application types. + * + * Add-on records exist for each known {add-on, app-id} pair in the Sync client + * set. Each record has a randomly chosen GUID. The records then contain + * basic metadata about the add-on. + * + * We currently synchronize: + * + * - Installations + * - Uninstallations + * - User enabling and disabling + * + * Synchronization is influenced by the following preferences: + * + * - services.sync.addons.ignoreUserEnabledChanges + * - services.sync.addons.trustedSourceHostnames + * + * and also influenced by whether addons have repository caching enabled and + * whether they allow installation of addons from insecure options (both of + * which are themselves influenced by the "extensions." pref branch) + * + * See the documentation in all.js for the behavior of these prefs. + */ + +import { AddonUtils } from "resource://services-sync/addonutils.sys.mjs"; +import { AddonsReconciler } from "resource://services-sync/addonsreconciler.sys.mjs"; +import { + Store, + SyncEngine, + LegacyTracker, +} from "resource://services-sync/engines.sys.mjs"; +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs"; +import { CollectionValidator } from "resource://services-sync/collection_validator.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs", +}); + +// 7 days in milliseconds. +const PRUNE_ADDON_CHANGES_THRESHOLD = 60 * 60 * 24 * 7 * 1000; + +/** + * AddonRecord represents the state of an add-on in an application. + * + * Each add-on has its own record for each application ID it is installed + * on. + * + * The ID of add-on records is a randomly-generated GUID. It is random instead + * of deterministic so the URIs of the records cannot be guessed and so + * compromised server credentials won't result in disclosure of the specific + * add-ons present in a Sync account. + * + * The record contains the following fields: + * + * addonID + * ID of the add-on. This correlates to the "id" property on an Addon type. + * + * applicationID + * The application ID this record is associated with. + * + * enabled + * Boolean stating whether add-on is enabled or disabled by the user. + * + * source + * String indicating where an add-on is from. Currently, we only support + * the value "amo" which indicates that the add-on came from the official + * add-ons repository, addons.mozilla.org. In the future, we may support + * installing add-ons from other sources. This provides a future-compatible + * mechanism for clients to only apply records they know how to handle. + */ +function AddonRecord(collection, id) { + CryptoWrapper.call(this, collection, id); +} +AddonRecord.prototype = { + _logName: "Record.Addon", +}; +Object.setPrototypeOf(AddonRecord.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(AddonRecord, "cleartext", [ + "addonID", + "applicationID", + "enabled", + "source", +]); + +/** + * The AddonsEngine handles synchronization of add-ons between clients. + * + * The engine maintains an instance of an AddonsReconciler, which is the entity + * maintaining state for add-ons. It provides the history and tracking APIs + * that AddonManager doesn't. + * + * The engine instance overrides a handful of functions on the base class. The + * rationale for each is documented by that function. + */ +export function AddonsEngine(service) { + SyncEngine.call(this, "Addons", service); + + this._reconciler = new AddonsReconciler(this._tracker.asyncObserver); +} + +AddonsEngine.prototype = { + _storeObj: AddonsStore, + _trackerObj: AddonsTracker, + _recordObj: AddonRecord, + version: 1, + + syncPriority: 5, + + _reconciler: null, + + async initialize() { + await SyncEngine.prototype.initialize.call(this); + await this._reconciler.ensureStateLoaded(); + }, + + /** + * Override parent method to find add-ons by their public ID, not Sync GUID. + */ + async _findDupe(item) { + let id = item.addonID; + + // The reconciler should have been updated at the top of the sync, so we + // can assume it is up to date when this function is called. + let addons = this._reconciler.addons; + if (!(id in addons)) { + return null; + } + + let addon = addons[id]; + if (addon.guid != item.id) { + return addon.guid; + } + + return null; + }, + + /** + * Override getChangedIDs to pull in tracker changes plus changes from the + * reconciler log. + */ + async getChangedIDs() { + let changes = {}; + const changedIDs = await this._tracker.getChangedIDs(); + for (let [id, modified] of Object.entries(changedIDs)) { + changes[id] = modified; + } + + let lastSync = await this.getLastSync(); + let lastSyncDate = new Date(lastSync * 1000); + + // The reconciler should have been refreshed at the beginning of a sync and + // we assume this function is only called from within a sync. + let reconcilerChanges = this._reconciler.getChangesSinceDate(lastSyncDate); + let addons = this._reconciler.addons; + for (let change of reconcilerChanges) { + let changeTime = change[0]; + let id = change[2]; + + if (!(id in addons)) { + continue; + } + + // Keep newest modified time. + if (id in changes && changeTime < changes[id]) { + continue; + } + + if (!(await this.isAddonSyncable(addons[id]))) { + continue; + } + + this._log.debug("Adding changed add-on from changes log: " + id); + let addon = addons[id]; + changes[addon.guid] = changeTime.getTime() / 1000; + } + + return changes; + }, + + /** + * Override start of sync function to refresh reconciler. + * + * Many functions in this class assume the reconciler is refreshed at the + * top of a sync. If this ever changes, those functions should be revisited. + * + * Technically speaking, we don't need to refresh the reconciler on every + * sync since it is installed as an AddonManager listener. However, add-ons + * are complicated and we force a full refresh, just in case the listeners + * missed something. + */ + async _syncStartup() { + // We refresh state before calling parent because syncStartup in the parent + // looks for changed IDs, which is dependent on add-on state being up to + // date. + await this._refreshReconcilerState(); + return SyncEngine.prototype._syncStartup.call(this); + }, + + /** + * Override end of sync to perform a little housekeeping on the reconciler. + * + * We prune changes to prevent the reconciler state from growing without + * bound. Even if it grows unbounded, there would have to be many add-on + * changes (thousands) for it to slow things down significantly. This is + * highly unlikely to occur. Still, we exercise defense just in case. + */ + async _syncCleanup() { + let lastSync = await this.getLastSync(); + let ms = 1000 * lastSync - PRUNE_ADDON_CHANGES_THRESHOLD; + this._reconciler.pruneChangesBeforeDate(new Date(ms)); + return SyncEngine.prototype._syncCleanup.call(this); + }, + + /** + * Helper function to ensure reconciler is up to date. + * + * This will load the reconciler's state from the file + * system (if needed) and refresh the state of the reconciler. + */ + async _refreshReconcilerState() { + this._log.debug("Refreshing reconciler state"); + return this._reconciler.refreshGlobalState(); + }, + + // Returns a promise + isAddonSyncable(addon, ignoreRepoCheck) { + return this._store.isAddonSyncable(addon, ignoreRepoCheck); + }, +}; +Object.setPrototypeOf(AddonsEngine.prototype, SyncEngine.prototype); + +/** + * This is the primary interface between Sync and the Addons Manager. + * + * In addition to the core store APIs, we provide convenience functions to wrap + * Add-on Manager APIs with Sync-specific semantics. + */ +function AddonsStore(name, engine) { + Store.call(this, name, engine); +} +AddonsStore.prototype = { + // Define the add-on types (.type) that we support. + _syncableTypes: ["extension", "theme"], + + _extensionsPrefs: Services.prefs.getBranch("extensions."), + + get reconciler() { + return this.engine._reconciler; + }, + + /** + * Override applyIncoming to filter out records we can't handle. + */ + async applyIncoming(record) { + // The fields we look at aren't present when the record is deleted. + if (!record.deleted) { + // Ignore records not belonging to our application ID because that is the + // current policy. + if (record.applicationID != Services.appinfo.ID) { + this._log.info( + "Ignoring incoming record from other App ID: " + record.id + ); + return; + } + + // Ignore records that aren't from the official add-on repository, as that + // is our current policy. + if (record.source != "amo") { + this._log.info( + "Ignoring unknown add-on source (" + + record.source + + ")" + + " for " + + record.id + ); + return; + } + } + + // Ignore incoming records for which an existing non-syncable addon + // exists. Note that we do not insist that the addon manager already have + // metadata for this addon - it's possible our reconciler previously saw the + // addon but the addon-manager cache no longer has it - which is fine for a + // new incoming addon. + // (Note that most other cases where the addon-manager cache is invalid + // doesn't get this treatment because that cache self-repairs after some + // time - but it only re-populates addons which are currently installed.) + let existingMeta = this.reconciler.addons[record.addonID]; + if ( + existingMeta && + !(await this.isAddonSyncable(existingMeta, /* ignoreRepoCheck */ true)) + ) { + this._log.info( + "Ignoring incoming record for an existing but non-syncable addon", + record.addonID + ); + return; + } + + await Store.prototype.applyIncoming.call(this, record); + }, + + /** + * Provides core Store API to create/install an add-on from a record. + */ + async create(record) { + // This will throw if there was an error. This will get caught by the sync + // engine and the record will try to be applied later. + const results = await AddonUtils.installAddons([ + { + id: record.addonID, + syncGUID: record.id, + enabled: record.enabled, + requireSecureURI: this._extensionsPrefs.getBoolPref( + "install.requireSecureOrigin", + true + ), + }, + ]); + + if (results.skipped.includes(record.addonID)) { + this._log.info("Add-on skipped: " + record.addonID); + // Just early-return for skipped addons - we don't want to arrange to + // try again next time because the condition that caused up to skip + // will remain true for this addon forever. + return; + } + + let addon; + for (let a of results.addons) { + if (a.id == record.addonID) { + addon = a; + break; + } + } + + // This should never happen, but is present as a fail-safe. + if (!addon) { + throw new Error("Add-on not found after install: " + record.addonID); + } + + this._log.info("Add-on installed: " + record.addonID); + }, + + /** + * Provides core Store API to remove/uninstall an add-on from a record. + */ + async remove(record) { + // If this is called, the payload is empty, so we have to find by GUID. + let addon = await this.getAddonByGUID(record.id); + if (!addon) { + // We don't throw because if the add-on could not be found then we assume + // it has already been uninstalled and there is nothing for this function + // to do. + return; + } + + this._log.info("Uninstalling add-on: " + addon.id); + await AddonUtils.uninstallAddon(addon); + }, + + /** + * Provides core Store API to update an add-on from a record. + */ + async update(record) { + let addon = await this.getAddonByID(record.addonID); + + // update() is called if !this.itemExists. And, since itemExists consults + // the reconciler only, we need to take care of some corner cases. + // + // First, the reconciler could know about an add-on that was uninstalled + // and no longer present in the add-ons manager. + if (!addon) { + await this.create(record); + return; + } + + // It's also possible that the add-on is non-restartless and has pending + // install/uninstall activity. + // + // We wouldn't get here if the incoming record was for a deletion. So, + // check for pending uninstall and cancel if necessary. + if (addon.pendingOperations & lazy.AddonManager.PENDING_UNINSTALL) { + addon.cancelUninstall(); + + // We continue with processing because there could be state or ID change. + } + + await this.updateUserDisabled(addon, !record.enabled); + }, + + /** + * Provide core Store API to determine if a record exists. + */ + async itemExists(guid) { + let addon = this.reconciler.getAddonStateFromSyncGUID(guid); + + return !!addon; + }, + + /** + * Create an add-on record from its GUID. + * + * @param guid + * Add-on GUID (from extensions DB) + * @param collection + * Collection to add record to. + * + * @return AddonRecord instance + */ + async createRecord(guid, collection) { + let record = new AddonRecord(collection, guid); + record.applicationID = Services.appinfo.ID; + + let addon = this.reconciler.getAddonStateFromSyncGUID(guid); + + // If we don't know about this GUID or if it has been uninstalled, we mark + // the record as deleted. + if (!addon || !addon.installed) { + record.deleted = true; + return record; + } + + record.modified = addon.modified.getTime() / 1000; + + record.addonID = addon.id; + record.enabled = addon.enabled; + + // This needs to be dynamic when add-ons don't come from AddonRepository. + record.source = "amo"; + + return record; + }, + + /** + * Changes the id of an add-on. + * + * This implements a core API of the store. + */ + async changeItemID(oldID, newID) { + // We always update the GUID in the reconciler because it will be + // referenced later in the sync process. + let state = this.reconciler.getAddonStateFromSyncGUID(oldID); + if (state) { + state.guid = newID; + await this.reconciler.saveState(); + } + + let addon = await this.getAddonByGUID(oldID); + if (!addon) { + this._log.debug( + "Cannot change item ID (" + + oldID + + ") in Add-on " + + "Manager because old add-on not present: " + + oldID + ); + return; + } + + addon.syncGUID = newID; + }, + + /** + * Obtain the set of all syncable add-on Sync GUIDs. + * + * This implements a core Store API. + */ + async getAllIDs() { + let ids = {}; + + let addons = this.reconciler.addons; + for (let id in addons) { + let addon = addons[id]; + if (await this.isAddonSyncable(addon)) { + ids[addon.guid] = true; + } + } + + return ids; + }, + + /** + * Wipe engine data. + * + * This uninstalls all syncable addons from the application. In case of + * error, it logs the error and keeps trying with other add-ons. + */ + async wipe() { + this._log.info("Processing wipe."); + + await this.engine._refreshReconcilerState(); + + // We only wipe syncable add-ons. Wipe is a Sync feature not a security + // feature. + let ids = await this.getAllIDs(); + for (let guid in ids) { + let addon = await this.getAddonByGUID(guid); + if (!addon) { + this._log.debug( + "Ignoring add-on because it couldn't be obtained: " + guid + ); + continue; + } + + this._log.info("Uninstalling add-on as part of wipe: " + addon.id); + await Utils.catch.call(this, () => addon.uninstall())(); + } + }, + + /** ************************************************************************* + * Functions below are unique to this store and not part of the Store API * + ***************************************************************************/ + + /** + * Obtain an add-on from its public ID. + * + * @param id + * Add-on ID + * @return Addon or undefined if not found + */ + async getAddonByID(id) { + return lazy.AddonManager.getAddonByID(id); + }, + + /** + * Obtain an add-on from its Sync GUID. + * + * @param guid + * Add-on Sync GUID + * @return DBAddonInternal or null + */ + async getAddonByGUID(guid) { + return lazy.AddonManager.getAddonBySyncGUID(guid); + }, + + /** + * Determines whether an add-on is suitable for Sync. + * + * @param addon + * Addon instance + * @param ignoreRepoCheck + * Should we skip checking the Addons repository (primarially useful + * for testing and validation). + * @return Boolean indicating whether it is appropriate for Sync + */ + async isAddonSyncable(addon, ignoreRepoCheck = false) { + // Currently, we limit syncable add-ons to those that are: + // 1) In a well-defined set of types + // 2) Installed in the current profile + // 3) Not installed by a foreign entity (i.e. installed by the app) + // since they act like global extensions. + // 4) Is not a hotfix. + // 5) The addons XPIProvider doesn't veto it (i.e not being installed in + // the profile directory, or any other reasons it says the addon can't + // be synced) + // 6) Are installed from AMO + + // We could represent the test as a complex boolean expression. We go the + // verbose route so the failure reason is logged. + if (!addon) { + this._log.debug("Null object passed to isAddonSyncable."); + return false; + } + + if (!this._syncableTypes.includes(addon.type)) { + this._log.debug( + addon.id + " not syncable: type not in allowed list: " + addon.type + ); + return false; + } + + if (!(addon.scope & lazy.AddonManager.SCOPE_PROFILE)) { + this._log.debug(addon.id + " not syncable: not installed in profile."); + return false; + } + + // If the addon manager says it's not syncable, we skip it. + if (!addon.isSyncable) { + this._log.debug(addon.id + " not syncable: vetoed by the addon manager."); + return false; + } + + // This may be too aggressive. If an add-on is downloaded from AMO and + // manually placed in the profile directory, foreignInstall will be set. + // Arguably, that add-on should be syncable. + // TODO Address the edge case and come up with more robust heuristics. + if (addon.foreignInstall) { + this._log.debug(addon.id + " not syncable: is foreign install."); + return false; + } + + // If the AddonRepository's cache isn't enabled (which it typically isn't + // in tests), getCachedAddonByID always returns null - so skip the check + // in that case. We also provide a way to specifically opt-out of the check + // even if the cache is enabled, which is used by the validators. + if (ignoreRepoCheck || !lazy.AddonRepository.cacheEnabled) { + return true; + } + + let result = await new Promise(res => { + lazy.AddonRepository.getCachedAddonByID(addon.id, res); + }); + + if (!result) { + this._log.debug( + addon.id + " not syncable: add-on not found in add-on repository." + ); + return false; + } + + return this.isSourceURITrusted(result.sourceURI); + }, + + /** + * Determine whether an add-on's sourceURI field is trusted and the add-on + * can be installed. + * + * This function should only ever be called from isAddonSyncable(). It is + * exposed as a separate function to make testing easier. + * + * @param uri + * nsIURI instance to validate + * @return bool + */ + isSourceURITrusted: function isSourceURITrusted(uri) { + // For security reasons, we currently limit synced add-ons to those + // installed from trusted hostname(s). We additionally require TLS with + // the add-ons site to help prevent forgeries. + let trustedHostnames = Svc.PrefBranch.getStringPref( + "addons.trustedSourceHostnames", + "" + ).split(","); + + if (!uri) { + this._log.debug("Undefined argument to isSourceURITrusted()."); + return false; + } + + // Scheme is validated before the hostname because uri.host may not be + // populated for certain schemes. It appears to always be populated for + // https, so we avoid the potential NS_ERROR_FAILURE on field access. + if (uri.scheme != "https") { + this._log.debug("Source URI not HTTPS: " + uri.spec); + return false; + } + + if (!trustedHostnames.includes(uri.host)) { + this._log.debug("Source hostname not trusted: " + uri.host); + return false; + } + + return true; + }, + + /** + * Update the userDisabled flag on an add-on. + * + * This will enable or disable an add-on. It has no return value and does + * not catch or handle exceptions thrown by the addon manager. If no action + * is needed it will return immediately. + * + * @param addon + * Addon instance to manipulate. + * @param value + * Boolean to which to set userDisabled on the passed Addon. + */ + async updateUserDisabled(addon, value) { + if (addon.userDisabled == value) { + return; + } + + // A pref allows changes to the enabled flag to be ignored. + if (Svc.PrefBranch.getBoolPref("addons.ignoreUserEnabledChanges", false)) { + this._log.info( + "Ignoring enabled state change due to preference: " + addon.id + ); + return; + } + + AddonUtils.updateUserDisabled(addon, value); + // updating this flag doesn't send a notification for appDisabled addons, + // meaning the reconciler will not update its state and may resync the + // addon - so explicitly rectify the state (bug 1366994) + if (addon.appDisabled) { + await this.reconciler.rectifyStateFromAddon(addon); + } + }, +}; + +Object.setPrototypeOf(AddonsStore.prototype, Store.prototype); + +/** + * The add-ons tracker keeps track of real-time changes to add-ons. + * + * It hooks up to the reconciler and receives notifications directly from it. + */ +function AddonsTracker(name, engine) { + LegacyTracker.call(this, name, engine); +} +AddonsTracker.prototype = { + get reconciler() { + return this.engine._reconciler; + }, + + get store() { + return this.engine._store; + }, + + /** + * This callback is executed whenever the AddonsReconciler sends out a change + * notification. See AddonsReconciler.addChangeListener(). + */ + async changeListener(date, change, addon) { + this._log.debug("changeListener invoked: " + change + " " + addon.id); + // Ignore changes that occur during sync. + if (this.ignoreAll) { + return; + } + + if (!(await this.store.isAddonSyncable(addon))) { + this._log.debug( + "Ignoring change because add-on isn't syncable: " + addon.id + ); + return; + } + + const added = await this.addChangedID(addon.guid, date.getTime() / 1000); + if (added) { + this.score += SCORE_INCREMENT_XLARGE; + } + }, + + onStart() { + this.reconciler.startListening(); + this.reconciler.addChangeListener(this); + }, + + onStop() { + this.reconciler.removeChangeListener(this); + this.reconciler.stopListening(); + }, +}; + +Object.setPrototypeOf(AddonsTracker.prototype, LegacyTracker.prototype); + +export class AddonValidator extends CollectionValidator { + constructor(engine = null) { + super("addons", "id", ["addonID", "enabled", "applicationID", "source"]); + this.engine = engine; + } + + async getClientItems() { + return lazy.AddonManager.getAllAddons(); + } + + normalizeClientItem(item) { + let enabled = !item.userDisabled; + if (item.pendingOperations & lazy.AddonManager.PENDING_ENABLE) { + enabled = true; + } else if (item.pendingOperations & lazy.AddonManager.PENDING_DISABLE) { + enabled = false; + } + return { + enabled, + id: item.syncGUID, + addonID: item.id, + applicationID: Services.appinfo.ID, + source: "amo", // check item.foreignInstall? + original: item, + }; + } + + async normalizeServerItem(item) { + let guid = await this.engine._findDupe(item); + if (guid) { + item.id = guid; + } + return item; + } + + clientUnderstands(item) { + return item.applicationID === Services.appinfo.ID; + } + + async syncedByClient(item) { + return ( + !item.original.hidden && + !item.original.isSystem && + !( + item.original.pendingOperations & lazy.AddonManager.PENDING_UNINSTALL + ) && + // No need to await the returned promise explicitely: + // |expr1 && expr2| evaluates to expr2 if expr1 is true. + this.engine.isAddonSyncable(item.original, true) + ); + } +} diff --git a/services/sync/modules/engines/bookmarks.sys.mjs b/services/sync/modules/engines/bookmarks.sys.mjs new file mode 100644 index 0000000000..3c1396f67d --- /dev/null +++ b/services/sync/modules/engines/bookmarks.sys.mjs @@ -0,0 +1,950 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs"; +import { + Changeset, + Store, + SyncEngine, + Tracker, +} from "resource://services-sync/engines.sys.mjs"; +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Async: "resource://services-common/async.sys.mjs", + Observers: "resource://services-common/observers.sys.mjs", + PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs", + PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs", + PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", + Resource: "resource://services-sync/resource.sys.mjs", + SyncedBookmarksMirror: "resource://gre/modules/SyncedBookmarksMirror.sys.mjs", +}); + +const PLACES_MAINTENANCE_INTERVAL_SECONDS = 4 * 60 * 60; // 4 hours. + +const FOLDER_SORTINDEX = 1000000; + +// Roots that should be deleted from the server, instead of applied locally. +// This matches `AndroidBrowserBookmarksRepositorySession::forbiddenGUID`, +// but allows tags because we don't want to reparent tag folders or tag items +// to "unfiled". +const FORBIDDEN_INCOMING_IDS = ["pinned", "places", "readinglist"]; + +// Items with these parents should be deleted from the server. We allow +// children of the Places root, to avoid orphaning left pane queries and other +// descendants of custom roots. +const FORBIDDEN_INCOMING_PARENT_IDS = ["pinned", "readinglist"]; + +// The tracker ignores changes made by import and restore, to avoid bumping the +// score and triggering syncs during the process, as well as changes made by +// Sync. +ChromeUtils.defineLazyGetter(lazy, "IGNORED_SOURCES", () => [ + lazy.PlacesUtils.bookmarks.SOURCES.SYNC, + lazy.PlacesUtils.bookmarks.SOURCES.IMPORT, + lazy.PlacesUtils.bookmarks.SOURCES.RESTORE, + lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP, + lazy.PlacesUtils.bookmarks.SOURCES.SYNC_REPARENT_REMOVED_FOLDER_CHILDREN, +]); + +// The validation telemetry version for the engine. Version 1 is collected +// by `bookmark_validator.js`, and checks value as well as structure +// differences. Version 2 is collected by the engine as part of building the +// remote tree, and checks structure differences only. +const BOOKMARK_VALIDATOR_VERSION = 2; + +// The maximum time that the engine should wait before aborting a bookmark +// merge. +const BOOKMARK_APPLY_TIMEOUT_MS = 5 * 60 * 60 * 1000; // 5 minutes + +// The default frecency value to use when not known. +const FRECENCY_UNKNOWN = -1; + +// Returns the constructor for a bookmark record type. +function getTypeObject(type) { + switch (type) { + case "bookmark": + return Bookmark; + case "query": + return BookmarkQuery; + case "folder": + return BookmarkFolder; + case "livemark": + return Livemark; + case "separator": + return BookmarkSeparator; + case "item": + return PlacesItem; + } + return null; +} + +export function PlacesItem(collection, id, type) { + CryptoWrapper.call(this, collection, id); + this.type = type || "item"; +} + +PlacesItem.prototype = { + async decrypt(keyBundle) { + // Do the normal CryptoWrapper decrypt, but change types before returning + let clear = await CryptoWrapper.prototype.decrypt.call(this, keyBundle); + + // Convert the abstract places item to the actual object type + if (!this.deleted) { + Object.setPrototypeOf(this, this.getTypeObject(this.type).prototype); + } + + return clear; + }, + + getTypeObject: function PlacesItem_getTypeObject(type) { + let recordObj = getTypeObject(type); + if (!recordObj) { + throw new Error("Unknown places item object type: " + type); + } + return recordObj; + }, + + _logName: "Sync.Record.PlacesItem", + + // Converts the record to a Sync bookmark object that can be passed to + // `PlacesSyncUtils.bookmarks.{insert, update}`. + toSyncBookmark() { + let result = { + kind: this.type, + recordId: this.id, + parentRecordId: this.parentid, + }; + let dateAdded = lazy.PlacesSyncUtils.bookmarks.ratchetTimestampBackwards( + this.dateAdded, + +this.modified * 1000 + ); + if (dateAdded > 0) { + result.dateAdded = dateAdded; + } + return result; + }, + + // Populates the record from a Sync bookmark object returned from + // `PlacesSyncUtils.bookmarks.fetch`. + fromSyncBookmark(item) { + this.parentid = item.parentRecordId; + this.parentName = item.parentTitle; + if (item.dateAdded) { + this.dateAdded = item.dateAdded; + } + }, +}; + +Object.setPrototypeOf(PlacesItem.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(PlacesItem, "cleartext", [ + "hasDupe", + "parentid", + "parentName", + "type", + "dateAdded", +]); + +export function Bookmark(collection, id, type) { + PlacesItem.call(this, collection, id, type || "bookmark"); +} + +Bookmark.prototype = { + _logName: "Sync.Record.Bookmark", + + toSyncBookmark() { + let info = PlacesItem.prototype.toSyncBookmark.call(this); + info.title = this.title; + info.url = this.bmkUri; + info.description = this.description; + info.tags = this.tags; + info.keyword = this.keyword; + return info; + }, + + fromSyncBookmark(item) { + PlacesItem.prototype.fromSyncBookmark.call(this, item); + this.title = item.title; + this.bmkUri = item.url.href; + this.description = item.description; + this.tags = item.tags; + this.keyword = item.keyword; + }, +}; + +Object.setPrototypeOf(Bookmark.prototype, PlacesItem.prototype); + +Utils.deferGetSet(Bookmark, "cleartext", [ + "title", + "bmkUri", + "description", + "tags", + "keyword", +]); + +export function BookmarkQuery(collection, id) { + Bookmark.call(this, collection, id, "query"); +} + +BookmarkQuery.prototype = { + _logName: "Sync.Record.BookmarkQuery", + + toSyncBookmark() { + let info = Bookmark.prototype.toSyncBookmark.call(this); + info.folder = this.folderName || undefined; // empty string -> undefined + info.query = this.queryId; + return info; + }, + + fromSyncBookmark(item) { + Bookmark.prototype.fromSyncBookmark.call(this, item); + this.folderName = item.folder || undefined; // empty string -> undefined + this.queryId = item.query; + }, +}; + +Object.setPrototypeOf(BookmarkQuery.prototype, Bookmark.prototype); + +Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName", "queryId"]); + +export function BookmarkFolder(collection, id, type) { + PlacesItem.call(this, collection, id, type || "folder"); +} + +BookmarkFolder.prototype = { + _logName: "Sync.Record.Folder", + + toSyncBookmark() { + let info = PlacesItem.prototype.toSyncBookmark.call(this); + info.description = this.description; + info.title = this.title; + return info; + }, + + fromSyncBookmark(item) { + PlacesItem.prototype.fromSyncBookmark.call(this, item); + this.title = item.title; + this.description = item.description; + this.children = item.childRecordIds; + }, +}; + +Object.setPrototypeOf(BookmarkFolder.prototype, PlacesItem.prototype); + +Utils.deferGetSet(BookmarkFolder, "cleartext", [ + "description", + "title", + "children", +]); + +export function Livemark(collection, id) { + BookmarkFolder.call(this, collection, id, "livemark"); +} + +Livemark.prototype = { + _logName: "Sync.Record.Livemark", + + toSyncBookmark() { + let info = BookmarkFolder.prototype.toSyncBookmark.call(this); + info.feed = this.feedUri; + info.site = this.siteUri; + return info; + }, + + fromSyncBookmark(item) { + BookmarkFolder.prototype.fromSyncBookmark.call(this, item); + this.feedUri = item.feed.href; + if (item.site) { + this.siteUri = item.site.href; + } + }, +}; + +Object.setPrototypeOf(Livemark.prototype, BookmarkFolder.prototype); + +Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]); + +export function BookmarkSeparator(collection, id) { + PlacesItem.call(this, collection, id, "separator"); +} + +BookmarkSeparator.prototype = { + _logName: "Sync.Record.Separator", + + fromSyncBookmark(item) { + PlacesItem.prototype.fromSyncBookmark.call(this, item); + this.pos = item.index; + }, +}; + +Object.setPrototypeOf(BookmarkSeparator.prototype, PlacesItem.prototype); + +Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos"); + +/** + * The bookmarks engine uses a different store that stages downloaded bookmarks + * in a separate database, instead of writing directly to Places. The buffer + * handles reconciliation, so we stub out `_reconcile`, and wait to pull changes + * until we're ready to upload. + */ +export function BookmarksEngine(service) { + SyncEngine.call(this, "Bookmarks", service); +} + +BookmarksEngine.prototype = { + _recordObj: PlacesItem, + _trackerObj: BookmarksTracker, + _storeObj: BookmarksStore, + version: 2, + // Used to override the engine name in telemetry, so that we can distinguish + // this engine from the old, now removed non-buffered engine. + overrideTelemetryName: "bookmarks-buffered", + + // Needed to ensure we don't miss items when resuming a sync that failed or + // aborted early. + _defaultSort: "oldest", + + syncPriority: 4, + allowSkippedRecord: false, + + async _ensureCurrentSyncID(newSyncID) { + await lazy.PlacesSyncUtils.bookmarks.ensureCurrentSyncId(newSyncID); + let buf = await this._store.ensureOpenMirror(); + await buf.ensureCurrentSyncId(newSyncID); + }, + + async ensureCurrentSyncID(newSyncID) { + let shouldWipeRemote = + await lazy.PlacesSyncUtils.bookmarks.shouldWipeRemote(); + if (!shouldWipeRemote) { + this._log.debug( + "Checking if server sync ID ${newSyncID} matches existing", + { newSyncID } + ); + await this._ensureCurrentSyncID(newSyncID); + return newSyncID; + } + // We didn't take the new sync ID because we need to wipe the server + // and other clients after a restore. Send the command, wipe the + // server, and reset our sync ID to reupload everything. + this._log.debug( + "Ignoring server sync ID ${newSyncID} after restore; " + + "wiping server and resetting sync ID", + { newSyncID } + ); + await this.service.clientsEngine.sendCommand( + "wipeEngine", + [this.name], + null, + { reason: "bookmark-restore" } + ); + let assignedSyncID = await this.resetSyncID(); + return assignedSyncID; + }, + + async getSyncID() { + return lazy.PlacesSyncUtils.bookmarks.getSyncId(); + }, + + async resetSyncID() { + await this._deleteServerCollection(); + return this.resetLocalSyncID(); + }, + + async resetLocalSyncID() { + let newSyncID = await lazy.PlacesSyncUtils.bookmarks.resetSyncId(); + this._log.debug("Assigned new sync ID ${newSyncID}", { newSyncID }); + let buf = await this._store.ensureOpenMirror(); + await buf.ensureCurrentSyncId(newSyncID); + return newSyncID; + }, + + async getLastSync() { + let mirror = await this._store.ensureOpenMirror(); + return mirror.getCollectionHighWaterMark(); + }, + + async setLastSync(lastSync) { + let mirror = await this._store.ensureOpenMirror(); + await mirror.setCollectionLastModified(lastSync); + // Update the last sync time in Places so that reverting to the original + // bookmarks engine doesn't download records we've already applied. + await lazy.PlacesSyncUtils.bookmarks.setLastSync(lastSync); + }, + + async _syncStartup() { + await super._syncStartup(); + + try { + // For first syncs, back up the user's bookmarks. + let lastSync = await this.getLastSync(); + if (!lastSync) { + this._log.debug("Bookmarks backup starting"); + await lazy.PlacesBackups.create(null, true); + this._log.debug("Bookmarks backup done"); + } + } catch (ex) { + // Failure to create a backup is somewhat bad, but probably not bad + // enough to prevent syncing of bookmarks - so just log the error and + // continue. + this._log.warn( + "Error while backing up bookmarks, but continuing with sync", + ex + ); + } + }, + + async _sync() { + try { + await super._sync(); + if (this._ranMaintenanceOnLastSync) { + // If the last sync failed, we ran maintenance, and this sync succeeded, + // maintenance likely fixed the issue. + this._ranMaintenanceOnLastSync = false; + this.service.recordTelemetryEvent("maintenance", "fix", "bookmarks"); + } + } catch (ex) { + if ( + lazy.Async.isShutdownException(ex) || + ex.status > 0 || + ex.name == "InterruptedError" + ) { + // Don't run maintenance on shutdown or HTTP errors, or if we aborted + // the sync because the user changed their bookmarks during merging. + throw ex; + } + if (ex.name == "MergeConflictError") { + this._log.warn( + "Bookmark syncing ran into a merge conflict error...will retry later" + ); + return; + } + // Run Places maintenance periodically to try to recover from corruption + // that might have caused the sync to fail. We cap the interval because + // persistent failures likely indicate a problem that won't be fixed by + // running maintenance after every failed sync. + let elapsedSinceMaintenance = + Date.now() / 1000 - + Services.prefs.getIntPref("places.database.lastMaintenance", 0); + if (elapsedSinceMaintenance >= PLACES_MAINTENANCE_INTERVAL_SECONDS) { + this._log.error( + "Bookmark sync failed, ${elapsedSinceMaintenance}s " + + "elapsed since last run; running Places maintenance", + { elapsedSinceMaintenance } + ); + await lazy.PlacesDBUtils.maintenanceOnIdle(); + this._ranMaintenanceOnLastSync = true; + this.service.recordTelemetryEvent("maintenance", "run", "bookmarks"); + } else { + this._ranMaintenanceOnLastSync = false; + } + throw ex; + } + }, + + async _syncFinish() { + await SyncEngine.prototype._syncFinish.call(this); + await lazy.PlacesSyncUtils.bookmarks.ensureMobileQuery(); + }, + + async pullAllChanges() { + return this.pullNewChanges(); + }, + + async trackRemainingChanges() { + let changes = this._modified.changes; + await lazy.PlacesSyncUtils.bookmarks.pushChanges(changes); + }, + + _deleteId(id) { + this._noteDeletedId(id); + }, + + // The bookmarks engine rarely calls this method directly, except in tests or + // when handling a `reset{All, Engine}` command from another client. We + // usually reset local Sync metadata on a sync ID mismatch, which both engines + // override with logic that lives in Places and the mirror. + async _resetClient() { + await super._resetClient(); + await lazy.PlacesSyncUtils.bookmarks.reset(); + let buf = await this._store.ensureOpenMirror(); + await buf.reset(); + }, + + // Cleans up the Places root, reading list items (ignored in bug 762118, + // removed in bug 1155684), and pinned sites. + _shouldDeleteRemotely(incomingItem) { + return ( + FORBIDDEN_INCOMING_IDS.includes(incomingItem.id) || + FORBIDDEN_INCOMING_PARENT_IDS.includes(incomingItem.parentid) + ); + }, + + emptyChangeset() { + return new BookmarksChangeset(); + }, + + async _apply() { + let buf = await this._store.ensureOpenMirror(); + let watchdog = this._newWatchdog(); + watchdog.start(BOOKMARK_APPLY_TIMEOUT_MS); + + try { + let recordsToUpload = await buf.apply({ + remoteTimeSeconds: lazy.Resource.serverTime, + signal: watchdog.signal, + }); + this._modified.replace(recordsToUpload); + } finally { + watchdog.stop(); + if (watchdog.abortReason) { + this._log.warn(`Aborting bookmark merge: ${watchdog.abortReason}`); + } + } + }, + + async _processIncoming(newitems) { + await super._processIncoming(newitems); + await this._apply(); + }, + + async _reconcile(item) { + return true; + }, + + async _createRecord(id) { + let record = await this._doCreateRecord(id); + if (!record.deleted) { + // Set hasDupe on all (non-deleted) records since we don't use it and we + // want to minimize the risk of older clients corrupting records. Note + // that the SyncedBookmarksMirror sets it for all records that it created, + // but we would like to ensure that weakly uploaded records are marked as + // hasDupe as well. + record.hasDupe = true; + } + return record; + }, + + async _doCreateRecord(id) { + let change = this._modified.changes[id]; + if (!change) { + this._log.error( + "Creating record for item ${id} not in strong changeset", + { id } + ); + throw new TypeError("Can't create record for unchanged item"); + } + let record = this._recordFromCleartext(id, change.cleartext); + record.sortindex = await this._store._calculateIndex(record); + return record; + }, + + _recordFromCleartext(id, cleartext) { + let recordObj = getTypeObject(cleartext.type); + if (!recordObj) { + this._log.warn( + "Creating record for item ${id} with unknown type ${type}", + { id, type: cleartext.type } + ); + recordObj = PlacesItem; + } + let record = new recordObj(this.name, id); + record.cleartext = cleartext; + return record; + }, + + async pullChanges() { + return {}; + }, + + /** + * Writes successfully uploaded records back to the mirror, so that the + * mirror matches the server. We update the mirror before updating Places, + * which has implications for interrupted syncs. + * + * 1. Sync interrupted during upload; server doesn't support atomic uploads. + * We'll download and reapply everything that we uploaded before the + * interruption. All locally changed items retain their change counters. + * 2. Sync interrupted during upload; atomic uploads enabled. The server + * discards the batch. All changed local items retain their change + * counters, so the next sync resumes cleanly. + * 3. Sync interrupted during upload; outgoing records can't fit in a single + * batch. We'll download and reapply all records through the most recent + * committed batch. This is a variation of (1). + * 4. Sync interrupted after we update the mirror, but before cleanup. The + * mirror matches the server, but locally changed items retain their change + * counters. Reuploading them on the next sync should be idempotent, though + * unnecessary. If another client makes a conflicting remote change before + * we sync again, we may incorrectly prefer the local state. + * 5. Sync completes successfully. We'll update the mirror, and reset the + * change counters for all items. + */ + async _onRecordsWritten(succeeded, failed, serverModifiedTime) { + let records = []; + for (let id of succeeded) { + let change = this._modified.changes[id]; + if (!change) { + // TODO (Bug 1433178): Write weakly uploaded records back to the mirror. + this._log.info("Uploaded record not in strong changeset", id); + continue; + } + if (!change.synced) { + this._log.info("Record in strong changeset not uploaded", id); + continue; + } + let cleartext = change.cleartext; + if (!cleartext) { + this._log.error( + "Missing Sync record cleartext for ${id} in ${change}", + { id, change } + ); + throw new TypeError("Missing cleartext for uploaded Sync record"); + } + let record = this._recordFromCleartext(id, cleartext); + record.modified = serverModifiedTime; + records.push(record); + } + let buf = await this._store.ensureOpenMirror(); + await buf.store(records, { needsMerge: false }); + }, + + async finalize() { + await super.finalize(); + await this._store.finalize(); + }, +}; + +Object.setPrototypeOf(BookmarksEngine.prototype, SyncEngine.prototype); + +/** + * The bookmarks store delegates to the mirror for staging and applying + * records. Most `Store` methods intentionally remain abstract, so you can't use + * this store to create or update bookmarks in Places. All changes must go + * through the mirror, which takes care of merging and producing a valid tree. + */ +function BookmarksStore(name, engine) { + Store.call(this, name, engine); +} + +BookmarksStore.prototype = { + _openMirrorPromise: null, + + // For tests. + _batchChunkSize: 500, + + // Create a record starting from the weave id (places guid) + async createRecord(id, collection) { + let item = await lazy.PlacesSyncUtils.bookmarks.fetch(id); + if (!item) { + // deleted item + let record = new PlacesItem(collection, id); + record.deleted = true; + return record; + } + + let recordObj = getTypeObject(item.kind); + if (!recordObj) { + this._log.warn("Unknown item type, cannot serialize: " + item.kind); + recordObj = PlacesItem; + } + let record = new recordObj(collection, id); + record.fromSyncBookmark(item); + + record.sortindex = await this._calculateIndex(record); + + return record; + }, + + async _calculateIndex(record) { + // Ensure folders have a very high sort index so they're not synced last. + if (record.type == "folder") { + return FOLDER_SORTINDEX; + } + + // For anything directly under the toolbar, give it a boost of more than an + // unvisited bookmark + let index = 0; + if (record.parentid == "toolbar") { + index += 150; + } + + // Add in the bookmark's frecency if we have something. + if (record.bmkUri != null) { + let frecency = FRECENCY_UNKNOWN; + try { + frecency = await lazy.PlacesSyncUtils.history.fetchURLFrecency( + record.bmkUri + ); + } catch (ex) { + this._log.warn( + `Failed to fetch frecency for ${record.id}; assuming default`, + ex + ); + this._log.trace("Record {id} has invalid URL ${bmkUri}", record); + } + if (frecency != FRECENCY_UNKNOWN) { + index += frecency; + } + } + + return index; + }, + + async wipe() { + // Save a backup before clearing out all bookmarks. + await lazy.PlacesBackups.create(null, true); + await lazy.PlacesSyncUtils.bookmarks.wipe(); + }, + + ensureOpenMirror() { + if (!this._openMirrorPromise) { + this._openMirrorPromise = this._openMirror().catch(err => { + // We may have failed to open the mirror temporarily; for example, if + // the database is locked. Clear the promise so that subsequent + // `ensureOpenMirror` calls can try to open the mirror again. + this._openMirrorPromise = null; + throw err; + }); + } + return this._openMirrorPromise; + }, + + async _openMirror() { + let mirrorPath = PathUtils.join( + PathUtils.profileDir, + "weave", + "bookmarks.sqlite" + ); + await IOUtils.makeDirectory(PathUtils.parent(mirrorPath), { + createAncestors: true, + }); + + return lazy.SyncedBookmarksMirror.open({ + path: mirrorPath, + recordStepTelemetry: (name, took, counts) => { + lazy.Observers.notify( + "weave:engine:sync:step", + { + name, + took, + counts, + }, + this.name + ); + }, + recordValidationTelemetry: (took, checked, problems) => { + lazy.Observers.notify( + "weave:engine:validate:finish", + { + version: BOOKMARK_VALIDATOR_VERSION, + took, + checked, + problems, + }, + this.name + ); + }, + }); + }, + + async applyIncomingBatch(records, countTelemetry) { + let buf = await this.ensureOpenMirror(); + for (let chunk of lazy.PlacesUtils.chunkArray( + records, + this._batchChunkSize + )) { + await buf.store(chunk); + } + // Array of failed records. + return []; + }, + + async applyIncoming(record) { + let buf = await this.ensureOpenMirror(); + await buf.store([record]); + }, + + async finalize() { + if (!this._openMirrorPromise) { + return; + } + let buf = await this._openMirrorPromise; + await buf.finalize(); + }, +}; + +Object.setPrototypeOf(BookmarksStore.prototype, Store.prototype); + +// The bookmarks tracker is a special flower. Instead of listening for changes +// via observer notifications, it queries Places for the set of items that have +// changed since the last sync. Because it's a "pull-based" tracker, it ignores +// all concepts of "add a changed ID." However, it still registers an observer +// to bump the score, so that changed bookmarks are synced immediately. +function BookmarksTracker(name, engine) { + Tracker.call(this, name, engine); +} +BookmarksTracker.prototype = { + onStart() { + this._placesListener = new PlacesWeakCallbackWrapper( + this.handlePlacesEvents.bind(this) + ); + lazy.PlacesUtils.observers.addListener( + [ + "bookmark-added", + "bookmark-removed", + "bookmark-moved", + "bookmark-guid-changed", + "bookmark-keyword-changed", + "bookmark-tags-changed", + "bookmark-time-changed", + "bookmark-title-changed", + "bookmark-url-changed", + ], + this._placesListener + ); + Svc.Obs.add("bookmarks-restore-begin", this); + Svc.Obs.add("bookmarks-restore-success", this); + Svc.Obs.add("bookmarks-restore-failed", this); + }, + + onStop() { + lazy.PlacesUtils.observers.removeListener( + [ + "bookmark-added", + "bookmark-removed", + "bookmark-moved", + "bookmark-guid-changed", + "bookmark-keyword-changed", + "bookmark-tags-changed", + "bookmark-time-changed", + "bookmark-title-changed", + "bookmark-url-changed", + ], + this._placesListener + ); + Svc.Obs.remove("bookmarks-restore-begin", this); + Svc.Obs.remove("bookmarks-restore-success", this); + Svc.Obs.remove("bookmarks-restore-failed", this); + }, + + async getChangedIDs() { + return lazy.PlacesSyncUtils.bookmarks.pullChanges(); + }, + + observe(subject, topic, data) { + switch (topic) { + case "bookmarks-restore-begin": + this._log.debug("Ignoring changes from importing bookmarks."); + break; + case "bookmarks-restore-success": + this._log.debug("Tracking all items on successful import."); + + if (data == "json") { + this._log.debug( + "Restore succeeded: wiping server and other clients." + ); + // Trigger an immediate sync. `ensureCurrentSyncID` will notice we + // restored, wipe the server and other clients, reset the sync ID, and + // upload the restored tree. + this.score += SCORE_INCREMENT_XLARGE; + } else { + // "html", "html-initial", or "json-append" + this._log.debug("Import succeeded."); + } + break; + case "bookmarks-restore-failed": + this._log.debug("Tracking all items on failed import."); + break; + } + }, + + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + + /* Every add/remove/change will trigger a sync for MULTI_DEVICE */ + _upScore: function BMT__upScore() { + this.score += SCORE_INCREMENT_XLARGE; + }, + + handlePlacesEvents(events) { + for (let event of events) { + switch (event.type) { + case "bookmark-added": + case "bookmark-removed": + case "bookmark-moved": + case "bookmark-keyword-changed": + case "bookmark-tags-changed": + case "bookmark-time-changed": + case "bookmark-title-changed": + case "bookmark-url-changed": + if (lazy.IGNORED_SOURCES.includes(event.source)) { + continue; + } + + this._log.trace(`'${event.type}': ${event.id}`); + this._upScore(); + break; + case "bookmark-guid-changed": + if (event.source !== lazy.PlacesUtils.bookmarks.SOURCES.SYNC) { + this._log.warn( + "The source of bookmark-guid-changed event shoud be sync." + ); + continue; + } + + this._log.trace(`'${event.type}': ${event.id}`); + this._upScore(); + break; + case "purge-caches": + this._log.trace("purge-caches"); + this._upScore(); + break; + } + } + }, +}; + +Object.setPrototypeOf(BookmarksTracker.prototype, Tracker.prototype); + +/** + * A changeset that stores extra metadata in a change record for each ID. The + * engine updates this metadata when uploading Sync records, and writes it back + * to Places in `BookmarksEngine#trackRemainingChanges`. + * + * The `synced` property on a change record means its corresponding item has + * been uploaded, and we should pretend it doesn't exist in the changeset. + */ +class BookmarksChangeset extends Changeset { + // Only `_reconcile` calls `getModifiedTimestamp` and `has`, and the engine + // does its own reconciliation. + getModifiedTimestamp(id) { + throw new Error("Don't use timestamps to resolve bookmark conflicts"); + } + + has(id) { + throw new Error("Don't use the changeset to resolve bookmark conflicts"); + } + + delete(id) { + let change = this.changes[id]; + if (change) { + // Mark the change as synced without removing it from the set. We do this + // so that we can update Places in `trackRemainingChanges`. + change.synced = true; + } + } + + ids() { + let results = new Set(); + for (let id in this.changes) { + if (!this.changes[id].synced) { + results.add(id); + } + } + return [...results]; + } +} diff --git a/services/sync/modules/engines/clients.sys.mjs b/services/sync/modules/engines/clients.sys.mjs new file mode 100644 index 0000000000..eda92bd75b --- /dev/null +++ b/services/sync/modules/engines/clients.sys.mjs @@ -0,0 +1,1122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * How does the clients engine work? + * + * - We use 2 files - commands.json and commands-syncing.json. + * + * - At sync upload time, we attempt a rename of commands.json to + * commands-syncing.json, and ignore errors (helps for crash during sync!). + * - We load commands-syncing.json and stash the contents in + * _currentlySyncingCommands which lives for the duration of the upload process. + * - We use _currentlySyncingCommands to build the outgoing records + * - Immediately after successful upload, we delete commands-syncing.json from + * disk (and clear _currentlySyncingCommands). We reconcile our local records + * with what we just wrote in the server, and add failed IDs commands + * back in commands.json + * - Any time we need to "save" a command for future syncs, we load + * commands.json, update it, and write it back out. + */ + +import { Async } from "resource://services-common/async.sys.mjs"; + +import { + DEVICE_TYPE_DESKTOP, + DEVICE_TYPE_MOBILE, + SINGLE_USER_THRESHOLD, + SYNC_API_VERSION, +} from "resource://services-sync/constants.sys.mjs"; + +import { + Store, + SyncEngine, + LegacyTracker, +} from "resource://services-sync/engines.sys.mjs"; +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Resource } from "resource://services-sync/resource.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +import { PREF_ACCOUNT_ROOT } from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const CLIENTS_TTL = 15552000; // 180 days +const CLIENTS_TTL_REFRESH = 604800; // 7 days +const STALE_CLIENT_REMOTE_AGE = 604800; // 7 days + +// TTL of the message sent to another device when sending a tab +const NOTIFY_TAB_SENT_TTL_SECS = 1 * 3600; // 1 hour + +// How often we force a refresh of the FxA device list. +const REFRESH_FXA_DEVICE_INTERVAL_MS = 2 * 60 * 60 * 1000; // 2 hours + +// Reasons behind sending collection_changed push notifications. +const COLLECTION_MODIFIED_REASON_SENDTAB = "sendtab"; +const COLLECTION_MODIFIED_REASON_FIRSTSYNC = "firstsync"; + +const SUPPORTED_PROTOCOL_VERSIONS = [SYNC_API_VERSION]; +const LAST_MODIFIED_ON_PROCESS_COMMAND_PREF = + "services.sync.clients.lastModifiedOnProcessCommands"; + +function hasDupeCommand(commands, action) { + if (!commands) { + return false; + } + return commands.some( + other => + other.command == action.command && + Utils.deepEquals(other.args, action.args) + ); +} + +export function ClientsRec(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +ClientsRec.prototype = { + _logName: "Sync.Record.Clients", + ttl: CLIENTS_TTL, +}; +Object.setPrototypeOf(ClientsRec.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(ClientsRec, "cleartext", [ + "name", + "type", + "commands", + "version", + "protocols", + "formfactor", + "os", + "appPackage", + "application", + "device", + "fxaDeviceId", +]); + +export function ClientEngine(service) { + SyncEngine.call(this, "Clients", service); + + this.fxAccounts = lazy.fxAccounts; + this.addClientCommandQueue = Async.asyncQueueCaller(this._log); + Utils.defineLazyIDProperty(this, "localID", "services.sync.client.GUID"); +} + +ClientEngine.prototype = { + _storeObj: ClientStore, + _recordObj: ClientsRec, + _trackerObj: ClientsTracker, + allowSkippedRecord: false, + _knownStaleFxADeviceIds: null, + _lastDeviceCounts: null, + _lastFxaDeviceRefresh: 0, + + async initialize() { + // Reset the last sync timestamp on every startup so that we fetch all clients + await this.resetLastSync(); + }, + + // These two properties allow us to avoid replaying the same commands + // continuously if we cannot manage to upload our own record. + _localClientLastModified: 0, + get _lastModifiedOnProcessCommands() { + return Services.prefs.getIntPref(LAST_MODIFIED_ON_PROCESS_COMMAND_PREF, -1); + }, + + set _lastModifiedOnProcessCommands(value) { + Services.prefs.setIntPref(LAST_MODIFIED_ON_PROCESS_COMMAND_PREF, value); + }, + + get isFirstSync() { + return !this.lastRecordUpload; + }, + + // Always sync client data as it controls other sync behavior + get enabled() { + return true; + }, + + get lastRecordUpload() { + return Svc.PrefBranch.getIntPref(this.name + ".lastRecordUpload", 0); + }, + set lastRecordUpload(value) { + Svc.PrefBranch.setIntPref( + this.name + ".lastRecordUpload", + Math.floor(value) + ); + }, + + get remoteClients() { + // return all non-stale clients for external consumption. + return Object.values(this._store._remoteClients).filter(v => !v.stale); + }, + + remoteClient(id) { + let client = this._store._remoteClients[id]; + return client && !client.stale ? client : null; + }, + + remoteClientExists(id) { + return !!this.remoteClient(id); + }, + + // Aggregate some stats on the composition of clients on this account + get stats() { + let stats = { + hasMobile: this.localType == DEVICE_TYPE_MOBILE, + names: [this.localName], + numClients: 1, + }; + + for (let id in this._store._remoteClients) { + let { name, type, stale } = this._store._remoteClients[id]; + if (!stale) { + stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE; + stats.names.push(name); + stats.numClients++; + } + } + + return stats; + }, + + /** + * Obtain information about device types. + * + * Returns a Map of device types to integer counts. Guaranteed to include + * "desktop" (which will have at least 1 - this device) and "mobile" (which + * may have zero) counts. It almost certainly will include only these 2. + */ + get deviceTypes() { + let counts = new Map(); + + counts.set(this.localType, 1); // currently this must be DEVICE_TYPE_DESKTOP + counts.set(DEVICE_TYPE_MOBILE, 0); + + for (let id in this._store._remoteClients) { + let record = this._store._remoteClients[id]; + if (record.stale) { + continue; // pretend "stale" records don't exist. + } + let type = record.type; + if (!counts.has(type)) { + counts.set(type, 0); + } + + counts.set(type, counts.get(type) + 1); + } + + return counts; + }, + + get brandName() { + let brand = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); + return brand.GetStringFromName("brandShortName"); + }, + + get localName() { + return this.fxAccounts.device.getLocalName(); + }, + set localName(value) { + this.fxAccounts.device.setLocalName(value); + }, + + get localType() { + return this.fxAccounts.device.getLocalType(); + }, + + getClientName(id) { + if (id == this.localID) { + return this.localName; + } + let client = this._store._remoteClients[id]; + if (!client) { + return ""; + } + // Sometimes the sync clients don't always correctly update the device name + // However FxA always does, so try to pull the name from there first + let fxaDevice = this.fxAccounts.device.recentDeviceList?.find( + device => device.id === client.fxaDeviceId + ); + + // should be very rare, but could happen if we have yet to fetch devices, + // or the client recently disconnected + if (!fxaDevice) { + this._log.warn( + "Couldn't find associated FxA device, falling back to client name" + ); + return client.name; + } + return fxaDevice.name; + }, + + getClientFxaDeviceId(id) { + if (this._store._remoteClients[id]) { + return this._store._remoteClients[id].fxaDeviceId; + } + return null; + }, + + getClientByFxaDeviceId(fxaDeviceId) { + for (let id in this._store._remoteClients) { + let client = this._store._remoteClients[id]; + if (client.stale) { + continue; + } + if (client.fxaDeviceId == fxaDeviceId) { + return client; + } + } + return null; + }, + + getClientType(id) { + const client = this._store._remoteClients[id]; + if (client.type == DEVICE_TYPE_DESKTOP) { + return "desktop"; + } + if (client.formfactor && client.formfactor.includes("tablet")) { + return "tablet"; + } + return "phone"; + }, + + async _readCommands() { + let commands = await Utils.jsonLoad("commands", this); + return commands || {}; + }, + + /** + * Low level function, do not use directly (use _addClientCommand instead). + */ + async _saveCommands(commands) { + try { + await Utils.jsonSave("commands", this, commands); + } catch (error) { + this._log.error("Failed to save JSON outgoing commands", error); + } + }, + + async _prepareCommandsForUpload() { + try { + await Utils.jsonMove("commands", "commands-syncing", this); + } catch (e) { + // Ignore errors + } + let commands = await Utils.jsonLoad("commands-syncing", this); + return commands || {}; + }, + + async _deleteUploadedCommands() { + delete this._currentlySyncingCommands; + try { + await Utils.jsonRemove("commands-syncing", this); + } catch (err) { + this._log.error("Failed to delete syncing-commands file", err); + } + }, + + // Gets commands for a client we are yet to write to the server. Doesn't + // include commands for that client which are already on the server. + // We should rename this! + async getClientCommands(clientId) { + const allCommands = await this._readCommands(); + return allCommands[clientId] || []; + }, + + async removeLocalCommand(command) { + // the implementation of this engine is such that adding a command to + // the local client is how commands are deleted! ¯\_(ツ)_/¯ + await this._addClientCommand(this.localID, command); + }, + + async _addClientCommand(clientId, command) { + this.addClientCommandQueue.enqueueCall(async () => { + try { + const localCommands = await this._readCommands(); + const localClientCommands = localCommands[clientId] || []; + const remoteClient = this._store._remoteClients[clientId]; + let remoteClientCommands = []; + if (remoteClient && remoteClient.commands) { + remoteClientCommands = remoteClient.commands; + } + const clientCommands = localClientCommands.concat(remoteClientCommands); + if (hasDupeCommand(clientCommands, command)) { + return false; + } + localCommands[clientId] = localClientCommands.concat(command); + await this._saveCommands(localCommands); + return true; + } catch (e) { + // Failing to save a command should not "break the queue" of pending operations. + this._log.error(e); + return false; + } + }); + + return this.addClientCommandQueue.promiseCallsComplete(); + }, + + async _removeClientCommands(clientId) { + const allCommands = await this._readCommands(); + delete allCommands[clientId]; + await this._saveCommands(allCommands); + }, + + async updateKnownStaleClients() { + this._log.debug("Updating the known stale clients"); + // _fetchFxADevices side effect updates this._knownStaleFxADeviceIds. + await this._fetchFxADevices(); + let localFxADeviceId = await lazy.fxAccounts.device.getLocalId(); + // Process newer records first, so that if we hit a record with a device ID + // we've seen before, we can mark it stale immediately. + let clientList = Object.values(this._store._remoteClients).sort( + (a, b) => b.serverLastModified - a.serverLastModified + ); + let seenDeviceIds = new Set([localFxADeviceId]); + for (let client of clientList) { + // Clients might not have an `fxaDeviceId` if they fail the FxA + // registration process. + if (!client.fxaDeviceId) { + continue; + } + if (this._knownStaleFxADeviceIds.includes(client.fxaDeviceId)) { + this._log.info( + `Hiding stale client ${client.id} - in known stale clients list` + ); + client.stale = true; + } else if (seenDeviceIds.has(client.fxaDeviceId)) { + this._log.info( + `Hiding stale client ${client.id}` + + ` - duplicate device id ${client.fxaDeviceId}` + ); + client.stale = true; + } else { + seenDeviceIds.add(client.fxaDeviceId); + } + } + }, + + async _fetchFxADevices() { + // We only force a refresh periodically to keep the load on the servers + // down, and because we expect FxA to have received a push message in + // most cases when the FxA device list would have changed. For this reason + // we still go ahead and check the stale list even if we didn't force a + // refresh. + let now = this.fxAccounts._internal.now(); // tests mock this .now() impl. + if (now - REFRESH_FXA_DEVICE_INTERVAL_MS > this._lastFxaDeviceRefresh) { + this._lastFxaDeviceRefresh = now; + try { + await this.fxAccounts.device.refreshDeviceList(); + } catch (e) { + this._log.error("Could not refresh the FxA device list", e); + } + } + + // We assume that clients not present in the FxA Device Manager list have been + // disconnected and so are stale + this._log.debug("Refreshing the known stale clients list"); + let localClients = Object.values(this._store._remoteClients) + .filter(client => client.fxaDeviceId) // iOS client records don't have fxaDeviceId + .map(client => client.fxaDeviceId); + const fxaClients = this.fxAccounts.device.recentDeviceList + ? this.fxAccounts.device.recentDeviceList.map(device => device.id) + : []; + this._knownStaleFxADeviceIds = Utils.arraySub(localClients, fxaClients); + }, + + async _syncStartup() { + // Reupload new client record periodically. + if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) { + await this._tracker.addChangedID(this.localID); + } + return SyncEngine.prototype._syncStartup.call(this); + }, + + async _processIncoming() { + // Fetch all records from the server. + await this.resetLastSync(); + this._incomingClients = {}; + try { + await SyncEngine.prototype._processIncoming.call(this); + // Update FxA Device list. + await this._fetchFxADevices(); + // Since clients are synced unconditionally, any records in the local store + // that don't exist on the server must be for disconnected clients. Remove + // them, so that we don't upload records with commands for clients that will + // never see them. We also do this to filter out stale clients from the + // tabs collection, since showing their list of tabs is confusing. + for (let id in this._store._remoteClients) { + if (!this._incomingClients[id]) { + this._log.info(`Removing local state for deleted client ${id}`); + await this._removeRemoteClient(id); + } + } + let localFxADeviceId = await lazy.fxAccounts.device.getLocalId(); + // Bug 1264498: Mobile clients don't remove themselves from the clients + // collection when the user disconnects Sync, so we mark as stale clients + // with the same name that haven't synced in over a week. + // (Note we can't simply delete them, or we re-apply them next sync - see + // bug 1287687) + this._localClientLastModified = Math.round( + this._incomingClients[this.localID] + ); + delete this._incomingClients[this.localID]; + let names = new Set([this.localName]); + let seenDeviceIds = new Set([localFxADeviceId]); + let idToLastModifiedList = Object.entries(this._incomingClients).sort( + (a, b) => b[1] - a[1] + ); + for (let [id, serverLastModified] of idToLastModifiedList) { + let record = this._store._remoteClients[id]; + // stash the server last-modified time on the record. + record.serverLastModified = serverLastModified; + if ( + record.fxaDeviceId && + this._knownStaleFxADeviceIds.includes(record.fxaDeviceId) + ) { + this._log.info( + `Hiding stale client ${id} - in known stale clients list` + ); + record.stale = true; + } + if (!names.has(record.name)) { + if (record.fxaDeviceId) { + seenDeviceIds.add(record.fxaDeviceId); + } + names.add(record.name); + continue; + } + let remoteAge = Resource.serverTime - this._incomingClients[id]; + if (remoteAge > STALE_CLIENT_REMOTE_AGE) { + this._log.info(`Hiding stale client ${id} with age ${remoteAge}`); + record.stale = true; + continue; + } + if (record.fxaDeviceId && seenDeviceIds.has(record.fxaDeviceId)) { + this._log.info( + `Hiding stale client ${record.id}` + + ` - duplicate device id ${record.fxaDeviceId}` + ); + record.stale = true; + } else if (record.fxaDeviceId) { + seenDeviceIds.add(record.fxaDeviceId); + } + } + } finally { + this._incomingClients = null; + } + }, + + async _uploadOutgoing() { + this._currentlySyncingCommands = await this._prepareCommandsForUpload(); + const clientWithPendingCommands = Object.keys( + this._currentlySyncingCommands + ); + for (let clientId of clientWithPendingCommands) { + if (this._store._remoteClients[clientId] || this.localID == clientId) { + this._modified.set(clientId, 0); + } + } + let updatedIDs = this._modified.ids(); + await SyncEngine.prototype._uploadOutgoing.call(this); + // Record the response time as the server time for each item we uploaded. + let lastSync = await this.getLastSync(); + for (let id of updatedIDs) { + if (id == this.localID) { + this.lastRecordUpload = lastSync; + } else { + this._store._remoteClients[id].serverLastModified = lastSync; + } + } + }, + + async _onRecordsWritten(succeeded, failed) { + // Reconcile the status of the local records with what we just wrote on the + // server + for (let id of succeeded) { + const commandChanges = this._currentlySyncingCommands[id]; + if (id == this.localID) { + if (this.isFirstSync) { + this._log.info( + "Uploaded our client record for the first time, notifying other clients." + ); + this._notifyClientRecordUploaded(); + } + if (this.localCommands) { + this.localCommands = this.localCommands.filter( + command => !hasDupeCommand(commandChanges, command) + ); + } + } else { + const clientRecord = this._store._remoteClients[id]; + if (!commandChanges || !clientRecord) { + // should be impossible, else we wouldn't have been writing it. + this._log.warn( + "No command/No record changes for a client we uploaded" + ); + continue; + } + // fixup the client record, so our copy of _remoteClients matches what we uploaded. + this._store._remoteClients[id] = await this._store.createRecord(id); + // we could do better and pass the reference to the record we just uploaded, + // but this will do for now + } + } + + // Re-add failed commands + for (let id of failed) { + const commandChanges = this._currentlySyncingCommands[id]; + if (!commandChanges) { + continue; + } + await this._addClientCommand(id, commandChanges); + } + + await this._deleteUploadedCommands(); + + // Notify other devices that their own client collection changed + const idsToNotify = succeeded.reduce((acc, id) => { + if (id == this.localID) { + return acc; + } + const fxaDeviceId = this.getClientFxaDeviceId(id); + return fxaDeviceId ? acc.concat(fxaDeviceId) : acc; + }, []); + if (idsToNotify.length) { + this._notifyOtherClientsModified(idsToNotify); + } + }, + + _notifyOtherClientsModified(ids) { + // We are not waiting on this promise on purpose. + this._notifyCollectionChanged( + ids, + NOTIFY_TAB_SENT_TTL_SECS, + COLLECTION_MODIFIED_REASON_SENDTAB + ); + }, + + _notifyClientRecordUploaded() { + // We are not waiting on this promise on purpose. + this._notifyCollectionChanged( + null, + 0, + COLLECTION_MODIFIED_REASON_FIRSTSYNC + ); + }, + + /** + * @param {?string[]} ids FxA Client IDs to notify. null means everyone else. + * @param {number} ttl TTL of the push notification. + * @param {string} reason Reason for sending this push notification. + */ + async _notifyCollectionChanged(ids, ttl, reason) { + const message = { + version: 1, + command: "sync:collection_changed", + data: { + collections: ["clients"], + reason, + }, + }; + let excludedIds = null; + if (!ids) { + const localFxADeviceId = await lazy.fxAccounts.device.getLocalId(); + excludedIds = [localFxADeviceId]; + } + try { + await this.fxAccounts.notifyDevices(ids, excludedIds, message, ttl); + } catch (e) { + this._log.error("Could not notify of changes in the collection", e); + } + }, + + async _syncFinish() { + // Record histograms for our device types, and also write them to a pref + // so non-histogram telemetry (eg, UITelemetry) and the sync scheduler + // has easy access to them, and so they are accurate even before we've + // successfully synced the first time after startup. + let deviceTypeCounts = this.deviceTypes; + for (let [deviceType, count] of deviceTypeCounts) { + let hid; + let prefName = this.name + ".devices."; + switch (deviceType) { + case DEVICE_TYPE_DESKTOP: + hid = "WEAVE_DEVICE_COUNT_DESKTOP"; + prefName += "desktop"; + break; + case DEVICE_TYPE_MOBILE: + hid = "WEAVE_DEVICE_COUNT_MOBILE"; + prefName += "mobile"; + break; + default: + this._log.warn( + `Unexpected deviceType "${deviceType}" recording device telemetry.` + ); + continue; + } + Services.telemetry.getHistogramById(hid).add(count); + // Optimization: only write the pref if it changed since our last sync. + if ( + this._lastDeviceCounts == null || + this._lastDeviceCounts.get(prefName) != count + ) { + Svc.PrefBranch.setIntPref(prefName, count); + } + } + this._lastDeviceCounts = deviceTypeCounts; + return SyncEngine.prototype._syncFinish.call(this); + }, + + async _reconcile(item) { + // Every incoming record is reconciled, so we use this to track the + // contents of the collection on the server. + this._incomingClients[item.id] = item.modified; + + if (!(await this._store.itemExists(item.id))) { + return true; + } + // Clients are synced unconditionally, so we'll always have new records. + // Unfortunately, this will cause the scheduler to use the immediate sync + // interval for the multi-device case, instead of the active interval. We + // work around this by updating the record during reconciliation, and + // returning false to indicate that the record doesn't need to be applied + // later. + await this._store.update(item); + return false; + }, + + // Treat reset the same as wiping for locally cached clients + async _resetClient() { + await this._wipeClient(); + }, + + async _wipeClient() { + await SyncEngine.prototype._resetClient.call(this); + this._knownStaleFxADeviceIds = null; + delete this.localCommands; + await this._store.wipe(); + try { + await Utils.jsonRemove("commands", this); + } catch (err) { + this._log.warn("Could not delete commands.json", err); + } + try { + await Utils.jsonRemove("commands-syncing", this); + } catch (err) { + this._log.warn("Could not delete commands-syncing.json", err); + } + }, + + async removeClientData() { + let res = this.service.resource(this.engineURL + "/" + this.localID); + await res.delete(); + }, + + // Override the default behavior to delete bad records from the server. + async handleHMACMismatch(item, mayRetry) { + this._log.debug("Handling HMAC mismatch for " + item.id); + + let base = await SyncEngine.prototype.handleHMACMismatch.call( + this, + item, + mayRetry + ); + if (base != SyncEngine.kRecoveryStrategy.error) { + return base; + } + + // It's a bad client record. Save it to be deleted at the end of the sync. + this._log.debug("Bad client record detected. Scheduling for deletion."); + await this._deleteId(item.id); + + // Neither try again nor error; we're going to delete it. + return SyncEngine.kRecoveryStrategy.ignore; + }, + + /** + * A hash of valid commands that the client knows about. The key is a command + * and the value is a hash containing information about the command such as + * number of arguments, description, and importance (lower importance numbers + * indicate higher importance. + */ + _commands: { + resetAll: { + args: 0, + importance: 0, + desc: "Clear temporary local data for all engines", + }, + resetEngine: { + args: 1, + importance: 0, + desc: "Clear temporary local data for engine", + }, + wipeEngine: { + args: 1, + importance: 0, + desc: "Delete all client data for engine", + }, + logout: { args: 0, importance: 0, desc: "Log out client" }, + }, + + /** + * Sends a command+args pair to a specific client. + * + * @param command Command string + * @param args Array of arguments/data for command + * @param clientId Client to send command to + */ + async _sendCommandToClient(command, args, clientId, telemetryExtra) { + this._log.trace("Sending " + command + " to " + clientId); + + let client = this._store._remoteClients[clientId]; + if (!client) { + throw new Error("Unknown remote client ID: '" + clientId + "'."); + } + if (client.stale) { + throw new Error("Stale remote client ID: '" + clientId + "'."); + } + + let action = { + command, + args, + // We send the flowID to the other client so *it* can report it in its + // telemetry - we record it in ours below. + flowID: telemetryExtra.flowID, + }; + + if (await this._addClientCommand(clientId, action)) { + this._log.trace(`Client ${clientId} got a new action`, [command, args]); + await this._tracker.addChangedID(clientId); + try { + telemetryExtra.deviceID = + this.service.identity.hashedDeviceID(clientId); + } catch (_) {} + + this.service.recordTelemetryEvent( + "sendcommand", + command, + undefined, + telemetryExtra + ); + } else { + this._log.trace(`Client ${clientId} got a duplicate action`, [ + command, + args, + ]); + } + }, + + /** + * Check if the local client has any remote commands and perform them. + * + * @return false to abort sync + */ + async processIncomingCommands() { + return this._notify("clients:process-commands", "", async function () { + if ( + !this.localCommands || + (this._lastModifiedOnProcessCommands == this._localClientLastModified && + !this.ignoreLastModifiedOnProcessCommands) + ) { + return true; + } + this._lastModifiedOnProcessCommands = this._localClientLastModified; + + const clearedCommands = await this._readCommands()[this.localID]; + const commands = this.localCommands.filter( + command => !hasDupeCommand(clearedCommands, command) + ); + let didRemoveCommand = false; + // Process each command in order. + for (let rawCommand of commands) { + let shouldRemoveCommand = true; // most commands are auto-removed. + let { command, args, flowID } = rawCommand; + this._log.debug("Processing command " + command, args); + + this.service.recordTelemetryEvent( + "processcommand", + command, + undefined, + { flowID } + ); + + let engines = [args[0]]; + switch (command) { + case "resetAll": + engines = null; + // Fallthrough + case "resetEngine": + await this.service.resetClient(engines); + break; + case "wipeEngine": + await this.service.wipeClient(engines); + break; + case "logout": + this.service.logout(); + return false; + default: + this._log.warn("Received an unknown command: " + command); + break; + } + // Add the command to the "cleared" commands list + if (shouldRemoveCommand) { + await this.removeLocalCommand(rawCommand); + didRemoveCommand = true; + } + } + if (didRemoveCommand) { + await this._tracker.addChangedID(this.localID); + } + + return true; + })(); + }, + + /** + * Validates and sends a command to a client or all clients. + * + * Calling this does not actually sync the command data to the server. If the + * client already has the command/args pair, it won't receive a duplicate + * command. + * This method is async since it writes the command to a file. + * + * @param command + * Command to invoke on remote clients + * @param args + * Array of arguments to give to the command + * @param clientId + * Client ID to send command to. If undefined, send to all remote + * clients. + * @param flowID + * A unique identifier used to track success for this operation across + * devices. + */ + async sendCommand(command, args, clientId = null, telemetryExtra = {}) { + let commandData = this._commands[command]; + // Don't send commands that we don't know about. + if (!commandData) { + this._log.error("Unknown command to send: " + command); + return; + } else if (!args || args.length != commandData.args) { + // Don't send a command with the wrong number of arguments. + this._log.error( + "Expected " + + commandData.args + + " args for '" + + command + + "', but got " + + args + ); + return; + } + + // We allocate a "flowID" here, so it is used for each client. + telemetryExtra = Object.assign({}, telemetryExtra); // don't clobber the caller's object + if (!telemetryExtra.flowID) { + telemetryExtra.flowID = Utils.makeGUID(); + } + + if (clientId) { + await this._sendCommandToClient(command, args, clientId, telemetryExtra); + } else { + for (let [id, record] of Object.entries(this._store._remoteClients)) { + if (!record.stale) { + await this._sendCommandToClient(command, args, id, telemetryExtra); + } + } + } + }, + + async _removeRemoteClient(id) { + delete this._store._remoteClients[id]; + await this._tracker.removeChangedID(id); + await this._removeClientCommands(id); + this._modified.delete(id); + }, +}; +Object.setPrototypeOf(ClientEngine.prototype, SyncEngine.prototype); + +function ClientStore(name, engine) { + Store.call(this, name, engine); +} +ClientStore.prototype = { + _remoteClients: {}, + + async create(record) { + await this.update(record); + }, + + async update(record) { + if (record.id == this.engine.localID) { + // Only grab commands from the server; local name/type always wins + this.engine.localCommands = record.commands; + } else { + this._remoteClients[record.id] = record.cleartext; + } + }, + + async createRecord(id, collection) { + let record = new ClientsRec(collection, id); + + const commandsChanges = this.engine._currentlySyncingCommands + ? this.engine._currentlySyncingCommands[id] + : []; + + // Package the individual components into a record for the local client + if (id == this.engine.localID) { + try { + record.fxaDeviceId = await this.engine.fxAccounts.device.getLocalId(); + } catch (error) { + this._log.warn("failed to get fxa device id", error); + } + record.name = this.engine.localName; + record.type = this.engine.localType; + record.version = Services.appinfo.version; + record.protocols = SUPPORTED_PROTOCOL_VERSIONS; + + // Substract the commands we recorded that we've already executed + if ( + commandsChanges && + commandsChanges.length && + this.engine.localCommands && + this.engine.localCommands.length + ) { + record.commands = this.engine.localCommands.filter( + command => !hasDupeCommand(commandsChanges, command) + ); + } + + // Optional fields. + record.os = Services.appinfo.OS; // "Darwin" + record.appPackage = Services.appinfo.ID; + record.application = this.engine.brandName; // "Nightly" + + // We can't compute these yet. + // record.device = ""; // Bug 1100723 + // record.formfactor = ""; // Bug 1100722 + } else { + record.cleartext = Object.assign({}, this._remoteClients[id]); + delete record.cleartext.serverLastModified; // serverLastModified is a local only attribute. + + // Add the commands we have to send + if (commandsChanges && commandsChanges.length) { + const recordCommands = record.cleartext.commands || []; + const newCommands = commandsChanges.filter( + command => !hasDupeCommand(recordCommands, command) + ); + record.cleartext.commands = recordCommands.concat(newCommands); + } + + if (record.cleartext.stale) { + // It's almost certainly a logic error for us to upload a record we + // consider stale, so make log noise, but still remove the flag. + this._log.error( + `Preparing to upload record ${id} that we consider stale` + ); + delete record.cleartext.stale; + } + } + if (record.commands) { + const maxPayloadSize = + this.engine.service.getMemcacheMaxRecordPayloadSize(); + let origOrder = new Map(record.commands.map((c, i) => [c, i])); + // we sort first by priority, and second by age (indicated by order in the + // original list) + let commands = record.commands.slice().sort((a, b) => { + let infoA = this.engine._commands[a.command]; + let infoB = this.engine._commands[b.command]; + // Treat unknown command types as highest priority, to allow us to add + // high priority commands in the future without worrying about clients + // removing them on each-other unnecessarially. + let importA = infoA ? infoA.importance : 0; + let importB = infoB ? infoB.importance : 0; + // Higher importantance numbers indicate that we care less, so they + // go to the end of the list where they'll be popped off. + let importDelta = importA - importB; + if (importDelta != 0) { + return importDelta; + } + let origIdxA = origOrder.get(a); + let origIdxB = origOrder.get(b); + // Within equivalent priorities, we put older entries near the end + // of the list, so that they are removed first. + return origIdxB - origIdxA; + }); + let truncatedCommands = Utils.tryFitItems(commands, maxPayloadSize); + if (truncatedCommands.length != record.commands.length) { + this._log.warn( + `Removing commands from client ${id} (from ${record.commands.length} to ${truncatedCommands.length})` + ); + // Restore original order. + record.commands = truncatedCommands.sort( + (a, b) => origOrder.get(a) - origOrder.get(b) + ); + } + } + return record; + }, + + async itemExists(id) { + return id in (await this.getAllIDs()); + }, + + async getAllIDs() { + let ids = {}; + ids[this.engine.localID] = true; + for (let id in this._remoteClients) { + ids[id] = true; + } + return ids; + }, + + async wipe() { + this._remoteClients = {}; + }, +}; +Object.setPrototypeOf(ClientStore.prototype, Store.prototype); + +function ClientsTracker(name, engine) { + LegacyTracker.call(this, name, engine); +} +ClientsTracker.prototype = { + _enabled: false, + + onStart() { + Svc.Obs.add("fxaccounts:new_device_id", this.asyncObserver); + Services.prefs.addObserver( + PREF_ACCOUNT_ROOT + "device.name", + this.asyncObserver + ); + }, + onStop() { + Services.prefs.removeObserver( + PREF_ACCOUNT_ROOT + "device.name", + this.asyncObserver + ); + Svc.Obs.remove("fxaccounts:new_device_id", this.asyncObserver); + }, + + async observe(subject, topic, data) { + switch (topic) { + case "nsPref:changed": + this._log.debug("client.name preference changed"); + // Fallthrough intended. + case "fxaccounts:new_device_id": + await this.addChangedID(this.engine.localID); + this.score += SINGLE_USER_THRESHOLD + 1; // ALWAYS SYNC NOW. + break; + } + }, +}; +Object.setPrototypeOf(ClientsTracker.prototype, LegacyTracker.prototype); diff --git a/services/sync/modules/engines/extension-storage.sys.mjs b/services/sync/modules/engines/extension-storage.sys.mjs new file mode 100644 index 0000000000..d2671978c8 --- /dev/null +++ b/services/sync/modules/engines/extension-storage.sys.mjs @@ -0,0 +1,308 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { + BridgedEngine, + BridgeWrapperXPCOM, + LogAdapter, +} from "resource://services-sync/bridged_engine.sys.mjs"; +import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + MULTI_DEVICE_THRESHOLD: "resource://services-sync/constants.sys.mjs", + Observers: "resource://services-common/observers.sys.mjs", + SCORE_INCREMENT_MEDIUM: "resource://services-sync/constants.sys.mjs", + Svc: "resource://services-sync/util.sys.mjs", + extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs", + + extensionStorageSyncKinto: + "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "StorageSyncService", + "@mozilla.org/extensions/storage/sync;1", + "nsIInterfaceRequestor" +); + +const PREF_FORCE_ENABLE = "engine.extension-storage.force"; + +// A helper to indicate whether extension-storage is enabled - it's based on +// the "addons" pref. The same logic is shared between both engine impls. +function getEngineEnabled() { + // By default, we sync extension storage if we sync addons. This + // lets us simplify the UX since users probably don't consider + // "extension preferences" a separate category of syncing. + // However, we also respect engine.extension-storage.force, which + // can be set to true or false, if a power user wants to customize + // the behavior despite the lack of UI. + if ( + lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) != + Ci.nsIPrefBranch.PREF_INVALID + ) { + return lazy.Svc.PrefBranch.getBoolPref(PREF_FORCE_ENABLE); + } + return lazy.Svc.PrefBranch.getBoolPref("engine.addons", false); +} + +function setEngineEnabled(enabled) { + // This will be called by the engine manager when declined on another device. + // Things will go a bit pear-shaped if the engine manager tries to end up + // with 'addons' and 'extension-storage' in different states - however, this + // *can* happen given we support the `engine.extension-storage.force` + // preference. So if that pref exists, we set it to this value. If that pref + // doesn't exist, we just ignore it and hope that the 'addons' engine is also + // going to be set to the same state. + if ( + lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) != + Ci.nsIPrefBranch.PREF_INVALID + ) { + lazy.Svc.PrefBranch.setBoolPref(PREF_FORCE_ENABLE, enabled); + } +} + +// A "bridged engine" to our webext-storage component. +export function ExtensionStorageEngineBridge(service) { + this.component = lazy.StorageSyncService.getInterface( + Ci.mozIBridgedSyncEngine + ); + BridgedEngine.call(this, "Extension-Storage", service); + this._bridge = new BridgeWrapperXPCOM(this.component); + + let app_services_logger = Cc["@mozilla.org/appservices/logger;1"].getService( + Ci.mozIAppServicesLogger + ); + let logger_target = "app-services:webext_storage:sync"; + app_services_logger.register(logger_target, new LogAdapter(this._log)); +} + +ExtensionStorageEngineBridge.prototype = { + syncPriority: 10, + + // Used to override the engine name in telemetry, so that we can distinguish . + overrideTelemetryName: "rust-webext-storage", + + _notifyPendingChanges() { + return new Promise(resolve => { + this.component + .QueryInterface(Ci.mozISyncedExtensionStorageArea) + .fetchPendingSyncChanges({ + QueryInterface: ChromeUtils.generateQI([ + "mozIExtensionStorageListener", + "mozIExtensionStorageCallback", + ]), + onChanged: (extId, json) => { + try { + lazy.extensionStorageSync.notifyListeners( + extId, + JSON.parse(json) + ); + } catch (ex) { + this._log.warn( + `Error notifying change listeners for ${extId}`, + ex + ); + } + }, + handleSuccess: resolve, + handleError: (code, message) => { + this._log.warn( + "Error fetching pending synced changes", + message, + code + ); + resolve(); + }, + }); + }); + }, + + _takeMigrationInfo() { + return new Promise((resolve, reject) => { + this.component + .QueryInterface(Ci.mozIExtensionStorageArea) + .takeMigrationInfo({ + QueryInterface: ChromeUtils.generateQI([ + "mozIExtensionStorageCallback", + ]), + handleSuccess: result => { + resolve(result ? JSON.parse(result) : null); + }, + handleError: (code, message) => { + this._log.warn("Error fetching migration info", message, code); + // `takeMigrationInfo` doesn't actually perform the migration, + // just reads (and clears) any data stored in the DB from the + // previous migration. + // + // Any errors here are very likely occurring a good while + // after the migration ran, so we just warn and pretend + // nothing was there. + resolve(null); + }, + }); + }); + }, + + async _syncStartup() { + let result = await super._syncStartup(); + let info = await this._takeMigrationInfo(); + if (info) { + lazy.Observers.notify( + "weave:telemetry:migration", + info, + "webext-storage" + ); + } + return result; + }, + + async _processIncoming() { + await super._processIncoming(); + try { + await this._notifyPendingChanges(); + } catch (ex) { + // Failing to notify `storage.onChanged` observers is bad, but shouldn't + // interrupt syncing. + this._log.warn("Error notifying about synced changes", ex); + } + }, + + get enabled() { + return getEngineEnabled(); + }, + set enabled(enabled) { + setEngineEnabled(enabled); + }, +}; +Object.setPrototypeOf( + ExtensionStorageEngineBridge.prototype, + BridgedEngine.prototype +); + +/** + ***************************************************************************** + * + * Deprecated support for Kinto + * + ***************************************************************************** + */ + +/** + * The Engine that manages syncing for the web extension "storage" + * API, and in particular ext.storage.sync. + * + * ext.storage.sync is implemented using Kinto, so it has mechanisms + * for syncing that we do not need to integrate in the Firefox Sync + * framework, so this is something of a stub. + */ +export function ExtensionStorageEngineKinto(service) { + SyncEngine.call(this, "Extension-Storage", service); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_skipPercentageChance", + "services.sync.extension-storage.skipPercentageChance", + 0 + ); +} + +ExtensionStorageEngineKinto.prototype = { + _trackerObj: ExtensionStorageTracker, + // we don't need these since we implement our own sync logic + _storeObj: undefined, + _recordObj: undefined, + + syncPriority: 10, + allowSkippedRecord: false, + + async _sync() { + return lazy.extensionStorageSyncKinto.syncAll(); + }, + + get enabled() { + return getEngineEnabled(); + }, + // We only need the enabled setter for the edge-case where info/collections + // has `extension-storage` - which could happen if the pref to flip the new + // engine on was once set but no longer is. + set enabled(enabled) { + setEngineEnabled(enabled); + }, + + _wipeClient() { + return lazy.extensionStorageSyncKinto.clearAll(); + }, + + shouldSkipSync(syncReason) { + if (syncReason == "user" || syncReason == "startup") { + this._log.info( + `Not skipping extension storage sync: reason == ${syncReason}` + ); + // Always sync if a user clicks the button, or if we're starting up. + return false; + } + // Ensure this wouldn't cause a resync... + if (this._tracker.score >= lazy.MULTI_DEVICE_THRESHOLD) { + this._log.info( + "Not skipping extension storage sync: Would trigger resync anyway" + ); + return false; + } + + let probability = this._skipPercentageChance / 100.0; + // Math.random() returns a value in the interval [0, 1), so `>` is correct: + // if `probability` is 1 skip every time, and if it's 0, never skip. + let shouldSkip = probability > Math.random(); + + this._log.info( + `Skipping extension-storage sync with a chance of ${probability}: ${shouldSkip}` + ); + return shouldSkip; + }, +}; +Object.setPrototypeOf( + ExtensionStorageEngineKinto.prototype, + SyncEngine.prototype +); + +function ExtensionStorageTracker(name, engine) { + Tracker.call(this, name, engine); + this._ignoreAll = false; +} +ExtensionStorageTracker.prototype = { + get ignoreAll() { + return this._ignoreAll; + }, + + set ignoreAll(value) { + this._ignoreAll = value; + }, + + onStart() { + lazy.Svc.Obs.add("ext.storage.sync-changed", this.asyncObserver); + }, + + onStop() { + lazy.Svc.Obs.remove("ext.storage.sync-changed", this.asyncObserver); + }, + + async observe(subject, topic, data) { + if (this.ignoreAll) { + return; + } + + if (topic !== "ext.storage.sync-changed") { + return; + } + + // Single adds, removes and changes are not so important on their + // own, so let's just increment score a bit. + this.score += lazy.SCORE_INCREMENT_MEDIUM; + }, +}; +Object.setPrototypeOf(ExtensionStorageTracker.prototype, Tracker.prototype); diff --git a/services/sync/modules/engines/forms.sys.mjs b/services/sync/modules/engines/forms.sys.mjs new file mode 100644 index 0000000000..3516327659 --- /dev/null +++ b/services/sync/modules/engines/forms.sys.mjs @@ -0,0 +1,298 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + Store, + SyncEngine, + LegacyTracker, +} from "resource://services-sync/engines.sys.mjs"; + +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { SCORE_INCREMENT_MEDIUM } from "resource://services-sync/constants.sys.mjs"; +import { + CollectionProblemData, + CollectionValidator, +} from "resource://services-sync/collection_validator.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + FormHistory: "resource://gre/modules/FormHistory.sys.mjs", +}); + +const FORMS_TTL = 3 * 365 * 24 * 60 * 60; // Three years in seconds. + +export function FormRec(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +FormRec.prototype = { + _logName: "Sync.Record.Form", + ttl: FORMS_TTL, +}; +Object.setPrototypeOf(FormRec.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]); + +var FormWrapper = { + _log: Log.repository.getLogger("Sync.Engine.Forms"), + + _getEntryCols: ["fieldname", "value"], + _guidCols: ["guid"], + + _search(terms, searchData) { + return lazy.FormHistory.search(terms, searchData); + }, + + async _update(changes) { + if (!lazy.FormHistory.enabled) { + return; // update isn't going to do anything. + } + await lazy.FormHistory.update(changes).catch(console.error); + }, + + async getEntry(guid) { + let results = await this._search(this._getEntryCols, { guid }); + if (!results.length) { + return null; + } + return { name: results[0].fieldname, value: results[0].value }; + }, + + async getGUID(name, value) { + // Query for the provided entry. + let query = { fieldname: name, value }; + let results = await this._search(this._guidCols, query); + return results.length ? results[0].guid : null; + }, + + async hasGUID(guid) { + // We could probably use a count function here, but search exists... + let results = await this._search(this._guidCols, { guid }); + return !!results.length; + }, + + async replaceGUID(oldGUID, newGUID) { + let changes = { + op: "update", + guid: oldGUID, + newGuid: newGUID, + }; + await this._update(changes); + }, +}; + +export function FormEngine(service) { + SyncEngine.call(this, "Forms", service); +} + +FormEngine.prototype = { + _storeObj: FormStore, + _trackerObj: FormTracker, + _recordObj: FormRec, + + syncPriority: 6, + + get prefName() { + return "history"; + }, + + async _findDupe(item) { + return FormWrapper.getGUID(item.name, item.value); + }, +}; +Object.setPrototypeOf(FormEngine.prototype, SyncEngine.prototype); + +function FormStore(name, engine) { + Store.call(this, name, engine); +} +FormStore.prototype = { + async _processChange(change) { + // If this._changes is defined, then we are applying a batch, so we + // can defer it. + if (this._changes) { + this._changes.push(change); + return; + } + + // Otherwise we must handle the change right now. + await FormWrapper._update(change); + }, + + async applyIncomingBatch(records, countTelemetry) { + Async.checkAppReady(); + // We collect all the changes to be made then apply them all at once. + this._changes = []; + let failures = await Store.prototype.applyIncomingBatch.call( + this, + records, + countTelemetry + ); + if (this._changes.length) { + await FormWrapper._update(this._changes); + } + delete this._changes; + return failures; + }, + + async getAllIDs() { + let results = await FormWrapper._search(["guid"], []); + let guids = {}; + for (let result of results) { + guids[result.guid] = true; + } + return guids; + }, + + async changeItemID(oldID, newID) { + await FormWrapper.replaceGUID(oldID, newID); + }, + + async itemExists(id) { + return FormWrapper.hasGUID(id); + }, + + async createRecord(id, collection) { + let record = new FormRec(collection, id); + let entry = await FormWrapper.getEntry(id); + if (entry != null) { + record.name = entry.name; + record.value = entry.value; + } else { + record.deleted = true; + } + return record; + }, + + async create(record) { + this._log.trace("Adding form record for " + record.name); + let change = { + op: "add", + guid: record.id, + fieldname: record.name, + value: record.value, + }; + await this._processChange(change); + }, + + async remove(record) { + this._log.trace("Removing form record: " + record.id); + let change = { + op: "remove", + guid: record.id, + }; + await this._processChange(change); + }, + + async update(record) { + this._log.trace("Ignoring form record update request!"); + }, + + async wipe() { + let change = { + op: "remove", + }; + await FormWrapper._update(change); + }, +}; +Object.setPrototypeOf(FormStore.prototype, Store.prototype); + +function FormTracker(name, engine) { + LegacyTracker.call(this, name, engine); +} +FormTracker.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + + onStart() { + Svc.Obs.add("satchel-storage-changed", this.asyncObserver); + }, + + onStop() { + Svc.Obs.remove("satchel-storage-changed", this.asyncObserver); + }, + + async observe(subject, topic, data) { + if (this.ignoreAll) { + return; + } + switch (topic) { + case "satchel-storage-changed": + if (data == "formhistory-add" || data == "formhistory-remove") { + let guid = subject.QueryInterface(Ci.nsISupportsString).toString(); + await this.trackEntry(guid); + } + break; + } + }, + + async trackEntry(guid) { + const added = await this.addChangedID(guid); + if (added) { + this.score += SCORE_INCREMENT_MEDIUM; + } + }, +}; +Object.setPrototypeOf(FormTracker.prototype, LegacyTracker.prototype); + +class FormsProblemData extends CollectionProblemData { + getSummary() { + // We don't support syncing deleted form data, so "clientMissing" isn't a problem + return super.getSummary().filter(entry => entry.name !== "clientMissing"); + } +} + +export class FormValidator extends CollectionValidator { + constructor() { + super("forms", "id", ["name", "value"]); + this.ignoresMissingClients = true; + } + + emptyProblemData() { + return new FormsProblemData(); + } + + async getClientItems() { + return FormWrapper._search(["guid", "fieldname", "value"], {}); + } + + normalizeClientItem(item) { + return { + id: item.guid, + guid: item.guid, + name: item.fieldname, + fieldname: item.fieldname, + value: item.value, + original: item, + }; + } + + async normalizeServerItem(item) { + let res = Object.assign( + { + guid: item.id, + fieldname: item.name, + original: item, + }, + item + ); + // Missing `name` or `value` causes the getGUID call to throw + if (item.name !== undefined && item.value !== undefined) { + let guid = await FormWrapper.getGUID(item.name, item.value); + if (guid) { + res.guid = guid; + res.id = guid; + res.duped = true; + } + } + + return res; + } +} diff --git a/services/sync/modules/engines/history.sys.mjs b/services/sync/modules/engines/history.sys.mjs new file mode 100644 index 0000000000..44014e4d9e --- /dev/null +++ b/services/sync/modules/engines/history.sys.mjs @@ -0,0 +1,654 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const HISTORY_TTL = 5184000; // 60 days in milliseconds +const THIRTY_DAYS_IN_MS = 2592000000; // 30 days in milliseconds +// Sync may bring new fields from other clients, not yet understood by our engine. +// Unknown fields outside these fields are aggregated into 'unknownFields' and +// safely synced to prevent data loss. +const VALID_HISTORY_FIELDS = ["id", "title", "histUri", "visits"]; +const VALID_VISIT_FIELDS = ["date", "type", "transition"]; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { + MAX_HISTORY_DOWNLOAD, + MAX_HISTORY_UPLOAD, + SCORE_INCREMENT_SMALL, + SCORE_INCREMENT_XLARGE, +} from "resource://services-sync/constants.sys.mjs"; + +import { + Store, + SyncEngine, + LegacyTracker, +} from "resource://services-sync/engines.sys.mjs"; +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Utils } from "resource://services-sync/util.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +export function HistoryRec(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +HistoryRec.prototype = { + _logName: "Sync.Record.History", + ttl: HISTORY_TTL, +}; +Object.setPrototypeOf(HistoryRec.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]); + +export function HistoryEngine(service) { + SyncEngine.call(this, "History", service); +} + +HistoryEngine.prototype = { + _recordObj: HistoryRec, + _storeObj: HistoryStore, + _trackerObj: HistoryTracker, + downloadLimit: MAX_HISTORY_DOWNLOAD, + + syncPriority: 7, + + async getSyncID() { + return lazy.PlacesSyncUtils.history.getSyncId(); + }, + + async ensureCurrentSyncID(newSyncID) { + this._log.debug( + "Checking if server sync ID ${newSyncID} matches existing", + { newSyncID } + ); + await lazy.PlacesSyncUtils.history.ensureCurrentSyncId(newSyncID); + return newSyncID; + }, + + async resetSyncID() { + // First, delete the collection on the server. It's fine if we're + // interrupted here: on the next sync, we'll detect that our old sync ID is + // now stale, and start over as a first sync. + await this._deleteServerCollection(); + // Then, reset our local sync ID. + return this.resetLocalSyncID(); + }, + + async resetLocalSyncID() { + let newSyncID = await lazy.PlacesSyncUtils.history.resetSyncId(); + this._log.debug("Assigned new sync ID ${newSyncID}", { newSyncID }); + return newSyncID; + }, + + async getLastSync() { + let lastSync = await lazy.PlacesSyncUtils.history.getLastSync(); + return lastSync; + }, + + async setLastSync(lastSync) { + await lazy.PlacesSyncUtils.history.setLastSync(lastSync); + }, + + shouldSyncURL(url) { + return !url.startsWith("file:"); + }, + + async pullNewChanges() { + const changedIDs = await this._tracker.getChangedIDs(); + let modifiedGUIDs = Object.keys(changedIDs); + if (!modifiedGUIDs.length) { + return {}; + } + + let guidsToRemove = + await lazy.PlacesSyncUtils.history.determineNonSyncableGuids( + modifiedGUIDs + ); + await this._tracker.removeChangedID(...guidsToRemove); + return changedIDs; + }, + + async _resetClient() { + await super._resetClient(); + await lazy.PlacesSyncUtils.history.reset(); + }, +}; +Object.setPrototypeOf(HistoryEngine.prototype, SyncEngine.prototype); + +function HistoryStore(name, engine) { + Store.call(this, name, engine); +} + +HistoryStore.prototype = { + // We try and only update this many visits at one time. + MAX_VISITS_PER_INSERT: 500, + + // Some helper functions to handle GUIDs + async setGUID(uri, guid) { + if (!guid) { + guid = Utils.makeGUID(); + } + + try { + await lazy.PlacesSyncUtils.history.changeGuid(uri, guid); + } catch (e) { + this._log.error("Error setting GUID ${guid} for URI ${uri}", guid, uri); + } + + return guid; + }, + + async GUIDForUri(uri, create) { + // Use the existing GUID if it exists + let guid; + try { + guid = await lazy.PlacesSyncUtils.history.fetchGuidForURL(uri); + } catch (e) { + this._log.error("Error fetching GUID for URL ${uri}", uri); + } + + // If the URI has an existing GUID, return it. + if (guid) { + return guid; + } + + // If the URI doesn't have a GUID and we were indicated to create one. + if (create) { + return this.setGUID(uri); + } + + // If the URI doesn't have a GUID and we didn't create one for it. + return null; + }, + + async changeItemID(oldID, newID) { + let info = await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(oldID); + if (!info) { + throw new Error(`Can't change ID for nonexistent history entry ${oldID}`); + } + this.setGUID(info.url, newID); + }, + + async getAllIDs() { + let urls = await lazy.PlacesSyncUtils.history.getAllURLs({ + since: new Date(Date.now() - THIRTY_DAYS_IN_MS), + limit: MAX_HISTORY_UPLOAD, + }); + + let urlsByGUID = {}; + for (let url of urls) { + if (!this.engine.shouldSyncURL(url)) { + continue; + } + let guid = await this.GUIDForUri(url, true); + urlsByGUID[guid] = url; + } + return urlsByGUID; + }, + + async applyIncomingBatch(records, countTelemetry) { + // Convert incoming records to mozIPlaceInfo objects which are applied as + // either history additions or removals. + let failed = []; + let toAdd = []; + let toRemove = []; + let pageGuidsWithUnknownFields = new Map(); + let visitTimesWithUnknownFields = new Map(); + await Async.yieldingForEach(records, async record => { + if (record.deleted) { + toRemove.push(record); + } else { + try { + let pageInfo = await this._recordToPlaceInfo(record); + if (pageInfo) { + toAdd.push(pageInfo); + + // Pull any unknown fields that may have come from other clients + let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields( + record.cleartext, + VALID_HISTORY_FIELDS + ); + if (unknownFields) { + pageGuidsWithUnknownFields.set(pageInfo.guid, { unknownFields }); + } + + // Visits themselves could also contain unknown fields + for (const visit of pageInfo.visits) { + let unknownVisitFields = + lazy.PlacesSyncUtils.extractUnknownFields( + visit, + VALID_VISIT_FIELDS + ); + if (unknownVisitFields) { + // Visits don't have an id at the time of sync so we'll need + // to use the time instead until it's inserted in the DB + visitTimesWithUnknownFields.set(visit.date.getTime(), { + unknownVisitFields, + }); + } + } + } + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.error("Failed to create a place info", ex); + this._log.trace("The record that failed", record); + failed.push(record.id); + countTelemetry.addIncomingFailedReason(ex.message); + } + } + }); + if (toAdd.length || toRemove.length) { + if (toRemove.length) { + // PlacesUtils.history.remove takes an array of visits to remove, + // but the error semantics are tricky - a single "bad" entry will cause + // an exception before anything is removed. So we do remove them one at + // a time. + await Async.yieldingForEach(toRemove, async record => { + try { + await this.remove(record); + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.error("Failed to delete a place info", ex); + this._log.trace("The record that failed", record); + failed.push(record.id); + countTelemetry.addIncomingFailedReason(ex.message); + } + }); + } + for (let chunk of this._generateChunks(toAdd)) { + // Per bug 1415560, we ignore any exceptions returned by insertMany + // as they are likely to be spurious. We do supply an onError handler + // and log the exceptions seen there as they are likely to be + // informative, but we still never abort the sync based on them. + let unknownFieldsToInsert = []; + try { + await lazy.PlacesUtils.history.insertMany( + chunk, + result => { + const placeToUpdate = pageGuidsWithUnknownFields.get(result.guid); + // Extract the placeId from this result so we can add the unknownFields + // to the proper table + if (placeToUpdate) { + unknownFieldsToInsert.push({ + placeId: result.placeId, + unknownFields: placeToUpdate.unknownFields, + }); + } + // same for visits + result.visits.forEach(visit => { + let visitToUpdate = visitTimesWithUnknownFields.get( + visit.date.getTime() + ); + if (visitToUpdate) { + unknownFieldsToInsert.push({ + visitId: visit.visitId, + unknownFields: visitToUpdate.unknownVisitFields, + }); + } + }); + }, + failedVisit => { + this._log.info( + "Failed to insert a history record", + failedVisit.guid + ); + this._log.trace("The record that failed", failedVisit); + failed.push(failedVisit.guid); + } + ); + } catch (ex) { + this._log.info("Failed to insert history records", ex); + countTelemetry.addIncomingFailedReason(ex.message); + } + + // All the top level places or visits that had unknown fields are sent + // to be added to the appropiate tables + await lazy.PlacesSyncUtils.history.updateUnknownFieldsBatch( + unknownFieldsToInsert + ); + } + } + + return failed; + }, + + /** + * Returns a generator that splits records into sanely sized chunks suitable + * for passing to places to prevent places doing bad things at shutdown. + */ + *_generateChunks(records) { + // We chunk based on the number of *visits* inside each record. However, + // we do not split a single record into multiple records, because at some + // time in the future, we intend to ensure these records are ordered by + // lastModified, and advance the engine's timestamp as we process them, + // meaning we can resume exactly where we left off next sync - although + // currently that's not done, so we will retry the entire batch next sync + // if interrupted. + // ie, this means that if a single record has more than MAX_VISITS_PER_INSERT + // visits, we will call insertMany() with exactly 1 record, but with + // more than MAX_VISITS_PER_INSERT visits. + let curIndex = 0; + this._log.debug(`adding ${records.length} records to history`); + while (curIndex < records.length) { + Async.checkAppReady(); // may throw if we are shutting down. + let toAdd = []; // what we are going to insert. + let count = 0; // a counter which tells us when toAdd is full. + do { + let record = records[curIndex]; + curIndex += 1; + toAdd.push(record); + count += record.visits.length; + } while ( + curIndex < records.length && + count + records[curIndex].visits.length <= this.MAX_VISITS_PER_INSERT + ); + this._log.trace(`adding ${toAdd.length} items in this chunk`); + yield toAdd; + } + }, + + /* An internal helper to determine if we can add an entry to places. + Exists primarily so tests can override it. + */ + _canAddURI(uri) { + return lazy.PlacesUtils.history.canAddURI(uri); + }, + + /** + * Converts a Sync history record to a mozIPlaceInfo. + * + * Throws if an invalid record is encountered (invalid URI, etc.), + * returns a new PageInfo object if the record is to be applied, null + * otherwise (no visits to add, etc.), + */ + async _recordToPlaceInfo(record) { + // Sort out invalid URIs and ones Places just simply doesn't want. + record.url = lazy.PlacesUtils.normalizeToURLOrGUID(record.histUri); + record.uri = CommonUtils.makeURI(record.histUri); + + if (!Utils.checkGUID(record.id)) { + this._log.warn("Encountered record with invalid GUID: " + record.id); + return null; + } + record.guid = record.id; + + if ( + !this._canAddURI(record.uri) || + !this.engine.shouldSyncURL(record.uri.spec) + ) { + this._log.trace( + "Ignoring record " + + record.id + + " with URI " + + record.uri.spec + + ": can't add this URI." + ); + return null; + } + + // We dupe visits by date and type. So an incoming visit that has + // the same timestamp and type as a local one won't get applied. + // To avoid creating new objects, we rewrite the query result so we + // can simply check for containment below. + let curVisitsAsArray = []; + let curVisits = new Set(); + try { + curVisitsAsArray = await lazy.PlacesSyncUtils.history.fetchVisitsForURL( + record.histUri + ); + } catch (e) { + this._log.error( + "Error while fetching visits for URL ${record.histUri}", + record.histUri + ); + } + let oldestAllowed = + lazy.PlacesSyncUtils.bookmarks.EARLIEST_BOOKMARK_TIMESTAMP; + if (curVisitsAsArray.length == 20) { + let oldestVisit = curVisitsAsArray[curVisitsAsArray.length - 1]; + oldestAllowed = lazy.PlacesSyncUtils.history.clampVisitDate( + lazy.PlacesUtils.toDate(oldestVisit.date).getTime() + ); + } + + let i, k; + for (i = 0; i < curVisitsAsArray.length; i++) { + // Same logic as used in the loop below to generate visitKey. + let { date, type } = curVisitsAsArray[i]; + let dateObj = lazy.PlacesUtils.toDate(date); + let millis = lazy.PlacesSyncUtils.history + .clampVisitDate(dateObj) + .getTime(); + curVisits.add(`${millis},${type}`); + } + + // Walk through the visits, make sure we have sound data, and eliminate + // dupes. The latter is done by rewriting the array in-place. + for (i = 0, k = 0; i < record.visits.length; i++) { + let visit = (record.visits[k] = record.visits[i]); + + if ( + !visit.date || + typeof visit.date != "number" || + !Number.isInteger(visit.date) + ) { + this._log.warn( + "Encountered record with invalid visit date: " + visit.date + ); + continue; + } + + if ( + !visit.type || + !Object.values(lazy.PlacesUtils.history.TRANSITIONS).includes( + visit.type + ) + ) { + this._log.warn( + "Encountered record with invalid visit type: " + + visit.type + + "; ignoring." + ); + continue; + } + + // Dates need to be integers. Future and far past dates are clamped to the + // current date and earliest sensible date, respectively. + let originalVisitDate = lazy.PlacesUtils.toDate(Math.round(visit.date)); + visit.date = + lazy.PlacesSyncUtils.history.clampVisitDate(originalVisitDate); + + if (visit.date.getTime() < oldestAllowed) { + // Visit is older than the oldest visit we have, and we have so many + // visits for this uri that we hit our limit when inserting. + continue; + } + let visitKey = `${visit.date.getTime()},${visit.type}`; + if (curVisits.has(visitKey)) { + // Visit is a dupe, don't increment 'k' so the element will be + // overwritten. + continue; + } + + // Note the visit key, so that we don't add duplicate visits with + // clamped timestamps. + curVisits.add(visitKey); + + visit.transition = visit.type; + k += 1; + } + record.visits.length = k; // truncate array + + // No update if there aren't any visits to apply. + // History wants at least one visit. + // In any case, the only thing we could change would be the title + // and that shouldn't change without a visit. + if (!record.visits.length) { + this._log.trace( + "Ignoring record " + + record.id + + " with URI " + + record.uri.spec + + ": no visits to add." + ); + return null; + } + + // PageInfo is validated using validateItemProperties which does a shallow + // copy of the properties. Since record uses getters some of the properties + // are not copied over. Thus we create and return a new object. + let pageInfo = { + title: record.title, + url: record.url, + guid: record.guid, + visits: record.visits, + }; + + return pageInfo; + }, + + async remove(record) { + this._log.trace("Removing page: " + record.id); + let removed = await lazy.PlacesUtils.history.remove(record.id); + if (removed) { + this._log.trace("Removed page: " + record.id); + } else { + this._log.debug("Page already removed: " + record.id); + } + }, + + async itemExists(id) { + return !!(await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(id)); + }, + + async createRecord(id, collection) { + let foo = await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(id); + let record = new HistoryRec(collection, id); + if (foo) { + record.histUri = foo.url; + record.title = foo.title; + record.sortindex = foo.frecency; + + // If we had any unknown fields, ensure we put it back on the + // top-level record + if (foo.unknownFields) { + let unknownFields = JSON.parse(foo.unknownFields); + Object.assign(record.cleartext, unknownFields); + } + + try { + record.visits = await lazy.PlacesSyncUtils.history.fetchVisitsForURL( + record.histUri + ); + } catch (e) { + this._log.error( + "Error while fetching visits for URL ${record.histUri}", + record.histUri + ); + record.visits = []; + } + } else { + record.deleted = true; + } + + return record; + }, + + async wipe() { + return lazy.PlacesSyncUtils.history.wipe(); + }, +}; +Object.setPrototypeOf(HistoryStore.prototype, Store.prototype); + +function HistoryTracker(name, engine) { + LegacyTracker.call(this, name, engine); +} +HistoryTracker.prototype = { + onStart() { + this._log.info("Adding Places observer."); + this._placesObserver = new PlacesWeakCallbackWrapper( + this.handlePlacesEvents.bind(this) + ); + PlacesObservers.addListener( + ["page-visited", "history-cleared", "page-removed"], + this._placesObserver + ); + }, + + onStop() { + this._log.info("Removing Places observer."); + if (this._placesObserver) { + PlacesObservers.removeListener( + ["page-visited", "history-cleared", "page-removed"], + this._placesObserver + ); + } + }, + + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + + handlePlacesEvents(aEvents) { + this.asyncObserver.enqueueCall(() => this._handlePlacesEvents(aEvents)); + }, + + async _handlePlacesEvents(aEvents) { + if (this.ignoreAll) { + this._log.trace( + "ignoreAll: ignoring visits [" + + aEvents.map(v => v.guid).join(",") + + "]" + ); + return; + } + for (let event of aEvents) { + switch (event.type) { + case "page-visited": { + this._log.trace("'page-visited': " + event.url); + if ( + this.engine.shouldSyncURL(event.url) && + (await this.addChangedID(event.pageGuid)) + ) { + this.score += SCORE_INCREMENT_SMALL; + } + break; + } + case "history-cleared": { + this._log.trace("history-cleared"); + // Note that we're going to trigger a sync, but none of the cleared + // pages are tracked, so the deletions will not be propagated. + // See Bug 578694. + this.score += SCORE_INCREMENT_XLARGE; + break; + } + case "page-removed": { + if (event.reason === PlacesVisitRemoved.REASON_EXPIRED) { + return; + } + + this._log.trace( + "page-removed: " + event.url + ", reason " + event.reason + ); + const added = await this.addChangedID(event.pageGuid); + if (added) { + this.score += event.isRemovedFromStore + ? SCORE_INCREMENT_XLARGE + : SCORE_INCREMENT_SMALL; + } + break; + } + } + } + }, +}; +Object.setPrototypeOf(HistoryTracker.prototype, LegacyTracker.prototype); diff --git a/services/sync/modules/engines/passwords.sys.mjs b/services/sync/modules/engines/passwords.sys.mjs new file mode 100644 index 0000000000..8dea5664be --- /dev/null +++ b/services/sync/modules/engines/passwords.sys.mjs @@ -0,0 +1,546 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; + +import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs"; +import { CollectionValidator } from "resource://services-sync/collection_validator.sys.mjs"; +import { + Changeset, + Store, + SyncEngine, + Tracker, +} from "resource://services-sync/engines.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +// These are valid fields the server could have for a logins record +// we mainly use this to detect if there are any unknownFields and +// store (but don't process) those fields to roundtrip them back +const VALID_LOGIN_FIELDS = [ + "id", + "displayOrigin", + "formSubmitURL", + "formActionOrigin", + "httpRealm", + "hostname", + "origin", + "password", + "passwordField", + "timeCreated", + "timeLastUsed", + "timePasswordChanged", + "timesUsed", + "username", + "usernameField", + "everSynced", + "syncCounter", + "unknownFields", +]; + +import { LoginManagerStorage } from "resource://passwordmgr/passwordstorage.sys.mjs"; + +// Sync and many tests rely on having an time that is rounded to the nearest +// 100th of a second otherwise tests can fail intermittently. +function roundTimeForSync(time) { + return Math.round(time / 10) / 100; +} + +export function LoginRec(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +LoginRec.prototype = { + _logName: "Sync.Record.Login", + + cleartextToString() { + let o = Object.assign({}, this.cleartext); + if (o.password) { + o.password = "X".repeat(o.password.length); + } + return JSON.stringify(o); + }, +}; +Object.setPrototypeOf(LoginRec.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(LoginRec, "cleartext", [ + "hostname", + "formSubmitURL", + "httpRealm", + "username", + "password", + "usernameField", + "passwordField", + "timeCreated", + "timePasswordChanged", +]); + +export function PasswordEngine(service) { + SyncEngine.call(this, "Passwords", service); +} + +PasswordEngine.prototype = { + _storeObj: PasswordStore, + _trackerObj: PasswordTracker, + _recordObj: LoginRec, + + syncPriority: 2, + + emptyChangeset() { + return new PasswordsChangeset(); + }, + + async ensureCurrentSyncID(newSyncID) { + return Services.logins.ensureCurrentSyncID(newSyncID); + }, + + async getLastSync() { + let legacyValue = await super.getLastSync(); + if (legacyValue) { + await this.setLastSync(legacyValue); + Svc.PrefBranch.clearUserPref(this.name + ".lastSync"); + this._log.debug( + `migrated timestamp of ${legacyValue} to the logins store` + ); + return legacyValue; + } + return this._store.storage.getLastSync(); + }, + + async setLastSync(timestamp) { + await this._store.storage.setLastSync(timestamp); + }, + + // Testing function to emulate that a login has been synced. + async markSynced(guid) { + this._store.storage.resetSyncCounter(guid, 0); + }, + + async pullAllChanges() { + return this._getChangedIDs(true); + }, + + async getChangedIDs() { + return this._getChangedIDs(false); + }, + + async _getChangedIDs(getAll) { + let changes = {}; + + let logins = await this._store.storage.getAllLogins(true); + for (let login of logins) { + if (getAll || login.syncCounter > 0) { + if (Utils.getSyncCredentialsHosts().has(login.origin)) { + continue; + } + + changes[login.guid] = { + counter: login.syncCounter, // record the initial counter value + modified: roundTimeForSync(login.timePasswordChanged), + deleted: this._store.storage.loginIsDeleted(login.guid), + }; + } + } + + return changes; + }, + + async trackRemainingChanges() { + // Reset the syncCounter on the items that were changed. + for (let [guid, { counter, synced }] of Object.entries( + this._modified.changes + )) { + if (synced) { + this._store.storage.resetSyncCounter(guid, counter); + } + } + }, + + async _findDupe(item) { + let login = this._store._nsLoginInfoFromRecord(item); + if (!login) { + return null; + } + + let logins = await this._store.storage.searchLoginsAsync({ + origin: login.origin, + formActionOrigin: login.formActionOrigin, + httpRealm: login.httpRealm, + }); + + // Look for existing logins that match the origin, but ignore the password. + for (let local of logins) { + if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo) { + return local.guid; + } + } + + return null; + }, + + _deleteId(id) { + this._noteDeletedId(id); + }, + + getValidator() { + return new PasswordValidator(); + }, +}; +Object.setPrototypeOf(PasswordEngine.prototype, SyncEngine.prototype); + +function PasswordStore(name, engine) { + Store.call(this, name, engine); + this._nsLoginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" + ); + this.storage = LoginManagerStorage.create(); +} +PasswordStore.prototype = { + _newPropertyBag() { + return Cc["@mozilla.org/hash-property-bag;1"].createInstance( + Ci.nsIWritablePropertyBag2 + ); + }, + + // Returns an stringified object of any fields not "known" by this client + // mainly used to to prevent data loss for other clients by roundtripping + // these fields without processing them + _processUnknownFields(record) { + let unknownFields = {}; + let keys = Object.keys(record); + keys + .filter(key => !VALID_LOGIN_FIELDS.includes(key)) + .forEach(key => { + unknownFields[key] = record[key]; + }); + // If we found some unknown fields, we stringify it to be able + // to properly encrypt it for roundtripping since we can't know if + // it contained sensitive fields or not + if (Object.keys(unknownFields).length) { + return JSON.stringify(unknownFields); + } + return null; + }, + + /** + * Return an instance of nsILoginInfo (and, implicitly, nsILoginMetaInfo). + */ + _nsLoginInfoFromRecord(record) { + function nullUndefined(x) { + return x == undefined ? null : x; + } + + function stringifyNullUndefined(x) { + return x == undefined || x == null ? "" : x; + } + + if (record.formSubmitURL && record.httpRealm) { + this._log.warn( + "Record " + + record.id + + " has both formSubmitURL and httpRealm. Skipping." + ); + return null; + } + + // Passing in "undefined" results in an empty string, which later + // counts as a value. Explicitly `|| null` these fields according to JS + // truthiness. Records with empty strings or null will be unmolested. + let info = new this._nsLoginInfo( + record.hostname, + nullUndefined(record.formSubmitURL), + nullUndefined(record.httpRealm), + stringifyNullUndefined(record.username), + record.password, + record.usernameField, + record.passwordField + ); + + info.QueryInterface(Ci.nsILoginMetaInfo); + info.guid = record.id; + if (record.timeCreated && !isNaN(new Date(record.timeCreated).getTime())) { + info.timeCreated = record.timeCreated; + } + if ( + record.timePasswordChanged && + !isNaN(new Date(record.timePasswordChanged).getTime()) + ) { + info.timePasswordChanged = record.timePasswordChanged; + } + + // Check the record if there are any unknown fields from other clients + // that we want to roundtrip during sync to prevent data loss + let unknownFields = this._processUnknownFields(record.cleartext); + if (unknownFields) { + info.unknownFields = unknownFields; + } + return info; + }, + + async _getLoginFromGUID(guid) { + let logins = await this.storage.searchLoginsAsync({ guid }, true); + if (logins.length) { + this._log.trace(logins.length + " items matching " + guid + " found."); + return logins[0]; + } + + this._log.trace("No items matching " + guid + " found. Ignoring"); + return null; + }, + + async applyIncoming(record) { + if (record.deleted) { + // Need to supply the sourceSync flag. + await this.remove(record, { sourceSync: true }); + return; + } + + await super.applyIncoming(record); + }, + + async getAllIDs() { + let items = {}; + let logins = await this.storage.getAllLogins(true); + + for (let i = 0; i < logins.length; i++) { + // Skip over Weave password/passphrase entries. + let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo); + if (Utils.getSyncCredentialsHosts().has(metaInfo.origin)) { + continue; + } + + items[metaInfo.guid] = metaInfo; + } + + return items; + }, + + async changeItemID(oldID, newID) { + this._log.trace("Changing item ID: " + oldID + " to " + newID); + + if (!(await this.itemExists(oldID))) { + this._log.trace("Can't change item ID: item doesn't exist"); + return; + } + if (await this._getLoginFromGUID(newID)) { + this._log.trace("Can't change item ID: new ID already in use"); + return; + } + + let prop = this._newPropertyBag(); + prop.setPropertyAsAUTF8String("guid", newID); + + let oldLogin = await this._getLoginFromGUID(oldID); + this.storage.modifyLogin(oldLogin, prop, true); + }, + + async itemExists(id) { + let login = await this._getLoginFromGUID(id); + return login && !this.storage.loginIsDeleted(id); + }, + + async createRecord(id, collection) { + let record = new LoginRec(collection, id); + let login = await this._getLoginFromGUID(id); + + if (!login || this.storage.loginIsDeleted(id)) { + record.deleted = true; + return record; + } + + record.hostname = login.origin; + record.formSubmitURL = login.formActionOrigin; + record.httpRealm = login.httpRealm; + record.username = login.username; + record.password = login.password; + record.usernameField = login.usernameField; + record.passwordField = login.passwordField; + + // Optional fields. + login.QueryInterface(Ci.nsILoginMetaInfo); + record.timeCreated = login.timeCreated; + record.timePasswordChanged = login.timePasswordChanged; + + // put the unknown fields back to the top-level record + // during upload + if (login.unknownFields) { + let unknownFields = JSON.parse(login.unknownFields); + if (unknownFields) { + Object.keys(unknownFields).forEach(key => { + // We have to manually add it to the cleartext since that's + // what gets processed during upload + record.cleartext[key] = unknownFields[key]; + }); + } + } + + return record; + }, + + async create(record) { + let login = this._nsLoginInfoFromRecord(record); + if (!login) { + return; + } + + login.everSynced = true; + + this._log.trace("Adding login for " + record.hostname); + this._log.trace( + "httpRealm: " + + JSON.stringify(login.httpRealm) + + "; " + + "formSubmitURL: " + + JSON.stringify(login.formActionOrigin) + ); + await Services.logins.addLoginAsync(login); + }, + + async remove(record, { sourceSync = false } = {}) { + this._log.trace("Removing login " + record.id); + + let loginItem = await this._getLoginFromGUID(record.id); + if (!loginItem) { + this._log.trace("Asked to remove record that doesn't exist, ignoring"); + return; + } + + this.storage.removeLogin(loginItem, sourceSync); + }, + + async update(record) { + let loginItem = await this._getLoginFromGUID(record.id); + if (!loginItem || this.storage.loginIsDeleted(record.id)) { + this._log.trace("Skipping update for unknown item: " + record.hostname); + return; + } + + this._log.trace("Updating " + record.hostname); + let newinfo = this._nsLoginInfoFromRecord(record); + if (!newinfo) { + return; + } + + loginItem.everSynced = true; + + this.storage.modifyLogin(loginItem, newinfo, true); + }, + + async wipe() { + this.storage.removeAllUserFacingLogins(true); + }, +}; +Object.setPrototypeOf(PasswordStore.prototype, Store.prototype); + +function PasswordTracker(name, engine) { + Tracker.call(this, name, engine); +} +PasswordTracker.prototype = { + onStart() { + Svc.Obs.add("passwordmgr-storage-changed", this.asyncObserver); + }, + + onStop() { + Svc.Obs.remove("passwordmgr-storage-changed", this.asyncObserver); + }, + + async observe(subject, topic, data) { + if (this.ignoreAll) { + return; + } + + switch (data) { + case "modifyLogin": + // The syncCounter should have been incremented only for + // those items that need to be sycned. + if ( + subject.QueryInterface(Ci.nsIArrayExtensions).GetElementAt(1) + .syncCounter > 0 + ) { + this.score += SCORE_INCREMENT_XLARGE; + } + break; + + case "addLogin": + case "removeLogin": + case "importLogins": + this.score += SCORE_INCREMENT_XLARGE; + break; + + case "removeAllLogins": + this.score += + SCORE_INCREMENT_XLARGE * + (subject.QueryInterface(Ci.nsIArrayExtensions).Count() + 1); + break; + } + }, +}; +Object.setPrototypeOf(PasswordTracker.prototype, Tracker.prototype); + +export class PasswordValidator extends CollectionValidator { + constructor() { + super("passwords", "id", [ + "hostname", + "formSubmitURL", + "httpRealm", + "password", + "passwordField", + "username", + "usernameField", + ]); + } + + async getClientItems() { + let logins = await Services.logins.getAllLogins(); + let syncHosts = Utils.getSyncCredentialsHosts(); + let result = logins + .map(l => l.QueryInterface(Ci.nsILoginMetaInfo)) + .filter(l => !syncHosts.has(l.origin)); + return Promise.resolve(result); + } + + normalizeClientItem(item) { + return { + id: item.guid, + guid: item.guid, + hostname: item.hostname, + formSubmitURL: item.formSubmitURL, + httpRealm: item.httpRealm, + password: item.password, + passwordField: item.passwordField, + username: item.username, + usernameField: item.usernameField, + original: item, + }; + } + + async normalizeServerItem(item) { + return Object.assign({ guid: item.id }, item); + } +} + +export class PasswordsChangeset extends Changeset { + getModifiedTimestamp(id) { + return this.changes[id].modified; + } + + has(id) { + let change = this.changes[id]; + if (change) { + return !change.synced; + } + return false; + } + + delete(id) { + let change = this.changes[id]; + if (change) { + // Mark the change as synced without removing it from the set. + // This allows the sync counter to be reset when sync is complete + // within trackRemainingChanges. + change.synced = true; + } + } +} diff --git a/services/sync/modules/engines/prefs.sys.mjs b/services/sync/modules/engines/prefs.sys.mjs new file mode 100644 index 0000000000..f29a9e7b59 --- /dev/null +++ b/services/sync/modules/engines/prefs.sys.mjs @@ -0,0 +1,503 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Prefs which start with this prefix are our "control" prefs - they indicate +// which preferences should be synced. +const PREF_SYNC_PREFS_PREFIX = "services.sync.prefs.sync."; + +// Prefs which have a default value are usually not synced - however, if the +// preference exists under this prefix and the value is: +// * `true`, then we do sync default values. +// * `false`, then as soon as we ever sync a non-default value out, or sync +// any value in, then we toggle the value to `true`. +// +// We never explicitly set this pref back to false, so it's one-shot. +// Some preferences which are known to have a different default value on +// different platforms have this preference with a default value of `false`, +// so they don't sync until one device changes to the non-default value, then +// that value forever syncs, even if it gets reset back to the default. +// Note that preferences handled this way *must also* have the "normal" +// control pref set. +// A possible future enhancement would be to sync these prefs so that +// other distributions can flag them if they change the default, but that +// doesn't seem worthwhile until we can be confident they'd actually create +// this special control pref at the same time they flip the default. +const PREF_SYNC_SEEN_PREFIX = "services.sync.prefs.sync-seen."; + +import { + Store, + SyncEngine, + Tracker, +} from "resource://services-sync/engines.sys.mjs"; +import { CryptoWrapper } from "resource://services-sync/record.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; +import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineLazyGetter(lazy, "PREFS_GUID", () => + CommonUtils.encodeBase64URL(Services.appinfo.ID) +); + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", +}); + +// In bug 1538015, we decided that it isn't always safe to allow all "incoming" +// preferences to be applied locally. So we introduced another preference to control +// this for backward compatibility. We removed that capability in bug 1854698, but in the +// interests of working well between different versions of Firefox, we still forever +// want to prevent this preference from syncing. +// This was the name of the "control" pref. +const PREF_SYNC_PREFS_ARBITRARY = + "services.sync.prefs.dangerously_allow_arbitrary"; + +// Check for a local control pref or PREF_SYNC_PREFS_ARBITRARY +function isAllowedPrefName(prefName) { + if (prefName == PREF_SYNC_PREFS_ARBITRARY) { + return false; // never allow this. + } + // The pref must already have a control pref set, although it doesn't matter + // here whether that value is true or false. We can't use prefHasUserValue + // here because we also want to check prefs still with default values. + try { + Services.prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + prefName); + // pref exists! + return true; + } catch (_) { + return false; + } +} + +export function PrefRec(collection, id) { + CryptoWrapper.call(this, collection, id); +} + +PrefRec.prototype = { + _logName: "Sync.Record.Pref", +}; +Object.setPrototypeOf(PrefRec.prototype, CryptoWrapper.prototype); + +Utils.deferGetSet(PrefRec, "cleartext", ["value"]); + +export function PrefsEngine(service) { + SyncEngine.call(this, "Prefs", service); +} + +PrefsEngine.prototype = { + _storeObj: PrefStore, + _trackerObj: PrefTracker, + _recordObj: PrefRec, + version: 2, + + syncPriority: 1, + allowSkippedRecord: false, + + async getChangedIDs() { + // No need for a proper timestamp (no conflict resolution needed). + let changedIDs = {}; + if (this._tracker.modified) { + changedIDs[lazy.PREFS_GUID] = 0; + } + return changedIDs; + }, + + async _wipeClient() { + await SyncEngine.prototype._wipeClient.call(this); + this.justWiped = true; + }, + + async _reconcile(item) { + // Apply the incoming item if we don't care about the local data + if (this.justWiped) { + this.justWiped = false; + return true; + } + return SyncEngine.prototype._reconcile.call(this, item); + }, + + async _uploadOutgoing() { + try { + await SyncEngine.prototype._uploadOutgoing.call(this); + } finally { + this._store._incomingPrefs = null; + } + }, + + async trackRemainingChanges() { + if (this._modified.count() > 0) { + this._tracker.modified = true; + } + }, +}; +Object.setPrototypeOf(PrefsEngine.prototype, SyncEngine.prototype); + +// We don't use services.sync.engine.tabs.filteredSchemes since it includes +// about: pages and the like, which we want to be syncable in preferences. +// Blob, moz-extension, data and file uris are never safe to sync, +// so we limit our check to those. +const UNSYNCABLE_URL_REGEXP = /^(moz-extension|blob|data|file):/i; +function isUnsyncableURLPref(prefName) { + if (Services.prefs.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_STRING) { + return false; + } + const prefValue = Services.prefs.getStringPref(prefName, ""); + return UNSYNCABLE_URL_REGEXP.test(prefValue); +} + +function PrefStore(name, engine) { + Store.call(this, name, engine); + Svc.Obs.add( + "profile-before-change", + function () { + this.__prefs = null; + }, + this + ); +} +PrefStore.prototype = { + __prefs: null, + // used just for logging so we can work out why we chose to re-upload + _incomingPrefs: null, + get _prefs() { + if (!this.__prefs) { + this.__prefs = Services.prefs.getBranch(""); + } + return this.__prefs; + }, + + _getSyncPrefs() { + let syncPrefs = Services.prefs + .getBranch(PREF_SYNC_PREFS_PREFIX) + .getChildList("") + .filter(pref => isAllowedPrefName(pref) && !isUnsyncableURLPref(pref)); + // Also sync preferences that determine which prefs get synced. + let controlPrefs = syncPrefs.map(pref => PREF_SYNC_PREFS_PREFIX + pref); + return controlPrefs.concat(syncPrefs); + }, + + _isSynced(pref) { + if (pref.startsWith(PREF_SYNC_PREFS_PREFIX)) { + // this is an incoming control pref, which is ignored if there's not already + // a local control pref for the preference. + let controlledPref = pref.slice(PREF_SYNC_PREFS_PREFIX.length); + return isAllowedPrefName(controlledPref); + } + + // This is the pref itself - it must be both allowed, and have a control + // pref which is true. + if (!this._prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + pref, false)) { + return false; + } + return isAllowedPrefName(pref); + }, + + // Given a preference name, returns either a string, bool, number or null. + _getPrefValue(pref) { + switch (this._prefs.getPrefType(pref)) { + case Ci.nsIPrefBranch.PREF_STRING: + return this._prefs.getStringPref(pref); + case Ci.nsIPrefBranch.PREF_INT: + return this._prefs.getIntPref(pref); + case Ci.nsIPrefBranch.PREF_BOOL: + return this._prefs.getBoolPref(pref); + // case Ci.nsIPrefBranch.PREF_INVALID: handled by the fallthrough + } + return null; + }, + + _getAllPrefs() { + let values = {}; + for (let pref of this._getSyncPrefs()) { + // Note: _isSynced doesn't call isUnsyncableURLPref since it would cause + // us not to apply (syncable) changes to preferences that are set locally + // which have unsyncable urls. + if (this._isSynced(pref) && !isUnsyncableURLPref(pref)) { + let isSet = this._prefs.prefHasUserValue(pref); + // Missing and default prefs get the null value, unless that `seen` + // pref is set, in which case it always gets the value. + let forceValue = this._prefs.getBoolPref( + PREF_SYNC_SEEN_PREFIX + pref, + false + ); + if (isSet || forceValue) { + values[pref] = this._getPrefValue(pref); + } else { + values[pref] = null; + } + // If incoming and outgoing don't match then either the user toggled a + // pref that doesn't match an incoming non-default value for that pref + // during a sync (unlikely!) or it refused to stick and is behaving oddly. + if (this._incomingPrefs) { + let inValue = this._incomingPrefs[pref]; + let outValue = values[pref]; + if (inValue != null && outValue != null && inValue != outValue) { + this._log.debug(`Incoming pref '${pref}' refused to stick?`); + this._log.trace(`Incoming: '${inValue}', outgoing: '${outValue}'`); + } + } + // If this is a special "sync-seen" pref, and it's not the default value, + // set the seen pref to true. + if ( + isSet && + this._prefs.getBoolPref(PREF_SYNC_SEEN_PREFIX + pref, false) === false + ) { + this._log.trace(`toggling sync-seen pref for '${pref}' to true`); + this._prefs.setBoolPref(PREF_SYNC_SEEN_PREFIX + pref, true); + } + } + } + return values; + }, + + _maybeLogPrefChange(pref, incomingValue, existingValue) { + if (incomingValue != existingValue) { + this._log.debug(`Adjusting preference "${pref}" to the incoming value`); + // values are PII, so must only be logged at trace. + this._log.trace(`Existing: ${existingValue}. Incoming: ${incomingValue}`); + } + }, + + _setAllPrefs(values) { + const selectedThemeIDPref = "extensions.activeThemeID"; + let selectedThemeIDBefore = this._prefs.getStringPref( + selectedThemeIDPref, + null + ); + let selectedThemeIDAfter = selectedThemeIDBefore; + + // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise + // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device). + let prefs = Object.keys(values).sort( + a => -a.indexOf(PREF_SYNC_PREFS_PREFIX) + ); + for (let pref of prefs) { + let value = values[pref]; + if (!this._isSynced(pref)) { + // It's unusual for us to find an incoming preference (ie, a pref some other + // instance thinks is syncable) which we don't think is syncable. + this._log.trace(`Ignoring incoming unsyncable preference "${pref}"`); + continue; + } + + if (typeof value == "string" && UNSYNCABLE_URL_REGEXP.test(value)) { + this._log.trace(`Skipping incoming unsyncable url for pref: ${pref}`); + continue; + } + + switch (pref) { + // Some special prefs we don't want to set directly. + case selectedThemeIDPref: + selectedThemeIDAfter = value; + break; + + // default is to just set the pref + default: + if (value == null) { + // Pref has gone missing. The best we can do is reset it. + if (this._prefs.prefHasUserValue(pref)) { + this._log.debug(`Clearing existing local preference "${pref}"`); + this._log.trace( + `Existing local value for preference: ${this._getPrefValue( + pref + )}` + ); + } + this._prefs.clearUserPref(pref); + } else { + try { + switch (typeof value) { + case "string": + this._maybeLogPrefChange( + pref, + value, + this._prefs.getStringPref(pref, undefined) + ); + this._prefs.setStringPref(pref, value); + break; + case "number": + this._maybeLogPrefChange( + pref, + value, + this._prefs.getIntPref(pref, undefined) + ); + this._prefs.setIntPref(pref, value); + break; + case "boolean": + this._maybeLogPrefChange( + pref, + value, + this._prefs.getBoolPref(pref, undefined) + ); + this._prefs.setBoolPref(pref, value); + break; + } + } catch (ex) { + this._log.trace(`Failed to set pref: ${pref}`, ex); + } + } + // If there's a "sync-seen" pref for this it gets toggled to true + // regardless of the value. + let seenPref = PREF_SYNC_SEEN_PREFIX + pref; + if ( + this._prefs.getPrefType(seenPref) != Ci.nsIPrefBranch.PREF_INVALID + ) { + this._prefs.setBoolPref(PREF_SYNC_SEEN_PREFIX + pref, true); + } + } + } + // Themes are a little messy. Themes which have been installed are handled + // by the addons engine - but default themes aren't seen by that engine. + // So if there's a new default theme ID and that ID corresponds to a + // system addon, then we arrange to enable that addon here. + if (selectedThemeIDBefore != selectedThemeIDAfter) { + this._maybeEnableBuiltinTheme(selectedThemeIDAfter).catch(e => { + this._log.error("Failed to maybe update the default theme", e); + }); + } + }, + + async _maybeEnableBuiltinTheme(themeId) { + let addon = null; + try { + addon = await lazy.AddonManager.getAddonByID(themeId); + } catch (ex) { + this._log.trace( + `There's no addon with ID '${themeId} - it can't be a builtin theme` + ); + return; + } + if (addon && addon.isBuiltin && addon.type == "theme") { + this._log.trace(`Enabling builtin theme '${themeId}'`); + await addon.enable(); + } else { + this._log.trace( + `Have incoming theme ID of '${themeId}' but it's not a builtin theme` + ); + } + }, + + async getAllIDs() { + /* We store all prefs in just one WBO, with just one GUID */ + let allprefs = {}; + allprefs[lazy.PREFS_GUID] = true; + return allprefs; + }, + + async changeItemID(oldID, newID) { + this._log.trace("PrefStore GUID is constant!"); + }, + + async itemExists(id) { + return id === lazy.PREFS_GUID; + }, + + async createRecord(id, collection) { + let record = new PrefRec(collection, id); + + if (id == lazy.PREFS_GUID) { + record.value = this._getAllPrefs(); + } else { + record.deleted = true; + } + + return record; + }, + + async create(record) { + this._log.trace("Ignoring create request"); + }, + + async remove(record) { + this._log.trace("Ignoring remove request"); + }, + + async update(record) { + // Silently ignore pref updates that are for other apps. + if (record.id != lazy.PREFS_GUID) { + return; + } + + this._log.trace("Received pref updates, applying..."); + this._incomingPrefs = record.value; + this._setAllPrefs(record.value); + }, + + async wipe() { + this._log.trace("Ignoring wipe request"); + }, +}; +Object.setPrototypeOf(PrefStore.prototype, Store.prototype); + +function PrefTracker(name, engine) { + Tracker.call(this, name, engine); + this._ignoreAll = false; + Svc.Obs.add("profile-before-change", this.asyncObserver); +} +PrefTracker.prototype = { + get ignoreAll() { + return this._ignoreAll; + }, + + set ignoreAll(value) { + this._ignoreAll = value; + }, + + get modified() { + return Svc.PrefBranch.getBoolPref("engine.prefs.modified", false); + }, + set modified(value) { + Svc.PrefBranch.setBoolPref("engine.prefs.modified", value); + }, + + clearChangedIDs: function clearChangedIDs() { + this.modified = false; + }, + + __prefs: null, + get _prefs() { + if (!this.__prefs) { + this.__prefs = Services.prefs.getBranch(""); + } + return this.__prefs; + }, + + onStart() { + Services.prefs.addObserver("", this.asyncObserver); + }, + + onStop() { + this.__prefs = null; + Services.prefs.removeObserver("", this.asyncObserver); + }, + + async observe(subject, topic, data) { + switch (topic) { + case "profile-before-change": + await this.stop(); + break; + case "nsPref:changed": + if (this.ignoreAll) { + break; + } + // Trigger a sync for MULTI-DEVICE for a change that determines + // which prefs are synced or a regular pref change. + if ( + data.indexOf(PREF_SYNC_PREFS_PREFIX) == 0 || + this._prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + data, false) + ) { + this.score += SCORE_INCREMENT_XLARGE; + this.modified = true; + this._log.trace("Preference " + data + " changed"); + } + break; + } + }, +}; +Object.setPrototypeOf(PrefTracker.prototype, Tracker.prototype); + +export function getPrefsGUIDForTest() { + return lazy.PREFS_GUID; +} diff --git a/services/sync/modules/engines/tabs.sys.mjs b/services/sync/modules/engines/tabs.sys.mjs new file mode 100644 index 0000000000..861e051d1a --- /dev/null +++ b/services/sync/modules/engines/tabs.sys.mjs @@ -0,0 +1,625 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const STORAGE_VERSION = 1; // This needs to be kept in-sync with the rust storage version + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; +import { + SCORE_INCREMENT_SMALL, + STATUS_OK, + URI_LENGTH_MAX, +} from "resource://services-sync/constants.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { Async } from "resource://services-common/async.sys.mjs"; +import { + SyncRecord, + SyncTelemetry, +} from "resource://services-sync/telemetry.sys.mjs"; +import { BridgedEngine } from "resource://services-sync/bridged_engine.sys.mjs"; + +const FAR_FUTURE = 4102405200000; // 2100/01/01 + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs", + TabsStore: "resource://gre/modules/RustTabs.sys.mjs", + RemoteTabRecord: "resource://gre/modules/RustTabs.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "TABS_FILTERED_SCHEMES", + "services.sync.engine.tabs.filteredSchemes", + "", + null, + val => { + return new Set(val.split("|")); + } +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "SYNC_AFTER_DELAY_MS", + "services.sync.syncedTabs.syncDelayAfterTabChange", + 0 +); + +// A "bridged engine" to our tabs component. +export function TabEngine(service) { + BridgedEngine.call(this, "Tabs", service); +} + +TabEngine.prototype = { + _trackerObj: TabTracker, + syncPriority: 3, + + async prepareTheBridge(isQuickWrite) { + let clientsEngine = this.service.clientsEngine; + // Tell the bridged engine about clients. + // This is the same shape as ClientData in app-services. + // schema: https://github.com/mozilla/application-services/blob/a1168751231ed4e88c44d85f6dccc09c3b412bd2/components/sync15/src/client_types.rs#L14 + let clientData = { + local_client_id: clientsEngine.localID, + recent_clients: {}, + }; + + // We shouldn't upload tabs past what the server will accept + let tabs = await this.getTabsWithinPayloadSize(); + await this._rustStore.setLocalTabs( + tabs.map(tab => { + // rust wants lastUsed in MS but the provider gives it in seconds + tab.lastUsed = tab.lastUsed * 1000; + return new lazy.RemoteTabRecord(tab); + }) + ); + + for (let remoteClient of clientsEngine.remoteClients) { + let id = remoteClient.id; + if (!id) { + throw new Error("Remote client somehow did not have an id"); + } + let client = { + fxa_device_id: remoteClient.fxaDeviceId, + // device_name and device_type are soft-deprecated - every client + // prefers what's in the FxA record. But fill them correctly anyway. + device_name: clientsEngine.getClientName(id) ?? "", + device_type: clientsEngine.getClientType(id), + }; + clientData.recent_clients[id] = client; + } + + // put ourself in there too so we record the correct device info in our sync record. + clientData.recent_clients[clientsEngine.localID] = { + fxa_device_id: await clientsEngine.fxAccounts.device.getLocalId(), + device_name: clientsEngine.localName, + device_type: clientsEngine.localType, + }; + + // Quick write needs to adjust the lastSync so we can POST to the server + // see quickWrite() for details + if (isQuickWrite) { + await this.setLastSync(FAR_FUTURE); + await this._bridge.prepareForSync(JSON.stringify(clientData)); + return; + } + + // Just incase we crashed while the lastSync timestamp was FAR_FUTURE, we + // reset it to zero + if ((await this.getLastSync()) === FAR_FUTURE) { + await this._bridge.setLastSync(0); + } + await this._bridge.prepareForSync(JSON.stringify(clientData)); + }, + + async _syncStartup() { + await super._syncStartup(); + await this.prepareTheBridge(); + }, + + async initialize() { + await SyncEngine.prototype.initialize.call(this); + + let path = PathUtils.join(PathUtils.profileDir, "synced-tabs.db"); + this._rustStore = await lazy.TabsStore.init(path); + this._bridge = await this._rustStore.bridgedEngine(); + + // Uniffi doesn't currently only support async methods, so we'll need to hardcode + // these values for now (which is fine for now as these hardly ever change) + this._bridge.storageVersion = STORAGE_VERSION; + this._bridge.allowSkippedRecord = true; + + this._log.info("Got a bridged engine!"); + this._tracker.modified = true; + }, + + async getChangedIDs() { + // No need for a proper timestamp (no conflict resolution needed). + let changedIDs = {}; + if (this._tracker.modified) { + changedIDs[this.service.clientsEngine.localID] = 0; + } + return changedIDs; + }, + + // API for use by Sync UI code to give user choices of tabs to open. + async getAllClients() { + let remoteTabs = await this._rustStore.getAll(); + let remoteClientTabs = []; + for (let remoteClient of this.service.clientsEngine.remoteClients) { + // We get the some client info from the rust tabs engine and some from + // the clients engine. + let rustClient = remoteTabs.find( + x => x.clientId === remoteClient.fxaDeviceId + ); + if (!rustClient) { + continue; + } + let client = { + // rust gives us ms but js uses seconds, so fix them up. + tabs: rustClient.remoteTabs.map(tab => { + tab.lastUsed = tab.lastUsed / 1000; + return tab; + }), + lastModified: rustClient.lastModified / 1000, + ...remoteClient, + }; + remoteClientTabs.push(client); + } + return remoteClientTabs; + }, + + async removeClientData() { + let url = this.engineURL + "/" + this.service.clientsEngine.localID; + await this.service.resource(url).delete(); + }, + + async trackRemainingChanges() { + if (this._modified.count() > 0) { + this._tracker.modified = true; + } + }, + + async getTabsWithinPayloadSize() { + const maxPayloadSize = this.service.getMaxRecordPayloadSize(); + // See bug 535326 comment 8 for an explanation of the estimation + const maxSerializedSize = (maxPayloadSize / 4) * 3 - 1500; + return TabProvider.getAllTabsWithEstimatedMax(true, maxSerializedSize); + }, + + // Support for "quick writes" + _engineLock: Utils.lock, + _engineLocked: false, + + // Tabs has a special lock to help support its "quick write" + get locked() { + return this._engineLocked; + }, + lock() { + if (this._engineLocked) { + return false; + } + this._engineLocked = true; + return true; + }, + unlock() { + this._engineLocked = false; + }, + + // Quickly do a POST of our current tabs if possible. + // This does things that would be dangerous for other engines - eg, posting + // without checking what's on the server could cause data-loss for other + // engines, but because each device exclusively owns exactly 1 tabs record + // with a known ID, it's safe here. + // Returns true if we successfully synced, false otherwise (either on error + // or because we declined to sync for any reason.) The return value is + // primarily for tests. + async quickWrite() { + if (!this.enabled) { + // this should be very rare, and only if tabs are disabled after the + // timer is created. + this._log.info("Can't do a quick-sync as tabs is disabled"); + return false; + } + // This quick-sync doesn't drive the login state correctly, so just + // decline to sync if out status is bad + if (this.service.status.checkSetup() != STATUS_OK) { + this._log.info( + "Can't do a quick-sync due to the service status", + this.service.status.toString() + ); + return false; + } + if (!this.service.serverConfiguration) { + this._log.info("Can't do a quick sync before the first full sync"); + return false; + } + try { + return await this._engineLock("tabs.js: quickWrite", async () => { + // We want to restore the lastSync timestamp when complete so next sync + // takes tabs written by other devices since our last real sync. + // And for this POST we don't want the protections offered by + // X-If-Unmodified-Since - we want the POST to work even if the remote + // has moved on and we will catch back up next full sync. + const origLastSync = await this.getLastSync(); + try { + return this._doQuickWrite(); + } finally { + // set the lastSync to it's original value for regular sync + await this.setLastSync(origLastSync); + } + })(); + } catch (ex) { + if (!Utils.isLockException(ex)) { + throw ex; + } + this._log.info( + "Can't do a quick-write as another tab sync is in progress" + ); + return false; + } + }, + + // The guts of the quick-write sync, after we've taken the lock, checked + // the service status etc. + async _doQuickWrite() { + // We need to track telemetry for these syncs too! + const name = "tabs"; + let telemetryRecord = new SyncRecord( + SyncTelemetry.allowedEngines, + "quick-write" + ); + telemetryRecord.onEngineStart(name); + try { + Async.checkAppReady(); + // We need to prep the bridge before we try to POST since it grabs + // the most recent local client id and properly sets a lastSync + // which is needed for a proper POST request + await this.prepareTheBridge(true); + this._tracker.clearChangedIDs(); + this._tracker.resetScore(); + + Async.checkAppReady(); + // now just the "upload" part of a sync, + // which for a rust engine is not obvious. + // We need to do is ask the rust engine for the changes. Although + // this is kinda abusing the bridged-engine interface, we know the tabs + // implementation of it works ok + let outgoing = await this._bridge.apply(); + // We know we always have exactly 1 record. + let mine = outgoing[0]; + this._log.trace("outgoing bso", mine); + // `this._recordObj` is a `BridgedRecord`, which isn't exported. + let record = this._recordObj.fromOutgoingBso(this.name, JSON.parse(mine)); + let changeset = {}; + changeset[record.id] = { synced: false, record }; + this._modified.replace(changeset); + + Async.checkAppReady(); + await this._uploadOutgoing(); + telemetryRecord.onEngineStop(name, null); + return true; + } catch (ex) { + this._log.warn("quicksync sync failed", ex); + telemetryRecord.onEngineStop(name, ex); + return false; + } finally { + // The top-level sync is never considered to fail here, just the engine + telemetryRecord.finished(null); + SyncTelemetry.takeTelemetryRecord(telemetryRecord); + } + }, + + async _sync() { + try { + await this._engineLock("tabs.js: fullSync", async () => { + await super._sync(); + })(); + } catch (ex) { + if (!Utils.isLockException(ex)) { + throw ex; + } + this._log.info( + "Can't do full tabs sync as a quick-write is currently running" + ); + } + }, +}; +Object.setPrototypeOf(TabEngine.prototype, BridgedEngine.prototype); + +export const TabProvider = { + getWindowEnumerator() { + return Services.wm.getEnumerator("navigator:browser"); + }, + + shouldSkipWindow(win) { + return win.closed || lazy.PrivateBrowsingUtils.isWindowPrivate(win); + }, + + getAllBrowserTabs() { + let tabs = []; + for (let win of this.getWindowEnumerator()) { + if (this.shouldSkipWindow(win)) { + continue; + } + // Get all the tabs from the browser + for (let tab of win.gBrowser.tabs) { + tabs.push(tab); + } + } + + return tabs.sort(function (a, b) { + return b.lastAccessed - a.lastAccessed; + }); + }, + + // This function creates tabs records up to a specified amount of bytes + // It is an "estimation" since we don't accurately calculate how much the + // favicon and JSON overhead is and give a rough estimate (for optimization purposes) + async getAllTabsWithEstimatedMax(filter, bytesMax) { + let log = Log.repository.getLogger(`Sync.Engine.Tabs.Provider`); + let tabRecords = []; + let iconPromises = []; + let runningByteLength = 0; + let encoder = new TextEncoder(); + + // Fetch all the tabs the user has open + let winTabs = this.getAllBrowserTabs(); + + for (let tab of winTabs) { + // We don't want to process any more tabs than we can sync + if (runningByteLength >= bytesMax) { + log.warn( + `Can't fit all tabs in sync payload: have ${winTabs.length}, + but can only fit ${tabRecords.length}.` + ); + break; + } + + // Note that we used to sync "tab history" (ie, the "back button") state, + // but in practice this hasn't been used - only the current URI is of + // interest to clients. + // We stopped recording this in bug 1783991. + if (!tab?.linkedBrowser) { + continue; + } + let acceptable = !filter + ? url => url + : url => + url && + !lazy.TABS_FILTERED_SCHEMES.has(Services.io.extractScheme(url)); + + let url = tab.linkedBrowser.currentURI?.spec; + // Special case for reader mode. + if (url && url.startsWith("about:reader?")) { + url = lazy.ReaderMode.getOriginalUrl(url); + } + // We ignore the tab completely if the current entry url is + // not acceptable (we need something accurate to open). + if (!acceptable(url)) { + continue; + } + + if (url.length > URI_LENGTH_MAX) { + log.trace("Skipping over-long URL."); + continue; + } + + let thisTab = new lazy.RemoteTabRecord({ + title: tab.linkedBrowser.contentTitle || "", + urlHistory: [url], + icon: "", + lastUsed: Math.floor((tab.lastAccessed || 0) / 1000), + }); + tabRecords.push(thisTab); + + // we don't want to wait for each favicon to resolve to get the bytes + // so we estimate a conservative 100 chars for the favicon and json overhead + // Rust will further optimize and trim if we happened to be wildly off + runningByteLength += + encoder.encode(thisTab.title + thisTab.lastUsed + url).byteLength + 100; + + // Use the favicon service for the icon url - we can wait for the promises at the end. + let iconPromise = lazy.PlacesUtils.promiseFaviconData(url) + .then(iconData => { + thisTab.icon = iconData.uri.spec; + }) + .catch(ex => { + log.trace( + `Failed to fetch favicon for ${url}`, + thisTab.urlHistory[0] + ); + }); + iconPromises.push(iconPromise); + } + + await Promise.allSettled(iconPromises); + return tabRecords; + }, +}; + +function TabTracker(name, engine) { + Tracker.call(this, name, engine); + + // Make sure "this" pointer is always set correctly for event listeners. + this.onTab = Utils.bind2(this, this.onTab); + this._unregisterListeners = Utils.bind2(this, this._unregisterListeners); +} +TabTracker.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + clearChangedIDs() { + this.modified = false; + }, + + // We do not track TabSelect because that almost always triggers + // the web progress listeners (onLocationChange), which we already track + _topics: ["TabOpen", "TabClose"], + + _registerListenersForWindow(window) { + this._log.trace("Registering tab listeners in window"); + for (let topic of this._topics) { + window.addEventListener(topic, this.onTab); + } + window.addEventListener("unload", this._unregisterListeners); + // If it's got a tab browser we can listen for things like navigation. + if (window.gBrowser) { + window.gBrowser.addProgressListener(this); + } + }, + + _unregisterListeners(event) { + this._unregisterListenersForWindow(event.target); + }, + + _unregisterListenersForWindow(window) { + this._log.trace("Removing tab listeners in window"); + window.removeEventListener("unload", this._unregisterListeners); + for (let topic of this._topics) { + window.removeEventListener(topic, this.onTab); + } + if (window.gBrowser) { + window.gBrowser.removeProgressListener(this); + } + }, + + onStart() { + Svc.Obs.add("domwindowopened", this.asyncObserver); + for (let win of Services.wm.getEnumerator("navigator:browser")) { + this._registerListenersForWindow(win); + } + }, + + onStop() { + Svc.Obs.remove("domwindowopened", this.asyncObserver); + for (let win of Services.wm.getEnumerator("navigator:browser")) { + this._unregisterListenersForWindow(win); + } + }, + + async observe(subject, topic, data) { + switch (topic) { + case "domwindowopened": + let onLoad = () => { + subject.removeEventListener("load", onLoad); + // Only register after the window is done loading to avoid unloads. + this._registerListenersForWindow(subject); + }; + + // Add tab listeners now that a window has opened. + subject.addEventListener("load", onLoad); + break; + } + }, + + onTab(event) { + if (event.originalTarget.linkedBrowser) { + let browser = event.originalTarget.linkedBrowser; + if ( + lazy.PrivateBrowsingUtils.isBrowserPrivate(browser) && + !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing + ) { + this._log.trace("Ignoring tab event from private browsing."); + return; + } + } + this._log.trace("onTab event: " + event.type); + + switch (event.type) { + case "TabOpen": + /* We do not have a reliable way of checking the URI on the TabOpen + * so we will rely on the other methods (onLocationChange, getAllTabsWithEstimatedMax) + * to filter these when going through sync + */ + this.callScheduleSync(SCORE_INCREMENT_SMALL); + break; + case "TabClose": + // If event target has `linkedBrowser`, the event target can be assumed <tab> element. + // Else, event target is assumed <browser> element, use the target as it is. + const tab = event.target.linkedBrowser || event.target; + + // TabClose means the tab has already loaded and we can check the URI + // and ignore if it's a scheme we don't care about + if (lazy.TABS_FILTERED_SCHEMES.has(tab.currentURI.scheme)) { + return; + } + this.callScheduleSync(SCORE_INCREMENT_SMALL); + break; + } + }, + + // web progress listeners. + onLocationChange(webProgress, request, locationURI, flags) { + // We only care about top-level location changes. We do want location changes in the + // same document because if a page uses the `pushState()` API, they *appear* as though + // they are in the same document even if the URL changes. It also doesn't hurt to accurately + // reflect the fragment changing - so we allow LOCATION_CHANGE_SAME_DOCUMENT + if ( + flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD || + !webProgress.isTopLevel || + !locationURI + ) { + return; + } + + // We can't filter out tabs that we don't sync here, because we might be + // navigating from a tab that we *did* sync to one we do not, and that + // tab we *did* sync should no longer be synced. + this.callScheduleSync(); + }, + + callScheduleSync(scoreIncrement) { + this.modified = true; + let { scheduler } = this.engine.service; + let delayInMs = lazy.SYNC_AFTER_DELAY_MS; + + // Schedule a sync once we detect a tab change + // to ensure the server always has the most up to date tabs + if ( + delayInMs > 0 && + scheduler.numClients > 1 // Only schedule quick syncs for multi client users + ) { + if (this.tabsQuickWriteTimer) { + this._log.debug( + "Detected a tab change, but a quick-write is already scheduled" + ); + return; + } + this._log.debug( + "Detected a tab change: scheduling a quick-write in " + delayInMs + "ms" + ); + CommonUtils.namedTimer( + () => { + this._log.trace("tab quick-sync timer fired."); + this.engine + .quickWrite() + .then(() => { + this._log.trace("tab quick-sync done."); + }) + .catch(ex => { + this._log.error("tab quick-sync failed.", ex); + }); + }, + delayInMs, + this, + "tabsQuickWriteTimer" + ); + } else if (scoreIncrement) { + this._log.debug( + "Detected a tab change, but conditions aren't met for a quick write - bumping score" + ); + this.score += scoreIncrement; + } else { + this._log.debug( + "Detected a tab change, but conditions aren't met for a quick write or a score bump" + ); + } + }, +}; +Object.setPrototypeOf(TabTracker.prototype, Tracker.prototype); diff --git a/services/sync/modules/keys.sys.mjs b/services/sync/modules/keys.sys.mjs new file mode 100644 index 0000000000..b6a1dce19a --- /dev/null +++ b/services/sync/modules/keys.sys.mjs @@ -0,0 +1,166 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Weave } from "resource://services-sync/main.sys.mjs"; + +/** + * Represents a pair of keys. + * + * Each key stored in a key bundle is 256 bits. One key is used for symmetric + * encryption. The other is used for HMAC. + * + * A KeyBundle by itself is just an anonymous pair of keys. Other types + * deriving from this one add semantics, such as associated collections or + * generating a key bundle via HKDF from another key. + */ +function KeyBundle() { + this._encrypt = null; + this._encryptB64 = null; + this._hmac = null; + this._hmacB64 = null; +} +KeyBundle.prototype = { + _encrypt: null, + _encryptB64: null, + _hmac: null, + _hmacB64: null, + + equals: function equals(bundle) { + return ( + bundle && + bundle.hmacKey == this.hmacKey && + bundle.encryptionKey == this.encryptionKey + ); + }, + + /* + * Accessors for the two keys. + */ + get encryptionKey() { + return this._encrypt; + }, + + set encryptionKey(value) { + if (!value || typeof value != "string") { + throw new Error("Encryption key can only be set to string values."); + } + + if (value.length < 16) { + throw new Error("Encryption key must be at least 128 bits long."); + } + + this._encrypt = value; + this._encryptB64 = btoa(value); + }, + + get encryptionKeyB64() { + return this._encryptB64; + }, + + get hmacKey() { + return this._hmac; + }, + + set hmacKey(value) { + if (!value || typeof value != "string") { + throw new Error("HMAC key can only be set to string values."); + } + + if (value.length < 16) { + throw new Error("HMAC key must be at least 128 bits long."); + } + + this._hmac = value; + this._hmacB64 = btoa(value); + }, + + get hmacKeyB64() { + return this._hmacB64; + }, + + /** + * Populate this key pair with 2 new, randomly generated keys. + */ + async generateRandom() { + // Compute both at that same time + let [generatedHMAC, generatedEncr] = await Promise.all([ + Weave.Crypto.generateRandomKey(), + Weave.Crypto.generateRandomKey(), + ]); + this.keyPairB64 = [generatedEncr, generatedHMAC]; + }, +}; + +/** + * Represents a KeyBundle associated with a collection. + * + * This is just a KeyBundle with a collection attached. + */ +export function BulkKeyBundle(collection) { + let log = Log.repository.getLogger("Sync.BulkKeyBundle"); + log.info("BulkKeyBundle being created for " + collection); + KeyBundle.call(this); + + this._collection = collection; +} + +BulkKeyBundle.fromHexKey = function (hexKey) { + let key = CommonUtils.hexToBytes(hexKey); + let bundle = new BulkKeyBundle(); + // [encryptionKey, hmacKey] + bundle.keyPair = [key.slice(0, 32), key.slice(32, 64)]; + return bundle; +}; + +BulkKeyBundle.fromJWK = function (jwk) { + if (!jwk || !jwk.k || jwk.kty !== "oct") { + throw new Error("Invalid JWK provided to BulkKeyBundle.fromJWK"); + } + return BulkKeyBundle.fromHexKey(CommonUtils.base64urlToHex(jwk.k)); +}; + +BulkKeyBundle.prototype = { + get collection() { + return this._collection; + }, + + /** + * Obtain the key pair in this key bundle. + * + * The returned keys are represented as raw byte strings. + */ + get keyPair() { + return [this.encryptionKey, this.hmacKey]; + }, + + set keyPair(value) { + if (!Array.isArray(value) || value.length != 2) { + throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys."); + } + + this.encryptionKey = value[0]; + this.hmacKey = value[1]; + }, + + get keyPairB64() { + return [this.encryptionKeyB64, this.hmacKeyB64]; + }, + + set keyPairB64(value) { + if (!Array.isArray(value) || value.length != 2) { + throw new Error( + "BulkKeyBundle.keyPairB64 value must be an array of 2 keys." + ); + } + + this.encryptionKey = CommonUtils.safeAtoB(value[0]); + this.hmacKey = CommonUtils.safeAtoB(value[1]); + }, +}; + +Object.setPrototypeOf(BulkKeyBundle.prototype, KeyBundle.prototype); diff --git a/services/sync/modules/main.sys.mjs b/services/sync/modules/main.sys.mjs new file mode 100644 index 0000000000..838da15742 --- /dev/null +++ b/services/sync/modules/main.sys.mjs @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export { lazy as Weave }; + +const lazy = {}; + +// We want these to be lazily loaded, which helps performance and also tests +// to not have these loaded before they are ready. +ChromeUtils.defineESModuleGetters(lazy, { + Service: "resource://services-sync/service.sys.mjs", + Status: "resource://services-sync/status.sys.mjs", + Svc: "resource://services-sync/util.sys.mjs", + Utils: "resource://services-sync/util.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "Crypto", () => { + let { WeaveCrypto } = ChromeUtils.importESModule( + "resource://services-crypto/WeaveCrypto.sys.mjs" + ); + return new WeaveCrypto(); +}); diff --git a/services/sync/modules/policies.sys.mjs b/services/sync/modules/policies.sys.mjs new file mode 100644 index 0000000000..290e793b8e --- /dev/null +++ b/services/sync/modules/policies.sys.mjs @@ -0,0 +1,1055 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { + CREDENTIALS_CHANGED, + ENGINE_APPLY_FAIL, + ENGINE_UNKNOWN_FAIL, + IDLE_OBSERVER_BACK_DELAY, + LOGIN_FAILED_INVALID_PASSPHRASE, + LOGIN_FAILED_LOGIN_REJECTED, + LOGIN_FAILED_NETWORK_ERROR, + LOGIN_FAILED_NO_PASSPHRASE, + LOGIN_SUCCEEDED, + MASTER_PASSWORD_LOCKED, + MASTER_PASSWORD_LOCKED_RETRY_INTERVAL, + MAX_ERROR_COUNT_BEFORE_BACKOFF, + MINIMUM_BACKOFF_INTERVAL, + MULTI_DEVICE_THRESHOLD, + NO_SYNC_NODE_FOUND, + NO_SYNC_NODE_INTERVAL, + OVER_QUOTA, + RESPONSE_OVER_QUOTA, + SCORE_UPDATE_DELAY, + SERVER_MAINTENANCE, + SINGLE_USER_THRESHOLD, + STATUS_OK, + SYNC_FAILED_PARTIAL, + SYNC_SUCCEEDED, + kSyncBackoffNotMet, + kSyncMasterPasswordLocked, +} from "resource://services-sync/constants.sys.mjs"; + +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { logManager } from "resource://gre/modules/FxAccountsCommon.sys.mjs"; +import { Async } from "resource://services-common/async.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + Status: "resource://services-sync/status.sys.mjs", +}); +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); +XPCOMUtils.defineLazyServiceGetter( + lazy, + "IdleService", + "@mozilla.org/widget/useridleservice;1", + "nsIUserIdleService" +); +XPCOMUtils.defineLazyServiceGetter( + lazy, + "CaptivePortalService", + "@mozilla.org/network/captive-portal-service;1", + "nsICaptivePortalService" +); + +// Get the value for an interval that's stored in preferences. To save users +// from themselves (and us from them!) the minimum time they can specify +// is 60s. +function getThrottledIntervalPreference(prefName) { + return Math.max(Svc.PrefBranch.getIntPref(prefName), 60) * 1000; +} + +export function SyncScheduler(service) { + this.service = service; + this.init(); +} + +SyncScheduler.prototype = { + _log: Log.repository.getLogger("Sync.SyncScheduler"), + + _fatalLoginStatus: [ + LOGIN_FAILED_NO_PASSPHRASE, + LOGIN_FAILED_INVALID_PASSPHRASE, + LOGIN_FAILED_LOGIN_REJECTED, + ], + + /** + * The nsITimer object that schedules the next sync. See scheduleNextSync(). + */ + syncTimer: null, + + setDefaults: function setDefaults() { + this._log.trace("Setting SyncScheduler policy values to defaults."); + + this.singleDeviceInterval = getThrottledIntervalPreference( + "scheduler.fxa.singleDeviceInterval" + ); + this.idleInterval = getThrottledIntervalPreference( + "scheduler.idleInterval" + ); + this.activeInterval = getThrottledIntervalPreference( + "scheduler.activeInterval" + ); + this.immediateInterval = getThrottledIntervalPreference( + "scheduler.immediateInterval" + ); + + // A user is non-idle on startup by default. + this.idle = false; + + this.hasIncomingItems = false; + // This is the last number of clients we saw when previously updating the + // client mode. If this != currentNumClients (obtained from prefs written + // by the clients engine) then we need to transition to and from + // single and multi-device mode. + this.numClientsLastSync = 0; + + this._resyncs = 0; + + this.clearSyncTriggers(); + }, + + // nextSync is in milliseconds, but prefs can't hold that much + get nextSync() { + return Svc.PrefBranch.getIntPref("nextSync", 0) * 1000; + }, + set nextSync(value) { + Svc.PrefBranch.setIntPref("nextSync", Math.floor(value / 1000)); + }, + + get missedFxACommandsFetchInterval() { + return Services.prefs.getIntPref( + "identity.fxaccounts.commands.missed.fetch_interval" + ); + }, + + get missedFxACommandsLastFetch() { + return Services.prefs.getIntPref( + "identity.fxaccounts.commands.missed.last_fetch", + 0 + ); + }, + + set missedFxACommandsLastFetch(val) { + Services.prefs.setIntPref( + "identity.fxaccounts.commands.missed.last_fetch", + val + ); + }, + + get syncInterval() { + return this._syncInterval; + }, + set syncInterval(value) { + if (value != this._syncInterval) { + Services.prefs.setIntPref("services.sync.syncInterval", value); + } + }, + + get syncThreshold() { + return this._syncThreshold; + }, + set syncThreshold(value) { + if (value != this._syncThreshold) { + Services.prefs.setIntPref("services.sync.syncThreshold", value); + } + }, + + get globalScore() { + return this._globalScore; + }, + set globalScore(value) { + if (this._globalScore != value) { + Services.prefs.setIntPref("services.sync.globalScore", value); + } + }, + + // Managed by the clients engine (by way of prefs) + get numClients() { + return this.numDesktopClients + this.numMobileClients; + }, + set numClients(value) { + throw new Error("Don't set numClients - the clients engine manages it."); + }, + + get offline() { + // Services.io.offline has slowly become fairly useless over the years - it + // no longer attempts to track the actual network state by default, but one + // thing stays true: if it says we're offline then we are definitely not online. + // + // We also ask the captive portal service if we are behind a locked captive + // portal. + // + // We don't check on the NetworkLinkService however, because it gave us + // false positives in the past in a vm environment. + try { + if ( + Services.io.offline || + lazy.CaptivePortalService.state == + lazy.CaptivePortalService.LOCKED_PORTAL + ) { + return true; + } + } catch (ex) { + this._log.warn("Could not determine network status.", ex); + } + return false; + }, + + _initPrefGetters() { + XPCOMUtils.defineLazyPreferenceGetter( + this, + "idleTime", + "services.sync.scheduler.idleTime" + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "maxResyncs", + "services.sync.maxResyncs", + 0 + ); + + // The number of clients we have is maintained in preferences via the + // clients engine, and only updated after a successsful sync. + XPCOMUtils.defineLazyPreferenceGetter( + this, + "numDesktopClients", + "services.sync.clients.devices.desktop", + 0 + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "numMobileClients", + "services.sync.clients.devices.mobile", + 0 + ); + + // Scheduler state that seems to be read more often than it's written. + // We also check if the value has changed before writing in the setters. + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_syncThreshold", + "services.sync.syncThreshold", + SINGLE_USER_THRESHOLD + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_syncInterval", + "services.sync.syncInterval", + this.singleDeviceInterval + ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_globalScore", + "services.sync.globalScore", + 0 + ); + }, + + init: function init() { + this._log.manageLevelFromPref("services.sync.log.logger.service.main"); + this.setDefaults(); + this._initPrefGetters(); + Svc.Obs.add("weave:engine:score:updated", this); + Svc.Obs.add("network:offline-status-changed", this); + Svc.Obs.add("network:link-status-changed", this); + Svc.Obs.add("captive-portal-detected", this); + Svc.Obs.add("weave:service:sync:start", this); + Svc.Obs.add("weave:service:sync:finish", this); + Svc.Obs.add("weave:engine:sync:finish", this); + Svc.Obs.add("weave:engine:sync:error", this); + Svc.Obs.add("weave:service:login:error", this); + Svc.Obs.add("weave:service:logout:finish", this); + Svc.Obs.add("weave:service:sync:error", this); + Svc.Obs.add("weave:service:backoff:interval", this); + Svc.Obs.add("weave:engine:sync:applied", this); + Svc.Obs.add("weave:service:setup-complete", this); + Svc.Obs.add("weave:service:start-over", this); + Svc.Obs.add("FxA:hawk:backoff:interval", this); + + if (lazy.Status.checkSetup() == STATUS_OK) { + Svc.Obs.add("wake_notification", this); + Svc.Obs.add("captive-portal-login-success", this); + Svc.Obs.add("sleep_notification", this); + lazy.IdleService.addIdleObserver(this, this.idleTime); + } + }, + + // eslint-disable-next-line complexity + observe: function observe(subject, topic, data) { + this._log.trace("Handling " + topic); + switch (topic) { + case "weave:engine:score:updated": + if (lazy.Status.login == LOGIN_SUCCEEDED) { + CommonUtils.namedTimer( + this.calculateScore, + SCORE_UPDATE_DELAY, + this, + "_scoreTimer" + ); + } + break; + case "network:link-status-changed": + // Note: NetworkLinkService is unreliable, we get false negatives for it + // in cases such as VMs (bug 1420802), so we don't want to use it in + // `get offline`, but we assume that it's probably reliable if we're + // getting status changed events. (We might be wrong about this, but + // if that's true, then the only downside is that we won't sync as + // promptly). + let isOffline = this.offline; + this._log.debug( + `Network link status changed to "${data}". Offline?`, + isOffline + ); + // Data may be one of `up`, `down`, `change`, or `unknown`. We only want + // to sync if it's "up". + if (data == "up" && !isOffline) { + this._log.debug("Network link looks up. Syncing."); + this.scheduleNextSync(0, { why: topic }); + } else if (data == "down") { + // Unschedule pending syncs if we know we're going down. We don't do + // this via `checkSyncStatus`, since link status isn't reflected in + // `this.offline`. + this.clearSyncTriggers(); + } + break; + case "network:offline-status-changed": + case "captive-portal-detected": + // Whether online or offline, we'll reschedule syncs + this._log.trace("Network offline status change: " + data); + this.checkSyncStatus(); + break; + case "weave:service:sync:start": + // Clear out any potentially pending syncs now that we're syncing + this.clearSyncTriggers(); + + // reset backoff info, if the server tells us to continue backing off, + // we'll handle that later + lazy.Status.resetBackoff(); + + this.globalScore = 0; + break; + case "weave:service:sync:finish": + this.nextSync = 0; + this.adjustSyncInterval(); + + if ( + lazy.Status.service == SYNC_FAILED_PARTIAL && + this.requiresBackoff + ) { + this.requiresBackoff = false; + this.handleSyncError(); + return; + } + + let sync_interval; + let nextSyncReason = "schedule"; + this.updateGlobalScore(); + if ( + this.globalScore > this.syncThreshold && + lazy.Status.service == STATUS_OK + ) { + // The global score should be 0 after a sync. If it's not, either + // items were changed during the last sync (and we should schedule an + // immediate follow-up sync), or an engine skipped + this._resyncs++; + if (this._resyncs <= this.maxResyncs) { + sync_interval = 0; + nextSyncReason = "resync"; + } else { + this._log.warn( + `Resync attempt ${this._resyncs} exceeded ` + + `maximum ${this.maxResyncs}` + ); + Svc.Obs.notify("weave:service:resyncs-finished"); + } + } else { + this._resyncs = 0; + Svc.Obs.notify("weave:service:resyncs-finished"); + } + + this._syncErrors = 0; + if (lazy.Status.sync == NO_SYNC_NODE_FOUND) { + // If we don't have a Sync node, override the interval, even if we've + // scheduled a follow-up sync. + this._log.trace("Scheduling a sync at interval NO_SYNC_NODE_FOUND."); + sync_interval = NO_SYNC_NODE_INTERVAL; + } + this.scheduleNextSync(sync_interval, { why: nextSyncReason }); + break; + case "weave:engine:sync:finish": + if (data == "clients") { + // Update the client mode because it might change what we sync. + this.updateClientMode(); + } + break; + case "weave:engine:sync:error": + // `subject` is the exception thrown by an engine's sync() method. + let exception = subject; + if (exception.status >= 500 && exception.status <= 504) { + this.requiresBackoff = true; + } + break; + case "weave:service:login:error": + this.clearSyncTriggers(); + + if (lazy.Status.login == MASTER_PASSWORD_LOCKED) { + // Try again later, just as if we threw an error... only without the + // error count. + this._log.debug("Couldn't log in: master password is locked."); + this._log.trace( + "Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL" + ); + this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL); + } else if (!this._fatalLoginStatus.includes(lazy.Status.login)) { + // Not a fatal login error, just an intermittent network or server + // issue. Keep on syncin'. + this.checkSyncStatus(); + } + break; + case "weave:service:logout:finish": + // Start or cancel the sync timer depending on if + // logged in or logged out + this.checkSyncStatus(); + break; + case "weave:service:sync:error": + // There may be multiple clients but if the sync fails, client mode + // should still be updated so that the next sync has a correct interval. + this.updateClientMode(); + this.adjustSyncInterval(); + this.nextSync = 0; + this.handleSyncError(); + break; + case "FxA:hawk:backoff:interval": + case "weave:service:backoff:interval": + let requested_interval = subject * 1000; + this._log.debug( + "Got backoff notification: " + requested_interval + "ms" + ); + // Leave up to 25% more time for the back off. + let interval = requested_interval * (1 + Math.random() * 0.25); + lazy.Status.backoffInterval = interval; + lazy.Status.minimumNextSync = Date.now() + requested_interval; + this._log.debug( + "Fuzzed minimum next sync: " + lazy.Status.minimumNextSync + ); + break; + case "weave:engine:sync:applied": + let numItems = subject.succeeded; + this._log.trace( + "Engine " + data + " successfully applied " + numItems + " items." + ); + // Bug 1800186 - the tabs engine always reports incoming items, so we don't + // want special scheduling in this scenario. + // (However, even when we fix the underlying cause of that, we probably still can + // ignore tabs here - new incoming tabs don't need to trigger the extra syncs we do + // based on this flag.) + if (data != "tabs" && numItems) { + this.hasIncomingItems = true; + } + if (subject.newFailed) { + this._log.error( + `Engine ${data} found ${subject.newFailed} new records that failed to apply` + ); + } + break; + case "weave:service:setup-complete": + Services.prefs.savePrefFile(null); + lazy.IdleService.addIdleObserver(this, this.idleTime); + Svc.Obs.add("wake_notification", this); + Svc.Obs.add("captive-portal-login-success", this); + Svc.Obs.add("sleep_notification", this); + break; + case "weave:service:start-over": + this.setDefaults(); + try { + lazy.IdleService.removeIdleObserver(this, this.idleTime); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_FAILURE) { + throw ex; + } + // In all likelihood we didn't have an idle observer registered yet. + // It's all good. + } + break; + case "idle": + this._log.trace("We're idle."); + this.idle = true; + // Adjust the interval for future syncs. This won't actually have any + // effect until the next pending sync (which will happen soon since we + // were just active.) + this.adjustSyncInterval(); + break; + case "active": + this._log.trace("Received notification that we're back from idle."); + this.idle = false; + CommonUtils.namedTimer( + function onBack() { + if (this.idle) { + this._log.trace( + "... and we're idle again. " + + "Ignoring spurious back notification." + ); + return; + } + + this._log.trace("Genuine return from idle. Syncing."); + // Trigger a sync if we have multiple clients. + if (this.numClients > 1) { + this.scheduleNextSync(0, { why: topic }); + } + }, + IDLE_OBSERVER_BACK_DELAY, + this, + "idleDebouncerTimer" + ); + break; + case "wake_notification": + this._log.debug("Woke from sleep."); + CommonUtils.nextTick(() => { + // Trigger a sync if we have multiple clients. We give it 2 seconds + // so the browser can recover from the wake and do more important + // operations first (timers etc). + if (this.numClients > 1) { + if (!this.offline) { + this._log.debug("Online, will sync in 2s."); + this.scheduleNextSync(2000, { why: topic }); + } + } + }); + break; + case "captive-portal-login-success": + this._log.debug("Captive portal login success. Scheduling a sync."); + CommonUtils.nextTick(() => { + this.scheduleNextSync(3000, { why: topic }); + }); + break; + case "sleep_notification": + if (this.service.engineManager.get("tabs")._tracker.modified) { + this._log.debug("Going to sleep, doing a quick sync."); + this.scheduleNextSync(0, { engines: ["tabs"], why: "sleep" }); + } + break; + } + }, + + adjustSyncInterval: function adjustSyncInterval() { + if (this.numClients <= 1) { + this._log.trace("Adjusting syncInterval to singleDeviceInterval."); + this.syncInterval = this.singleDeviceInterval; + return; + } + + // Only MULTI_DEVICE clients will enter this if statement + // since SINGLE_USER clients will be handled above. + if (this.idle) { + this._log.trace("Adjusting syncInterval to idleInterval."); + this.syncInterval = this.idleInterval; + return; + } + + if (this.hasIncomingItems) { + this._log.trace("Adjusting syncInterval to immediateInterval."); + this.hasIncomingItems = false; + this.syncInterval = this.immediateInterval; + } else { + this._log.trace("Adjusting syncInterval to activeInterval."); + this.syncInterval = this.activeInterval; + } + }, + + updateGlobalScore() { + let engines = [this.service.clientsEngine].concat( + this.service.engineManager.getEnabled() + ); + let globalScore = this.globalScore; + for (let i = 0; i < engines.length; i++) { + this._log.trace(engines[i].name + ": score: " + engines[i].score); + globalScore += engines[i].score; + engines[i]._tracker.resetScore(); + } + this.globalScore = globalScore; + this._log.trace("Global score updated: " + globalScore); + }, + + calculateScore() { + this.updateGlobalScore(); + this.checkSyncStatus(); + }, + + /** + * Query the number of known clients to figure out what mode to be in + */ + updateClientMode: function updateClientMode() { + // Nothing to do if it's the same amount + let numClients = this.numClients; + if (numClients == this.numClientsLastSync) { + return; + } + + this._log.debug( + `Client count: ${this.numClientsLastSync} -> ${numClients}` + ); + this.numClientsLastSync = numClients; + + if (numClients <= 1) { + this._log.trace("Adjusting syncThreshold to SINGLE_USER_THRESHOLD"); + this.syncThreshold = SINGLE_USER_THRESHOLD; + } else { + this._log.trace("Adjusting syncThreshold to MULTI_DEVICE_THRESHOLD"); + this.syncThreshold = MULTI_DEVICE_THRESHOLD; + } + this.adjustSyncInterval(); + }, + + /** + * Check if we should be syncing and schedule the next sync, if it's not scheduled + */ + checkSyncStatus: function checkSyncStatus() { + // Should we be syncing now, if not, cancel any sync timers and return + // if we're in backoff, we'll schedule the next sync. + let ignore = [kSyncBackoffNotMet, kSyncMasterPasswordLocked]; + let skip = this.service._checkSync(ignore); + this._log.trace('_checkSync returned "' + skip + '".'); + if (skip) { + this.clearSyncTriggers(); + return; + } + + let why = "schedule"; + // Only set the wait time to 0 if we need to sync right away + let wait; + if (this.globalScore > this.syncThreshold) { + this._log.debug("Global Score threshold hit, triggering sync."); + wait = 0; + why = "score"; + } + this.scheduleNextSync(wait, { why }); + }, + + /** + * Call sync() if Master Password is not locked. + * + * Otherwise, reschedule a sync for later. + */ + syncIfMPUnlocked(engines, why) { + // No point if we got kicked out by the master password dialog. + if (lazy.Status.login == MASTER_PASSWORD_LOCKED && Utils.mpLocked()) { + this._log.debug( + "Not initiating sync: Login status is " + lazy.Status.login + ); + + // If we're not syncing now, we need to schedule the next one. + this._log.trace( + "Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL" + ); + this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL); + return; + } + + if (!Async.isAppReady()) { + this._log.debug("Not initiating sync: app is shutting down"); + return; + } + Services.tm.dispatchToMainThread(() => { + this.service.sync({ engines, why }); + const now = Math.round(new Date().getTime() / 1000); + // Only fetch missed messages in a "scheduled" sync so we don't race against + // the Push service reconnecting on a network link change for example. + if ( + why == "schedule" && + now >= + this.missedFxACommandsLastFetch + this.missedFxACommandsFetchInterval + ) { + lazy.fxAccounts.commands + .pollDeviceCommands() + .then(() => { + this.missedFxACommandsLastFetch = now; + }) + .catch(e => { + this._log.error("Fetching missed remote commands failed.", e); + }); + } + }); + }, + + /** + * Set a timer for the next sync + */ + scheduleNextSync(interval, { engines = null, why = null } = {}) { + // If no interval was specified, use the current sync interval. + if (interval == null) { + interval = this.syncInterval; + } + + // Ensure the interval is set to no less than the backoff. + if (lazy.Status.backoffInterval && interval < lazy.Status.backoffInterval) { + this._log.trace( + "Requested interval " + + interval + + " ms is smaller than the backoff interval. " + + "Using backoff interval " + + lazy.Status.backoffInterval + + " ms instead." + ); + interval = lazy.Status.backoffInterval; + } + let nextSync = this.nextSync; + if (nextSync != 0) { + // There's already a sync scheduled. Don't reschedule if there's already + // a timer scheduled for sooner than requested. + let currentInterval = nextSync - Date.now(); + this._log.trace( + "There's already a sync scheduled in " + currentInterval + " ms." + ); + if (currentInterval < interval && this.syncTimer) { + this._log.trace( + "Ignoring scheduling request for next sync in " + interval + " ms." + ); + return; + } + } + + // Start the sync right away if we're already late. + if (interval <= 0) { + this._log.trace(`Requested sync should happen right away. (why=${why})`); + this.syncIfMPUnlocked(engines, why); + return; + } + + this._log.debug(`Next sync in ${interval} ms. (why=${why})`); + CommonUtils.namedTimer( + () => { + this.syncIfMPUnlocked(engines, why); + }, + interval, + this, + "syncTimer" + ); + + // Save the next sync time in-case sync is disabled (logout/offline/etc.) + this.nextSync = Date.now() + interval; + }, + + /** + * Incorporates the backoff/retry logic used in error handling and elective + * non-syncing. + */ + scheduleAtInterval: function scheduleAtInterval(minimumInterval) { + let interval = Utils.calculateBackoff( + this._syncErrors, + MINIMUM_BACKOFF_INTERVAL, + lazy.Status.backoffInterval + ); + if (minimumInterval) { + interval = Math.max(minimumInterval, interval); + } + + this._log.debug( + "Starting client-initiated backoff. Next sync in " + interval + " ms." + ); + this.scheduleNextSync(interval, { why: "client-backoff-schedule" }); + }, + + autoConnect: function autoConnect() { + if (this.service._checkSetup() == STATUS_OK && !this.service._checkSync()) { + // Schedule a sync based on when a previous sync was scheduled. + // scheduleNextSync() will do the right thing if that time lies in + // the past. + this.scheduleNextSync(this.nextSync - Date.now(), { why: "startup" }); + } + }, + + _syncErrors: 0, + /** + * Deal with sync errors appropriately + */ + handleSyncError: function handleSyncError() { + this._log.trace("In handleSyncError. Error count: " + this._syncErrors); + this._syncErrors++; + + // Do nothing on the first couple of failures, if we're not in + // backoff due to 5xx errors. + if (!lazy.Status.enforceBackoff) { + if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) { + this.scheduleNextSync(null, { why: "reschedule" }); + return; + } + this._log.debug( + "Sync error count has exceeded " + + MAX_ERROR_COUNT_BEFORE_BACKOFF + + "; enforcing backoff." + ); + lazy.Status.enforceBackoff = true; + } + + this.scheduleAtInterval(); + }, + + /** + * Remove any timers/observers that might trigger a sync + */ + clearSyncTriggers: function clearSyncTriggers() { + this._log.debug("Clearing sync triggers and the global score."); + this.globalScore = this.nextSync = 0; + + // Clear out any scheduled syncs + if (this.syncTimer) { + this.syncTimer.clear(); + } + }, +}; + +export function ErrorHandler(service) { + this.service = service; + this.init(); +} + +ErrorHandler.prototype = { + init() { + Svc.Obs.add("weave:engine:sync:applied", this); + Svc.Obs.add("weave:engine:sync:error", this); + Svc.Obs.add("weave:service:login:error", this); + Svc.Obs.add("weave:service:sync:error", this); + Svc.Obs.add("weave:service:sync:finish", this); + Svc.Obs.add("weave:service:start-over:finish", this); + + this.initLogs(); + }, + + initLogs: function initLogs() { + // Set the root Sync logger level based on a pref. All other logs will + // inherit this level unless they specifically override it. + Log.repository + .getLogger("Sync") + .manageLevelFromPref(`services.sync.log.logger`); + // And allow our specific log to have a custom level via a pref. + this._log = Log.repository.getLogger("Sync.ErrorHandler"); + this._log.manageLevelFromPref("services.sync.log.logger.service.main"); + }, + + observe(subject, topic, data) { + this._log.trace("Handling " + topic); + switch (topic) { + case "weave:engine:sync:applied": + if (subject.newFailed) { + // An engine isn't able to apply one or more incoming records. + // We don't fail hard on this, but it usually indicates a bug, + // so for now treat it as sync error (c.f. Service._syncEngine()) + lazy.Status.engines = [data, ENGINE_APPLY_FAIL]; + this._log.debug(data + " failed to apply some records."); + } + break; + case "weave:engine:sync:error": { + let exception = subject; // exception thrown by engine's sync() method + let engine_name = data; // engine name that threw the exception + + this.checkServerError(exception); + + lazy.Status.engines = [ + engine_name, + exception.failureCode || ENGINE_UNKNOWN_FAIL, + ]; + if (Async.isShutdownException(exception)) { + this._log.debug( + engine_name + + " was interrupted due to the application shutting down" + ); + } else { + this._log.debug(engine_name + " failed", exception); + } + break; + } + case "weave:service:login:error": + this._log.error("Sync encountered a login error"); + this.resetFileLog(); + break; + case "weave:service:sync:error": { + if (lazy.Status.sync == CREDENTIALS_CHANGED) { + this.service.logout(); + } + + let exception = subject; + if (Async.isShutdownException(exception)) { + // If we are shutting down we just log the fact, attempt to flush + // the log file and get out of here! + this._log.error( + "Sync was interrupted due to the application shutting down" + ); + this.resetFileLog(); + break; + } + + // Not a shutdown related exception... + this._log.error("Sync encountered an error", exception); + this.resetFileLog(); + break; + } + case "weave:service:sync:finish": + this._log.trace("Status.service is " + lazy.Status.service); + + // Check both of these status codes: in the event of a failure in one + // engine, Status.service will be SYNC_FAILED_PARTIAL despite + // Status.sync being SYNC_SUCCEEDED. + // *facepalm* + if ( + lazy.Status.sync == SYNC_SUCCEEDED && + lazy.Status.service == STATUS_OK + ) { + // Great. Let's clear our mid-sync 401 note. + this._log.trace("Clearing lastSyncReassigned."); + Svc.PrefBranch.clearUserPref("lastSyncReassigned"); + } + + if (lazy.Status.service == SYNC_FAILED_PARTIAL) { + this._log.error("Some engines did not sync correctly."); + } + this.resetFileLog(); + break; + case "weave:service:start-over:finish": + // ensure we capture any logs between the last sync and the reset completing. + this.resetFileLog() + .then(() => { + // although for privacy reasons we also delete all logs (but we allow + // a preference to avoid this to help with debugging.) + if (!Svc.PrefBranch.getBoolPref("log.keepLogsOnReset", false)) { + return logManager.removeAllLogs().then(() => { + Svc.Obs.notify("weave:service:remove-file-log"); + }); + } + return null; + }) + .catch(err => { + // So we failed to delete the logs - take the ironic option of + // writing this error to the logs we failed to delete! + this._log.error("Failed to delete logs on reset", err); + }); + break; + } + }, + + async _dumpAddons() { + // Just dump the items that sync may be concerned with. Specifically, + // active extensions that are not hidden. + let addons = []; + try { + addons = await lazy.AddonManager.getAddonsByTypes(["extension"]); + } catch (e) { + this._log.warn("Failed to dump addons", e); + } + + let relevantAddons = addons.filter(x => x.isActive && !x.hidden); + this._log.trace("Addons installed", relevantAddons.length); + for (let addon of relevantAddons) { + this._log.trace(" - ${name}, version ${version}, id ${id}", addon); + } + }, + + /** + * Generate a log file for the sync that just completed + * and refresh the input & output streams. + */ + async resetFileLog() { + // If we're writing an error log, dump extensions that may be causing problems. + if (logManager.sawError) { + await this._dumpAddons(); + } + const logType = await logManager.resetFileLog(); + if (logType == logManager.ERROR_LOG_WRITTEN) { + console.error( + "Sync encountered an error - see about:sync-log for the log file." + ); + } + Svc.Obs.notify("weave:service:reset-file-log"); + }, + + /** + * Handle HTTP response results or exceptions and set the appropriate + * Status.* bits. + * + * This method also looks for "side-channel" warnings. + */ + checkServerError(resp) { + // In this case we were passed a resolved value of Resource#_doRequest. + switch (resp.status) { + case 400: + if (resp == RESPONSE_OVER_QUOTA) { + lazy.Status.sync = OVER_QUOTA; + } + break; + + case 401: + this.service.logout(); + this._log.info("Got 401 response; resetting clusterURL."); + this.service.clusterURL = null; + + let delay = 0; + if (Svc.PrefBranch.getBoolPref("lastSyncReassigned", false)) { + // We got a 401 in the middle of the previous sync, and we just got + // another. Login must have succeeded in order for us to get here, so + // the password should be correct. + // This is likely to be an intermittent server issue, so back off and + // give it time to recover. + this._log.warn("Last sync also failed for 401. Delaying next sync."); + delay = MINIMUM_BACKOFF_INTERVAL; + } else { + this._log.debug("New mid-sync 401 failure. Making a note."); + Svc.PrefBranch.setBoolPref("lastSyncReassigned", true); + } + this._log.info("Attempting to schedule another sync."); + this.service.scheduler.scheduleNextSync(delay, { why: "reschedule" }); + break; + + case 500: + case 502: + case 503: + case 504: + lazy.Status.enforceBackoff = true; + if (resp.status == 503 && resp.headers["retry-after"]) { + let retryAfter = resp.headers["retry-after"]; + this._log.debug("Got Retry-After: " + retryAfter); + if (this.service.isLoggedIn) { + lazy.Status.sync = SERVER_MAINTENANCE; + } else { + lazy.Status.login = SERVER_MAINTENANCE; + } + Svc.Obs.notify( + "weave:service:backoff:interval", + parseInt(retryAfter, 10) + ); + } + break; + } + + // In this other case we were passed a rejection value. + switch (resp.result) { + case Cr.NS_ERROR_UNKNOWN_HOST: + case Cr.NS_ERROR_CONNECTION_REFUSED: + case Cr.NS_ERROR_NET_TIMEOUT: + case Cr.NS_ERROR_NET_RESET: + case Cr.NS_ERROR_NET_INTERRUPT: + case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED: + // The constant says it's about login, but in fact it just + // indicates general network error. + if (this.service.isLoggedIn) { + lazy.Status.sync = LOGIN_FAILED_NETWORK_ERROR; + } else { + lazy.Status.login = LOGIN_FAILED_NETWORK_ERROR; + } + break; + } + }, +}; diff --git a/services/sync/modules/record.sys.mjs b/services/sync/modules/record.sys.mjs new file mode 100644 index 0000000000..7d5918a8ca --- /dev/null +++ b/services/sync/modules/record.sys.mjs @@ -0,0 +1,1335 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const CRYPTO_COLLECTION = "crypto"; +const KEYS_WBO = "keys"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { + DEFAULT_DOWNLOAD_BATCH_SIZE, + DEFAULT_KEYBUNDLE_NAME, +} from "resource://services-sync/constants.sys.mjs"; +import { BulkKeyBundle } from "resource://services-sync/keys.sys.mjs"; +import { Weave } from "resource://services-sync/main.sys.mjs"; +import { Resource } from "resource://services-sync/resource.sys.mjs"; +import { Utils } from "resource://services-sync/util.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; + +/** + * The base class for all Sync basic storage objects (BSOs). This is the format + * used to store all records on the Sync server. In an earlier version of the + * Sync protocol, BSOs used to be called WBOs, or Weave Basic Objects. This + * class retains the old name. + * + * @class + * @param {String} collection The collection name for this BSO. + * @param {String} id The ID of this BSO. + */ +export function WBORecord(collection, id) { + this.data = {}; + this.payload = {}; + this.collection = collection; // Optional. + this.id = id; // Optional. +} + +WBORecord.prototype = { + _logName: "Sync.Record.WBO", + + get sortindex() { + if (this.data.sortindex) { + return this.data.sortindex; + } + return 0; + }, + + // Get thyself from your URI, then deserialize. + // Set thine 'response' field. + async fetch(resource) { + if (!(resource instanceof Resource)) { + throw new Error("First argument must be a Resource instance."); + } + + let r = await resource.get(); + if (r.success) { + this.deserialize(r.obj); // Warning! Muffles exceptions! + } + this.response = r; + return this; + }, + + upload(resource) { + if (!(resource instanceof Resource)) { + throw new Error("First argument must be a Resource instance."); + } + + return resource.put(this); + }, + + // Take a base URI string, with trailing slash, and return the URI of this + // WBO based on collection and ID. + uri(base) { + if (this.collection && this.id) { + let url = CommonUtils.makeURI(base + this.collection + "/" + this.id); + url.QueryInterface(Ci.nsIURL); + return url; + } + return null; + }, + + deserialize: function deserialize(json) { + if (!json || typeof json !== "object") { + throw new TypeError("Can't deserialize record from: " + json); + } + this.data = json; + try { + // The payload is likely to be JSON, but if not, keep it as a string + this.payload = JSON.parse(this.payload); + } catch (ex) {} + }, + + toJSON: function toJSON() { + // Copy fields from data to be stringified, making sure payload is a string + let obj = {}; + for (let [key, val] of Object.entries(this.data)) { + obj[key] = key == "payload" ? JSON.stringify(val) : val; + } + if (this.ttl) { + obj.ttl = this.ttl; + } + return obj; + }, + + toString: function toString() { + return ( + "{ " + + "id: " + + this.id + + " " + + "index: " + + this.sortindex + + " " + + "modified: " + + this.modified + + " " + + "ttl: " + + this.ttl + + " " + + "payload: " + + JSON.stringify(this.payload) + + " }" + ); + }, +}; + +Utils.deferGetSet(WBORecord, "data", [ + "id", + "modified", + "sortindex", + "payload", +]); + +/** + * An encrypted BSO record. This subclass handles encrypting and decrypting the + * BSO payload, but doesn't parse or interpret the cleartext string. Subclasses + * must override `transformBeforeEncrypt` and `transformAfterDecrypt` to process + * the cleartext. + * + * This class is only exposed for bridged engines, which handle serialization + * and deserialization in Rust. Sync engines implemented in JS should subclass + * `CryptoWrapper` instead, which takes care of transforming the cleartext into + * an object, and ensuring its contents are valid. + * + * @class + * @template Cleartext + * @param {String} collection The collection name for this BSO. + * @param {String} id The ID of this BSO. + */ +export function RawCryptoWrapper(collection, id) { + // Setting properties before calling the superclass constructor isn't allowed + // in new-style classes (`class MyRecord extends RawCryptoWrapper`), but + // allowed with plain functions. This is also why `defaultCleartext` is a + // method, and not simply set in the subclass constructor. + this.cleartext = this.defaultCleartext(); + WBORecord.call(this, collection, id); + this.ciphertext = null; +} + +RawCryptoWrapper.prototype = { + _logName: "Sync.Record.RawCryptoWrapper", + + /** + * Returns the default empty cleartext for this record type. This is exposed + * as a method so that subclasses can override it, and access the default + * cleartext in their constructors. `CryptoWrapper`, for example, overrides + * this to return an empty object, so that initializing the `id` in its + * constructor calls its overridden `id` setter. + * + * @returns {Cleartext} An empty cleartext. + */ + defaultCleartext() { + return null; + }, + + /** + * Transforms the cleartext into a string that can be encrypted and wrapped + * in a BSO payload. This is called before uploading the record to the server. + * + * @param {Cleartext} outgoingCleartext The cleartext to upload. + * @returns {String} The serialized cleartext. + */ + transformBeforeEncrypt(outgoingCleartext) { + throw new TypeError("Override to stringify outgoing records"); + }, + + /** + * Transforms an incoming cleartext string into an instance of the + * `Cleartext` type. This is called when fetching the record from the + * server. + * + * @param {String} incomingCleartext The decrypted cleartext string. + * @returns {Cleartext} The parsed cleartext. + */ + transformAfterDecrypt(incomingCleartext) { + throw new TypeError("Override to parse incoming records"); + }, + + ciphertextHMAC: async function ciphertextHMAC(keyBundle) { + let hmacKeyByteString = keyBundle.hmacKey; + if (!hmacKeyByteString) { + throw new Error("Cannot compute HMAC without an HMAC key."); + } + let hmacKey = CommonUtils.byteStringToArrayBuffer(hmacKeyByteString); + // NB: this.ciphertext is a base64-encoded string. For some reason this + // implementation computes the HMAC on the encoded value. + let data = CommonUtils.byteStringToArrayBuffer(this.ciphertext); + let hmac = await CryptoUtils.hmac("SHA-256", hmacKey, data); + return CommonUtils.bytesAsHex(CommonUtils.arrayBufferToByteString(hmac)); + }, + + /* + * Don't directly use the sync key. Instead, grab a key for this + * collection, which is decrypted with the sync key. + * + * Cache those keys; invalidate the cache if the time on the keys collection + * changes, or other auth events occur. + * + * Optional key bundle overrides the collection key lookup. + */ + async encrypt(keyBundle) { + if (!keyBundle) { + throw new Error("A key bundle must be supplied to encrypt."); + } + + this.IV = Weave.Crypto.generateRandomIV(); + this.ciphertext = await Weave.Crypto.encrypt( + this.transformBeforeEncrypt(this.cleartext), + keyBundle.encryptionKeyB64, + this.IV + ); + this.hmac = await this.ciphertextHMAC(keyBundle); + this.cleartext = null; + }, + + // Optional key bundle. + async decrypt(keyBundle) { + if (!this.ciphertext) { + throw new Error("No ciphertext: nothing to decrypt?"); + } + + if (!keyBundle) { + throw new Error("A key bundle must be supplied to decrypt."); + } + + // Authenticate the encrypted blob with the expected HMAC + let computedHMAC = await this.ciphertextHMAC(keyBundle); + + if (computedHMAC != this.hmac) { + Utils.throwHMACMismatch(this.hmac, computedHMAC); + } + + let cleartext = await Weave.Crypto.decrypt( + this.ciphertext, + keyBundle.encryptionKeyB64, + this.IV + ); + this.cleartext = this.transformAfterDecrypt(cleartext); + this.ciphertext = null; + + return this.cleartext; + }, +}; + +Object.setPrototypeOf(RawCryptoWrapper.prototype, WBORecord.prototype); + +Utils.deferGetSet(RawCryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]); + +/** + * An encrypted BSO record with a JSON payload. All engines implemented in JS + * should subclass this class to describe their own record types. + * + * @class + * @param {String} collection The collection name for this BSO. + * @param {String} id The ID of this BSO. + */ +export function CryptoWrapper(collection, id) { + RawCryptoWrapper.call(this, collection, id); +} + +CryptoWrapper.prototype = { + _logName: "Sync.Record.CryptoWrapper", + + defaultCleartext() { + return {}; + }, + + transformBeforeEncrypt(cleartext) { + return JSON.stringify(cleartext); + }, + + transformAfterDecrypt(cleartext) { + // Handle invalid data here. Elsewhere we assume that cleartext is an object. + let json_result = JSON.parse(cleartext); + + if (!(json_result && json_result instanceof Object)) { + throw new Error( + `Decryption failed: result is <${json_result}>, not an object.` + ); + } + + // If the payload has an encrypted id ensure it matches the requested record's id. + if (json_result.id && json_result.id != this.id) { + throw new Error(`Record id mismatch: ${json_result.id} != ${this.id}`); + } + + return json_result; + }, + + cleartextToString() { + return JSON.stringify(this.cleartext); + }, + + toString: function toString() { + let payload = this.deleted ? "DELETED" : this.cleartextToString(); + + return ( + "{ " + + "id: " + + this.id + + " " + + "index: " + + this.sortindex + + " " + + "modified: " + + this.modified + + " " + + "ttl: " + + this.ttl + + " " + + "payload: " + + payload + + " " + + "collection: " + + (this.collection || "undefined") + + " }" + ); + }, + + // The custom setter below masks the parent's getter, so explicitly call it :( + get id() { + return super.id; + }, + + // Keep both plaintext and encrypted versions of the id to verify integrity + set id(val) { + super.id = val; + this.cleartext.id = val; + }, +}; + +Object.setPrototypeOf(CryptoWrapper.prototype, RawCryptoWrapper.prototype); + +Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted"); + +/** + * An interface and caching layer for records. + */ +export function RecordManager(service) { + this.service = service; + + this._log = Log.repository.getLogger(this._logName); + this._records = {}; +} + +RecordManager.prototype = { + _recordType: CryptoWrapper, + _logName: "Sync.RecordManager", + + async import(url) { + this._log.trace("Importing record: " + (url.spec ? url.spec : url)); + try { + // Clear out the last response with empty object if GET fails + this.response = {}; + this.response = await this.service.resource(url).get(); + + // Don't parse and save the record on failure + if (!this.response.success) { + return null; + } + + let record = new this._recordType(url); + record.deserialize(this.response.obj); + + return this.set(url, record); + } catch (ex) { + if (Async.isShutdownException(ex)) { + throw ex; + } + this._log.debug("Failed to import record", ex); + return null; + } + }, + + get(url) { + // Use a url string as the key to the hash + let spec = url.spec ? url.spec : url; + if (spec in this._records) { + return Promise.resolve(this._records[spec]); + } + return this.import(url); + }, + + set: function RecordMgr_set(url, record) { + let spec = url.spec ? url.spec : url; + return (this._records[spec] = record); + }, + + contains: function RecordMgr_contains(url) { + if ((url.spec || url) in this._records) { + return true; + } + return false; + }, + + clearCache: function recordMgr_clearCache() { + this._records = {}; + }, + + del: function RecordMgr_del(url) { + delete this._records[url]; + }, +}; + +/** + * Keeps track of mappings between collection names ('tabs') and KeyBundles. + * + * You can update this thing simply by giving it /info/collections. It'll + * use the last modified time to bring itself up to date. + */ +export function CollectionKeyManager(lastModified, default_, collections) { + this.lastModified = lastModified || 0; + this._default = default_ || null; + this._collections = collections || {}; + + this._log = Log.repository.getLogger("Sync.CollectionKeyManager"); +} + +// TODO: persist this locally as an Identity. Bug 610913. +// Note that the last modified time needs to be preserved. +CollectionKeyManager.prototype = { + /** + * Generate a new CollectionKeyManager that has the same attributes + * as this one. + */ + clone() { + const newCollections = {}; + for (let c in this._collections) { + newCollections[c] = this._collections[c]; + } + + return new CollectionKeyManager( + this.lastModified, + this._default, + newCollections + ); + }, + + // Return information about old vs new keys: + // * same: true if two collections are equal + // * changed: an array of collection names that changed. + _compareKeyBundleCollections: function _compareKeyBundleCollections(m1, m2) { + let changed = []; + + function process(m1, m2) { + for (let k1 in m1) { + let v1 = m1[k1]; + let v2 = m2[k1]; + if (!(v1 && v2 && v1.equals(v2))) { + changed.push(k1); + } + } + } + + // Diffs both ways. + process(m1, m2); + process(m2, m1); + + // Return a sorted, unique array. + changed.sort(); + let last; + changed = changed.filter(x => x != last && (last = x)); + return { same: !changed.length, changed }; + }, + + get isClear() { + return !this._default; + }, + + clear: function clear() { + this._log.info("Clearing collection keys..."); + this.lastModified = 0; + this._collections = {}; + this._default = null; + }, + + keyForCollection(collection) { + if (collection && this._collections[collection]) { + return this._collections[collection]; + } + + return this._default; + }, + + /** + * If `collections` (an array of strings) is provided, iterate + * over it and generate random keys for each collection. + * Create a WBO for the given data. + */ + _makeWBO(collections, defaultBundle) { + let wbo = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); + let c = {}; + for (let k in collections) { + c[k] = collections[k].keyPairB64; + } + wbo.cleartext = { + default: defaultBundle ? defaultBundle.keyPairB64 : null, + collections: c, + collection: CRYPTO_COLLECTION, + id: KEYS_WBO, + }; + return wbo; + }, + + /** + * Create a WBO for the current keys. + */ + asWBO(collection, id) { + return this._makeWBO(this._collections, this._default); + }, + + /** + * Compute a new default key, and new keys for any specified collections. + */ + async newKeys(collections) { + let newDefaultKeyBundle = await this.newDefaultKeyBundle(); + + let newColls = {}; + if (collections) { + for (let c of collections) { + let b = new BulkKeyBundle(c); + await b.generateRandom(); + newColls[c] = b; + } + } + return [newDefaultKeyBundle, newColls]; + }, + + /** + * Generates new keys, but does not replace our local copy. Use this to + * verify an upload before storing. + */ + async generateNewKeysWBO(collections) { + let newDefaultKey, newColls; + [newDefaultKey, newColls] = await this.newKeys(collections); + + return this._makeWBO(newColls, newDefaultKey); + }, + + /** + * Create a new default key. + * + * @returns {BulkKeyBundle} + */ + async newDefaultKeyBundle() { + const key = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME); + await key.generateRandom(); + return key; + }, + + /** + * Create a new default key and store it as this._default, since without one you cannot use setContents. + */ + async generateDefaultKey() { + this._default = await this.newDefaultKeyBundle(); + }, + + /** + * Return true if keys are already present for each of the given + * collections. + */ + hasKeysFor(collections) { + // We can't use filter() here because sometimes collections is an iterator. + for (let collection of collections) { + if (!this._collections[collection]) { + return false; + } + } + return true; + }, + + /** + * Return a new CollectionKeyManager that has keys for each of the + * given collections (creating new ones for collections where we + * don't already have keys). + */ + async ensureKeysFor(collections) { + const newKeys = Object.assign({}, this._collections); + for (let c of collections) { + if (newKeys[c]) { + continue; // don't replace existing keys + } + + const b = new BulkKeyBundle(c); + await b.generateRandom(); + newKeys[c] = b; + } + return new CollectionKeyManager(this.lastModified, this._default, newKeys); + }, + + // Take the fetched info/collections WBO, checking the change + // time of the crypto collection. + updateNeeded(info_collections) { + this._log.info( + "Testing for updateNeeded. Last modified: " + this.lastModified + ); + + // No local record of modification time? Need an update. + if (!this.lastModified) { + return true; + } + + // No keys on the server? We need an update, though our + // update handling will be a little more drastic... + if (!(CRYPTO_COLLECTION in info_collections)) { + return true; + } + + // Otherwise, we need an update if our modification time is stale. + return info_collections[CRYPTO_COLLECTION] > this.lastModified; + }, + + // + // Set our keys and modified time to the values fetched from the server. + // Returns one of three values: + // + // * If the default key was modified, return true. + // * If the default key was not modified, but per-collection keys were, + // return an array of such. + // * Otherwise, return false -- we were up-to-date. + // + setContents: function setContents(payload, modified) { + let self = this; + + this._log.info( + "Setting collection keys contents. Our last modified: " + + this.lastModified + + ", input modified: " + + modified + + "." + ); + + if (!payload) { + throw new Error("No payload in CollectionKeyManager.setContents()."); + } + + if (!payload.default) { + this._log.warn("No downloaded default key: this should not occur."); + this._log.warn("Not clearing local keys."); + throw new Error( + "No default key in CollectionKeyManager.setContents(). Cannot proceed." + ); + } + + // Process the incoming default key. + let b = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME); + b.keyPairB64 = payload.default; + let newDefault = b; + + // Process the incoming collections. + let newCollections = {}; + if ("collections" in payload) { + this._log.info("Processing downloaded per-collection keys."); + let colls = payload.collections; + for (let k in colls) { + let v = colls[k]; + if (v) { + let keyObj = new BulkKeyBundle(k); + keyObj.keyPairB64 = v; + newCollections[k] = keyObj; + } + } + } + + // Check to see if these are already our keys. + let sameDefault = this._default && this._default.equals(newDefault); + let collComparison = this._compareKeyBundleCollections( + newCollections, + this._collections + ); + let sameColls = collComparison.same; + + if (sameDefault && sameColls) { + self._log.info("New keys are the same as our old keys!"); + if (modified) { + self._log.info("Bumped local modified time."); + self.lastModified = modified; + } + return false; + } + + // Make sure things are nice and tidy before we set. + this.clear(); + + this._log.info("Saving downloaded keys."); + this._default = newDefault; + this._collections = newCollections; + + // Always trust the server. + if (modified) { + self._log.info("Bumping last modified to " + modified); + self.lastModified = modified; + } + + return sameDefault ? collComparison.changed : true; + }, + + async updateContents(syncKeyBundle, storage_keys) { + let log = this._log; + log.info("Updating collection keys..."); + + // storage_keys is a WBO, fetched from storage/crypto/keys. + // Its payload is the default key, and a map of collections to keys. + // We lazily compute the key objects from the strings we're given. + + let payload; + try { + payload = await storage_keys.decrypt(syncKeyBundle); + } catch (ex) { + log.warn("Got exception decrypting storage keys with sync key.", ex); + log.info("Aborting updateContents. Rethrowing."); + throw ex; + } + + let r = this.setContents(payload, storage_keys.modified); + log.info("Collection keys updated."); + return r; + }, +}; + +export function Collection(uri, recordObj, service) { + if (!service) { + throw new Error("Collection constructor requires a service."); + } + + Resource.call(this, uri); + + // This is a bit hacky, but gets the job done. + let res = service.resource(uri); + this.authenticator = res.authenticator; + + this._recordObj = recordObj; + this._service = service; + + this._full = false; + this._ids = null; + this._limit = 0; + this._older = 0; + this._newer = 0; + this._data = []; + // optional members used by batch upload operations. + this._batch = null; + this._commit = false; + // Used for batch download operations -- note that this is explicitly an + // opaque value and not (necessarily) a number. + this._offset = null; +} + +Collection.prototype = { + _logName: "Sync.Collection", + + _rebuildURL: function Coll__rebuildURL() { + // XXX should consider what happens if it's not a URL... + this.uri.QueryInterface(Ci.nsIURL); + + let args = []; + if (this.older) { + args.push("older=" + this.older); + } + if (this.newer) { + args.push("newer=" + this.newer); + } + if (this.full) { + args.push("full=1"); + } + if (this.sort) { + args.push("sort=" + this.sort); + } + if (this.ids != null) { + args.push("ids=" + this.ids); + } + if (this.limit > 0 && this.limit != Infinity) { + args.push("limit=" + this.limit); + } + if (this._batch) { + args.push("batch=" + encodeURIComponent(this._batch)); + } + if (this._commit) { + args.push("commit=true"); + } + if (this._offset) { + args.push("offset=" + encodeURIComponent(this._offset)); + } + + this.uri = this.uri + .mutate() + .setQuery(args.length ? "?" + args.join("&") : "") + .finalize(); + }, + + // get full items + get full() { + return this._full; + }, + set full(value) { + this._full = value; + this._rebuildURL(); + }, + + // Apply the action to a certain set of ids + get ids() { + return this._ids; + }, + set ids(value) { + this._ids = value; + this._rebuildURL(); + }, + + // Limit how many records to get + get limit() { + return this._limit; + }, + set limit(value) { + this._limit = value; + this._rebuildURL(); + }, + + // get only items modified before some date + get older() { + return this._older; + }, + set older(value) { + this._older = value; + this._rebuildURL(); + }, + + // get only items modified since some date + get newer() { + return this._newer; + }, + set newer(value) { + this._newer = value; + this._rebuildURL(); + }, + + // get items sorted by some criteria. valid values: + // oldest (oldest first) + // newest (newest first) + // index + get sort() { + return this._sort; + }, + set sort(value) { + if (value && value != "oldest" && value != "newest" && value != "index") { + throw new TypeError( + `Illegal value for sort: "${value}" (should be "oldest", "newest", or "index").` + ); + } + this._sort = value; + this._rebuildURL(); + }, + + get offset() { + return this._offset; + }, + set offset(value) { + this._offset = value; + this._rebuildURL(); + }, + + // Set information about the batch for this request. + get batch() { + return this._batch; + }, + set batch(value) { + this._batch = value; + this._rebuildURL(); + }, + + get commit() { + return this._commit; + }, + set commit(value) { + this._commit = value && true; + this._rebuildURL(); + }, + + // Similar to get(), but will page through the items `batchSize` at a time, + // deferring calling the record handler until we've gotten them all. + // + // Returns the last response processed, and doesn't run the record handler + // on any items if a non-success status is received while downloading the + // records (or if a network error occurs). + async getBatched(batchSize = DEFAULT_DOWNLOAD_BATCH_SIZE) { + let totalLimit = Number(this.limit) || Infinity; + if (batchSize <= 0 || batchSize >= totalLimit) { + throw new Error("Invalid batch size"); + } + + if (!this.full) { + throw new Error("getBatched is unimplemented for guid-only GETs"); + } + + // _onComplete and _onProgress are reset after each `get` by Resource. + let { _onComplete, _onProgress } = this; + let recordBuffer = []; + let resp; + try { + let lastModifiedTime; + this.limit = batchSize; + + do { + this._onProgress = _onProgress; + this._onComplete = _onComplete; + if (batchSize + recordBuffer.length > totalLimit) { + this.limit = totalLimit - recordBuffer.length; + } + this._log.trace("Performing batched GET", { + limit: this.limit, + offset: this.offset, + }); + // Actually perform the request + resp = await this.get(); + if (!resp.success) { + recordBuffer = []; + break; + } + for (let json of resp.obj) { + let record = new this._recordObj(); + record.deserialize(json); + recordBuffer.push(record); + } + + // Initialize last modified, or check that something broken isn't happening. + let lastModified = resp.headers["x-last-modified"]; + if (!lastModifiedTime) { + lastModifiedTime = lastModified; + this.setHeader("X-If-Unmodified-Since", lastModified); + } else if (lastModified != lastModifiedTime) { + // Should be impossible -- We'd get a 412 in this case. + throw new Error( + "X-Last-Modified changed in the middle of a download batch! " + + `${lastModified} => ${lastModifiedTime}` + ); + } + + // If this is missing, we're finished. + this.offset = resp.headers["x-weave-next-offset"]; + } while (this.offset && totalLimit > recordBuffer.length); + } finally { + // Ensure we undo any temporary state so that subsequent calls to get() + // or getBatched() work properly. We do this before calling the record + // handler so that we can more convincingly pretend to be a normal get() + // call. Note: we're resetting these to the values they had before this + // function was called. + this._limit = totalLimit; + this._offset = null; + delete this._headers["x-if-unmodified-since"]; + this._rebuildURL(); + } + return { response: resp, records: recordBuffer }; + }, + + // This object only supports posting via the postQueue object. + post() { + throw new Error( + "Don't directly post to a collection - use newPostQueue instead" + ); + }, + + newPostQueue(log, timestamp, postCallback) { + let poster = (data, headers, batch, commit) => { + this.batch = batch; + this.commit = commit; + for (let [header, value] of headers) { + this.setHeader(header, value); + } + return Resource.prototype.post.call(this, data); + }; + return new PostQueue( + poster, + timestamp, + this._service.serverConfiguration || {}, + log, + postCallback + ); + }, +}; + +Object.setPrototypeOf(Collection.prototype, Resource.prototype); + +// These are limits for requests provided by the server at the +// info/configuration endpoint -- server documentation is available here: +// http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions +// +// All are optional, however we synthesize (non-infinite) default values for the +// "max_request_bytes" and "max_record_payload_bytes" options. For the others, +// we ignore them (we treat the limit is infinite) if they're missing. +// +// These are also the only ones that all servers (even batching-disabled +// servers) should support, at least once this sync-serverstorage patch is +// everywhere https://github.com/mozilla-services/server-syncstorage/pull/74 +// +// Batching enabled servers also limit the amount of payload data and number +// of and records we can send in a single post as well as in the whole batch. +// Note that the byte limits for these there are just with respect to the +// *payload* data, e.g. the data appearing in the payload property (a +// string) of the object. +// +// Note that in practice, these limits should be sensible, but the code makes +// no assumptions about this. If we hit any of the limits, we perform the +// corresponding action (e.g. submit a request, possibly committing the +// current batch). +const DefaultPostQueueConfig = Object.freeze({ + // Number of total bytes allowed in a request + max_request_bytes: 260 * 1024, + + // Maximum number of bytes allowed in the "payload" property of a record. + max_record_payload_bytes: 256 * 1024, + + // The limit for how many bytes worth of data appearing in "payload" + // properties are allowed in a single post. + max_post_bytes: Infinity, + + // The limit for the number of records allowed in a single post. + max_post_records: Infinity, + + // The limit for how many bytes worth of data appearing in "payload" + // properties are allowed in a batch. (Same as max_post_bytes, but for + // batches). + max_total_bytes: Infinity, + + // The limit for the number of records allowed in a single post. (Same + // as max_post_records, but for batches). + max_total_records: Infinity, +}); + +// Manages a pair of (byte, count) limits for a PostQueue, such as +// (max_post_bytes, max_post_records) or (max_total_bytes, max_total_records). +class LimitTracker { + constructor(maxBytes, maxRecords) { + this.maxBytes = maxBytes; + this.maxRecords = maxRecords; + this.curBytes = 0; + this.curRecords = 0; + } + + clear() { + this.curBytes = 0; + this.curRecords = 0; + } + + canAddRecord(payloadSize) { + // The record counts are inclusive, but depending on the version of the + // server, the byte counts may or may not be inclusive (See + // https://github.com/mozilla-services/server-syncstorage/issues/73). + return ( + this.curRecords + 1 <= this.maxRecords && + this.curBytes + payloadSize < this.maxBytes + ); + } + + canNeverAdd(recordSize) { + return recordSize >= this.maxBytes; + } + + didAddRecord(recordSize) { + if (!this.canAddRecord(recordSize)) { + // This is a bug, caller is expected to call canAddRecord first. + throw new Error( + "LimitTracker.canAddRecord must be checked before adding record" + ); + } + this.curRecords += 1; + this.curBytes += recordSize; + } +} + +/* A helper to manage the posting of records while respecting the various + size limits. + + This supports the concept of a server-side "batch". The general idea is: + * We queue as many records as allowed in memory, then make a single POST. + * This first POST (optionally) gives us a batch ID, which we use for + all subsequent posts, until... + * At some point we hit a batch-maximum, and jump through a few hoops to + commit the current batch (ie, all previous POSTs) and start a new one. + * Eventually commit the final batch. + + In most cases we expect there to be exactly 1 batch consisting of possibly + multiple POSTs. +*/ +export function PostQueue(poster, timestamp, serverConfig, log, postCallback) { + // The "post" function we should use when it comes time to do the post. + this.poster = poster; + this.log = log; + + let config = Object.assign({}, DefaultPostQueueConfig, serverConfig); + + if (!serverConfig.max_request_bytes && serverConfig.max_post_bytes) { + // Use max_post_bytes for max_request_bytes if it's missing. Only needed + // until server-syncstorage/pull/74 is everywhere, and even then it's + // unnecessary if the server limits are configured sanely (there's no + // guarantee of -- at least before that is fully deployed) + config.max_request_bytes = serverConfig.max_post_bytes; + } + + this.log.trace("new PostQueue config (after defaults): ", config); + + // The callback we make with the response when we do get around to making the + // post (which could be during any of the enqueue() calls or the final flush()) + // This callback may be called multiple times and must not add new items to + // the queue. + // The second argument passed to this callback is a boolean value that is true + // if we're in the middle of a batch, and false if either the batch is + // complete, or it's a post to a server that does not understand batching. + this.postCallback = postCallback; + + // Tracks the count and combined payload size for the records we've queued + // so far but are yet to POST. + this.postLimits = new LimitTracker( + config.max_post_bytes, + config.max_post_records + ); + + // As above, but for the batch size. + this.batchLimits = new LimitTracker( + config.max_total_bytes, + config.max_total_records + ); + + // Limit for the size of `this.queued` before we do a post. + this.maxRequestBytes = config.max_request_bytes; + + // Limit for the size of incoming record payloads. + this.maxPayloadBytes = config.max_record_payload_bytes; + + // The string where we are capturing the stringified version of the records + // queued so far. It will always be invalid JSON as it is always missing the + // closing bracket. It's also used to track whether or not we've gone past + // maxRequestBytes. + this.queued = ""; + + // The ID of our current batch. Can be undefined (meaning we are yet to make + // the first post of a patch, so don't know if we have a batch), null (meaning + // we've made the first post but the server response indicated no batching + // semantics), otherwise we have made the first post and it holds the batch ID + // returned from the server. + this.batchID = undefined; + + // Time used for X-If-Unmodified-Since -- should be the timestamp from the last GET. + this.lastModified = timestamp; +} + +PostQueue.prototype = { + async enqueue(record) { + // We want to ensure the record has a .toJSON() method defined - even + // though JSON.stringify() would implicitly call it, the stringify might + // still work even if it isn't defined, which isn't what we want. + let jsonRepr = record.toJSON(); + if (!jsonRepr) { + throw new Error( + "You must only call this with objects that explicitly support JSON" + ); + } + + let bytes = JSON.stringify(jsonRepr); + + // We use the payload size for the LimitTrackers, since that's what the + // byte limits other than max_request_bytes refer to. + let payloadLength = jsonRepr.payload.length; + + // The `+ 2` is to account for the 2-byte (maximum) overhead (one byte for + // the leading comma or "[", which all records will have, and the other for + // the final trailing "]", only present for the last record). + let encodedLength = bytes.length + 2; + + // Check first if there's some limit that indicates we cannot ever enqueue + // this record. + let isTooBig = + this.postLimits.canNeverAdd(payloadLength) || + this.batchLimits.canNeverAdd(payloadLength) || + encodedLength >= this.maxRequestBytes || + payloadLength >= this.maxPayloadBytes; + + if (isTooBig) { + return { + enqueued: false, + error: new Error("Single record too large to submit to server"), + }; + } + + let canPostRecord = this.postLimits.canAddRecord(payloadLength); + let canBatchRecord = this.batchLimits.canAddRecord(payloadLength); + let canSendRecord = + this.queued.length + encodedLength < this.maxRequestBytes; + + if (!canPostRecord || !canBatchRecord || !canSendRecord) { + this.log.trace("PostQueue flushing: ", { + canPostRecord, + canSendRecord, + canBatchRecord, + }); + // We need to write the queue out before handling this one, but we only + // commit the batch (and thus start a new one) if the record couldn't fit + // inside the batch. + await this.flush(!canBatchRecord); + } + + this.postLimits.didAddRecord(payloadLength); + this.batchLimits.didAddRecord(payloadLength); + + // Either a ',' or a '[' depending on whether this is the first record. + this.queued += this.queued.length ? "," : "["; + this.queued += bytes; + return { enqueued: true }; + }, + + async flush(finalBatchPost) { + if (!this.queued) { + // nothing queued - we can't be in a batch, and something has gone very + // bad if we think we are. + if (this.batchID) { + throw new Error( + `Flush called when no queued records but we are in a batch ${this.batchID}` + ); + } + return; + } + // the batch query-param and headers we'll send. + let batch; + let headers = []; + if (this.batchID === undefined) { + // First commit in a (possible) batch. + batch = "true"; + } else if (this.batchID) { + // We have an existing batch. + batch = this.batchID; + } else { + // Not the first post and we know we have no batch semantics. + batch = null; + } + + headers.push(["x-if-unmodified-since", this.lastModified]); + + let numQueued = this.postLimits.curRecords; + this.log.info( + `Posting ${numQueued} records of ${ + this.queued.length + 1 + } bytes with batch=${batch}` + ); + let queued = this.queued + "]"; + if (finalBatchPost) { + this.batchLimits.clear(); + } + this.postLimits.clear(); + this.queued = ""; + let response = await this.poster( + queued, + headers, + batch, + !!(finalBatchPost && this.batchID !== null) + ); + + if (!response.success) { + this.log.trace("Server error response during a batch", response); + // not clear what we should do here - we expect the consumer of this to + // abort by throwing in the postCallback below. + await this.postCallback(this, response, !finalBatchPost); + return; + } + + if (finalBatchPost) { + this.log.trace("Committed batch", this.batchID); + this.batchID = undefined; // we are now in "first post for the batch" state. + this.lastModified = response.headers["x-last-modified"]; + await this.postCallback(this, response, false); + return; + } + + if (response.status != 202) { + if (this.batchID) { + throw new Error( + "Server responded non-202 success code while a batch was in progress" + ); + } + this.batchID = null; // no batch semantics are in place. + this.lastModified = response.headers["x-last-modified"]; + await this.postCallback(this, response, false); + return; + } + + // this response is saying the server has batch semantics - we should + // always have a batch ID in the response. + let responseBatchID = response.obj.batch; + this.log.trace("Server responsed 202 with batch", responseBatchID); + if (!responseBatchID) { + this.log.error( + "Invalid server response: 202 without a batch ID", + response + ); + throw new Error("Invalid server response: 202 without a batch ID"); + } + + if (this.batchID === undefined) { + this.batchID = responseBatchID; + if (!this.lastModified) { + this.lastModified = response.headers["x-last-modified"]; + if (!this.lastModified) { + throw new Error("Batch response without x-last-modified"); + } + } + } + + if (this.batchID != responseBatchID) { + throw new Error( + `Invalid client/server batch state - client has ${this.batchID}, server has ${responseBatchID}` + ); + } + + await this.postCallback(this, response, true); + }, +}; diff --git a/services/sync/modules/resource.sys.mjs b/services/sync/modules/resource.sys.mjs new file mode 100644 index 0000000000..537ffd5219 --- /dev/null +++ b/services/sync/modules/resource.sys.mjs @@ -0,0 +1,292 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Observers } from "resource://services-common/observers.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { Utils } from "resource://services-sync/util.sys.mjs"; +import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +/* global AbortController */ + +/* + * Resource represents a remote network resource, identified by a URI. + * Create an instance like so: + * + * let resource = new Resource("http://foobar.com/path/to/resource"); + * + * The 'resource' object has the following methods to issue HTTP requests + * of the corresponding HTTP methods: + * + * get(callback) + * put(data, callback) + * post(data, callback) + * delete(callback) + */ +export function Resource(uri) { + this._log = Log.repository.getLogger(this._logName); + this._log.manageLevelFromPref("services.sync.log.logger.network.resources"); + this.uri = uri; + this._headers = {}; +} + +// (static) Caches the latest server timestamp (X-Weave-Timestamp header). +Resource.serverTime = null; + +XPCOMUtils.defineLazyPreferenceGetter( + Resource, + "SEND_VERSION_INFO", + "services.sync.sendVersionInfo", + true +); +Resource.prototype = { + _logName: "Sync.Resource", + + /** + * Callback to be invoked at request time to add authentication details. + * If the callback returns a promise, it will be awaited upon. + * + * By default, a global authenticator is provided. If this is set, it will + * be used instead of the global one. + */ + authenticator: null, + + // Wait 5 minutes before killing a request. + ABORT_TIMEOUT: 300000, + + // Headers to be included when making a request for the resource. + // Note: Header names should be all lower case, there's no explicit + // check for duplicates due to case! + get headers() { + return this._headers; + }, + set headers(_) { + throw new Error("headers can't be mutated directly. Please use setHeader."); + }, + setHeader(header, value) { + this._headers[header.toLowerCase()] = value; + }, + + // URI representing this resource. + get uri() { + return this._uri; + }, + set uri(value) { + if (typeof value == "string") { + this._uri = CommonUtils.makeURI(value); + } else { + this._uri = value; + } + }, + + // Get the string representation of the URI. + get spec() { + if (this._uri) { + return this._uri.spec; + } + return null; + }, + + /** + * @param {string} method HTTP method + * @returns {Headers} + */ + async _buildHeaders(method) { + const headers = new Headers(this._headers); + + if (Resource.SEND_VERSION_INFO) { + headers.append("user-agent", Utils.userAgent); + } + + if (this.authenticator) { + const result = await this.authenticator(this, method); + if (result && result.headers) { + for (const [k, v] of Object.entries(result.headers)) { + headers.append(k.toLowerCase(), v); + } + } + } else { + this._log.debug("No authenticator found."); + } + + // PUT and POST are treated differently because they have payload data. + if (("PUT" == method || "POST" == method) && !headers.has("content-type")) { + headers.append("content-type", "text/plain"); + } + + if (this._log.level <= Log.Level.Trace) { + for (const [k, v] of headers) { + if (k == "authorization" || k == "x-client-state") { + this._log.trace(`HTTP Header ${k}: ***** (suppressed)`); + } else { + this._log.trace(`HTTP Header ${k}: ${v}`); + } + } + } + + if (!headers.has("accept")) { + headers.append("accept", "application/json;q=0.9,*/*;q=0.2"); + } + + return headers; + }, + + /** + * @param {string} method HTTP method + * @param {string} data HTTP body + * @param {object} signal AbortSignal instance + * @returns {Request} + */ + async _createRequest(method, data, signal) { + const headers = await this._buildHeaders(method); + const init = { + cache: "no-store", // No cache. + headers, + method, + signal, + mozErrors: true, // Return nsresult error codes instead of a generic + // NetworkError when fetch rejects. + }; + + if (data) { + if (!(typeof data == "string" || data instanceof String)) { + data = JSON.stringify(data); + } + this._log.debug(`${method} Length: ${data.length}`); + this._log.trace(`${method} Body: ${data}`); + init.body = data; + } + return new Request(this.uri.spec, init); + }, + + /** + * @param {string} method HTTP method + * @param {string} [data] HTTP body + * @returns {Response} + */ + async _doRequest(method, data = null) { + const controller = new AbortController(); + const request = await this._createRequest(method, data, controller.signal); + const responsePromise = fetch(request); // Rejects on network failure. + let didTimeout = false; + const timeoutId = setTimeout(() => { + didTimeout = true; + this._log.error( + `Request timed out after ${this.ABORT_TIMEOUT}ms. Aborting.` + ); + controller.abort(); + }, this.ABORT_TIMEOUT); + let response; + try { + response = await responsePromise; + } catch (e) { + this._log.warn(`${method} request to ${this.uri.spec} failed`, e); + if (!didTimeout) { + throw e; + } + throw Components.Exception( + "Request aborted (timeout)", + Cr.NS_ERROR_NET_TIMEOUT + ); + } finally { + clearTimeout(timeoutId); + } + return this._processResponse(response, method); + }, + + async _processResponse(response, method) { + const data = await response.text(); + this._logResponse(response, method, data); + this._processResponseHeaders(response); + + const ret = { + data, + url: response.url, + status: response.status, + success: response.ok, + headers: {}, + }; + for (const [k, v] of response.headers) { + ret.headers[k] = v; + } + + // Make a lazy getter to convert the json response into an object. + // Note that this can cause a parse error to be thrown far away from the + // actual fetch, so be warned! + ChromeUtils.defineLazyGetter(ret, "obj", () => { + try { + return JSON.parse(ret.data); + } catch (ex) { + this._log.warn("Got exception parsing response body", ex); + // Stringify to avoid possibly printing non-printable characters. + this._log.debug( + "Parse fail: Response body starts", + (ret.data + "").slice(0, 100) + ); + throw ex; + } + }); + + return ret; + }, + + _logResponse(response, method, data) { + const { status, ok: success, url } = response; + + // Log the status of the request. + this._log.debug( + `${method} ${success ? "success" : "fail"} ${status} ${url}` + ); + + // Additionally give the full response body when Trace logging. + if (this._log.level <= Log.Level.Trace) { + this._log.trace(`${method} body`, data); + } + + if (!success) { + this._log.warn( + `${method} request to ${url} failed with status ${status}` + ); + } + }, + + _processResponseHeaders({ headers, ok: success }) { + if (headers.has("x-weave-timestamp")) { + Resource.serverTime = parseFloat(headers.get("x-weave-timestamp")); + } + // This is a server-side safety valve to allow slowing down + // clients without hurting performance. + if (headers.has("x-weave-backoff")) { + let backoff = headers.get("x-weave-backoff"); + this._log.debug(`Got X-Weave-Backoff: ${backoff}`); + Observers.notify("weave:service:backoff:interval", parseInt(backoff, 10)); + } + + if (success && headers.has("x-weave-quota-remaining")) { + Observers.notify( + "weave:service:quota:remaining", + parseInt(headers.get("x-weave-quota-remaining"), 10) + ); + } + }, + + get() { + return this._doRequest("GET"); + }, + + put(data) { + return this._doRequest("PUT", data); + }, + + post(data) { + return this._doRequest("POST", data); + }, + + delete() { + return this._doRequest("DELETE"); + }, +}; diff --git a/services/sync/modules/service.sys.mjs b/services/sync/modules/service.sys.mjs new file mode 100644 index 0000000000..97ba0d32cd --- /dev/null +++ b/services/sync/modules/service.sys.mjs @@ -0,0 +1,1643 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const CRYPTO_COLLECTION = "crypto"; +const KEYS_WBO = "keys"; + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; + +import { + CLIENT_NOT_CONFIGURED, + CREDENTIALS_CHANGED, + HMAC_EVENT_INTERVAL, + LOGIN_FAILED, + LOGIN_FAILED_INVALID_PASSPHRASE, + LOGIN_FAILED_NETWORK_ERROR, + LOGIN_FAILED_NO_PASSPHRASE, + LOGIN_FAILED_NO_USERNAME, + LOGIN_FAILED_SERVER_ERROR, + LOGIN_SUCCEEDED, + MASTER_PASSWORD_LOCKED, + METARECORD_DOWNLOAD_FAIL, + NO_SYNC_NODE_FOUND, + PREFS_BRANCH, + STATUS_DISABLED, + STATUS_OK, + STORAGE_VERSION, + VERSION_OUT_OF_DATE, + WEAVE_VERSION, + kFirefoxShuttingDown, + kFirstSyncChoiceNotMade, + kSyncBackoffNotMet, + kSyncMasterPasswordLocked, + kSyncNetworkOffline, + kSyncNotConfigured, + kSyncWeaveDisabled, +} from "resource://services-sync/constants.sys.mjs"; + +import { EngineManager } from "resource://services-sync/engines.sys.mjs"; +import { ClientEngine } from "resource://services-sync/engines/clients.sys.mjs"; +import { Weave } from "resource://services-sync/main.sys.mjs"; +import { + ErrorHandler, + SyncScheduler, +} from "resource://services-sync/policies.sys.mjs"; +import { + CollectionKeyManager, + CryptoWrapper, + RecordManager, + WBORecord, +} from "resource://services-sync/record.sys.mjs"; +import { Resource } from "resource://services-sync/resource.sys.mjs"; +import { EngineSynchronizer } from "resource://services-sync/stages/enginesync.sys.mjs"; +import { DeclinedEngines } from "resource://services-sync/stages/declined.sys.mjs"; +import { Status } from "resource://services-sync/status.sys.mjs"; + +ChromeUtils.importESModule("resource://services-sync/telemetry.sys.mjs"); +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs"; + +const fxAccounts = getFxAccountsSingleton(); + +function getEngineModules() { + let result = { + Addons: { module: "addons.sys.mjs", symbol: "AddonsEngine" }, + Password: { module: "passwords.sys.mjs", symbol: "PasswordEngine" }, + Prefs: { module: "prefs.sys.mjs", symbol: "PrefsEngine" }, + }; + if (AppConstants.MOZ_APP_NAME != "thunderbird") { + result.Bookmarks = { + module: "bookmarks.sys.mjs", + symbol: "BookmarksEngine", + }; + result.Form = { module: "forms.sys.mjs", symbol: "FormEngine" }; + result.History = { module: "history.sys.mjs", symbol: "HistoryEngine" }; + result.Tab = { module: "tabs.sys.mjs", symbol: "TabEngine" }; + } + if (Svc.PrefBranch.getBoolPref("engine.addresses.available", false)) { + result.Addresses = { + module: "resource://autofill/FormAutofillSync.sys.mjs", + symbol: "AddressesEngine", + }; + } + if (Svc.PrefBranch.getBoolPref("engine.creditcards.available", false)) { + result.CreditCards = { + module: "resource://autofill/FormAutofillSync.sys.mjs", + symbol: "CreditCardsEngine", + }; + } + result["Extension-Storage"] = { + module: "extension-storage.sys.mjs", + controllingPref: "webextensions.storage.sync.kinto", + whenTrue: "ExtensionStorageEngineKinto", + whenFalse: "ExtensionStorageEngineBridge", + }; + return result; +} + +const lazy = {}; + +// A unique identifier for this browser session. Used for logging so +// we can easily see whether 2 logs are in the same browser session or +// after the browser restarted. +ChromeUtils.defineLazyGetter(lazy, "browserSessionID", Utils.makeGUID); + +function Sync11Service() { + this._notify = Utils.notify("weave:service:"); + Utils.defineLazyIDProperty(this, "syncID", "services.sync.client.syncID"); +} +Sync11Service.prototype = { + _lock: Utils.lock, + _locked: false, + _loggedIn: false, + + infoURL: null, + storageURL: null, + metaURL: null, + cryptoKeyURL: null, + // The cluster URL comes via the identity object, which in the FxA + // world is ebbedded in the token returned from the token server. + _clusterURL: null, + + get clusterURL() { + return this._clusterURL || ""; + }, + set clusterURL(value) { + if (value != null && typeof value != "string") { + throw new Error("cluster must be a string, got " + typeof value); + } + this._clusterURL = value; + this._updateCachedURLs(); + }, + + get isLoggedIn() { + return this._loggedIn; + }, + + get locked() { + return this._locked; + }, + lock: function lock() { + if (this._locked) { + return false; + } + this._locked = true; + return true; + }, + unlock: function unlock() { + this._locked = false; + }, + + // A specialized variant of Utils.catch. + // This provides a more informative error message when we're already syncing: + // see Bug 616568. + _catch(func) { + function lockExceptions(ex) { + if (Utils.isLockException(ex)) { + // This only happens if we're syncing already. + this._log.info("Cannot start sync: already syncing?"); + } + } + + return Utils.catch.call(this, func, lockExceptions); + }, + + get userBaseURL() { + // The user URL is the cluster URL. + return this.clusterURL; + }, + + _updateCachedURLs: function _updateCachedURLs() { + // Nothing to cache yet if we don't have the building blocks + if (!this.clusterURL) { + // Also reset all other URLs used by Sync to ensure we aren't accidentally + // using one cached earlier - if there's no cluster URL any cached ones + // are invalid. + this.infoURL = undefined; + this.storageURL = undefined; + this.metaURL = undefined; + this.cryptoKeysURL = undefined; + return; + } + + this._log.debug( + "Caching URLs under storage user base: " + this.userBaseURL + ); + + // Generate and cache various URLs under the storage API for this user + this.infoURL = this.userBaseURL + "info/collections"; + this.storageURL = this.userBaseURL + "storage/"; + this.metaURL = this.storageURL + "meta/global"; + this.cryptoKeysURL = this.storageURL + CRYPTO_COLLECTION + "/" + KEYS_WBO; + }, + + _checkCrypto: function _checkCrypto() { + let ok = false; + + try { + let iv = Weave.Crypto.generateRandomIV(); + if (iv.length == 24) { + ok = true; + } + } catch (e) { + this._log.debug("Crypto check failed: " + e); + } + + return ok; + }, + + /** + * Here is a disgusting yet reasonable way of handling HMAC errors deep in + * the guts of Sync. The astute reader will note that this is a hacky way of + * implementing something like continuable conditions. + * + * A handler function is glued to each engine. If the engine discovers an + * HMAC failure, we fetch keys from the server and update our keys, just as + * we would on startup. + * + * If our key collection changed, we signal to the engine (via our return + * value) that it should retry decryption. + * + * If our key collection did not change, it means that we already had the + * correct keys... and thus a different client has the wrong ones. Reupload + * the bundle that we fetched, which will bump the modified time on the + * server and (we hope) prompt a broken client to fix itself. + * + * We keep track of the time at which we last applied this reasoning, because + * thrashing doesn't solve anything. We keep a reasonable interval between + * these remedial actions. + */ + lastHMACEvent: 0, + + /* + * Returns whether to try again. + */ + async handleHMACEvent() { + let now = Date.now(); + + // Leave a sizable delay between HMAC recovery attempts. This gives us + // time for another client to fix themselves if we touch the record. + if (now - this.lastHMACEvent < HMAC_EVENT_INTERVAL) { + return false; + } + + this._log.info( + "Bad HMAC event detected. Attempting recovery " + + "or signaling to other clients." + ); + + // Set the last handled time so that we don't act again. + this.lastHMACEvent = now; + + // Fetch keys. + let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); + try { + let cryptoResp = ( + await cryptoKeys.fetch(this.resource(this.cryptoKeysURL)) + ).response; + + // Save out the ciphertext for when we reupload. If there's a bug in + // CollectionKeyManager, this will prevent us from uploading junk. + let cipherText = cryptoKeys.ciphertext; + + if (!cryptoResp.success) { + this._log.warn("Failed to download keys."); + return false; + } + + let keysChanged = await this.handleFetchedKeys( + this.identity.syncKeyBundle, + cryptoKeys, + true + ); + if (keysChanged) { + // Did they change? If so, carry on. + this._log.info("Suggesting retry."); + return true; // Try again. + } + + // If not, reupload them and continue the current sync. + cryptoKeys.ciphertext = cipherText; + cryptoKeys.cleartext = null; + + let uploadResp = await this._uploadCryptoKeys( + cryptoKeys, + cryptoResp.obj.modified + ); + if (uploadResp.success) { + this._log.info("Successfully re-uploaded keys. Continuing sync."); + } else { + this._log.warn( + "Got error response re-uploading keys. " + + "Continuing sync; let's try again later." + ); + } + + return false; // Don't try again: same keys. + } catch (ex) { + this._log.warn( + "Got exception fetching and handling crypto keys. " + + "Will try again later.", + ex + ); + return false; + } + }, + + async handleFetchedKeys(syncKey, cryptoKeys, skipReset) { + // Don't want to wipe if we're just starting up! + let wasBlank = this.collectionKeys.isClear; + let keysChanged = await this.collectionKeys.updateContents( + syncKey, + cryptoKeys + ); + + if (keysChanged && !wasBlank) { + this._log.debug("Keys changed: " + JSON.stringify(keysChanged)); + + if (!skipReset) { + this._log.info("Resetting client to reflect key change."); + + if (keysChanged.length) { + // Collection keys only. Reset individual engines. + await this.resetClient(keysChanged); + } else { + // Default key changed: wipe it all. + await this.resetClient(); + } + + this._log.info("Downloaded new keys, client reset. Proceeding."); + } + return true; + } + return false; + }, + + /** + * Prepare to initialize the rest of Weave after waiting a little bit + */ + async onStartup() { + this.status = Status; + this.identity = Status._authManager; + this.collectionKeys = new CollectionKeyManager(); + + this.scheduler = new SyncScheduler(this); + this.errorHandler = new ErrorHandler(this); + + this._log = Log.repository.getLogger("Sync.Service"); + this._log.manageLevelFromPref("services.sync.log.logger.service.main"); + + this._log.info("Loading Weave " + WEAVE_VERSION); + + this.recordManager = new RecordManager(this); + + this.enabled = true; + + await this._registerEngines(); + + let ua = Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ).userAgent; + this._log.info(ua); + + if (!this._checkCrypto()) { + this.enabled = false; + this._log.info( + "Could not load the Weave crypto component. Disabling " + + "Weave, since it will not work correctly." + ); + } + + Svc.Obs.add("weave:service:setup-complete", this); + Svc.Obs.add("sync:collection_changed", this); // Pulled from FxAccountsCommon + Svc.Obs.add("fxaccounts:device_disconnected", this); + Services.prefs.addObserver(PREFS_BRANCH + "engine.", this); + + if (!this.enabled) { + this._log.info("Firefox Sync disabled."); + } + + this._updateCachedURLs(); + + let status = this._checkSetup(); + if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) { + this._startTracking(); + } + + // Send an event now that Weave service is ready. We don't do this + // synchronously so that observers can import this module before + // registering an observer. + CommonUtils.nextTick(() => { + this.status.ready = true; + + // UI code uses the flag on the XPCOM service so it doesn't have + // to load a bunch of modules. + let xps = Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + xps.ready = true; + + Svc.Obs.notify("weave:service:ready"); + }); + }, + + _checkSetup: function _checkSetup() { + if (!this.enabled) { + return (this.status.service = STATUS_DISABLED); + } + return this.status.checkSetup(); + }, + + /** + * Register the built-in engines for certain applications + */ + async _registerEngines() { + this.engineManager = new EngineManager(this); + + let engineModules = getEngineModules(); + + let engines = []; + // We allow a pref, which has no default value, to limit the engines + // which are registered. We expect only tests will use this. + if ( + Svc.PrefBranch.getPrefType("registerEngines") != + Ci.nsIPrefBranch.PREF_INVALID + ) { + engines = Svc.PrefBranch.getStringPref("registerEngines").split(","); + this._log.info("Registering custom set of engines", engines); + } else { + // default is all engines. + engines = Object.keys(engineModules); + } + + let declined = []; + let pref = Svc.PrefBranch.getStringPref("declinedEngines", null); + if (pref) { + declined = pref.split(","); + } + + let clientsEngine = new ClientEngine(this); + // Ideally clientsEngine should not exist + // (or be a promise that calls initialize() before returning the engine) + await clientsEngine.initialize(); + this.clientsEngine = clientsEngine; + + for (let name of engines) { + if (!(name in engineModules)) { + this._log.info("Do not know about engine: " + name); + continue; + } + let modInfo = engineModules[name]; + if (!modInfo.module.includes(":")) { + modInfo.module = "resource://services-sync/engines/" + modInfo.module; + } + try { + let ns = ChromeUtils.importESModule(modInfo.module); + if (modInfo.symbol) { + let symbol = modInfo.symbol; + if (!(symbol in ns)) { + this._log.warn( + "Could not find exported engine instance: " + symbol + ); + continue; + } + await this.engineManager.register(ns[symbol]); + } else { + let { whenTrue, whenFalse, controllingPref } = modInfo; + if (!(whenTrue in ns) || !(whenFalse in ns)) { + this._log.warn("Could not find all exported engine instances", { + whenTrue, + whenFalse, + }); + continue; + } + await this.engineManager.registerAlternatives( + name.toLowerCase(), + controllingPref, + ns[whenTrue], + ns[whenFalse] + ); + } + } catch (ex) { + this._log.warn("Could not register engine " + name, ex); + } + } + + this.engineManager.setDeclined(declined); + }, + + /** + * This method updates the local engines state from an existing meta/global + * when Sync is disabled. + * Running this code if sync is enabled would end up in very weird results + * (but we're nice and we check before doing anything!). + */ + async updateLocalEnginesState() { + await this.promiseInitialized; + + // Sanity check, this method is not meant to be run if Sync is enabled! + if (Svc.PrefBranch.getStringPref("username", "")) { + throw new Error("Sync is enabled!"); + } + + // For historical reasons the behaviour of setCluster() is bizarre, + // so just check what we care about - the meta URL. + if (!this.metaURL) { + await this.identity.setCluster(); + if (!this.metaURL) { + this._log.warn("Could not find a cluster."); + return; + } + } + // Clear the cache so we always fetch the latest meta/global. + this.recordManager.clearCache(); + let meta = await this.recordManager.get(this.metaURL); + if (!meta) { + this._log.info("Meta record is null, aborting engine state update."); + return; + } + const declinedEngines = meta.payload.declined; + const allEngines = this.engineManager.getAll().map(e => e.name); + // We don't want our observer of the enabled prefs to treat the change as + // a user-change, otherwise we will do the wrong thing with declined etc. + this._ignorePrefObserver = true; + try { + for (const engine of allEngines) { + Svc.PrefBranch.setBoolPref( + `engine.${engine}`, + !declinedEngines.includes(engine) + ); + } + } finally { + this._ignorePrefObserver = false; + } + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + + observe(subject, topic, data) { + switch (topic) { + // Ideally this observer should be in the SyncScheduler, but it would require + // some work to know about the sync specific engines. We should move this there once it does. + case "sync:collection_changed": + // We check if we're running TPS here to avoid TPS failing because it + // couldn't get to get the sync lock, due to us currently syncing the + // clients engine. + if ( + data.includes("clients") && + !Svc.PrefBranch.getBoolPref("testing.tps", false) + ) { + // Sync in the background (it's fine not to wait on the returned promise + // because sync() has a lock). + // [] = clients collection only + this.sync({ why: "collection_changed", engines: [] }).catch(e => { + this._log.error(e); + }); + } + break; + case "fxaccounts:device_disconnected": + data = JSON.parse(data); + if (!data.isLocalDevice) { + // Refresh the known stale clients list in the background. + this.clientsEngine.updateKnownStaleClients().catch(e => { + this._log.error(e); + }); + } + break; + case "weave:service:setup-complete": + let status = this._checkSetup(); + if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) { + this._startTracking(); + } + break; + case "nsPref:changed": + if (this._ignorePrefObserver) { + return; + } + const engine = data.slice((PREFS_BRANCH + "engine.").length); + if (engine.includes(".")) { + // A sub-preference of the engine was changed. For example + // `services.sync.engine.bookmarks.validation.percentageChance`. + return; + } + this._handleEngineStatusChanged(engine); + break; + } + }, + + _handleEngineStatusChanged(engine) { + this._log.trace("Status for " + engine + " engine changed."); + if (Svc.PrefBranch.getBoolPref("engineStatusChanged." + engine, false)) { + // The enabled status being changed back to what it was before. + Svc.PrefBranch.clearUserPref("engineStatusChanged." + engine); + } else { + // Remember that the engine status changed locally until the next sync. + Svc.PrefBranch.setBoolPref("engineStatusChanged." + engine, true); + } + }, + + _startTracking() { + const engines = [this.clientsEngine, ...this.engineManager.getAll()]; + for (let engine of engines) { + try { + engine.startTracking(); + } catch (e) { + this._log.error(`Could not start ${engine.name} engine tracker`, e); + } + } + // This is for TPS. We should try to do better. + Svc.Obs.notify("weave:service:tracking-started"); + }, + + async _stopTracking() { + const engines = [this.clientsEngine, ...this.engineManager.getAll()]; + for (let engine of engines) { + try { + await engine.stopTracking(); + } catch (e) { + this._log.error(`Could not stop ${engine.name} engine tracker`, e); + } + } + Svc.Obs.notify("weave:service:tracking-stopped"); + }, + + /** + * Obtain a Resource instance with authentication credentials. + */ + resource: function resource(url) { + let res = new Resource(url); + res.authenticator = this.identity.getResourceAuthenticator(); + + return res; + }, + + /** + * Perform the info fetch as part of a login or key fetch, or + * inside engine sync. + */ + async _fetchInfo(url) { + let infoURL = url || this.infoURL; + + this._log.trace("In _fetchInfo: " + infoURL); + let info; + try { + info = await this.resource(infoURL).get(); + } catch (ex) { + this.errorHandler.checkServerError(ex); + throw ex; + } + + // Always check for errors. + this.errorHandler.checkServerError(info); + if (!info.success) { + this._log.error("Aborting sync: failed to get collections."); + throw info; + } + return info; + }, + + async verifyAndFetchSymmetricKeys(infoResponse) { + this._log.debug( + "Fetching and verifying -- or generating -- symmetric keys." + ); + + let syncKeyBundle = this.identity.syncKeyBundle; + if (!syncKeyBundle) { + this.status.login = LOGIN_FAILED_NO_PASSPHRASE; + this.status.sync = CREDENTIALS_CHANGED; + return false; + } + + try { + if (!infoResponse) { + infoResponse = await this._fetchInfo(); // Will throw an exception on failure. + } + + // This only applies when the server is already at version 4. + if (infoResponse.status != 200) { + this._log.warn( + "info/collections returned non-200 response. Failing key fetch." + ); + this.status.login = LOGIN_FAILED_SERVER_ERROR; + this.errorHandler.checkServerError(infoResponse); + return false; + } + + let infoCollections = infoResponse.obj; + + this._log.info( + "Testing info/collections: " + JSON.stringify(infoCollections) + ); + + if (this.collectionKeys.updateNeeded(infoCollections)) { + this._log.info("collection keys reports that a key update is needed."); + + // Don't always set to CREDENTIALS_CHANGED -- we will probably take care of this. + + // Fetch storage/crypto/keys. + let cryptoKeys; + + if (infoCollections && CRYPTO_COLLECTION in infoCollections) { + try { + cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); + let cryptoResp = ( + await cryptoKeys.fetch(this.resource(this.cryptoKeysURL)) + ).response; + + if (cryptoResp.success) { + await this.handleFetchedKeys(syncKeyBundle, cryptoKeys); + return true; + } else if (cryptoResp.status == 404) { + // On failure, ask to generate new keys and upload them. + // Fall through to the behavior below. + this._log.warn( + "Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating." + ); + cryptoKeys = null; + } else { + // Some other problem. + this.status.login = LOGIN_FAILED_SERVER_ERROR; + this.errorHandler.checkServerError(cryptoResp); + this._log.warn( + "Got status " + cryptoResp.status + " fetching crypto keys." + ); + return false; + } + } catch (ex) { + this._log.warn("Got exception fetching cryptoKeys.", ex); + // TODO: Um, what exceptions might we get here? Should we re-throw any? + + // One kind of exception: HMAC failure. + if (Utils.isHMACMismatch(ex)) { + this.status.login = LOGIN_FAILED_INVALID_PASSPHRASE; + this.status.sync = CREDENTIALS_CHANGED; + } else { + // In the absence of further disambiguation or more precise + // failure constants, just report failure. + this.status.login = LOGIN_FAILED; + } + return false; + } + } else { + this._log.info( + "... 'crypto' is not a reported collection. Generating new keys." + ); + } + + if (!cryptoKeys) { + this._log.info("No keys! Generating new ones."); + + // Better make some and upload them, and wipe the server to ensure + // consistency. This is all achieved via _freshStart. + // If _freshStart fails to clear the server or upload keys, it will + // throw. + await this._freshStart(); + return true; + } + + // Last-ditch case. + return false; + } + // No update needed: we're good! + return true; + } catch (ex) { + // This means no keys are present, or there's a network error. + this._log.debug("Failed to fetch and verify keys", ex); + this.errorHandler.checkServerError(ex); + return false; + } + }, + + getMaxRecordPayloadSize() { + let config = this.serverConfiguration; + if (!config || !config.max_record_payload_bytes) { + this._log.warn( + "No config or incomplete config in getMaxRecordPayloadSize." + + " Are we running tests?" + ); + return 256 * 1024; + } + let payloadMax = config.max_record_payload_bytes; + if (config.max_post_bytes && payloadMax <= config.max_post_bytes) { + return config.max_post_bytes - 4096; + } + return payloadMax; + }, + + getMemcacheMaxRecordPayloadSize() { + // Collections stored in memcached ("tabs", "clients" or "meta") have a + // different max size than ones stored in the normal storage server db. + // In practice, the real limit here is 1M (bug 1300451 comment 40), but + // there's overhead involved that is hard to calculate on the client, so we + // use 512k to be safe (at the recommendation of the server team). Note + // that if the server reports a lower limit (via info/configuration), we + // respect that limit instead. See also bug 1403052. + return Math.min(512 * 1024, this.getMaxRecordPayloadSize()); + }, + + async verifyLogin(allow40XRecovery = true) { + // Attaching auth credentials to a request requires access to + // passwords, which means that Resource.get can throw MP-related + // exceptions! + // So we ask the identity to verify the login state after unlocking the + // master password (ie, this call is expected to prompt for MP unlock + // if necessary) while we still have control. + this.status.login = await this.identity.unlockAndVerifyAuthState(); + this._log.debug( + "Fetching unlocked auth state returned " + this.status.login + ); + if (this.status.login != STATUS_OK) { + return false; + } + + try { + // Make sure we have a cluster to verify against. + // This is a little weird, if we don't get a node we pretend + // to succeed, since that probably means we just don't have storage. + if (this.clusterURL == "" && !(await this.identity.setCluster())) { + this.status.sync = NO_SYNC_NODE_FOUND; + return true; + } + + // Fetch collection info on every startup. + let test = await this.resource(this.infoURL).get(); + + switch (test.status) { + case 200: + // The user is authenticated. + + // We have no way of verifying the passphrase right now, + // so wait until remoteSetup to do so. + // Just make the most trivial checks. + if (!this.identity.syncKeyBundle) { + this._log.warn("No passphrase in verifyLogin."); + this.status.login = LOGIN_FAILED_NO_PASSPHRASE; + return false; + } + + // Go ahead and do remote setup, so that we can determine + // conclusively that our passphrase is correct. + if (await this._remoteSetup(test)) { + // Username/password verified. + this.status.login = LOGIN_SUCCEEDED; + return true; + } + + this._log.warn("Remote setup failed."); + // Remote setup must have failed. + return false; + + case 401: + this._log.warn("401: login failed."); + // Fall through to the 404 case. + + case 404: + // Check that we're verifying with the correct cluster + if (allow40XRecovery && (await this.identity.setCluster())) { + return await this.verifyLogin(false); + } + + // We must have the right cluster, but the server doesn't expect us. + // For FxA this almost certainly means "transient error fetching token". + this.status.login = LOGIN_FAILED_NETWORK_ERROR; + return false; + + default: + // Server didn't respond with something that we expected + this.status.login = LOGIN_FAILED_SERVER_ERROR; + this.errorHandler.checkServerError(test); + return false; + } + } catch (ex) { + // Must have failed on some network issue + this._log.debug("verifyLogin failed", ex); + this.status.login = LOGIN_FAILED_NETWORK_ERROR; + this.errorHandler.checkServerError(ex); + return false; + } + }, + + async generateNewSymmetricKeys() { + this._log.info("Generating new keys WBO..."); + let wbo = await this.collectionKeys.generateNewKeysWBO(); + this._log.info("Encrypting new key bundle."); + await wbo.encrypt(this.identity.syncKeyBundle); + + let uploadRes = await this._uploadCryptoKeys(wbo, 0); + if (uploadRes.status != 200) { + this._log.warn( + "Got status " + + uploadRes.status + + " uploading new keys. What to do? Throw!" + ); + this.errorHandler.checkServerError(uploadRes); + throw new Error("Unable to upload symmetric keys."); + } + this._log.info("Got status " + uploadRes.status + " uploading keys."); + let serverModified = uploadRes.obj; // Modified timestamp according to server. + this._log.debug("Server reports crypto modified: " + serverModified); + + // Now verify that info/collections shows them! + this._log.debug("Verifying server collection records."); + let info = await this._fetchInfo(); + this._log.debug("info/collections is: " + info.data); + + if (info.status != 200) { + this._log.warn("Non-200 info/collections response. Aborting."); + throw new Error("Unable to upload symmetric keys."); + } + + info = info.obj; + if (!(CRYPTO_COLLECTION in info)) { + this._log.error( + "Consistency failure: info/collections excludes " + + "crypto after successful upload." + ); + throw new Error("Symmetric key upload failed."); + } + + // Can't check against local modified: clock drift. + if (info[CRYPTO_COLLECTION] < serverModified) { + this._log.error( + "Consistency failure: info/collections crypto entry " + + "is stale after successful upload." + ); + throw new Error("Symmetric key upload failed."); + } + + // Doesn't matter if the timestamp is ahead. + + // Download and install them. + let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); + let cryptoResp = (await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))) + .response; + if (cryptoResp.status != 200) { + this._log.warn("Failed to download keys."); + throw new Error("Symmetric key download failed."); + } + let keysChanged = await this.handleFetchedKeys( + this.identity.syncKeyBundle, + cryptoKeys, + true + ); + if (keysChanged) { + this._log.info("Downloaded keys differed, as expected."); + } + }, + + // configures/enabled/turns-on sync. There must be an FxA user signed in. + async configure() { + // We don't, and must not, throw if sync is already configured, because we + // might end up being called as part of a "reconnect" flow. We also want to + // avoid checking the FxA user is the same as the pref because the email + // address for the FxA account can change - we'd need to use the uid. + let user = await fxAccounts.getSignedInUser(); + if (!user) { + throw new Error("No FxA user is signed in"); + } + this._log.info("Configuring sync with current FxA user"); + Svc.PrefBranch.setStringPref("username", user.email); + Svc.Obs.notify("weave:connected"); + }, + + // resets/turns-off sync. + async startOver() { + this._log.trace("Invoking Service.startOver."); + await this._stopTracking(); + this.status.resetSync(); + + // Deletion doesn't make sense if we aren't set up yet! + if (this.clusterURL != "") { + // Clear client-specific data from the server, including disabled engines. + const engines = [this.clientsEngine, ...this.engineManager.getAll()]; + for (let engine of engines) { + try { + await engine.removeClientData(); + } catch (ex) { + this._log.warn(`Deleting client data for ${engine.name} failed`, ex); + } + } + this._log.debug("Finished deleting client data."); + } else { + this._log.debug("Skipping client data removal: no cluster URL."); + } + + this.identity.resetCredentials(); + this.status.login = LOGIN_FAILED_NO_USERNAME; + this.logout(); + Svc.Obs.notify("weave:service:start-over"); + + // Reset all engines and clear keys. + await this.resetClient(); + this.collectionKeys.clear(); + this.status.resetBackoff(); + + // Reset Weave prefs. + this._ignorePrefObserver = true; + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + this._ignorePrefObserver = false; + this.clusterURL = null; + + Svc.PrefBranch.setStringPref("lastversion", WEAVE_VERSION); + + try { + this.identity.finalize(); + this.status.__authManager = null; + this.identity = Status._authManager; + Svc.Obs.notify("weave:service:start-over:finish"); + } catch (err) { + this._log.error( + "startOver failed to re-initialize the identity manager", + err + ); + // Still send the observer notification so the current state is + // reflected in the UI. + Svc.Obs.notify("weave:service:start-over:finish"); + } + }, + + async login() { + async function onNotify() { + this._loggedIn = false; + if (this.scheduler.offline) { + this.status.login = LOGIN_FAILED_NETWORK_ERROR; + throw new Error("Application is offline, login should not be called"); + } + + this._log.info("User logged in successfully - verifying login."); + if (!(await this.verifyLogin())) { + // verifyLogin sets the failure states here. + throw new Error(`Login failed: ${this.status.login}`); + } + + this._updateCachedURLs(); + + this._loggedIn = true; + + return true; + } + + let notifier = this._notify("login", "", onNotify.bind(this)); + return this._catch(this._lock("service.js: login", notifier))(); + }, + + logout: function logout() { + // If we failed during login, we aren't going to have this._loggedIn set, + // but we still want to ask the identity to logout, so it doesn't try and + // reuse any old credentials next time we sync. + this._log.info("Logging out"); + this.identity.logout(); + this._loggedIn = false; + + Svc.Obs.notify("weave:service:logout:finish"); + }, + + // Note: returns false if we failed for a reason other than the server not yet + // supporting the api. + async _fetchServerConfiguration() { + // This is similar to _fetchInfo, but with different error handling. + + let infoURL = this.userBaseURL + "info/configuration"; + this._log.debug("Fetching server configuration", infoURL); + let configResponse; + try { + configResponse = await this.resource(infoURL).get(); + } catch (ex) { + // This is probably a network or similar error. + this._log.warn("Failed to fetch info/configuration", ex); + this.errorHandler.checkServerError(ex); + return false; + } + + if (configResponse.status == 404) { + // This server doesn't support the URL yet - that's OK. + this._log.debug( + "info/configuration returned 404 - using default upload semantics" + ); + } else if (configResponse.status != 200) { + this._log.warn( + `info/configuration returned ${configResponse.status} - using default configuration` + ); + this.errorHandler.checkServerError(configResponse); + return false; + } else { + this.serverConfiguration = configResponse.obj; + } + this._log.trace( + "info/configuration for this server", + this.serverConfiguration + ); + return true; + }, + + // Stuff we need to do after login, before we can really do + // anything (e.g. key setup). + async _remoteSetup(infoResponse, fetchConfig = true) { + if (fetchConfig && !(await this._fetchServerConfiguration())) { + return false; + } + + this._log.debug("Fetching global metadata record"); + let meta = await this.recordManager.get(this.metaURL); + + // Checking modified time of the meta record. + if ( + infoResponse && + infoResponse.obj.meta != this.metaModified && + (!meta || !meta.isNew) + ) { + // Delete the cached meta record... + this._log.debug( + "Clearing cached meta record. metaModified is " + + JSON.stringify(this.metaModified) + + ", setting to " + + JSON.stringify(infoResponse.obj.meta) + ); + + this.recordManager.del(this.metaURL); + + // ... fetch the current record from the server, and COPY THE FLAGS. + let newMeta = await this.recordManager.get(this.metaURL); + + // If we got a 401, we do not want to create a new meta/global - we + // should be able to get the existing meta after we get a new node. + if (this.recordManager.response.status == 401) { + this._log.debug( + "Fetching meta/global record on the server returned 401." + ); + this.errorHandler.checkServerError(this.recordManager.response); + return false; + } + + if (this.recordManager.response.status == 404) { + this._log.debug("No meta/global record on the server. Creating one."); + try { + await this._uploadNewMetaGlobal(); + } catch (uploadRes) { + this._log.warn( + "Unable to upload new meta/global. Failing remote setup." + ); + this.errorHandler.checkServerError(uploadRes); + return false; + } + } else if (!newMeta) { + this._log.warn("Unable to get meta/global. Failing remote setup."); + this.errorHandler.checkServerError(this.recordManager.response); + return false; + } else { + // If newMeta, then it stands to reason that meta != null. + newMeta.isNew = meta.isNew; + newMeta.changed = meta.changed; + } + + // Switch in the new meta object and record the new time. + meta = newMeta; + this.metaModified = infoResponse.obj.meta; + } + + let remoteVersion = + meta && meta.payload.storageVersion ? meta.payload.storageVersion : ""; + + this._log.debug( + [ + "Weave Version:", + WEAVE_VERSION, + "Local Storage:", + STORAGE_VERSION, + "Remote Storage:", + remoteVersion, + ].join(" ") + ); + + // Check for cases that require a fresh start. When comparing remoteVersion, + // we need to convert it to a number as older clients used it as a string. + if ( + !meta || + !meta.payload.storageVersion || + !meta.payload.syncID || + STORAGE_VERSION > parseFloat(remoteVersion) + ) { + this._log.info( + "One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed." + ); + + // abort the server wipe if the GET status was anything other than 404 or 200 + let status = this.recordManager.response.status; + if (status != 200 && status != 404) { + this.status.sync = METARECORD_DOWNLOAD_FAIL; + this.errorHandler.checkServerError(this.recordManager.response); + this._log.warn( + "Unknown error while downloading metadata record. Aborting sync." + ); + return false; + } + + if (!meta) { + this._log.info("No metadata record, server wipe needed"); + } + if (meta && !meta.payload.syncID) { + this._log.warn("No sync id, server wipe needed"); + } + + this._log.info("Wiping server data"); + await this._freshStart(); + + if (status == 404) { + this._log.info( + "Metadata record not found, server was wiped to ensure " + + "consistency." + ); + } else { + // 200 + this._log.info("Wiped server; incompatible metadata: " + remoteVersion); + } + return true; + } else if (remoteVersion > STORAGE_VERSION) { + this.status.sync = VERSION_OUT_OF_DATE; + this._log.warn("Upgrade required to access newer storage version."); + return false; + } else if (meta.payload.syncID != this.syncID) { + this._log.info( + "Sync IDs differ. Local is " + + this.syncID + + ", remote is " + + meta.payload.syncID + ); + await this.resetClient(); + this.collectionKeys.clear(); + this.syncID = meta.payload.syncID; + this._log.debug("Clear cached values and take syncId: " + this.syncID); + + if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) { + this._log.warn("Failed to fetch symmetric keys. Failing remote setup."); + return false; + } + + // bug 545725 - re-verify creds and fail sanely + if (!(await this.verifyLogin())) { + this.status.sync = CREDENTIALS_CHANGED; + this._log.info( + "Credentials have changed, aborting sync and forcing re-login." + ); + return false; + } + + return true; + } + if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) { + this._log.warn("Failed to fetch symmetric keys. Failing remote setup."); + return false; + } + + return true; + }, + + /** + * Return whether we should attempt login at the start of a sync. + * + * Note that this function has strong ties to _checkSync: callers + * of this function should typically use _checkSync to verify that + * any necessary login took place. + */ + _shouldLogin: function _shouldLogin() { + return ( + this.enabled && + !this.scheduler.offline && + !this.isLoggedIn && + Async.isAppReady() + ); + }, + + /** + * Determine if a sync should run. + * + * @param ignore [optional] + * array of reasons to ignore when checking + * + * @return Reason for not syncing; not-truthy if sync should run + */ + _checkSync: function _checkSync(ignore) { + let reason = ""; + // Ideally we'd call _checkSetup() here but that has too many side-effects. + if (Status.service == CLIENT_NOT_CONFIGURED) { + reason = kSyncNotConfigured; + } else if (Status.service == STATUS_DISABLED || !this.enabled) { + reason = kSyncWeaveDisabled; + } else if (this.scheduler.offline) { + reason = kSyncNetworkOffline; + } else if (this.status.minimumNextSync > Date.now()) { + reason = kSyncBackoffNotMet; + } else if ( + this.status.login == MASTER_PASSWORD_LOCKED && + Utils.mpLocked() + ) { + reason = kSyncMasterPasswordLocked; + } else if (Svc.PrefBranch.getStringPref("firstSync", null) == "notReady") { + reason = kFirstSyncChoiceNotMade; + } else if (!Async.isAppReady()) { + reason = kFirefoxShuttingDown; + } + + if (ignore && ignore.includes(reason)) { + return ""; + } + + return reason; + }, + + async sync({ engines, why } = {}) { + let dateStr = Utils.formatTimestamp(new Date()); + this._log.debug("User-Agent: " + Utils.userAgent); + await this.promiseInitialized; + this._log.info( + `Starting sync at ${dateStr} in browser session ${lazy.browserSessionID}` + ); + return this._catch(async function () { + // Make sure we're logged in. + if (this._shouldLogin()) { + this._log.debug("In sync: should login."); + if (!(await this.login())) { + this._log.debug("Not syncing: login returned false."); + return; + } + } else { + this._log.trace("In sync: no need to login."); + } + await this._lockedSync(engines, why); + })(); + }, + + /** + * Sync up engines with the server. + */ + async _lockedSync(engineNamesToSync, why) { + return this._lock( + "service.js: sync", + this._notify("sync", JSON.stringify({ why }), async function onNotify() { + let histogram = + Services.telemetry.getHistogramById("WEAVE_START_COUNT"); + histogram.add(1); + + let synchronizer = new EngineSynchronizer(this); + await synchronizer.sync(engineNamesToSync, why); // Might throw! + + histogram = Services.telemetry.getHistogramById( + "WEAVE_COMPLETE_SUCCESS_COUNT" + ); + histogram.add(1); + + // We successfully synchronized. + // Check if the identity wants to pre-fetch a migration sentinel from + // the server. + // If we have no clusterURL, we are probably doing a node reassignment + // so don't attempt to get it in that case. + if (this.clusterURL) { + this.identity.prefetchMigrationSentinel(this); + } + + // Now let's update our declined engines + await this._maybeUpdateDeclined(); + }) + )(); + }, + + /** + * Update the "declined" information in meta/global if necessary. + */ + async _maybeUpdateDeclined() { + // if Sync failed due to no node we will not have a meta URL, so can't + // update anything. + if (!this.metaURL) { + return; + } + let meta = await this.recordManager.get(this.metaURL); + if (!meta) { + this._log.warn("No meta/global; can't update declined state."); + return; + } + + let declinedEngines = new DeclinedEngines(this); + let didChange = declinedEngines.updateDeclined(meta, this.engineManager); + if (!didChange) { + this._log.info( + "No change to declined engines. Not reuploading meta/global." + ); + return; + } + + await this.uploadMetaGlobal(meta); + }, + + /** + * Upload a fresh meta/global record + * @throws the response object if the upload request was not a success + */ + async _uploadNewMetaGlobal() { + let meta = new WBORecord("meta", "global"); + meta.payload.syncID = this.syncID; + meta.payload.storageVersion = STORAGE_VERSION; + meta.payload.declined = this.engineManager.getDeclined(); + meta.modified = 0; + meta.isNew = true; + + await this.uploadMetaGlobal(meta); + }, + + /** + * Upload meta/global, throwing the response on failure + * @param {WBORecord} meta meta/global record + * @throws the response object if the request was not a success + */ + async uploadMetaGlobal(meta) { + this._log.debug("Uploading meta/global", meta); + let res = this.resource(this.metaURL); + res.setHeader("X-If-Unmodified-Since", meta.modified); + let response = await res.put(meta); + if (!response.success) { + throw response; + } + // From https://docs.services.mozilla.com/storage/apis-1.5.html: + // "Successful responses will return the new last-modified time for the collection." + meta.modified = response.obj; + this.recordManager.set(this.metaURL, meta); + }, + + /** + * Upload crypto/keys + * @param {WBORecord} cryptoKeys crypto/keys record + * @param {Number} lastModified known last modified timestamp (in decimal seconds), + * will be used to set the X-If-Unmodified-Since header + */ + async _uploadCryptoKeys(cryptoKeys, lastModified) { + this._log.debug(`Uploading crypto/keys (lastModified: ${lastModified})`); + let res = this.resource(this.cryptoKeysURL); + res.setHeader("X-If-Unmodified-Since", lastModified); + return res.put(cryptoKeys); + }, + + async _freshStart() { + this._log.info("Fresh start. Resetting client."); + await this.resetClient(); + this.collectionKeys.clear(); + + // Wipe the server. + await this.wipeServer(); + + // Upload a new meta/global record. + // _uploadNewMetaGlobal throws on failure -- including race conditions. + // If we got into a race condition, we'll abort the sync this way, too. + // That's fine. We'll just wait till the next sync. The client that we're + // racing is probably busy uploading stuff right now anyway. + await this._uploadNewMetaGlobal(); + + // Wipe everything we know about except meta because we just uploaded it + // TODO: there's a bug here. We should be calling resetClient, no? + + // Generate, upload, and download new keys. Do this last so we don't wipe + // them... + await this.generateNewSymmetricKeys(); + }, + + /** + * Wipe user data from the server. + * + * @param collections [optional] + * Array of collections to wipe. If not given, all collections are + * wiped by issuing a DELETE request for `storageURL`. + * + * @return the server's timestamp of the (last) DELETE. + */ + async wipeServer(collections) { + let response; + let histogram = Services.telemetry.getHistogramById( + "WEAVE_WIPE_SERVER_SUCCEEDED" + ); + if (!collections) { + // Strip the trailing slash. + let res = this.resource(this.storageURL.slice(0, -1)); + res.setHeader("X-Confirm-Delete", "1"); + try { + response = await res.delete(); + } catch (ex) { + this._log.debug("Failed to wipe server", ex); + histogram.add(false); + throw ex; + } + if (response.status != 200 && response.status != 404) { + this._log.debug( + "Aborting wipeServer. Server responded with " + + response.status + + " response for " + + this.storageURL + ); + histogram.add(false); + throw response; + } + histogram.add(true); + return response.headers["x-weave-timestamp"]; + } + + let timestamp; + for (let name of collections) { + let url = this.storageURL + name; + try { + response = await this.resource(url).delete(); + } catch (ex) { + this._log.debug("Failed to wipe '" + name + "' collection", ex); + histogram.add(false); + throw ex; + } + + if (response.status != 200 && response.status != 404) { + this._log.debug( + "Aborting wipeServer. Server responded with " + + response.status + + " response for " + + url + ); + histogram.add(false); + throw response; + } + + if ("x-weave-timestamp" in response.headers) { + timestamp = response.headers["x-weave-timestamp"]; + } + } + histogram.add(true); + return timestamp; + }, + + /** + * Wipe all local user data. + * + * @param engines [optional] + * Array of engine names to wipe. If not given, all engines are used. + */ + async wipeClient(engines) { + // If we don't have any engines, reset the service and wipe all engines + if (!engines) { + // Clear out any service data + await this.resetService(); + + engines = [this.clientsEngine, ...this.engineManager.getAll()]; + } else { + // Convert the array of names into engines + engines = this.engineManager.get(engines); + } + + // Fully wipe each engine if it's able to decrypt data + for (let engine of engines) { + if (await engine.canDecrypt()) { + await engine.wipeClient(); + } + } + }, + + /** + * Wipe all remote user data by wiping the server then telling each remote + * client to wipe itself. + * + * @param engines + * Array of engine names to wipe. + */ + async wipeRemote(engines) { + try { + // Make sure stuff gets uploaded. + await this.resetClient(engines); + + // Clear out any server data. + await this.wipeServer(engines); + + // Only wipe the engines provided. + let extra = { reason: "wipe-remote" }; + for (const e of engines) { + await this.clientsEngine.sendCommand("wipeEngine", [e], null, extra); + } + + // Make sure the changed clients get updated. + await this.clientsEngine.sync(); + } catch (ex) { + this.errorHandler.checkServerError(ex); + throw ex; + } + }, + + /** + * Reset local service information like logs, sync times, caches. + */ + async resetService() { + return this._catch(async function reset() { + this._log.info("Service reset."); + + // Pretend we've never synced to the server and drop cached data + this.syncID = ""; + this.recordManager.clearCache(); + })(); + }, + + /** + * Reset the client by getting rid of any local server data and client data. + * + * @param engines [optional] + * Array of engine names to reset. If not given, all engines are used. + */ + async resetClient(engines) { + return this._catch(async function doResetClient() { + // If we don't have any engines, reset everything including the service + if (!engines) { + // Clear out any service data + await this.resetService(); + + engines = [this.clientsEngine, ...this.engineManager.getAll()]; + } else { + // Convert the array of names into engines + engines = this.engineManager.get(engines); + } + + // Have each engine drop any temporary meta data + for (let engine of engines) { + await engine.resetClient(); + } + })(); + }, + + recordTelemetryEvent(object, method, value, extra = undefined) { + Svc.Obs.notify("weave:telemetry:event", { object, method, value, extra }); + }, +}; + +export var Service = new Sync11Service(); +Service.promiseInitialized = new Promise(resolve => { + Service.onStartup().then(resolve); +}); diff --git a/services/sync/modules/stages/declined.sys.mjs b/services/sync/modules/stages/declined.sys.mjs new file mode 100644 index 0000000000..2c74aab117 --- /dev/null +++ b/services/sync/modules/stages/declined.sys.mjs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains code for maintaining the set of declined engines, + * in conjunction with EngineManager. + */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { Observers } from "resource://services-common/observers.sys.mjs"; + +export var DeclinedEngines = function (service) { + this._log = Log.repository.getLogger("Sync.Declined"); + this._log.manageLevelFromPref("services.sync.log.logger.declined"); + + this.service = service; +}; + +DeclinedEngines.prototype = { + updateDeclined(meta, engineManager = this.service.engineManager) { + let enabled = new Set(engineManager.getEnabled().map(e => e.name)); + let known = new Set(engineManager.getAll().map(e => e.name)); + let remoteDeclined = new Set(meta.payload.declined || []); + let localDeclined = new Set(engineManager.getDeclined()); + + this._log.debug( + "Handling remote declined: " + JSON.stringify([...remoteDeclined]) + ); + this._log.debug( + "Handling local declined: " + JSON.stringify([...localDeclined]) + ); + + // Any engines that are locally enabled should be removed from the remote + // declined list. + // + // Any engines that are locally declined should be added to the remote + // declined list. + let newDeclined = CommonUtils.union( + localDeclined, + CommonUtils.difference(remoteDeclined, enabled) + ); + + // If our declined set has changed, put it into the meta object and mark + // it as changed. + let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined); + this._log.debug("Declined changed? " + declinedChanged); + if (declinedChanged) { + meta.changed = true; + meta.payload.declined = [...newDeclined]; + } + + // Update the engine manager regardless. + engineManager.setDeclined(newDeclined); + + // Any engines that are locally known, locally disabled, and not remotely + // or locally declined, are candidates for enablement. + let undecided = CommonUtils.difference( + CommonUtils.difference(known, enabled), + newDeclined + ); + if (undecided.size) { + let subject = { + declined: newDeclined, + enabled, + known, + undecided, + }; + CommonUtils.nextTick(() => { + Observers.notify("weave:engines:notdeclined", subject); + }); + } + + return declinedChanged; + }, +}; diff --git a/services/sync/modules/stages/enginesync.sys.mjs b/services/sync/modules/stages/enginesync.sys.mjs new file mode 100644 index 0000000000..190fd1f5fa --- /dev/null +++ b/services/sync/modules/stages/enginesync.sys.mjs @@ -0,0 +1,412 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file contains code for synchronizing engines. + */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { + ABORT_SYNC_COMMAND, + LOGIN_FAILED_NETWORK_ERROR, + NO_SYNC_NODE_FOUND, + STATUS_OK, + SYNC_FAILED_PARTIAL, + SYNC_SUCCEEDED, + WEAVE_VERSION, + kSyncNetworkOffline, +} from "resource://services-sync/constants.sys.mjs"; + +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + Doctor: "resource://services-sync/doctor.sys.mjs", +}); + +/** + * Perform synchronization of engines. + * + * This was originally split out of service.js. The API needs lots of love. + */ +export function EngineSynchronizer(service) { + this._log = Log.repository.getLogger("Sync.Synchronizer"); + this._log.manageLevelFromPref("services.sync.log.logger.synchronizer"); + + this.service = service; +} + +EngineSynchronizer.prototype = { + async sync(engineNamesToSync, why) { + let fastSync = why && why == "sleep"; + let startTime = Date.now(); + + this.service.status.resetSync(); + + // Make sure we should sync or record why we shouldn't. + let reason = this.service._checkSync(); + if (reason) { + if (reason == kSyncNetworkOffline) { + this.service.status.sync = LOGIN_FAILED_NETWORK_ERROR; + } + + // this is a purposeful abort rather than a failure, so don't set + // any status bits + reason = "Can't sync: " + reason; + throw new Error(reason); + } + + // If we don't have a node, get one. If that fails, retry in 10 minutes. + if ( + !this.service.clusterURL && + !(await this.service.identity.setCluster()) + ) { + this.service.status.sync = NO_SYNC_NODE_FOUND; + this._log.info("No cluster URL found. Cannot sync."); + return; + } + + // Ping the server with a special info request once a day. + let infoURL = this.service.infoURL; + let now = Math.floor(Date.now() / 1000); + let lastPing = Svc.PrefBranch.getIntPref("lastPing", 0); + if (now - lastPing > 86400) { + // 60 * 60 * 24 + infoURL += "?v=" + WEAVE_VERSION; + Svc.PrefBranch.setIntPref("lastPing", now); + } + + let engineManager = this.service.engineManager; + + // Figure out what the last modified time is for each collection + let info = await this.service._fetchInfo(infoURL); + + // Convert the response to an object and read out the modified times + for (let engine of [this.service.clientsEngine].concat( + engineManager.getAll() + )) { + engine.lastModified = info.obj[engine.name] || 0; + } + + if (!(await this.service._remoteSetup(info, !fastSync))) { + throw new Error("Aborting sync, remote setup failed"); + } + + if (!fastSync) { + // Make sure we have an up-to-date list of clients before sending commands + this._log.debug("Refreshing client list."); + if (!(await this._syncEngine(this.service.clientsEngine))) { + // Clients is an engine like any other; it can fail with a 401, + // and we can elect to abort the sync. + this._log.warn("Client engine sync failed. Aborting."); + return; + } + } + + // We only honor the "hint" of what engines to Sync if this isn't + // a first sync. + let allowEnginesHint = false; + // Wipe data in the desired direction if necessary + switch (Svc.PrefBranch.getStringPref("firstSync", null)) { + case "resetClient": + await this.service.resetClient(engineManager.enabledEngineNames); + break; + case "wipeClient": + await this.service.wipeClient(engineManager.enabledEngineNames); + break; + case "wipeRemote": + await this.service.wipeRemote(engineManager.enabledEngineNames); + break; + default: + allowEnginesHint = true; + break; + } + + if (!fastSync && this.service.clientsEngine.localCommands) { + try { + if (!(await this.service.clientsEngine.processIncomingCommands())) { + this.service.status.sync = ABORT_SYNC_COMMAND; + throw new Error("Processed command aborted sync."); + } + + // Repeat remoteSetup in-case the commands forced us to reset + if (!(await this.service._remoteSetup(info))) { + throw new Error("Remote setup failed after processing commands."); + } + } finally { + // Always immediately attempt to push back the local client (now + // without commands). + // Note that we don't abort here; if there's a 401 because we've + // been reassigned, we'll handle it around another engine. + await this._syncEngine(this.service.clientsEngine); + } + } + + // Update engines because it might change what we sync. + try { + await this._updateEnabledEngines(); + } catch (ex) { + this._log.debug("Updating enabled engines failed", ex); + this.service.errorHandler.checkServerError(ex); + throw ex; + } + + await this.service.engineManager.switchAlternatives(); + + // If the engines to sync has been specified, we sync in the order specified. + let enginesToSync; + if (allowEnginesHint && engineNamesToSync) { + this._log.info("Syncing specified engines", engineNamesToSync); + enginesToSync = engineManager + .get(engineNamesToSync) + .filter(e => e.enabled); + } else { + this._log.info("Syncing all enabled engines."); + enginesToSync = engineManager.getEnabled(); + } + try { + // We don't bother validating engines that failed to sync. + let enginesToValidate = []; + for (let engine of enginesToSync) { + if (engine.shouldSkipSync(why)) { + this._log.info(`Engine ${engine.name} asked to be skipped`); + continue; + } + // If there's any problems with syncing the engine, report the failure + if ( + !(await this._syncEngine(engine)) || + this.service.status.enforceBackoff + ) { + this._log.info("Aborting sync for failure in " + engine.name); + break; + } + enginesToValidate.push(engine); + } + + // If _syncEngine fails for a 401, we might not have a cluster URL here. + // If that's the case, break out of this immediately, rather than + // throwing an exception when trying to fetch metaURL. + if (!this.service.clusterURL) { + this._log.debug( + "Aborting sync, no cluster URL: not uploading new meta/global." + ); + return; + } + + // Upload meta/global if any engines changed anything. + let meta = await this.service.recordManager.get(this.service.metaURL); + if (meta.isNew || meta.changed) { + this._log.info("meta/global changed locally: reuploading."); + try { + await this.service.uploadMetaGlobal(meta); + delete meta.isNew; + delete meta.changed; + } catch (error) { + this._log.error( + "Unable to upload meta/global. Leaving marked as new." + ); + } + } + + if (!fastSync) { + await lazy.Doctor.consult(enginesToValidate); + } + + // If there were no sync engine failures + if (this.service.status.service != SYNC_FAILED_PARTIAL) { + this.service.status.sync = SYNC_SUCCEEDED; + } + + // Even if there were engine failures, bump lastSync even on partial since + // it's reflected in the UI (bug 1439777). + if ( + this.service.status.service == SYNC_FAILED_PARTIAL || + this.service.status.service == STATUS_OK + ) { + Svc.PrefBranch.setStringPref("lastSync", new Date().toString()); + } + } finally { + Svc.PrefBranch.clearUserPref("firstSync"); + + let syncTime = ((Date.now() - startTime) / 1000).toFixed(2); + let dateStr = Utils.formatTimestamp(new Date()); + this._log.info( + "Sync completed at " + dateStr + " after " + syncTime + " secs." + ); + } + }, + + // Returns true if sync should proceed. + // false / no return value means sync should be aborted. + async _syncEngine(engine) { + try { + await engine.sync(); + } catch (e) { + if (e.status == 401) { + // Maybe a 401, cluster update perhaps needed? + // We rely on ErrorHandler observing the sync failure notification to + // schedule another sync and clear node assignment values. + // Here we simply want to muffle the exception and return an + // appropriate value. + return false; + } + // Note that policies.js has already logged info about the exception... + if (Async.isShutdownException(e)) { + // Failure due to a shutdown exception should prevent other engines + // trying to start and immediately failing. + this._log.info( + `${engine.name} was interrupted by shutdown; no other engines will sync` + ); + return false; + } + } + + return true; + }, + + async _updateEnabledFromMeta( + meta, + numClients, + engineManager = this.service.engineManager + ) { + this._log.info("Updating enabled engines: " + numClients + " clients."); + + if (meta.isNew || !meta.payload.engines) { + this._log.debug( + "meta/global isn't new, or is missing engines. Not updating enabled state." + ); + return; + } + + // If we're the only client, and no engines are marked as enabled, + // thumb our noses at the server data: it can't be right. + // Belt-and-suspenders approach to Bug 615926. + let hasEnabledEngines = false; + for (let e in meta.payload.engines) { + if (e != "clients") { + hasEnabledEngines = true; + break; + } + } + + if (numClients <= 1 && !hasEnabledEngines) { + this._log.info( + "One client and no enabled engines: not touching local engine status." + ); + return; + } + + this.service._ignorePrefObserver = true; + + let enabled = engineManager.enabledEngineNames; + + let toDecline = new Set(); + let toUndecline = new Set(); + + for (let engineName in meta.payload.engines) { + if (engineName == "clients") { + // Clients is special. + continue; + } + let index = enabled.indexOf(engineName); + if (index != -1) { + // The engine is enabled locally. Nothing to do. + enabled.splice(index, 1); + continue; + } + let engine = engineManager.get(engineName); + if (!engine) { + // The engine doesn't exist locally. Nothing to do. + continue; + } + + let attemptedEnable = false; + // If the engine was enabled remotely, enable it locally. + if ( + !Svc.PrefBranch.getBoolPref( + "engineStatusChanged." + engine.prefName, + false + ) + ) { + this._log.trace( + "Engine " + engineName + " was enabled. Marking as non-declined." + ); + toUndecline.add(engineName); + this._log.trace(engineName + " engine was enabled remotely."); + engine.enabled = true; + // Note that setting engine.enabled to true might not have worked for + // the password engine if a master-password is enabled. However, it's + // still OK that we added it to undeclined - the user *tried* to enable + // it remotely - so it still winds up as not being flagged as declined + // even though it's disabled remotely. + attemptedEnable = true; + } + + // If either the engine was disabled locally or enabling the engine + // failed (see above re master-password) then wipe server data and + // disable it everywhere. + if (!engine.enabled) { + this._log.trace("Wiping data for " + engineName + " engine."); + await engine.wipeServer(); + delete meta.payload.engines[engineName]; + meta.changed = true; // the new enabled state must propagate + // We also here mark the engine as declined, because the pref + // was explicitly changed to false - unless we tried, and failed, + // to enable it - in which case we leave the declined state alone. + if (!attemptedEnable) { + // This will be reflected in meta/global in the next stage. + this._log.trace( + "Engine " + + engineName + + " was disabled locally. Marking as declined." + ); + toDecline.add(engineName); + } + } + } + + // Any remaining engines were either enabled locally or disabled remotely. + for (let engineName of enabled) { + let engine = engineManager.get(engineName); + if ( + Svc.PrefBranch.getBoolPref( + "engineStatusChanged." + engine.prefName, + false + ) + ) { + this._log.trace("The " + engineName + " engine was enabled locally."); + toUndecline.add(engineName); + } else { + this._log.trace("The " + engineName + " engine was disabled remotely."); + + // Don't automatically mark it as declined! + try { + engine.enabled = false; + } catch (e) { + this._log.trace("Failed to disable engine " + engineName); + } + } + } + + engineManager.decline(toDecline); + engineManager.undecline(toUndecline); + + for (const pref of Svc.PrefBranch.getChildList("engineStatusChanged.")) { + Svc.PrefBranch.clearUserPref(pref); + } + this.service._ignorePrefObserver = false; + }, + + async _updateEnabledEngines() { + let meta = await this.service.recordManager.get(this.service.metaURL); + let numClients = this.service.scheduler.numClients; + let engineManager = this.service.engineManager; + + await this._updateEnabledFromMeta(meta, numClients, engineManager); + }, +}; +Object.freeze(EngineSynchronizer.prototype); diff --git a/services/sync/modules/status.sys.mjs b/services/sync/modules/status.sys.mjs new file mode 100644 index 0000000000..429dbda7b6 --- /dev/null +++ b/services/sync/modules/status.sys.mjs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { + CLIENT_NOT_CONFIGURED, + ENGINE_SUCCEEDED, + LOGIN_FAILED, + LOGIN_FAILED_NO_PASSPHRASE, + LOGIN_FAILED_NO_USERNAME, + LOGIN_SUCCEEDED, + STATUS_OK, + SYNC_FAILED, + SYNC_FAILED_PARTIAL, + SYNC_SUCCEEDED, +} from "resource://services-sync/constants.sys.mjs"; + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { SyncAuthManager } from "resource://services-sync/sync_auth.sys.mjs"; + +export var Status = { + _log: Log.repository.getLogger("Sync.Status"), + __authManager: null, + ready: false, + + get _authManager() { + if (this.__authManager) { + return this.__authManager; + } + this.__authManager = new SyncAuthManager(); + return this.__authManager; + }, + + get service() { + return this._service; + }, + + set service(code) { + this._log.debug( + "Status.service: " + (this._service || undefined) + " => " + code + ); + this._service = code; + }, + + get login() { + return this._login; + }, + + set login(code) { + this._log.debug("Status.login: " + this._login + " => " + code); + this._login = code; + + if ( + code == LOGIN_FAILED_NO_USERNAME || + code == LOGIN_FAILED_NO_PASSPHRASE + ) { + this.service = CLIENT_NOT_CONFIGURED; + } else if (code != LOGIN_SUCCEEDED) { + this.service = LOGIN_FAILED; + } else { + this.service = STATUS_OK; + } + }, + + get sync() { + return this._sync; + }, + + set sync(code) { + this._log.debug("Status.sync: " + this._sync + " => " + code); + this._sync = code; + this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED; + }, + + get engines() { + return this._engines; + }, + + set engines([name, code]) { + this._log.debug("Status for engine " + name + ": " + code); + this._engines[name] = code; + + if (code != ENGINE_SUCCEEDED) { + this.service = SYNC_FAILED_PARTIAL; + } + }, + + // Implement toString because adding a logger introduces a cyclic object + // value, so we can't trivially debug-print Status as JSON. + toString: function toString() { + return ( + "<Status" + + ": login: " + + Status.login + + ", service: " + + Status.service + + ", sync: " + + Status.sync + + ">" + ); + }, + + checkSetup: function checkSetup() { + if (!this._authManager.username) { + Status.login = LOGIN_FAILED_NO_USERNAME; + Status.service = CLIENT_NOT_CONFIGURED; + } else if (Status.login == STATUS_OK) { + Status.service = STATUS_OK; + } + return Status.service; + }, + + resetBackoff: function resetBackoff() { + this.enforceBackoff = false; + this.backoffInterval = 0; + this.minimumNextSync = 0; + }, + + resetSync: function resetSync() { + // Logger setup. + this._log.manageLevelFromPref("services.sync.log.logger.status"); + + this._log.info("Resetting Status."); + this.service = STATUS_OK; + this._login = LOGIN_SUCCEEDED; + this._sync = SYNC_SUCCEEDED; + this._engines = {}; + this.partial = false; + }, +}; + +// Initialize various status values. +Status.resetBackoff(); +Status.resetSync(); diff --git a/services/sync/modules/sync_auth.sys.mjs b/services/sync/modules/sync_auth.sys.mjs new file mode 100644 index 0000000000..6b8da4061c --- /dev/null +++ b/services/sync/modules/sync_auth.sys.mjs @@ -0,0 +1,655 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +import { Async } from "resource://services-common/async.sys.mjs"; +import { TokenServerClient } from "resource://services-common/tokenserverclient.sys.mjs"; +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; +import { Svc, Utils } from "resource://services-sync/util.sys.mjs"; + +import { + LOGIN_FAILED_LOGIN_REJECTED, + LOGIN_FAILED_NETWORK_ERROR, + LOGIN_FAILED_NO_USERNAME, + LOGIN_SUCCEEDED, + MASTER_PASSWORD_LOCKED, + STATUS_OK, +} from "resource://services-sync/constants.sys.mjs"; + +const lazy = {}; + +// Lazy imports to prevent unnecessary load on startup. +ChromeUtils.defineESModuleGetters(lazy, { + BulkKeyBundle: "resource://services-sync/keys.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +ChromeUtils.defineLazyGetter(lazy, "log", function () { + let log = Log.repository.getLogger("Sync.SyncAuthManager"); + log.manageLevelFromPref("services.sync.log.logger.identity"); + return log; +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "IGNORE_CACHED_AUTH_CREDENTIALS", + "services.sync.debug.ignoreCachedAuthCredentials" +); + +// FxAccountsCommon.js doesn't use a "namespace", so create one here. +import * as fxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +const SCOPE_OLD_SYNC = fxAccountsCommon.SCOPE_OLD_SYNC; + +const OBSERVER_TOPICS = [ + fxAccountsCommon.ONLOGIN_NOTIFICATION, + fxAccountsCommon.ONVERIFIED_NOTIFICATION, + fxAccountsCommon.ONLOGOUT_NOTIFICATION, + fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, + "weave:connected", +]; + +/* + General authentication error for abstracting authentication + errors from multiple sources (e.g., from FxAccounts, TokenServer). + details is additional details about the error - it might be a string, or + some other error object (which should do the right thing when toString() is + called on it) +*/ +export function AuthenticationError(details, source) { + this.details = details; + this.source = source; +} + +AuthenticationError.prototype = { + toString() { + return "AuthenticationError(" + this.details + ")"; + }, +}; + +// The `SyncAuthManager` coordinates access authorization to the Sync server. +// Its job is essentially to get us from having a signed-in Firefox Accounts user, +// to knowing the user's sync storage node and having the necessary short-lived +// credentials in order to access it. +// + +export function SyncAuthManager() { + // NOTE: _fxaService and _tokenServerClient are replaced with mocks by + // the test suite. + this._fxaService = lazy.fxAccounts; + this._tokenServerClient = new TokenServerClient(); + this._tokenServerClient.observerPrefix = "weave:service"; + this._log = lazy.log; + XPCOMUtils.defineLazyPreferenceGetter( + this, + "_username", + "services.sync.username" + ); + + this.asyncObserver = Async.asyncObserver(this, lazy.log); + for (let topic of OBSERVER_TOPICS) { + Services.obs.addObserver(this.asyncObserver, topic); + } +} + +SyncAuthManager.prototype = { + _fxaService: null, + _tokenServerClient: null, + // https://docs.services.mozilla.com/token/apis.html + _token: null, + // protection against the user changing underneath us - the uid + // of the current user. + _userUid: null, + + hashedUID() { + const id = this._fxaService.telemetry.getSanitizedUID(); + if (!id) { + throw new Error("hashedUID: Don't seem to have previously seen a token"); + } + return id; + }, + + // Return a hashed version of a deviceID, suitable for telemetry. + hashedDeviceID(deviceID) { + const id = this._fxaService.telemetry.sanitizeDeviceId(deviceID); + if (!id) { + throw new Error("hashedUID: Don't seem to have previously seen a token"); + } + return id; + }, + + // The "node type" reported to telemetry or null if not specified. + get telemetryNodeType() { + return this._token && this._token.node_type ? this._token.node_type : null; + }, + + finalize() { + // After this is called, we can expect Service.identity != this. + for (let topic of OBSERVER_TOPICS) { + Services.obs.removeObserver(this.asyncObserver, topic); + } + this.resetCredentials(); + this._userUid = null; + }, + + async getSignedInUser() { + let data = await this._fxaService.getSignedInUser(); + if (!data) { + this._userUid = null; + return null; + } + if (this._userUid == null) { + this._userUid = data.uid; + } else if (this._userUid != data.uid) { + throw new Error("The signed in user has changed"); + } + return data; + }, + + logout() { + // This will be called when sync fails (or when the account is being + // unlinked etc). It may have failed because we got a 401 from a sync + // server, so we nuke the token. Next time sync runs and wants an + // authentication header, we will notice the lack of the token and fetch a + // new one. + this._token = null; + }, + + async observe(subject, topic, data) { + this._log.debug("observed " + topic); + if (!this.username) { + this._log.info("Sync is not configured, so ignoring the notification"); + return; + } + switch (topic) { + case "weave:connected": + case fxAccountsCommon.ONLOGIN_NOTIFICATION: { + this._log.info("Sync has been connected to a logged in user"); + this.resetCredentials(); + let accountData = await this.getSignedInUser(); + + if (!accountData.verified) { + // wait for a verified notification before we kick sync off. + this._log.info("The user is not verified"); + break; + } + } + // We've been configured with an already verified user, so fall-through. + // intentional fall-through - the user is verified. + case fxAccountsCommon.ONVERIFIED_NOTIFICATION: { + this._log.info("The user became verified"); + lazy.Weave.Status.login = LOGIN_SUCCEEDED; + + // And actually sync. If we've never synced before, we force a full sync. + // If we have, then we are probably just reauthenticating so it's a normal sync. + // We can use any pref that must be set if we've synced before, and check + // the sync lock state because we might already be doing that first sync. + let isFirstSync = + !lazy.Weave.Service.locked && + !Svc.PrefBranch.getStringPref("client.syncID", null); + if (isFirstSync) { + this._log.info("Doing initial sync actions"); + Svc.PrefBranch.setStringPref("firstSync", "resetClient"); + Services.obs.notifyObservers(null, "weave:service:setup-complete"); + } + // There's no need to wait for sync to complete and it would deadlock + // our AsyncObserver. + if (!Svc.PrefBranch.getBoolPref("testing.tps", false)) { + lazy.Weave.Service.sync({ why: "login" }); + } + break; + } + + case fxAccountsCommon.ONLOGOUT_NOTIFICATION: + lazy.Weave.Service.startOver() + .then(() => { + this._log.trace("startOver completed"); + }) + .catch(err => { + this._log.warn("Failed to reset sync", err); + }); + // startOver will cause this instance to be thrown away, so there's + // nothing else to do. + break; + + case fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION: + // throw away token forcing us to fetch a new one later. + this.resetCredentials(); + break; + } + }, + + /** + * Provide override point for testing token expiration. + */ + _now() { + return this._fxaService._internal.now(); + }, + + get _localtimeOffsetMsec() { + return this._fxaService._internal.localtimeOffsetMsec; + }, + + get syncKeyBundle() { + return this._syncKeyBundle; + }, + + get username() { + return this._username; + }, + + /** + * Set the username value. + * + * Changing the username has the side-effect of wiping credentials. + */ + set username(value) { + // setting .username is an old throwback, but it should no longer happen. + throw new Error("don't set the username"); + }, + + /** + * Resets all calculated credentials we hold for the current user. This will + * *not* force the user to reauthenticate, but instead will force us to + * calculate a new key bundle, fetch a new token, etc. + */ + resetCredentials() { + this._syncKeyBundle = null; + this._token = null; + // The cluster URL comes from the token, so resetting it to empty will + // force Sync to not accidentally use a value from an earlier token. + lazy.Weave.Service.clusterURL = null; + }, + + /** + * Pre-fetches any information that might help with migration away from this + * identity. Called after every sync and is really just an optimization that + * allows us to avoid a network request for when we actually need the + * migration info. + */ + prefetchMigrationSentinel(service) { + // nothing to do here until we decide to migrate away from FxA. + }, + + /** + * Verify the current auth state, unlocking the master-password if necessary. + * + * Returns a promise that resolves with the current auth state after + * attempting to unlock. + */ + async unlockAndVerifyAuthState() { + let data = await this.getSignedInUser(); + const fxa = this._fxaService; + if (!data) { + lazy.log.debug("unlockAndVerifyAuthState has no FxA user"); + return LOGIN_FAILED_NO_USERNAME; + } + if (!this.username) { + lazy.log.debug( + "unlockAndVerifyAuthState finds that sync isn't configured" + ); + return LOGIN_FAILED_NO_USERNAME; + } + if (!data.verified) { + // Treat not verified as if the user needs to re-auth, so the browser + // UI reflects the state. + lazy.log.debug("unlockAndVerifyAuthState has an unverified user"); + return LOGIN_FAILED_LOGIN_REJECTED; + } + if (await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC)) { + lazy.log.debug( + "unlockAndVerifyAuthState already has (or can fetch) sync keys" + ); + return STATUS_OK; + } + // so no keys - ensure MP unlocked. + if (!Utils.ensureMPUnlocked()) { + // user declined to unlock, so we don't know if they are stored there. + lazy.log.debug( + "unlockAndVerifyAuthState: user declined to unlock master-password" + ); + return MASTER_PASSWORD_LOCKED; + } + // If we still can't get keys it probably means the user authenticated + // without unlocking the MP or cleared the saved logins, so we've now + // lost them - the user will need to reauth before continuing. + let result; + if (await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC)) { + result = STATUS_OK; + } else { + result = LOGIN_FAILED_LOGIN_REJECTED; + } + lazy.log.debug( + "unlockAndVerifyAuthState re-fetched credentials and is returning", + result + ); + return result; + }, + + /** + * Do we have a non-null, not yet expired token for the user currently + * signed in? + */ + _hasValidToken() { + // If pref is set to ignore cached authentication credentials for debugging, + // then return false to force the fetching of a new token. + if (lazy.IGNORE_CACHED_AUTH_CREDENTIALS) { + return false; + } + if (!this._token) { + return false; + } + if (this._token.expiration < this._now()) { + return false; + } + return true; + }, + + // Get our tokenServerURL - a private helper. Returns a string. + get _tokenServerUrl() { + // We used to support services.sync.tokenServerURI but this was a + // pain-point for people using non-default servers as Sync may auto-reset + // all services.sync prefs. So if that still exists, it wins. + let url = Svc.PrefBranch.getStringPref("tokenServerURI", null); // Svc.PrefBranch "root" is services.sync + if (!url) { + url = Services.prefs.getStringPref("identity.sync.tokenserver.uri"); + } + while (url.endsWith("/")) { + // trailing slashes cause problems... + url = url.slice(0, -1); + } + return url; + }, + + // Refresh the sync token for our user. Returns a promise that resolves + // with a token, or rejects with an error. + async _fetchTokenForUser() { + const fxa = this._fxaService; + // We need keys for things to work. If we don't have them, just + // return null for the token - sync calling unlockAndVerifyAuthState() + // before actually syncing will setup the error states if necessary. + if (!(await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC))) { + this._log.info( + "Unable to fetch keys (master-password locked?), so aborting token fetch" + ); + throw new Error("Can't fetch a token as we can't get keys"); + } + + // Do the token dance, with a retry in case of transient auth failure. + // We need to prove that we know the sync key in order to get a token + // from the tokenserver. + let getToken = async key => { + this._log.info("Getting a sync token from", this._tokenServerUrl); + let token = await this._fetchTokenUsingOAuth(key); + this._log.trace("Successfully got a token"); + return token; + }; + + try { + let token, key; + try { + this._log.info("Getting sync key"); + key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + if (!key) { + throw new Error("browser does not have the sync key, cannot sync"); + } + token = await getToken(key); + } catch (err) { + // If we get a 401 fetching the token it may be that our auth tokens needed + // to be regenerated; retry exactly once. + if (!err.response || err.response.status !== 401) { + throw err; + } + this._log.warn( + "Token server returned 401, retrying token fetch with fresh credentials" + ); + key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); + token = await getToken(key); + } + // TODO: Make it be only 80% of the duration, so refresh the token + // before it actually expires. This is to avoid sync storage errors + // otherwise, we may briefly enter a "needs reauthentication" state. + // (XXX - the above may no longer be true - someone should check ;) + token.expiration = this._now() + token.duration * 1000 * 0.8; + if (!this._syncKeyBundle) { + this._syncKeyBundle = lazy.BulkKeyBundle.fromJWK(key); + } + lazy.Weave.Status.login = LOGIN_SUCCEEDED; + this._token = token; + return token; + } catch (caughtErr) { + let err = caughtErr; // The error we will rethrow. + + // TODO: unify these errors - we need to handle errors thrown by + // both tokenserverclient and hawkclient. + // A tokenserver error thrown based on a bad response. + if (err.response && err.response.status === 401) { + err = new AuthenticationError(err, "tokenserver"); + // A hawkclient error. + } else if (err.code && err.code === 401) { + err = new AuthenticationError(err, "hawkclient"); + // An FxAccounts.jsm error. + } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) { + err = new AuthenticationError(err, "fxaccounts"); + } + + // TODO: write tests to make sure that different auth error cases are handled here + // properly: auth error getting oauth token, auth error getting sync token (invalid + // generation or client-state error) + if (err instanceof AuthenticationError) { + this._log.error("Authentication error in _fetchTokenForUser", err); + // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason. + lazy.Weave.Status.login = LOGIN_FAILED_LOGIN_REJECTED; + } else { + this._log.error("Non-authentication error in _fetchTokenForUser", err); + // for now assume it is just a transient network related problem + // (although sadly, it might also be a regular unhandled exception) + lazy.Weave.Status.login = LOGIN_FAILED_NETWORK_ERROR; + } + throw err; + } + }, + + /** + * Generates an OAuth access_token using the OLD_SYNC scope and exchanges it + * for a TokenServer token. + * + * @returns {Promise} + * @private + */ + async _fetchTokenUsingOAuth(key) { + this._log.debug("Getting a token using OAuth"); + const fxa = this._fxaService; + const ttl = fxAccountsCommon.OAUTH_TOKEN_FOR_SYNC_LIFETIME_SECONDS; + const accessToken = await fxa.getOAuthToken({ scope: SCOPE_OLD_SYNC, ttl }); + const headers = { + "X-KeyId": key.kid, + }; + + return this._tokenServerClient + .getTokenUsingOAuth(this._tokenServerUrl, accessToken, headers) + .catch(async err => { + if (err.response && err.response.status === 401) { + // remove the cached token if we cannot authorize with it. + // we have to do this here because we know which `token` to remove + // from cache. + await fxa.removeCachedOAuthToken({ token: accessToken }); + } + + // continue the error chain, so other handlers can deal with the error. + throw err; + }); + }, + + // Returns a promise that is resolved with a valid token for the current + // user, or rejects if one can't be obtained. + // NOTE: This does all the authentication for Sync - it both sets the + // key bundle (ie, decryption keys) and does the token fetch. These 2 + // concepts could be decoupled, but there doesn't seem any value in that + // currently. + async _ensureValidToken(forceNewToken = false) { + let signedInUser = await this.getSignedInUser(); + if (!signedInUser) { + throw new Error("no user is logged in"); + } + if (!signedInUser.verified) { + throw new Error("user is not verified"); + } + + await this.asyncObserver.promiseObserversComplete(); + + if (!forceNewToken && this._hasValidToken()) { + this._log.trace("_ensureValidToken already has one"); + return this._token; + } + + // We are going to grab a new token - re-use the same promise if we are + // already fetching one. + if (!this._ensureValidTokenPromise) { + this._ensureValidTokenPromise = this.__ensureValidToken().finally(() => { + this._ensureValidTokenPromise = null; + }); + } + return this._ensureValidTokenPromise; + }, + + async __ensureValidToken() { + // reset this._token as a safety net to reduce the possibility of us + // repeatedly attempting to use an invalid token if _fetchTokenForUser throws. + this._token = null; + try { + let token = await this._fetchTokenForUser(); + this._token = token; + // This is a little bit of a hack. The tokenserver tells us a HMACed version + // of the FxA uid which we can use for metrics purposes without revealing the + // user's true uid. It conceptually belongs to FxA but we get it from tokenserver + // for legacy reasons. Hand it back to the FxA client code to deal with. + this._fxaService.telemetry._setHashedUID(token.hashed_fxa_uid); + return token; + } finally { + Services.obs.notifyObservers(null, "weave:service:login:got-hashed-id"); + } + }, + + getResourceAuthenticator() { + return this._getAuthenticationHeader.bind(this); + }, + + /** + * @return a Hawk HTTP Authorization Header, lightly wrapped, for the .uri + * of a RESTRequest or AsyncResponse object. + */ + async _getAuthenticationHeader(httpObject, method) { + // Note that in failure states we return null, causing the request to be + // made without authorization headers, thereby presumably causing a 401, + // which causes Sync to log out. If we throw, this may not happen as + // expected. + try { + await this._ensureValidToken(); + } catch (ex) { + this._log.error("Failed to fetch a token for authentication", ex); + return null; + } + if (!this._token) { + return null; + } + let credentials = { id: this._token.id, key: this._token.key }; + method = method || httpObject.method; + + // Get the local clock offset from the Firefox Accounts server. This should + // be close to the offset from the storage server. + let options = { + now: this._now(), + localtimeOffsetMsec: this._localtimeOffsetMsec, + credentials, + }; + + let headerValue = await CryptoUtils.computeHAWK( + httpObject.uri, + method, + options + ); + return { headers: { authorization: headerValue.field } }; + }, + + /** + * Determine the cluster for the current user and update state. + * Returns true if a new cluster URL was found and it is different from + * the existing cluster URL, false otherwise. + */ + async setCluster() { + // Make sure we didn't get some unexpected response for the cluster. + let cluster = await this._findCluster(); + this._log.debug("Cluster value = " + cluster); + if (cluster == null) { + return false; + } + + // Convert from the funky "String object with additional properties" that + // resource.js returns to a plain-old string. + cluster = cluster.toString(); + // Don't update stuff if we already have the right cluster + if (cluster == lazy.Weave.Service.clusterURL) { + return false; + } + + this._log.debug("Setting cluster to " + cluster); + lazy.Weave.Service.clusterURL = cluster; + + return true; + }, + + async _findCluster() { + try { + // Ensure we are ready to authenticate and have a valid token. + // We need to handle node reassignment here. If we are being asked + // for a clusterURL while the service already has a clusterURL, then + // it's likely a 401 was received using the existing token - in which + // case we just discard the existing token and fetch a new one. + let forceNewToken = false; + if (lazy.Weave.Service.clusterURL) { + this._log.debug( + "_findCluster has a pre-existing clusterURL, so fetching a new token token" + ); + forceNewToken = true; + } + let token = await this._ensureValidToken(forceNewToken); + let endpoint = token.endpoint; + // For Sync 1.5 storage endpoints, we use the base endpoint verbatim. + // However, it should end in "/" because we will extend it with + // well known path components. So we add a "/" if it's missing. + if (!endpoint.endsWith("/")) { + endpoint += "/"; + } + this._log.debug("_findCluster returning " + endpoint); + return endpoint; + } catch (err) { + this._log.info("Failed to fetch the cluster URL", err); + // service.js's verifyLogin() method will attempt to fetch a cluster + // URL when it sees a 401. If it gets null, it treats it as a "real" + // auth error and sets Status.login to LOGIN_FAILED_LOGIN_REJECTED, which + // in turn causes a notification bar to appear informing the user they + // need to re-authenticate. + // On the other hand, if fetching the cluster URL fails with an exception, + // verifyLogin() assumes it is a transient error, and thus doesn't show + // the notification bar under the assumption the issue will resolve + // itself. + // Thus: + // * On a real 401, we must return null. + // * On any other problem we must let an exception bubble up. + if (err instanceof AuthenticationError) { + return null; + } + throw err; + } + }, +}; diff --git a/services/sync/modules/telemetry.sys.mjs b/services/sync/modules/telemetry.sys.mjs new file mode 100644 index 0000000000..c08f405b0e --- /dev/null +++ b/services/sync/modules/telemetry.sys.mjs @@ -0,0 +1,1279 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Support for Sync-and-FxA-related telemetry, which is submitted in a special-purpose +// telemetry ping called the "sync ping", documented here: +// +// ../../../toolkit/components/telemetry/docs/data/sync-ping.rst +// +// The sync ping contains identifiers that are linked to the user's Firefox Account +// and are separate from the main telemetry client_id, so this file is also responsible +// for ensuring that we can delete those pings upon user request, by plumbing its +// identifiers into the "deletion-request" ping. + +import { Log } from "resource://gre/modules/Log.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Async: "resource://services-common/async.sys.mjs", + AuthenticationError: "resource://services-sync/sync_auth.sys.mjs", + FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs", + ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", + Observers: "resource://services-common/observers.sys.mjs", + Resource: "resource://services-sync/resource.sys.mjs", + Status: "resource://services-sync/status.sys.mjs", + Svc: "resource://services-sync/util.sys.mjs", + TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs", + TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", + TelemetryUtils: "resource://gre/modules/TelemetryUtils.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { + return ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" + ).getFxAccountsSingleton(); +}); + +import * as constants from "resource://services-sync/constants.sys.mjs"; + +ChromeUtils.defineLazyGetter( + lazy, + "WeaveService", + () => Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject +); +const log = Log.repository.getLogger("Sync.Telemetry"); + +const TOPICS = [ + // For tracking change to account/device identifiers. + "fxaccounts:new_device_id", + "fxaccounts:onlogout", + "weave:service:ready", + "weave:service:login:got-hashed-id", + + // For whole-of-sync metrics. + "weave:service:sync:start", + "weave:service:sync:finish", + "weave:service:sync:error", + + // For individual engine metrics. + "weave:engine:sync:start", + "weave:engine:sync:finish", + "weave:engine:sync:error", + "weave:engine:sync:applied", + "weave:engine:sync:step", + "weave:engine:sync:uploaded", + "weave:engine:validate:finish", + "weave:engine:validate:error", + + // For ad-hoc telemetry events. + "weave:telemetry:event", + "weave:telemetry:histogram", + "fxa:telemetry:event", + + "weave:telemetry:migration", +]; + +const PING_FORMAT_VERSION = 1; + +const EMPTY_UID = "0".repeat(32); + +// The set of engines we record telemetry for - any other engines are ignored. +const ENGINES = new Set([ + "addons", + "bookmarks", + "clients", + "forms", + "history", + "passwords", + "prefs", + "tabs", + "extension-storage", + "addresses", + "creditcards", +]); + +function tryGetMonotonicTimestamp() { + try { + return Services.telemetry.msSinceProcessStart(); + } catch (e) { + log.warn("Unable to get a monotonic timestamp!"); + return -1; + } +} + +function timeDeltaFrom(monotonicStartTime) { + let now = tryGetMonotonicTimestamp(); + if (monotonicStartTime !== -1 && now !== -1) { + return Math.round(now - monotonicStartTime); + } + return -1; +} + +const NS_ERROR_MODULE_BASE_OFFSET = 0x45; +const NS_ERROR_MODULE_NETWORK = 6; + +// A reimplementation of NS_ERROR_GET_MODULE, which surprisingly doesn't seem +// to exist anywhere in .js code in a way that can be reused. +// This is taken from DownloadCore.sys.mjs. +function NS_ERROR_GET_MODULE(code) { + return ((code & 0x7fff0000) >> 16) - NS_ERROR_MODULE_BASE_OFFSET; +} + +// Converts extra integer fields to strings, rounds floats to three +// decimal places (nanosecond precision for timings), and removes profile +// directory paths and URLs from potential error messages. +function normalizeExtraTelemetryFields(extra) { + let result = {}; + for (let key in extra) { + let value = extra[key]; + let type = typeof value; + if (type == "string") { + result[key] = ErrorSanitizer.cleanErrorMessage(value); + } else if (type == "number") { + result[key] = Number.isInteger(value) + ? value.toString(10) + : value.toFixed(3); + } else if (type != "undefined") { + throw new TypeError( + `Invalid type ${type} for extra telemetry field ${key}` + ); + } + } + return lazy.ObjectUtils.isEmpty(result) ? undefined : result; +} + +// Keps track of the counts of individual records fate during a sync cycle +// The main reason this is a class is to keep track of reasons individual records +// failure reasons without huge memory overhead. +export class SyncedRecordsTelemetry { + // applied => number of items that should be applied. + // failed => number of items that failed in this sync. + // newFailed => number of items that failed for the first time in this sync. + // reconciled => number of items that were reconciled. + // failedReasons => {name, count} of reasons a record failed + incomingCounts = { + applied: 0, + failed: 0, + newFailed: 0, + reconciled: 0, + failedReasons: null, + }; + outgoingCounts = { failed: 0, sent: 0, failedReasons: null }; + + addIncomingFailedReason(reason) { + if (!this.incomingCounts.failedReasons) { + this.incomingCounts.failedReasons = []; + } + let transformedReason = SyncTelemetry.transformError(reason); + // Some errors like http/nss errors don't have an error object + // those will be caught by the higher level telemetry + if (!transformedReason.error) { + return; + } + + let index = this.incomingCounts.failedReasons.findIndex( + reasons => reasons.name === transformedReason.error + ); + + if (index >= 0) { + this.incomingCounts.failedReasons[index].count += 1; + } else { + this.incomingCounts.failedReasons.push({ + name: transformedReason.error, + count: 1, + }); + } + } + + addOutgoingFailedReason(reason) { + if (!this.outgoingCounts.failedReasons) { + this.outgoingCounts.failedReasons = []; + } + let transformedReason = SyncTelemetry.transformError(reason); + // Some errors like http/nss errors don't have an error object + // those will be caught by the higher level telemetry + if (!transformedReason.error) { + return; + } + let index = this.outgoingCounts.failedReasons.findIndex( + reasons => reasons.name === transformedReason.error + ); + if (index >= 0) { + this.outgoingCounts.failedReasons[index].count += 1; + } else { + this.outgoingCounts.failedReasons.push({ + name: transformedReason.error, + count: 1, + }); + } + } +} + +// The `ErrorSanitizer` has 2 main jobs: +// * Remove PII from errors, such as the profile dir or URLs the user might +// have visited. +// * Normalize errors so different locales or operating systems etc don't +// generate different messages for the same underlying error. +// * [TODO] Normalize errors so environmental factors don't influence message. +// For example, timestamps or GUIDs should be replaced with something static. +export class ErrorSanitizer { + // Things we normalize - this isn't exhaustive, but covers the common error messages we see. + // Eg: + // > Win error 112 during operation write on file [profileDir]\weave\addonsreconciler.json (Espacio en disco insuficiente. ) + // > Win error 112 during operation write on file [profileDir]\weave\addonsreconciler.json (Diskte yeterli yer yok. ) + // > <snip many other translations of the error> + // > Unix error 28 during operation write on file [profileDir]/weave/addonsreconciler.json (No space left on device) + // These tend to crowd out other errors we might care about (eg, top 5 errors for some engines are + // variations of the "no space left on device") + + // Note that only errors that have same-but-different errors on Windows and Unix are here - we + // still sanitize ones that aren't in these maps to remove the translations etc - eg, + // `ERROR_SHARING_VIOLATION` doesn't really have a unix equivalent, so doesn't appear here, but + // we still strip the translations to avoid the variants. + static E_PERMISSION_DENIED = "OS error [Permission denied]"; + static E_NO_FILE_OR_DIR = "OS error [File/Path not found]"; + + static DOMErrorSubstitutions = { + NotFoundError: this.E_NO_FILE_OR_DIR, + NotAllowedError: this.E_PERMISSION_DENIED, + }; + + static #cleanOSErrorMessage(message, error = undefined) { + if (DOMException.isInstance(error)) { + const sub = this.DOMErrorSubstitutions[error.name]; + message = message.replaceAll("\\", "/"); + if (sub) { + return `${sub} ${message}`; + } + } + + return message; + } + + // A regex we can use to replace the profile dir in error messages. We use a + // regexp so we can simply replace all case-insensitive occurences. + // This escaping function is from: + // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions + static reProfileDir = new RegExp( + PathUtils.profileDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), + "gi" + ); + + /** + * Clean an error message, removing PII and normalizing OS-specific messages. + * + * @param {string} message The error message + * @param {Error?} error The error class instance, if any. + */ + static cleanErrorMessage(message, error = undefined) { + // There's a chance the profiledir is in the error string which is PII we + // want to avoid including in the ping. + message = message.replace(this.reProfileDir, "[profileDir]"); + // MSG_INVALID_URL from /dom/bindings/Errors.msg -- no way to access this + // directly from JS. + if (message.endsWith("is not a valid URL.")) { + message = "<URL> is not a valid URL."; + } + // Try to filter things that look somewhat like a URL (in that they contain a + // colon in the middle of non-whitespace), in case anything else is including + // these in error messages. Note that JSON.stringified stuff comes through + // here, so we explicitly ignore double-quotes as well. + message = message.replace(/[^\s"]+:[^\s"]+/g, "<URL>"); + + // Anywhere that's normalized the guid in errors we can easily filter + // to make it easier to aggregate these types of errors + message = message.replace(/<guid: ([^>]+)>/g, "<GUID>"); + + return this.#cleanOSErrorMessage(message, error); + } +} + +// This function validates the payload of a telemetry "event" - this can be +// removed once there are APIs available for the telemetry modules to collect +// these events (bug 1329530) - but for now we simulate that planned API as +// best we can. +function validateTelemetryEvent(eventDetails) { + let { object, method, value, extra } = eventDetails; + // Do do basic validation of the params - everything except "extra" must + // be a string. method and object are required. + if ( + typeof method != "string" || + typeof object != "string" || + (value && typeof value != "string") || + (extra && typeof extra != "object") + ) { + log.warn("Invalid event parameters - wrong types", eventDetails); + return false; + } + // length checks. + if ( + method.length > 20 || + object.length > 20 || + (value && value.length > 80) + ) { + log.warn("Invalid event parameters - wrong lengths", eventDetails); + return false; + } + + // extra can be falsey, or an object with string names and values. + if (extra) { + if (Object.keys(extra).length > 10) { + log.warn("Invalid event parameters - too many extra keys", eventDetails); + return false; + } + for (let [ename, evalue] of Object.entries(extra)) { + if ( + typeof ename != "string" || + ename.length > 15 || + typeof evalue != "string" || + evalue.length > 85 + ) { + log.warn( + `Invalid event parameters: extra item "${ename} is invalid`, + eventDetails + ); + return false; + } + } + } + return true; +} + +class EngineRecord { + constructor(name) { + // startTime is in ms from process start, but is monotonic (unlike Date.now()) + // so we need to keep both it and when. + this.startTime = tryGetMonotonicTimestamp(); + this.name = name; + + // This allows cases like bookmarks-buffered to have a separate name from + // the bookmarks engine. + let engineImpl = lazy.Weave.Service.engineManager.get(name); + if (engineImpl && engineImpl.overrideTelemetryName) { + this.overrideTelemetryName = engineImpl.overrideTelemetryName; + } + } + + toJSON() { + let result = { name: this.overrideTelemetryName || this.name }; + let properties = [ + "took", + "status", + "failureReason", + "incoming", + "outgoing", + "validation", + "steps", + ]; + for (let property of properties) { + result[property] = this[property]; + } + return result; + } + + finished(error) { + let took = timeDeltaFrom(this.startTime); + if (took > 0) { + this.took = took; + } + if (error) { + this.failureReason = SyncTelemetry.transformError(error); + } + } + + recordApplied(counts) { + if (this.incoming) { + log.error( + `Incoming records applied multiple times for engine ${this.name}!` + ); + return; + } + if (this.name === "clients" && !counts.failed) { + // ignore successful application of client records + // since otherwise they show up every time and are meaningless. + return; + } + + let incomingData = {}; + + if (counts.failedReasons) { + // sort the failed reasons in desc by count, then take top 10 + counts.failedReasons = counts.failedReasons + .sort((a, b) => b.count - a.count) + .slice(0, 10); + } + // Counts has extra stuff used for logging, but we only care about a few + let properties = ["applied", "failed", "failedReasons"]; + // Only record non-zero properties and only record incoming at all if + // there's at least one property we care about. + for (let property of properties) { + if (counts[property]) { + incomingData[property] = counts[property]; + this.incoming = incomingData; + } + } + } + + recordStep(stepResult) { + let step = { + name: stepResult.name, + }; + if (stepResult.took > 0) { + step.took = Math.round(stepResult.took); + } + if (stepResult.counts) { + let counts = stepResult.counts.filter(({ count }) => count > 0); + if (counts.length) { + step.counts = counts; + } + } + if (this.steps) { + this.steps.push(step); + } else { + this.steps = [step]; + } + } + + recordValidation(validationResult) { + if (this.validation) { + log.error(`Multiple validations occurred for engine ${this.name}!`); + return; + } + let { problems, version, took, checked } = validationResult; + let validation = { + version: version || 0, + checked: checked || 0, + }; + if (took > 0) { + validation.took = Math.round(took); + } + let summarized = problems.filter(({ count }) => count > 0); + if (summarized.length) { + validation.problems = summarized; + } + this.validation = validation; + } + + recordValidationError(e) { + if (this.validation) { + log.error(`Multiple validations occurred for engine ${this.name}!`); + return; + } + + this.validation = { + failureReason: SyncTelemetry.transformError(e), + }; + } + + recordUploaded(counts) { + if (counts.sent || counts.failed) { + if (!this.outgoing) { + this.outgoing = []; + } + if (counts.failedReasons) { + // sort the failed reasons in desc by count, then take top 10 + counts.failedReasons = counts.failedReasons + .sort((a, b) => b.count - a.count) + .slice(0, 10); + } + this.outgoing.push({ + sent: counts.sent || undefined, + failed: counts.failed || undefined, + failedReasons: counts.failedReasons || undefined, + }); + } + } +} + +// The record of a single "sync" - typically many of these are submitted in +// a single ping (ie, as a 'syncs' array) +export class SyncRecord { + constructor(allowedEngines, why) { + this.allowedEngines = allowedEngines; + // Our failure reason. This property only exists in the generated ping if an + // error actually occurred. + this.failureReason = undefined; + this.syncNodeType = null; + this.when = Date.now(); + this.startTime = tryGetMonotonicTimestamp(); + this.took = 0; // will be set later. + this.why = why; + + // All engines that have finished (ie, does not include the "current" one) + // We omit this from the ping if it's empty. + this.engines = []; + // The engine that has started but not yet stopped. + this.currentEngine = null; + } + + toJSON() { + let result = { + when: this.when, + took: this.took, + failureReason: this.failureReason, + status: this.status, + }; + if (this.why) { + result.why = this.why; + } + let engines = []; + for (let engine of this.engines) { + engines.push(engine.toJSON()); + } + if (engines.length) { + result.engines = engines; + } + return result; + } + + finished(error) { + this.took = timeDeltaFrom(this.startTime); + if (this.currentEngine != null) { + log.error( + "Finished called for the sync before the current engine finished" + ); + this.currentEngine.finished(null); + this.onEngineStop(this.currentEngine.name); + } + if (error) { + this.failureReason = SyncTelemetry.transformError(error); + } + + this.syncNodeType = lazy.Weave.Service.identity.telemetryNodeType; + + // Check for engine statuses. -- We do this now, and not in engine.finished + // to make sure any statuses that get set "late" are recorded + for (let engine of this.engines) { + let status = lazy.Status.engines[engine.name]; + if (status && status !== constants.ENGINE_SUCCEEDED) { + engine.status = status; + } + } + + let statusObject = {}; + + let serviceStatus = lazy.Status.service; + if (serviceStatus && serviceStatus !== constants.STATUS_OK) { + statusObject.service = serviceStatus; + this.status = statusObject; + } + let syncStatus = lazy.Status.sync; + if (syncStatus && syncStatus !== constants.SYNC_SUCCEEDED) { + statusObject.sync = syncStatus; + this.status = statusObject; + } + } + + onEngineStart(engineName) { + if (this._shouldIgnoreEngine(engineName, false)) { + return; + } + + if (this.currentEngine) { + log.error( + `Being told that engine ${engineName} has started, but current engine ${this.currentEngine.name} hasn't stopped` + ); + // Just discard the current engine rather than making up data for it. + } + this.currentEngine = new EngineRecord(engineName); + } + + onEngineStop(engineName, error) { + // We only care if it's the current engine if we have a current engine. + if (this._shouldIgnoreEngine(engineName, !!this.currentEngine)) { + return; + } + if (!this.currentEngine) { + // It's possible for us to get an error before the start message of an engine + // (somehow), in which case we still want to record that error. + if (!error) { + return; + } + log.error( + `Error triggered on ${engineName} when no current engine exists: ${error}` + ); + this.currentEngine = new EngineRecord(engineName); + } + this.currentEngine.finished(error); + this.engines.push(this.currentEngine); + this.currentEngine = null; + } + + onEngineApplied(engineName, counts) { + if (this._shouldIgnoreEngine(engineName)) { + return; + } + this.currentEngine.recordApplied(counts); + } + + onEngineStep(engineName, step) { + if (this._shouldIgnoreEngine(engineName)) { + return; + } + this.currentEngine.recordStep(step); + } + + onEngineValidated(engineName, validationData) { + if (this._shouldIgnoreEngine(engineName, false)) { + return; + } + let engine = this.engines.find(e => e.name === engineName); + if ( + !engine && + this.currentEngine && + engineName === this.currentEngine.name + ) { + engine = this.currentEngine; + } + if (engine) { + engine.recordValidation(validationData); + } else { + log.warn( + `Validation event triggered for engine ${engineName}, which hasn't been synced!` + ); + } + } + + onEngineValidateError(engineName, error) { + if (this._shouldIgnoreEngine(engineName, false)) { + return; + } + let engine = this.engines.find(e => e.name === engineName); + if ( + !engine && + this.currentEngine && + engineName === this.currentEngine.name + ) { + engine = this.currentEngine; + } + if (engine) { + engine.recordValidationError(error); + } else { + log.warn( + `Validation failure event triggered for engine ${engineName}, which hasn't been synced!` + ); + } + } + + onEngineUploaded(engineName, counts) { + if (this._shouldIgnoreEngine(engineName)) { + return; + } + this.currentEngine.recordUploaded(counts); + } + + _shouldIgnoreEngine(engineName, shouldBeCurrent = true) { + if (!this.allowedEngines.has(engineName)) { + log.info( + `Notification for engine ${engineName}, but we aren't recording telemetry for it` + ); + return true; + } + if (shouldBeCurrent) { + if (!this.currentEngine || engineName != this.currentEngine.name) { + log.info(`Notification for engine ${engineName} but it isn't current`); + return true; + } + } + return false; + } +} + +// The entire "sync ping" - it includes all the syncs, events etc recorded in +// the ping. +class SyncTelemetryImpl { + constructor(allowedEngines) { + log.manageLevelFromPref("services.sync.log.logger.telemetry"); + // This is accessible so we can enable custom engines during tests. + this.allowedEngines = allowedEngines; + this.current = null; + this.setupObservers(); + + this.payloads = []; + this.discarded = 0; + this.events = []; + this.histograms = {}; + this.migrations = []; + this.maxEventsCount = lazy.Svc.PrefBranch.getIntPref( + "telemetry.maxEventsCount", + 1000 + ); + this.maxPayloadCount = lazy.Svc.PrefBranch.getIntPref( + "telemetry.maxPayloadCount" + ); + this.submissionInterval = + lazy.Svc.PrefBranch.getIntPref("telemetry.submissionInterval") * 1000; + this.lastSubmissionTime = Services.telemetry.msSinceProcessStart(); + this.lastUID = EMPTY_UID; + this.lastSyncNodeType = null; + this.currentSyncNodeType = null; + // Note that the sessionStartDate is somewhat arbitrary - the telemetry + // modules themselves just use `new Date()`. This means that our startDate + // isn't going to be the same as the sessionStartDate in the main pings, + // but that's OK for now - if it's a problem we'd need to change the + // telemetry modules to expose what it thinks the sessionStartDate is. + let sessionStartDate = new Date(); + this.sessionStartDate = lazy.TelemetryUtils.toLocalTimeISOString( + lazy.TelemetryUtils.truncateToHours(sessionStartDate) + ); + lazy.TelemetryController.registerSyncPingShutdown(() => this.shutdown()); + } + + sanitizeFxaDeviceId(deviceId) { + return lazy.fxAccounts.telemetry.sanitizeDeviceId(deviceId); + } + + prepareFxaDevices(devices) { + // For non-sync users, the data per device is limited -- just an id and a + // type (and not even the id yet). For sync users, if we can correctly map + // the fxaDevice to a sync device, then we can get os and version info, + // which would be quite unfortunate to lose. + let extraInfoMap = new Map(); + if (this.syncIsEnabled()) { + for (let client of this.getClientsEngineRecords()) { + if (client.fxaDeviceId) { + extraInfoMap.set(client.fxaDeviceId, { + os: client.os, + version: client.version, + syncID: this.sanitizeFxaDeviceId(client.id), + }); + } + } + } + // Finally, sanitize and convert to the proper format. + return devices.map(d => { + let { os, version, syncID } = extraInfoMap.get(d.id) || { + os: undefined, + version: undefined, + syncID: undefined, + }; + return { + id: this.sanitizeFxaDeviceId(d.id) || EMPTY_UID, + type: d.type, + os, + version, + syncID, + }; + }); + } + + syncIsEnabled() { + return lazy.WeaveService.enabled && lazy.WeaveService.ready; + } + + // Separate for testing. + getClientsEngineRecords() { + if (!this.syncIsEnabled()) { + throw new Error("Bug: syncIsEnabled() must be true, check it first"); + } + return lazy.Weave.Service.clientsEngine.remoteClients; + } + + updateFxaDevices(devices) { + if (!devices) { + return {}; + } + let me = devices.find(d => d.isCurrentDevice); + let id = me ? this.sanitizeFxaDeviceId(me.id) : undefined; + let cleanDevices = this.prepareFxaDevices(devices); + return { deviceID: id, devices: cleanDevices }; + } + + getFxaDevices() { + return lazy.fxAccounts.device.recentDeviceList; + } + + getPingJSON(reason) { + let { devices, deviceID } = this.updateFxaDevices(this.getFxaDevices()); + return { + os: lazy.TelemetryEnvironment.currentEnvironment.system.os, + why: reason, + devices, + discarded: this.discarded || undefined, + version: PING_FORMAT_VERSION, + syncs: this.payloads.slice(), + uid: this.lastUID, + syncNodeType: this.lastSyncNodeType || undefined, + deviceID, + sessionStartDate: this.sessionStartDate, + events: !this.events.length ? undefined : this.events, + migrations: !this.migrations.length ? undefined : this.migrations, + histograms: !Object.keys(this.histograms).length + ? undefined + : this.histograms, + }; + } + + _addMigrationRecord(type, info) { + log.debug("Saw telemetry migration info", type, info); + // Updates to this need to be documented in `sync-ping.rst` + switch (type) { + case "webext-storage": + this.migrations.push({ + type: "webext-storage", + entries: +info.entries, + entriesSuccessful: +info.entries_successful, + extensions: +info.extensions, + extensionsSuccessful: +info.extensions_successful, + openFailure: !!info.open_failure, + }); + break; + default: + throw new Error("Bug: Unknown migration record type " + type); + } + } + + finish(reason) { + // Note that we might be in the middle of a sync right now, and so we don't + // want to touch this.current. + let result = this.getPingJSON(reason); + this.payloads = []; + this.discarded = 0; + this.events = []; + this.migrations = []; + this.histograms = {}; + this.submit(result); + } + + setupObservers() { + for (let topic of TOPICS) { + lazy.Observers.add(topic, this, this); + } + } + + shutdown() { + this.finish("shutdown"); + for (let topic of TOPICS) { + lazy.Observers.remove(topic, this, this); + } + } + + submit(record) { + if (!this.isProductionSyncUser()) { + return false; + } + // We still call submit() with possibly illegal payloads so that tests can + // know that the ping was built. We don't end up submitting them, however. + let numEvents = record.events ? record.events.length : 0; + let numMigrations = record.migrations ? record.migrations.length : 0; + if (record.syncs.length || numEvents || numMigrations) { + log.trace( + `submitting ${record.syncs.length} sync record(s) and ` + + `${numEvents} event(s) to telemetry` + ); + lazy.TelemetryController.submitExternalPing("sync", record, { + usePingSender: true, + }).catch(err => { + log.error("failed to submit ping", err); + }); + return true; + } + return false; + } + + isProductionSyncUser() { + // If FxA isn't production then we treat sync as not being production. + // Further, there's the deprecated "services.sync.tokenServerURI" pref we + // need to consider - fxa doesn't consider that as if that's the only + // pref set, they *are* running a production fxa, just not production sync. + if ( + !lazy.FxAccounts.config.isProductionConfig() || + Services.prefs.prefHasUserValue("services.sync.tokenServerURI") + ) { + log.trace(`Not sending telemetry ping for self-hosted Sync user`); + return false; + } + return true; + } + + onSyncStarted(data) { + const why = data && JSON.parse(data).why; + if (this.current) { + log.warn( + "Observed weave:service:sync:start, but we're already recording a sync!" + ); + // Just discard the old record, consistent with our handling of engines, above. + this.current = null; + } + this.current = new SyncRecord(this.allowedEngines, why); + } + + // We need to ensure that the telemetry `deletion-request` ping always contains the user's + // current sync device ID, because if the user opts out of telemetry then the deletion ping + // will be immediately triggered for sending, and we won't have a chance to fill it in later. + // This keeps the `deletion-ping` up-to-date when the user's account state changes. + onAccountInitOrChange() { + // We don't submit sync pings for self-hosters, so don't need to collect their device ids either. + if (!this.isProductionSyncUser()) { + return; + } + // Awkwardly async, but no need to await. If the user's account state changes while + // this promise is in flight, it will reject and we won't record any data in the ping. + // (And a new notification will trigger us to try again with the new state). + lazy.fxAccounts.device + .getLocalId() + .then(deviceId => { + let sanitizedDeviceId = + lazy.fxAccounts.telemetry.sanitizeDeviceId(deviceId); + // In the past we did not persist the FxA metrics identifiers to disk, + // so this might be missing until we can fetch it from the server for the + // first time. There will be a fresh notification tirggered when it's available. + if (sanitizedDeviceId) { + // Sanitized device ids are 64 characters long, but telemetry limits scalar strings to 50. + // The first 32 chars are sufficient to uniquely identify the device, so just send those. + // It's hard to change the sync ping itself to only send 32 chars, to b/w compat reasons. + sanitizedDeviceId = sanitizedDeviceId.substr(0, 32); + Services.telemetry.scalarSet( + "deletion.request.sync_device_id", + sanitizedDeviceId + ); + } + }) + .catch(err => { + log.warn( + `Failed to set sync identifiers in the deletion-request ping: ${err}` + ); + }); + } + + // This keeps the `deletion-request` ping up-to-date when the user signs out, + // clearing the now-nonexistent sync device id. + onAccountLogout() { + Services.telemetry.scalarSet("deletion.request.sync_device_id", ""); + } + + _checkCurrent(topic) { + if (!this.current) { + // This is only `info` because it happens when we do a tabs "quick-write" + log.info( + `Observed notification ${topic} but no current sync is being recorded.` + ); + return false; + } + return true; + } + + _shouldSubmitForDataChange() { + let newID = lazy.fxAccounts.telemetry.getSanitizedUID() || EMPTY_UID; + let oldID = this.lastUID; + if ( + newID != EMPTY_UID && + oldID != EMPTY_UID && + // Both are "real" uids, so we care if they've changed. + newID != oldID + ) { + log.trace( + `shouldSubmitForDataChange - uid from '${oldID}' -> '${newID}'` + ); + return true; + } + // We've gone from knowing one of the ids to not knowing it (which we + // ignore) or we've gone from not knowing it to knowing it (which is fine), + // Now check the node type because a change there also means we should + // submit. + if ( + this.lastSyncNodeType && + this.currentSyncNodeType != this.lastSyncNodeType + ) { + log.trace( + `shouldSubmitForDataChange - nodeType from '${this.lastSyncNodeType}' -> '${this.currentSyncNodeType}'` + ); + return true; + } + log.trace("shouldSubmitForDataChange - no need to submit"); + return false; + } + + maybeSubmitForDataChange() { + if (this._shouldSubmitForDataChange()) { + log.info( + "Early submission of sync telemetry due to changed IDs/NodeType" + ); + this.finish("idchange"); // this actually submits. + this.lastSubmissionTime = Services.telemetry.msSinceProcessStart(); + } + + // Only update the last UIDs if we actually know them. + let current_uid = lazy.fxAccounts.telemetry.getSanitizedUID(); + if (current_uid) { + this.lastUID = current_uid; + } + if (this.currentSyncNodeType) { + this.lastSyncNodeType = this.currentSyncNodeType; + } + } + + maybeSubmitForInterval() { + // We want to submit the ping every `this.submissionInterval` but only when + // there's no current sync in progress, otherwise we may end up submitting + // the sync and the events caused by it in different pings. + if ( + this.current == null && + Services.telemetry.msSinceProcessStart() - this.lastSubmissionTime > + this.submissionInterval + ) { + this.finish("schedule"); + this.lastSubmissionTime = Services.telemetry.msSinceProcessStart(); + } + } + + onSyncFinished(error) { + if (!this.current) { + log.warn("onSyncFinished but we aren't recording"); + return; + } + this.current.finished(error); + this.currentSyncNodeType = this.current.syncNodeType; + let current = this.current; + this.current = null; + this.takeTelemetryRecord(current); + } + + takeTelemetryRecord(record) { + // We check for "data change" before appending the current sync to payloads, + // as it is the current sync which has the data with the new data, and thus + // must go in the *next* submission. + this.maybeSubmitForDataChange(); + if (this.payloads.length < this.maxPayloadCount) { + this.payloads.push(record.toJSON()); + } else { + ++this.discarded; + } + // If we are submitting due to timing, it's desirable that the most recent + // sync is included, so we check after appending the record. + this.maybeSubmitForInterval(); + } + + _addHistogram(hist) { + let histogram = Services.telemetry.getHistogramById(hist); + let s = histogram.snapshot(); + this.histograms[hist] = s; + } + + _recordEvent(eventDetails) { + this.maybeSubmitForDataChange(); + + if (this.events.length >= this.maxEventsCount) { + log.warn("discarding event - already queued our maximum", eventDetails); + return; + } + + let { object, method, value, extra } = eventDetails; + if (extra) { + extra = normalizeExtraTelemetryFields(extra); + eventDetails = { object, method, value, extra }; + } + + if (!validateTelemetryEvent(eventDetails)) { + // we've already logged what the problem is... + return; + } + log.debug("recording event", eventDetails); + + if (extra && lazy.Resource.serverTime && !extra.serverTime) { + extra.serverTime = String(lazy.Resource.serverTime); + } + let category = "sync"; + let ts = Math.floor(tryGetMonotonicTimestamp()); + + // An event record is a simple array with at least 4 items. + let event = [ts, category, method, object]; + // It may have up to 6 elements if |extra| is defined + if (value) { + event.push(value); + if (extra) { + event.push(extra); + } + } else if (extra) { + event.push(null); // a null for the empty value. + event.push(extra); + } + this.events.push(event); + this.maybeSubmitForInterval(); + } + + observe(subject, topic, data) { + log.trace(`observed ${topic} ${data}`); + + switch (topic) { + case "weave:service:ready": + case "weave:service:login:got-hashed-id": + case "fxaccounts:new_device_id": + this.onAccountInitOrChange(); + break; + + case "fxaccounts:onlogout": + this.onAccountLogout(); + break; + + /* sync itself state changes */ + case "weave:service:sync:start": + this.onSyncStarted(data); + break; + + case "weave:service:sync:finish": + if (this._checkCurrent(topic)) { + this.onSyncFinished(null); + } + break; + + case "weave:service:sync:error": + // argument needs to be truthy (this should always be the case) + this.onSyncFinished(subject || "Unknown"); + break; + + /* engine sync state changes */ + case "weave:engine:sync:start": + if (this._checkCurrent(topic)) { + this.current.onEngineStart(data); + } + break; + case "weave:engine:sync:finish": + if (this._checkCurrent(topic)) { + this.current.onEngineStop(data, null); + } + break; + + case "weave:engine:sync:error": + if (this._checkCurrent(topic)) { + // argument needs to be truthy (this should always be the case) + this.current.onEngineStop(data, subject || "Unknown"); + } + break; + + /* engine counts */ + case "weave:engine:sync:applied": + if (this._checkCurrent(topic)) { + this.current.onEngineApplied(data, subject); + } + break; + + case "weave:engine:sync:step": + if (this._checkCurrent(topic)) { + this.current.onEngineStep(data, subject); + } + break; + + case "weave:engine:sync:uploaded": + if (this._checkCurrent(topic)) { + this.current.onEngineUploaded(data, subject); + } + break; + + case "weave:engine:validate:finish": + if (this._checkCurrent(topic)) { + this.current.onEngineValidated(data, subject); + } + break; + + case "weave:engine:validate:error": + if (this._checkCurrent(topic)) { + this.current.onEngineValidateError(data, subject || "Unknown"); + } + break; + + case "weave:telemetry:event": + case "fxa:telemetry:event": + this._recordEvent(subject); + break; + + case "weave:telemetry:histogram": + this._addHistogram(data); + break; + + case "weave:telemetry:migration": + this._addMigrationRecord(data, subject); + break; + + default: + log.warn(`unexpected observer topic ${topic}`); + break; + } + } + + // Transform an exception into a standard description. Exposed here for when + // this module isn't directly responsible for knowing the transform should + // happen (for example, when including an error in the |extra| field of + // event telemetry) + transformError(error) { + // Certain parts of sync will use this pattern as a way to communicate to + // processIncoming to abort the processing. However, there's no guarantee + // this can only happen then. + if (typeof error == "object" && error.code && error.cause) { + error = error.cause; + } + if (lazy.Async.isShutdownException(error)) { + return { name: "shutdownerror" }; + } + + if (typeof error === "string") { + if (error.startsWith("error.")) { + // This is hacky, but I can't imagine that it's not also accurate. + return { name: "othererror", error }; + } + error = ErrorSanitizer.cleanErrorMessage(error); + return { name: "unexpectederror", error }; + } + + if (error instanceof lazy.AuthenticationError) { + return { name: "autherror", from: error.source }; + } + + if (DOMException.isInstance(error)) { + return { + name: "unexpectederror", + error: ErrorSanitizer.cleanErrorMessage(error.message, error), + }; + } + + let httpCode = + error.status || (error.response && error.response.status) || error.code; + + if (httpCode) { + return { name: "httperror", code: httpCode }; + } + + if (error.failureCode) { + return { name: "othererror", error: error.failureCode }; + } + + if (error.result) { + // many "nsresult" errors are actually network errors - if they are + // associated with the "network" module we assume that's true. + // We also assume NS_ERROR_ABORT is such an error - for almost everything + // we care about, it acually is (eg, if the connection fails early enough + // or if we have a captive portal etc) - we don't lose anything by this + // assumption, it's just that the error will no longer be in the "nserror" + // category, so our analysis can still find them. + if ( + error.result == Cr.NS_ERROR_ABORT || + NS_ERROR_GET_MODULE(error.result) == NS_ERROR_MODULE_NETWORK + ) { + return { name: "httperror", code: error.result }; + } + return { name: "nserror", code: error.result }; + } + // It's probably an Error object, but it also could be some + // other object that may or may not override toString to do + // something useful. + let msg = String(error); + if (msg.startsWith("[object")) { + // Nothing useful in the default, check for a string "message" property. + if (typeof error.message == "string") { + msg = String(error.message); + } else { + // Hopefully it won't come to this... + msg = JSON.stringify(error); + } + } + return { + name: "unexpectederror", + error: ErrorSanitizer.cleanErrorMessage(msg), + }; + } +} + +export var SyncTelemetry = new SyncTelemetryImpl(ENGINES); diff --git a/services/sync/modules/util.sys.mjs b/services/sync/modules/util.sys.mjs new file mode 100644 index 0000000000..e68ae0e19f --- /dev/null +++ b/services/sync/modules/util.sys.mjs @@ -0,0 +1,780 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Observers } from "resource://services-common/observers.sys.mjs"; + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; + +import { + DEVICE_TYPE_DESKTOP, + MAXIMUM_BACKOFF_INTERVAL, + PREFS_BRANCH, + SYNC_KEY_DECODED_LENGTH, + SYNC_KEY_ENCODED_LENGTH, + WEAVE_VERSION, +} from "resource://services-sync/constants.sys.mjs"; + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +import * as FxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs"; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "cryptoSDR", + "@mozilla.org/login-manager/crypto/SDR;1", + "nsILoginManagerCrypto" +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "localDeviceType", + "services.sync.client.type", + DEVICE_TYPE_DESKTOP +); + +/* + * Custom exception types. + */ +class LockException extends Error { + constructor(message) { + super(message); + this.name = "LockException"; + } +} + +class HMACMismatch extends Error { + constructor(message) { + super(message); + this.name = "HMACMismatch"; + } +} + +/* + * Utility functions + */ +export var Utils = { + // Aliases from CryptoUtils. + generateRandomBytesLegacy: CryptoUtils.generateRandomBytesLegacy, + computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1, + digestUTF8: CryptoUtils.digestUTF8, + digestBytes: CryptoUtils.digestBytes, + sha256: CryptoUtils.sha256, + hkdfExpand: CryptoUtils.hkdfExpand, + pbkdf2Generate: CryptoUtils.pbkdf2Generate, + getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header, + + /** + * The string to use as the base User-Agent in Sync requests. + * This string will look something like + * + * Firefox/49.0a1 (Windows NT 6.1; WOW64; rv:46.0) FxSync/1.51.0.20160516142357.desktop + */ + _userAgent: null, + get userAgent() { + if (!this._userAgent) { + let hph = Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ); + /* eslint-disable no-multi-spaces */ + this._userAgent = + Services.appinfo.name + + "/" + + Services.appinfo.version + // Product. + " (" + + hph.oscpu + + ")" + // (oscpu) + " FxSync/" + + WEAVE_VERSION + + "." + // Sync. + Services.appinfo.appBuildID + + "."; // Build. + /* eslint-enable no-multi-spaces */ + } + return this._userAgent + lazy.localDeviceType; + }, + + /** + * Wrap a [promise-returning] function to catch all exceptions and log them. + * + * @usage MyObj._catch = Utils.catch; + * MyObj.foo = function() { this._catch(func)(); } + * + * Optionally pass a function which will be called if an + * exception occurs. + */ + catch(func, exceptionCallback) { + let thisArg = this; + return async function WrappedCatch() { + try { + return await func.call(thisArg); + } catch (ex) { + thisArg._log.debug( + "Exception calling " + (func.name || "anonymous function"), + ex + ); + if (exceptionCallback) { + return exceptionCallback.call(thisArg, ex); + } + return null; + } + }; + }, + + throwLockException(label) { + throw new LockException(`Could not acquire lock. Label: "${label}".`); + }, + + /** + * Wrap a [promise-returning] function to call lock before calling the function + * then unlock when it finishes executing or if it threw an error. + * + * @usage MyObj._lock = Utils.lock; + * MyObj.foo = async function() { await this._lock(func)(); } + */ + lock(label, func) { + let thisArg = this; + return async function WrappedLock() { + if (!thisArg.lock()) { + Utils.throwLockException(label); + } + + try { + return await func.call(thisArg); + } finally { + thisArg.unlock(); + } + }; + }, + + isLockException: function isLockException(ex) { + return ex instanceof LockException; + }, + + /** + * Wrap [promise-returning] functions to notify when it starts and + * finishes executing or if it threw an error. + * + * The message is a combination of a provided prefix, the local name, and + * the event. Possible events are: "start", "finish", "error". The subject + * is the function's return value on "finish" or the caught exception on + * "error". The data argument is the predefined data value. + * + * Example: + * + * @usage function MyObj(name) { + * this.name = name; + * this._notify = Utils.notify("obj:"); + * } + * MyObj.prototype = { + * foo: function() this._notify("func", "data-arg", async function () { + * //... + * }(), + * }; + */ + notify(prefix) { + return function NotifyMaker(name, data, func) { + let thisArg = this; + let notify = function (state, subject) { + let mesg = prefix + name + ":" + state; + thisArg._log.trace("Event: " + mesg); + Observers.notify(mesg, subject, data); + }; + + return async function WrappedNotify() { + notify("start", null); + try { + let ret = await func.call(thisArg); + notify("finish", ret); + return ret; + } catch (ex) { + notify("error", ex); + throw ex; + } + }; + }; + }, + + /** + * GUIDs are 9 random bytes encoded with base64url (RFC 4648). + * That makes them 12 characters long with 72 bits of entropy. + */ + makeGUID: function makeGUID() { + return CommonUtils.encodeBase64URL(Utils.generateRandomBytesLegacy(9)); + }, + + _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i, + checkGUID: function checkGUID(guid) { + return !!guid && this._base64url_regex.test(guid); + }, + + /** + * Add a simple getter/setter to an object that defers access of a property + * to an inner property. + * + * @param obj + * Object to add properties to defer in its prototype + * @param defer + * Property of obj to defer to + * @param prop + * Property name to defer (or an array of property names) + */ + deferGetSet: function Utils_deferGetSet(obj, defer, prop) { + if (Array.isArray(prop)) { + return prop.map(prop => Utils.deferGetSet(obj, defer, prop)); + } + + let prot = obj.prototype; + + // Create a getter if it doesn't exist yet + if (!prot.__lookupGetter__(prop)) { + prot.__defineGetter__(prop, function () { + return this[defer][prop]; + }); + } + + // Create a setter if it doesn't exist yet + if (!prot.__lookupSetter__(prop)) { + prot.__defineSetter__(prop, function (val) { + this[defer][prop] = val; + }); + } + }, + + deepEquals: function eq(a, b) { + // If they're triple equals, then it must be equals! + if (a === b) { + return true; + } + + // If they weren't equal, they must be objects to be different + if (typeof a != "object" || typeof b != "object") { + return false; + } + + // But null objects won't have properties to compare + if (a === null || b === null) { + return false; + } + + // Make sure all of a's keys have a matching value in b + for (let k in a) { + if (!eq(a[k], b[k])) { + return false; + } + } + + // Do the same for b's keys but skip those that we already checked + for (let k in b) { + if (!(k in a) && !eq(a[k], b[k])) { + return false; + } + } + + return true; + }, + + // Generator and discriminator for HMAC exceptions. + // Split these out in case we want to make them richer in future, and to + // avoid inevitable confusion if the message changes. + throwHMACMismatch: function throwHMACMismatch(shouldBe, is) { + throw new HMACMismatch( + `Record SHA256 HMAC mismatch: should be ${shouldBe}, is ${is}` + ); + }, + + isHMACMismatch: function isHMACMismatch(ex) { + return ex instanceof HMACMismatch; + }, + + /** + * Turn RFC 4648 base32 into our own user-friendly version. + * ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 + * becomes + * abcdefghijk8mn9pqrstuvwxyz234567 + */ + base32ToFriendly: function base32ToFriendly(input) { + return input.toLowerCase().replace(/l/g, "8").replace(/o/g, "9"); + }, + + base32FromFriendly: function base32FromFriendly(input) { + return input.toUpperCase().replace(/8/g, "L").replace(/9/g, "O"); + }, + + /** + * Key manipulation. + */ + + // Return an octet string in friendly base32 *with no trailing =*. + encodeKeyBase32: function encodeKeyBase32(keyData) { + return Utils.base32ToFriendly(CommonUtils.encodeBase32(keyData)).slice( + 0, + SYNC_KEY_ENCODED_LENGTH + ); + }, + + decodeKeyBase32: function decodeKeyBase32(encoded) { + return CommonUtils.decodeBase32( + Utils.base32FromFriendly(Utils.normalizePassphrase(encoded)) + ).slice(0, SYNC_KEY_DECODED_LENGTH); + }, + + jsonFilePath(...args) { + let [fileName] = args.splice(-1); + + return PathUtils.join( + PathUtils.profileDir, + "weave", + ...args, + `${fileName}.json` + ); + }, + + /** + * Load a JSON file from disk in the profile directory. + * + * @param filePath + * JSON file path load from profile. Loaded file will be + * extension. + * @param that + * Object to use for logging. + * + * @return Promise<> + * Promise resolved when the write has been performed. + */ + async jsonLoad(filePath, that) { + let path; + if (Array.isArray(filePath)) { + path = Utils.jsonFilePath(...filePath); + } else { + path = Utils.jsonFilePath(filePath); + } + + if (that._log && that._log.trace) { + that._log.trace("Loading json from disk: " + path); + } + + try { + return await IOUtils.readJSON(path); + } catch (e) { + if (!DOMException.isInstance(e) || e.name !== "NotFoundError") { + if (that._log) { + that._log.debug("Failed to load json", e); + } + } + return null; + } + }, + + /** + * Save a json-able object to disk in the profile directory. + * + * @param filePath + * JSON file path save to <filePath>.json + * @param that + * Object to use for logging. + * @param obj + * Function to provide json-able object to save. If this isn't a + * function, it'll be used as the object to make a json string.* + * Function called when the write has been performed. Optional. + * + * @return Promise<> + * Promise resolved when the write has been performed. + */ + async jsonSave(filePath, that, obj) { + let path = PathUtils.join( + PathUtils.profileDir, + "weave", + ...(filePath + ".json").split("/") + ); + let dir = PathUtils.parent(path); + + await IOUtils.makeDirectory(dir, { createAncestors: true }); + + if (that._log) { + that._log.trace("Saving json to disk: " + path); + } + + let json = typeof obj == "function" ? obj.call(that) : obj; + + return IOUtils.writeJSON(path, json); + }, + + /** + * Helper utility function to fit an array of records so that when serialized, + * they will be within payloadSizeMaxBytes. Returns a new array without the + * items. + * + * Note: This shouldn't be used for extremely large record sizes as + * it uses JSON.stringify, which could lead to a heavy performance hit. + * See Bug 1815151 for more details. + * + */ + tryFitItems(records, payloadSizeMaxBytes) { + // Copy this so that callers don't have to do it in advance. + records = records.slice(); + let encoder = Utils.utf8Encoder; + const computeSerializedSize = () => + encoder.encode(JSON.stringify(records)).byteLength; + // Figure out how many records we can pack into a payload. + // We use byteLength here because the data is not encrypted in ascii yet. + let size = computeSerializedSize(); + // See bug 535326 comment 8 for an explanation of the estimation + const maxSerializedSize = (payloadSizeMaxBytes / 4) * 3 - 1500; + if (maxSerializedSize < 0) { + // This is probably due to a test, but it causes very bad behavior if a + // test causes this accidentally. We could throw, but there's an obvious/ + // natural way to handle it, so we do that instead (otherwise we'd have a + // weird lower bound of ~1125b on the max record payload size). + return []; + } + if (size > maxSerializedSize) { + // Estimate a little more than the direct fraction to maximize packing + let cutoff = Math.ceil((records.length * maxSerializedSize) / size); + records = records.slice(0, cutoff + 1); + + // Keep dropping off the last entry until the data fits. + while (computeSerializedSize() > maxSerializedSize) { + records.pop(); + } + } + return records; + }, + + /** + * Move a json file in the profile directory. Will fail if a file exists at the + * destination. + * + * @returns a promise that resolves to undefined on success, or rejects on failure + * + * @param aFrom + * Current path to the JSON file saved on disk, relative to profileDir/weave + * .json will be appended to the file name. + * @param aTo + * New path to the JSON file saved on disk, relative to profileDir/weave + * .json will be appended to the file name. + * @param that + * Object to use for logging + */ + jsonMove(aFrom, aTo, that) { + let pathFrom = PathUtils.join( + PathUtils.profileDir, + "weave", + ...(aFrom + ".json").split("/") + ); + let pathTo = PathUtils.join( + PathUtils.profileDir, + "weave", + ...(aTo + ".json").split("/") + ); + if (that._log) { + that._log.trace("Moving " + pathFrom + " to " + pathTo); + } + return IOUtils.move(pathFrom, pathTo, { noOverwrite: true }); + }, + + /** + * Removes a json file in the profile directory. + * + * @returns a promise that resolves to undefined on success, or rejects on failure + * + * @param filePath + * Current path to the JSON file saved on disk, relative to profileDir/weave + * .json will be appended to the file name. + * @param that + * Object to use for logging + */ + jsonRemove(filePath, that) { + let path = PathUtils.join( + PathUtils.profileDir, + "weave", + ...(filePath + ".json").split("/") + ); + if (that._log) { + that._log.trace("Deleting " + path); + } + return IOUtils.remove(path, { ignoreAbsent: true }); + }, + + /** + * The following are the methods supported for UI use: + * + * * isPassphrase: + * determines whether a string is either a normalized or presentable + * passphrase. + * * normalizePassphrase: + * take a presentable passphrase and reduce it to a normalized + * representation for storage. normalizePassphrase can safely be called + * on normalized input. + */ + + isPassphrase(s) { + if (s) { + return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test( + Utils.normalizePassphrase(s) + ); + } + return false; + }, + + normalizePassphrase: function normalizePassphrase(pp) { + // Short var name... have you seen the lines below?! + // Allow leading and trailing whitespace. + pp = pp.trim().toLowerCase(); + + // 20-char sync key. + if (pp.length == 23 && [5, 11, 17].every(i => pp[i] == "-")) { + return ( + pp.slice(0, 5) + pp.slice(6, 11) + pp.slice(12, 17) + pp.slice(18, 23) + ); + } + + // "Modern" 26-char key. + if (pp.length == 31 && [1, 7, 13, 19, 25].every(i => pp[i] == "-")) { + return ( + pp.slice(0, 1) + + pp.slice(2, 7) + + pp.slice(8, 13) + + pp.slice(14, 19) + + pp.slice(20, 25) + + pp.slice(26, 31) + ); + } + + // Something else -- just return. + return pp; + }, + + /** + * Create an array like the first but without elements of the second. Reuse + * arrays if possible. + */ + arraySub: function arraySub(minuend, subtrahend) { + if (!minuend.length || !subtrahend.length) { + return minuend; + } + let setSubtrahend = new Set(subtrahend); + return minuend.filter(i => !setSubtrahend.has(i)); + }, + + /** + * Build the union of two arrays. Reuse arrays if possible. + */ + arrayUnion: function arrayUnion(foo, bar) { + if (!foo.length) { + return bar; + } + if (!bar.length) { + return foo; + } + return foo.concat(Utils.arraySub(bar, foo)); + }, + + /** + * Add all the items in `items` to the provided Set in-place. + * + * @return The provided set. + */ + setAddAll(set, items) { + for (let item of items) { + set.add(item); + } + return set; + }, + + /** + * Delete every items in `items` to the provided Set in-place. + * + * @return The provided set. + */ + setDeleteAll(set, items) { + for (let item of items) { + set.delete(item); + } + return set; + }, + + /** + * Take the first `size` items from the Set `items`. + * + * @return A Set of size at most `size` + */ + subsetOfSize(items, size) { + let result = new Set(); + let count = 0; + for (let item of items) { + if (count++ == size) { + return result; + } + result.add(item); + } + return result; + }, + + bind2: function Async_bind2(object, method) { + return function innerBind() { + return method.apply(object, arguments); + }; + }, + + /** + * Is there a master password configured and currently locked? + */ + mpLocked() { + return !lazy.cryptoSDR.isLoggedIn; + }, + + // If Master Password is enabled and locked, present a dialog to unlock it. + // Return whether the system is unlocked. + ensureMPUnlocked() { + if (lazy.cryptoSDR.uiBusy) { + return false; + } + try { + lazy.cryptoSDR.encrypt("bacon"); + return true; + } catch (e) {} + return false; + }, + + /** + * Return a value for a backoff interval. Maximum is eight hours, unless + * Status.backoffInterval is higher. + * + */ + calculateBackoff: function calculateBackoff( + attempts, + baseInterval, + statusInterval + ) { + let backoffInterval = + attempts * (Math.floor(Math.random() * baseInterval) + baseInterval); + return Math.max( + Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), + statusInterval + ); + }, + + /** + * Return a set of hostnames (including the protocol) which may have + * credentials for sync itself stored in the login manager. + * + * In general, these hosts will not have their passwords synced, will be + * reset when we drop sync credentials, etc. + */ + getSyncCredentialsHosts() { + let result = new Set(); + // the FxA host + result.add(FxAccountsCommon.FXA_PWDMGR_HOST); + // We used to include the FxA hosts (hence the Set() result) but we now + // don't give them special treatment (hence the Set() with exactly 1 item) + return result; + }, + + /** + * Helper to implement a more efficient version of fairly common pattern: + * + * Utils.defineLazyIDProperty(this, "syncID", "services.sync.client.syncID") + * + * is equivalent to (but more efficient than) the following: + * + * Foo.prototype = { + * ... + * get syncID() { + * let syncID = Svc.PrefBranch.getStringPref("client.syncID", ""); + * return syncID == "" ? this.syncID = Utils.makeGUID() : syncID; + * }, + * set syncID(value) { + * Svc.PrefBranch.setStringPref("client.syncID", value); + * }, + * ... + * }; + */ + defineLazyIDProperty(object, propName, prefName) { + // An object that exists to be the target of the lazy pref getter. + // We can't use `object` (at least, not using `propName`) since XPCOMUtils + // will stomp on any setter we define. + const storage = {}; + XPCOMUtils.defineLazyPreferenceGetter(storage, "value", prefName, ""); + Object.defineProperty(object, propName, { + configurable: true, + enumerable: true, + get() { + let value = storage.value; + if (!value) { + value = Utils.makeGUID(); + Services.prefs.setStringPref(prefName, value); + } + return value; + }, + set(value) { + Services.prefs.setStringPref(prefName, value); + }, + }); + }, + + getDeviceType() { + return lazy.localDeviceType; + }, + + formatTimestamp(date) { + // Format timestamp as: "%Y-%m-%d %H:%M:%S" + let year = String(date.getFullYear()); + let month = String(date.getMonth() + 1).padStart(2, "0"); + let day = String(date.getDate()).padStart(2, "0"); + let hours = String(date.getHours()).padStart(2, "0"); + let minutes = String(date.getMinutes()).padStart(2, "0"); + let seconds = String(date.getSeconds()).padStart(2, "0"); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + }, + + *walkTree(tree, parent = null) { + if (tree) { + // Skip root node + if (parent) { + yield [tree, parent]; + } + if (tree.children) { + for (let child of tree.children) { + yield* Utils.walkTree(child, tree); + } + } + } + }, +}; + +/** + * A subclass of Set that serializes as an Array when passed to JSON.stringify. + */ +export class SerializableSet extends Set { + toJSON() { + return Array.from(this); + } +} + +ChromeUtils.defineLazyGetter(Utils, "_utf8Converter", function () { + let converter = Cc[ + "@mozilla.org/intl/scriptableunicodeconverter" + ].createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + return converter; +}); + +ChromeUtils.defineLazyGetter(Utils, "utf8Encoder", () => new TextEncoder()); + +/* + * Commonly-used services + */ +export var Svc = {}; + +Svc.PrefBranch = Services.prefs.getBranch(PREFS_BRANCH); +Svc.Obs = Observers; + +Svc.Obs.add("xpcom-shutdown", function () { + for (let name in Svc) { + delete Svc[name]; + } +}); diff --git a/services/sync/moz.build b/services/sync/moz.build new file mode 100644 index 0000000000..a00472056e --- /dev/null +++ b/services/sync/moz.build @@ -0,0 +1,72 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Firefox", "Sync") + +XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.toml"] + +EXTRA_COMPONENTS += [ + "SyncComponents.manifest", +] + +EXTRA_JS_MODULES["services-sync"] += [ + "modules/addonsreconciler.sys.mjs", + "modules/addonutils.sys.mjs", + "modules/bridged_engine.sys.mjs", + "modules/collection_validator.sys.mjs", + "modules/constants.sys.mjs", + "modules/doctor.sys.mjs", + "modules/engines.sys.mjs", + "modules/keys.sys.mjs", + "modules/main.sys.mjs", + "modules/policies.sys.mjs", + "modules/record.sys.mjs", + "modules/resource.sys.mjs", + "modules/service.sys.mjs", + "modules/status.sys.mjs", + "modules/sync_auth.sys.mjs", + "modules/SyncDisconnect.sys.mjs", + "modules/SyncedTabs.sys.mjs", + "modules/telemetry.sys.mjs", + "modules/UIState.sys.mjs", + "modules/util.sys.mjs", + "Weave.sys.mjs", +] + +EXTRA_JS_MODULES["services-sync"].engines += [ + "modules/engines/addons.sys.mjs", + "modules/engines/clients.sys.mjs", + "modules/engines/extension-storage.sys.mjs", + "modules/engines/passwords.sys.mjs", + "modules/engines/prefs.sys.mjs", +] + +if not CONFIG["MOZ_THUNDERBIRD"]: + EXTRA_JS_MODULES["services-sync"].engines += [ + "modules/engines/bookmarks.sys.mjs", + "modules/engines/forms.sys.mjs", + "modules/engines/history.sys.mjs", + "modules/engines/tabs.sys.mjs", + ] + +EXTRA_JS_MODULES["services-sync"].stages += [ + "modules/stages/declined.sys.mjs", + "modules/stages/enginesync.sys.mjs", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +TESTING_JS_MODULES.services.sync += [ + "modules-testing/fakeservices.sys.mjs", + "modules-testing/fxa_utils.sys.mjs", + "modules-testing/rotaryengine.sys.mjs", + "modules-testing/utils.sys.mjs", +] + +SPHINX_TREES["/services/sync"] = "docs" diff --git a/services/sync/tests/tps/.eslintrc.js b/services/sync/tests/tps/.eslintrc.js new file mode 100644 index 0000000000..182e87933b --- /dev/null +++ b/services/sync/tests/tps/.eslintrc.js @@ -0,0 +1,28 @@ +"use strict"; + +module.exports = { + globals: { + // Injected into tests via tps.jsm + Addons: false, + Addresses: false, + Bookmarks: false, + CreditCards: false, + EnableEngines: false, + EnsureTracking: false, + ExtStorage: false, + Formdata: false, + History: false, + Login: false, + Passwords: false, + Phase: false, + Prefs: false, + STATE_DISABLED: false, + STATE_ENABLED: false, + Sync: false, + SYNC_WIPE_CLIENT: false, + SYNC_WIPE_REMOTE: false, + Tabs: false, + Windows: false, + WipeServer: false, + }, +}; diff --git a/services/sync/tests/tps/addons/api/restartless-xpi@tests.mozilla.org.json b/services/sync/tests/tps/addons/api/restartless-xpi@tests.mozilla.org.json new file mode 100644 index 0000000000..8593bad089 --- /dev/null +++ b/services/sync/tests/tps/addons/api/restartless-xpi@tests.mozilla.org.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Restartless Test XPI", + "type": "extension", + "guid": "restartless-xpi@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485, + "url": "http://127.0.0.1:4567/addons/restartless.xpi" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/tps/addons/api/test-webext@quality.mozilla.org.json b/services/sync/tests/tps/addons/api/test-webext@quality.mozilla.org.json new file mode 100644 index 0000000000..298ecc2ead --- /dev/null +++ b/services/sync/tests/tps/addons/api/test-webext@quality.mozilla.org.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Tet Webext XPI", + "type": "extension", + "guid": "test-webext@quality.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 3412, + "url": "http://127.0.0.1:4567/addons/webextension.xpi" + } + ] + }, + "last_updated": "2018-04-17T18:24:42Z" + } + ] +} diff --git a/services/sync/tests/tps/addons/restartless.xpi b/services/sync/tests/tps/addons/restartless.xpi Binary files differnew file mode 100644 index 0000000000..973bc00cb5 --- /dev/null +++ b/services/sync/tests/tps/addons/restartless.xpi diff --git a/services/sync/tests/tps/addons/webextension.xpi b/services/sync/tests/tps/addons/webextension.xpi Binary files differnew file mode 100644 index 0000000000..0ed64f79ac --- /dev/null +++ b/services/sync/tests/tps/addons/webextension.xpi diff --git a/services/sync/tests/tps/all_tests.json b/services/sync/tests/tps/all_tests.json new file mode 100644 index 0000000000..ea92e04b8f --- /dev/null +++ b/services/sync/tests/tps/all_tests.json @@ -0,0 +1,34 @@ +{ + "tests": { + "test_bookmark_conflict.js": {}, + "test_sync.js": {}, + "test_prefs.js": {}, + "test_tabs.js": {}, + "test_passwords.js": {}, + "test_history.js": {}, + "test_formdata.js": {}, + "test_bug530717.js": {}, + "test_bug531489.js": {}, + "test_bug538298.js": {}, + "test_bug556509.js": {}, + "test_bug562515.js": {}, + "test_bug535326.js": {}, + "test_bug501528.js": {}, + "test_bug575423.js": {}, + "test_bug546807.js": {}, + "test_history_collision.js": {}, + "test_privbrw_passwords.js": {}, + "test_privbrw_tabs.js": {}, + "test_bookmarks_in_same_named_folder.js": {}, + "test_client_wipe.js": {}, + "test_special_tabs.js": {}, + "test_addon_restartless_xpi.js": { "disabled": "Bug 1498974" }, + "test_addon_webext_xpi.js": { "disabled": "Bug 1498974" }, + "test_addon_reconciling.js": { "disabled": "Bug 1498974" }, + "test_addon_wipe.js": { "disabled": "Bug 1498974" }, + "test_existing_bookmarks.js": {}, + "test_addresses.js": {}, + "test_creditcards.js": {}, + "test_extstorage.js": {} + } +} diff --git a/services/sync/tests/tps/test_addon_reconciling.js b/services/sync/tests/tps/test_addon_reconciling.js new file mode 100644 index 0000000000..9c928734c8 --- /dev/null +++ b/services/sync/tests/tps/test_addon_reconciling.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test verifies that record reconciling works as expected. It makes +// similar changes to add-ons in separate profiles and does a sync to verify +// the proper action is taken. +EnableEngines(["addons"]); + +var phases = { + phase01: "profile1", + phase02: "profile2", + phase03: "profile1", + phase04: "profile2", + phase05: "profile1", + phase06: "profile2", +}; + +const id = "restartless-xpi@tests.mozilla.org"; + +// Install the add-on in 2 profiles. +Phase("phase01", [ + [Addons.verifyNot, [id]], + [Addons.install, [id]], + [Addons.verify, [id], STATE_ENABLED], + [Sync], +]); +Phase("phase02", [ + [Addons.verifyNot, [id]], + [Sync], + [Addons.verify, [id], STATE_ENABLED], +]); + +// Now we disable in one and uninstall in the other. +Phase("phase03", [ + [Sync], // Get GUID updates, potentially. + [Addons.setEnabled, [id], STATE_DISABLED], + // We've changed the state, but don't want this profile to sync until phase5, + // so if we ran a validation now we'd be expecting to find errors. + [Addons.skipValidation], +]); +Phase("phase04", [[EnsureTracking], [Addons.uninstall, [id]], [Sync]]); + +// When we sync, the uninstall should take precedence because it was newer. +Phase("phase05", [[Sync]]); +Phase("phase06", [[Sync], [Addons.verifyNot, [id]]]); diff --git a/services/sync/tests/tps/test_addon_restartless_xpi.js b/services/sync/tests/tps/test_addon_restartless_xpi.js new file mode 100644 index 0000000000..2b14245a6e --- /dev/null +++ b/services/sync/tests/tps/test_addon_restartless_xpi.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test verifies that install of restartless extensions syncs to +// other profiles. +EnableEngines(["addons"]); + +var phases = { + phase01: "profile1", + phase02: "profile2", + phase03: "profile1", + phase04: "profile2", + phase05: "profile1", + phase06: "profile2", + phase07: "profile1", + phase08: "profile2", +}; + +const id = "restartless-xpi@tests.mozilla.org"; + +// Verify install is synced +Phase("phase01", [ + [Addons.verifyNot, [id]], + [Addons.install, [id]], + [Addons.verify, [id], STATE_ENABLED], + [Sync], +]); +Phase("phase02", [ + [Addons.verifyNot, [id]], + [Sync], + [Addons.verify, [id], STATE_ENABLED], +]); + +// Now disable and see that is is synced. +Phase("phase03", [ + [EnsureTracking], + [Addons.setEnabled, [id], STATE_DISABLED], + [Addons.verify, [id], STATE_DISABLED], + [Sync], +]); +Phase("phase04", [[Sync], [Addons.verify, [id], STATE_DISABLED]]); + +// Enable and see it is synced. +Phase("phase05", [ + [EnsureTracking], + [Addons.setEnabled, [id], STATE_ENABLED], + [Addons.verify, [id], STATE_ENABLED], + [Sync], +]); +Phase("phase06", [[Sync], [Addons.verify, [id], STATE_ENABLED]]); + +// Uninstall and see it is synced. +Phase("phase07", [ + [EnsureTracking], + [Addons.verify, [id], STATE_ENABLED], + [Addons.uninstall, [id]], + [Addons.verifyNot, [id]], + [Sync], +]); +Phase("phase08", [ + [Addons.verify, [id], STATE_ENABLED], + [Sync], + [Addons.verifyNot, [id]], +]); diff --git a/services/sync/tests/tps/test_addon_webext_xpi.js b/services/sync/tests/tps/test_addon_webext_xpi.js new file mode 100644 index 0000000000..27065ee6af --- /dev/null +++ b/services/sync/tests/tps/test_addon_webext_xpi.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test verifies that install of web extensions sync to other profiles. +// It's more or less copied from test_addon_restartless_xpi with a different id. + +EnableEngines(["addons"]); + +var phases = { + phase01: "profile1", + phase02: "profile2", + phase03: "profile1", + phase04: "profile2", + phase05: "profile1", + phase06: "profile2", + phase07: "profile1", + phase08: "profile2", +}; + +const id = "test-webext@quality.mozilla.org"; + +// Verify install is synced +Phase("phase01", [ + [Addons.verifyNot, [id]], + [Addons.install, [id]], + [Addons.verify, [id], STATE_ENABLED], + [Sync], +]); +Phase("phase02", [ + [Addons.verifyNot, [id]], + [Sync], + [Addons.verify, [id], STATE_ENABLED], +]); + +// Now disable and see that is is synced. +Phase("phase03", [ + [EnsureTracking], + [Addons.setEnabled, [id], STATE_DISABLED], + [Addons.verify, [id], STATE_DISABLED], + [Sync], +]); +Phase("phase04", [[Sync], [Addons.verify, [id], STATE_DISABLED]]); + +// Enable and see it is synced. +Phase("phase05", [ + [EnsureTracking], + [Addons.setEnabled, [id], STATE_ENABLED], + [Addons.verify, [id], STATE_ENABLED], + [Sync], +]); +Phase("phase06", [[Sync], [Addons.verify, [id], STATE_ENABLED]]); + +// Uninstall and see it is synced. +Phase("phase07", [ + [EnsureTracking], + [Addons.verify, [id], STATE_ENABLED], + [Addons.uninstall, [id]], + [Addons.verifyNot, [id]], + [Sync], +]); +Phase("phase08", [ + [Addons.verify, [id], STATE_ENABLED], + [Sync], + [Addons.verifyNot, [id]], +]); diff --git a/services/sync/tests/tps/test_addon_wipe.js b/services/sync/tests/tps/test_addon_wipe.js new file mode 100644 index 0000000000..038f01d014 --- /dev/null +++ b/services/sync/tests/tps/test_addon_wipe.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test ensures that a client wipe followed by an "initial" sync will +// restore add-ons. This test should expose flaws in the reconciling logic, +// specifically around AddonsReconciler. This test is in response to bug +// 792990. + +EnableEngines(["addons"]); + +var phases = { + phase01: "profile1", + phase02: "profile1", + phase03: "profile1", +}; + +const id1 = "restartless-xpi@tests.mozilla.org"; +const id2 = "test-webext@quality.mozilla.org"; + +Phase("phase01", [[Addons.install, [id1]], [Addons.install, [id2]], [Sync]]); +Phase("phase02", [ + [Addons.verify, [id1], STATE_ENABLED], + [Addons.verify, [id2], STATE_ENABLED], + [Sync, SYNC_WIPE_CLIENT], + [Sync], +]); +Phase("phase03", [ + [Addons.verify, [id1], STATE_ENABLED], + [Addons.verify, [id2], STATE_ENABLED], + [Sync], // Sync to ensure that the addon validator can run without error +]); diff --git a/services/sync/tests/tps/test_addresses.js b/services/sync/tests/tps/test_addresses.js new file mode 100644 index 0000000000..33af349dd5 --- /dev/null +++ b/services/sync/tests/tps/test_addresses.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* global Services */ +Services.prefs.setBoolPref("services.sync.engine.addresses", true); + +EnableEngines(["addresses"]); + +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", +}; + +const address1 = [ + { + "given-name": "Timothy", + "additional-name": "John", + "family-name": "Berners-Lee", + organization: "World Wide Web Consortium", + "street-address": "32 Vassar Street\nMIT Room 32-G524", + "address-level2": "Cambridge", + "address-level1": "MA", + "postal-code": "02139", + country: "US", + tel: "+16172535702", + email: "timbl@w3.org", + changes: { + organization: "W3C", + }, + "unknown-1": "an unknown field from another client", + }, +]; + +const address1_after = [ + { + "given-name": "Timothy", + "additional-name": "John", + "family-name": "Berners-Lee", + organization: "W3C", + "street-address": "32 Vassar Street\nMIT Room 32-G524", + "address-level2": "Cambridge", + "address-level1": "MA", + "postal-code": "02139", + country: "US", + tel: "+16172535702", + email: "timbl@w3.org", + "unknown-1": "an unknown field from another client", + }, +]; + +const address2 = [ + { + "given-name": "John", + "additional-name": "R.", + "family-name": "Smith", + organization: "Mozilla", + "street-address": + "Geb\u00E4ude 3, 4. Obergeschoss\nSchlesische Stra\u00DFe 27", + "address-level2": "Berlin", + "address-level1": "BE", + "postal-code": "10997", + country: "DE", + tel: "+4930983333000", + email: "timbl@w3.org", + }, +]; + +Phase("phase1", [[Addresses.add, address1], [Sync]]); + +Phase("phase2", [ + [Sync], + [Addresses.verify, address1], + [Addresses.modify, address1], + [Addresses.add, address2], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [Addresses.verify, address1_after], + [Addresses.verify, address2], + [Sync], +]); diff --git a/services/sync/tests/tps/test_bookmark_conflict.js b/services/sync/tests/tps/test_bookmark_conflict.js new file mode 100644 index 0000000000..2832bded5d --- /dev/null +++ b/services/sync/tests/tps/test_bookmark_conflict.js @@ -0,0 +1,138 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +// the initial list of bookmarks to add to the browser +var bookmarksInitial = { + menu: [ + { folder: "foldera" }, + { folder: "folderb" }, + { folder: "folderc" }, + { folder: "folderd" }, + ], + + "menu/foldera": [{ uri: "http://www.cnn.com", title: "CNN" }], + "menu/folderb": [{ uri: "http://www.apple.com", title: "Apple", tags: [] }], + "menu/folderc": [{ uri: "http://www.yahoo.com", title: "Yahoo" }], + + "menu/folderd": [], +}; + +// a list of bookmarks to delete during a 'delete' action on P2 +var bookmarksToDelete = { + menu: [{ folder: "foldera" }, { folder: "folderb" }], + "menu/folderc": [{ uri: "http://www.yahoo.com", title: "Yahoo" }], +}; + +// the modifications to make on P1, after P2 has synced, but before P1 has gotten +// P2's changes +var bookmarkMods = { + menu: [ + { folder: "foldera" }, + { folder: "folderb" }, + { folder: "folderc" }, + { folder: "folderd" }, + ], + + // we move this child out of its folder (p1), after deleting the folder (p2) + // and expect the child to come back to p2 after sync. + "menu/foldera": [ + { + uri: "http://www.cnn.com", + title: "CNN", + changes: { location: "menu/folderd" }, + }, + ], + + // we rename this child (p1) after deleting the folder (p2), and expect the child + // to be moved into great grandparent (menu) + "menu/folderb": [ + { + uri: "http://www.apple.com", + title: "Apple", + tags: [], + changes: { title: "Mac" }, + }, + ], + + // we move this child (p1) after deleting the child (p2) and expect it to survive + "menu/folderc": [ + { + uri: "http://www.yahoo.com", + title: "Yahoo", + changes: { location: "menu/folderd" }, + }, + ], + + "menu/folderd": [], +}; + +// a list of bookmarks to delete during a 'delete' action +bookmarksToDelete = { + menu: [{ folder: "foldera" }, { folder: "folderb" }], + "menu/folderc": [{ uri: "http://www.yahoo.com", title: "Yahoo" }], +}; + +// expected bookmark state after conflict resolution +var bookmarksExpected = { + menu: [ + { folder: "folderc" }, + { folder: "folderd" }, + { uri: "http://www.apple.com", title: "Mac" }, + ], + + "menu/folderc": [], + + "menu/folderd": [ + { uri: "http://www.cnn.com", title: "CNN" }, + { uri: "http://www.yahoo.com", title: "Yahoo" }, + ], +}; + +// Add bookmarks to profile1 and sync. +Phase("phase1", [ + [Bookmarks.add, bookmarksInitial], + [Bookmarks.verify, bookmarksInitial], + [Sync], + [Bookmarks.verify, bookmarksInitial], +]); + +// Sync to profile2 and verify that the bookmarks are present. Delete +// bookmarks/folders, verify that it's not present, and sync +Phase("phase2", [ + [Sync], + [Bookmarks.verify, bookmarksInitial], + [Bookmarks.delete, bookmarksToDelete], + [Bookmarks.verifyNot, bookmarksToDelete], + [Sync], +]); + +// Using profile1, modify the bookmarks, and sync *after* the modification, +// and then sync again to propagate the reconciliation changes. +Phase("phase3", [ + [Bookmarks.verify, bookmarksInitial], + [Bookmarks.modify, bookmarkMods], + [Sync], + [Bookmarks.verify, bookmarksExpected], + [Bookmarks.verifyNot, bookmarksToDelete], +]); + +// Back in profile2, do a sync and verify that we're in the expected state +Phase("phase4", [ + [Sync], + [Bookmarks.verify, bookmarksExpected], + [Bookmarks.verifyNot, bookmarksToDelete], +]); diff --git a/services/sync/tests/tps/test_bookmarks_in_same_named_folder.js b/services/sync/tests/tps/test_bookmarks_in_same_named_folder.js new file mode 100644 index 0000000000..69dd8ba8a6 --- /dev/null +++ b/services/sync/tests/tps/test_bookmarks_in_same_named_folder.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// bug 558077 + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +var bookmarks_initial_1 = { + menu: [ + { folder: "aaa", description: "foo" }, + { uri: "http://www.mozilla.com" }, + ], + "menu/aaa": [ + { uri: "http://www.yahoo.com", title: "testing Yahoo" }, + { uri: "http://www.google.com", title: "testing Google" }, + ], +}; + +var bookmarks_initial_2 = { + menu: [ + { folder: "aaa", description: "bar" }, + { uri: "http://www.mozilla.com" }, + ], + "menu/aaa": [ + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + }, + { uri: "http://www.apple.com", tags: ["apple"] }, + ], +}; + +Phase("phase1", [[Bookmarks.add, bookmarks_initial_1], [Sync]]); + +Phase("phase2", [ + [Sync], + [Bookmarks.verify, bookmarks_initial_1], + [Bookmarks.add, bookmarks_initial_2], + [Sync], +]); + +Phase("phase3", [ + [Sync], + // XXX [Bookmarks.verify, bookmarks_initial_1], + [Bookmarks.verify, bookmarks_initial_2], +]); diff --git a/services/sync/tests/tps/test_bug501528.js b/services/sync/tests/tps/test_bug501528.js new file mode 100644 index 0000000000..f86ba02403 --- /dev/null +++ b/services/sync/tests/tps/test_bug501528.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["passwords"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Password lists + */ + +var passwords_initial = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "secret", + usernameField: "uname", + passwordField: "pword", + changes: { + password: "SeCrEt$$$", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +var passwords_after_first_update = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "SeCrEt$$$", + usernameField: "uname", + passwordField: "pword", + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Passwords.add, passwords_initial], [Sync]]); + +Phase("phase2", [[Passwords.add, passwords_initial], [Sync]]); + +Phase("phase3", [ + [Sync], + [Passwords.verify, passwords_initial], + [Passwords.modify, passwords_initial], + [Passwords.verify, passwords_after_first_update], + [Sync], +]); + +Phase("phase4", [[Sync], [Passwords.verify, passwords_after_first_update]]); diff --git a/services/sync/tests/tps/test_bug530717.js b/services/sync/tests/tps/test_bug530717.js new file mode 100644 index 0000000000..37e6711cb5 --- /dev/null +++ b/services/sync/tests/tps/test_bug530717.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["prefs"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +/* + * Preference lists + */ + +var prefs1 = [ + { name: "browser.startup.homepage", value: "http://www.getfirefox.com" }, + { name: "browser.urlbar.maxRichResults", value: 20 }, + { name: "privacy.clearOnShutdown.siteSettings", value: true }, +]; + +var prefs2 = [ + { name: "browser.startup.homepage", value: "http://www.mozilla.com" }, + { name: "browser.urlbar.maxRichResults", value: 18 }, + { name: "privacy.clearOnShutdown.siteSettings", value: false }, +]; + +/* + * Test phases + */ + +// Add prefs to profile1 and sync. +Phase("phase1", [[Prefs.modify, prefs1], [Prefs.verify, prefs1], [Sync]]); + +// Sync profile2 and verify same prefs are present. +Phase("phase2", [[Sync], [Prefs.verify, prefs1]]); + +// Using profile1, change some prefs, then do another sync with wipe-client. +// Verify that the cloud's prefs are restored, and the recent local changes +// discarded. +Phase("phase3", [ + [Prefs.modify, prefs2], + [Prefs.verify, prefs2], + [Sync, SYNC_WIPE_CLIENT], + [Prefs.verify, prefs1], +]); diff --git a/services/sync/tests/tps/test_bug531489.js b/services/sync/tests/tps/test_bug531489.js new file mode 100644 index 0000000000..3cc79c87ec --- /dev/null +++ b/services/sync/tests/tps/test_bug531489.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +/* + * Bookmark asset lists: these define bookmarks that are used during the test + */ + +// the initial list of bookmarks to add to the browser +var bookmarks_initial = { + menu: [ + { folder: "foldera" }, + { uri: "http://www.google.com", title: "Google" }, + ], + "menu/foldera": [{ uri: "http://www.google.com", title: "Google" }], + toolbar: [{ uri: "http://www.google.com", title: "Google" }], +}; + +/* + * Test phases + */ + +// Add three bookmarks with the same url to different locations and sync. +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +// Sync to profile2 and verify that all three bookmarks are present +Phase("phase2", [[Sync], [Bookmarks.verify, bookmarks_initial]]); + +// Sync again to profile1 and verify that all three bookmarks are still +// present. +Phase("phase3", [[Sync], [Bookmarks.verify, bookmarks_initial]]); diff --git a/services/sync/tests/tps/test_bug535326.js b/services/sync/tests/tps/test_bug535326.js new file mode 100644 index 0000000000..ede8f45c10 --- /dev/null +++ b/services/sync/tests/tps/test_bug535326.js @@ -0,0 +1,148 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["tabs"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2" }; + +var tabs1 = [ + { + uri: "data:text/html,<html><head><title>Howdy</title></head><body>Howdy</body></html>", + title: "Howdy", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>America</title></head><body>America</body></html>", + title: "America", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Apple</title></head><body>Apple</body></html>", + title: "Apple", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>This</title></head><body>This</body></html>", + title: "This", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Bug</title></head><body>Bug</body></html>", + title: "Bug", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>IRC</title></head><body>IRC</body></html>", + title: "IRC", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Tinderbox</title></head><body>Tinderbox</body></html>", + title: "Tinderbox", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Fox</title></head><body>Fox</body></html>", + title: "Fox", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Hello</title></head><body>Hello</body></html>", + title: "Hello", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Eagle</title></head><body>Eagle</body></html>", + title: "Eagle", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Train</title></head><body>Train</body></html>", + title: "Train", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Macbook</title></head><body>Macbook</body></html>", + title: "Macbook", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Clock</title></head><body>Clock</body></html>", + title: "Clock", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Google</title></head><body>Google</body></html>", + title: "Google", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Human</title></head><body>Human</body></html>", + title: "Human", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Jetpack</title></head><body>Jetpack</body></html>", + title: "Jetpack", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Selenium</title></head><body>Selenium</body></html>", + title: "Selenium", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Mozilla</title></head><body>Mozilla</body></html>", + title: "Mozilla", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Firefox</title></head><body>Firefox</body></html>", + title: "Firefox", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Weave</title></head><body>Weave</body></html>", + title: "Weave", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Android</title></head><body>Android</body></html>", + title: "Android", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Bye</title></head><body>Bye</body></html>", + title: "Bye", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Hi</title></head><body>Hi</body></html>", + title: "Hi", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Final</title></head><body>Final</body></html>", + title: "Final", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Fennec</title></head><body>Fennec</body></html>", + title: "Fennec", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Mobile</title></head><body>Mobile</body></html>", + title: "Mobile", + profile: "profile1", + }, +]; + +Phase("phase1", [[Tabs.add, tabs1], [Sync]]); + +Phase("phase2", [[Sync], [Tabs.verify, tabs1]]); diff --git a/services/sync/tests/tps/test_bug538298.js b/services/sync/tests/tps/test_bug538298.js new file mode 100644 index 0000000000..3f1abf2a96 --- /dev/null +++ b/services/sync/tests/tps/test_bug538298.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Bookmark asset lists: these define bookmarks that are used during the test + */ + +// the initial list of bookmarks to add to the browser +var bookmarks_initial = { + toolbar: [ + { uri: "http://www.google.com", title: "Google" }, + { + uri: "http://www.cnn.com", + title: "CNN", + changes: { + position: "Google", + }, + }, + { uri: "http://www.mozilla.com", title: "Mozilla" }, + { + uri: "http://www.firefox.com", + title: "Firefox", + changes: { + position: "Mozilla", + }, + }, + ], +}; + +var bookmarks_after_move = { + toolbar: [ + { uri: "http://www.cnn.com", title: "CNN" }, + { uri: "http://www.google.com", title: "Google" }, + { uri: "http://www.firefox.com", title: "Firefox" }, + { uri: "http://www.mozilla.com", title: "Mozilla" }, + ], +}; + +/* + * Test phases + */ + +// Add four bookmarks to the toolbar and sync. +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +// Sync to profile2 and verify that all four bookmarks are present. +Phase("phase2", [[Sync], [Bookmarks.verify, bookmarks_initial]]); + +// Change the order of the toolbar bookmarks, and sync. +Phase("phase3", [ + [Sync], + [Bookmarks.verify, bookmarks_initial], + [Bookmarks.modify, bookmarks_initial], + [Bookmarks.verify, bookmarks_after_move], + [Sync], +]); + +// Go back to profile2, sync, and verify that the bookmarks are reordered +// as expected. +Phase("phase4", [[Sync], [Bookmarks.verify, bookmarks_after_move]]); diff --git a/services/sync/tests/tps/test_bug546807.js b/services/sync/tests/tps/test_bug546807.js new file mode 100644 index 0000000000..f02f632b41 --- /dev/null +++ b/services/sync/tests/tps/test_bug546807.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["tabs"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2" }; + +/* + * Tabs data + */ + +var tabs1 = [ + { uri: "about:config", profile: "profile1" }, + { uri: "about:credits", profile: "profile1" }, + { + uri: "data:text/html,<html><head><title>Apple</title></head><body>Apple</body></html>", + title: "Apple", + profile: "profile1", + }, +]; + +var tabs_absent = [ + { uri: "about:config", profile: "profile1" }, + { uri: "about:credits", profile: "profile1" }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Tabs.add, tabs1], [Sync]]); + +Phase("phase2", [[Sync], [Tabs.verifyNot, tabs_absent]]); diff --git a/services/sync/tests/tps/test_bug556509.js b/services/sync/tests/tps/test_bug556509.js new file mode 100644 index 0000000000..3a46c3c23e --- /dev/null +++ b/services/sync/tests/tps/test_bug556509.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2" }; + +// the initial list of bookmarks to add to the browser +var bookmarks_initial = { + menu: [{ folder: "testfolder", description: "it's just me, a test folder" }], + "menu/testfolder": [{ uri: "http://www.mozilla.com", title: "Mozilla" }], +}; + +/* + * Test phases + */ + +// Add a bookmark folder which has a description, and sync. +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +// Sync to profile2 and verify that the bookmark folder is created, along +// with its description. +Phase("phase2", [[Sync], [Bookmarks.verify, bookmarks_initial]]); diff --git a/services/sync/tests/tps/test_bug562515.js b/services/sync/tests/tps/test_bug562515.js new file mode 100644 index 0000000000..3a1a6cbee2 --- /dev/null +++ b/services/sync/tests/tps/test_bug562515.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Bookmark lists + */ + +// the initial list of bookmarks to add to the browser +var bookmarks_initial = { + menu: [ + { + uri: "http://www.google.com", + tags: ["google", "computers", "internet", "www"], + }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + keyword: "bz", + }, + { folder: "foldera" }, + { uri: "http://www.mozilla.com" }, + { separator: true }, + { folder: "folderb" }, + ], + "menu/foldera": [ + { uri: "http://www.yahoo.com", title: "testing Yahoo" }, + { + uri: "http://www.cnn.com", + description: "This is a description of the site a at www.cnn.com", + }, + ], + "menu/folderb": [{ uri: "http://www.apple.com", tags: ["apple", "mac"] }], + toolbar: [ + { + uri: "place:queryType=0&sort=8&maxResults=10&beginTimeRef=1&beginTime=0", + title: "Visited Today", + }, + ], +}; + +// a list of bookmarks to delete during a 'delete' action +var bookmarks_to_delete = { + menu: [ + { + uri: "http://www.google.com", + tags: ["google", "computers", "internet", "www"], + }, + ], + "menu/foldera": [{ uri: "http://www.yahoo.com", title: "testing Yahoo" }], +}; + +/* + * Test phases + */ + +// add bookmarks to profile1 and sync +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +// sync to profile2 and verify that the bookmarks are present +Phase("phase2", [[Sync], [Bookmarks.verify, bookmarks_initial]]); + +// delete some bookmarks from profile1, then sync with "wipe-client" +// set; finally, verify that the deleted bookmarks were restored. +Phase("phase3", [ + [Bookmarks.delete, bookmarks_to_delete], + [Bookmarks.verifyNot, bookmarks_to_delete], + [Sync, SYNC_WIPE_CLIENT], + [Bookmarks.verify, bookmarks_initial], +]); + +// sync profile2 again, verify no bookmarks have been deleted +Phase("phase4", [[Sync], [Bookmarks.verify, bookmarks_initial]]); diff --git a/services/sync/tests/tps/test_bug575423.js b/services/sync/tests/tps/test_bug575423.js new file mode 100644 index 0000000000..53f46db5b7 --- /dev/null +++ b/services/sync/tests/tps/test_bug575423.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["history"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2" }; + +/* + * History data + */ + +// the history data to add to the browser +var history1 = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, +]; + +// Another history data to add to the browser +var history2 = [ + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.google.com/language_tools?hl=en", + title: "Language Tools", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -40 }, + ], + }, +]; + +/* + * Test phases + */ +Phase("phase1", [ + [History.add, history1], + [Sync], + [History.add, history2], + [Sync], +]); + +Phase("phase2", [[Sync], [History.verify, history2]]); diff --git a/services/sync/tests/tps/test_client_wipe.js b/services/sync/tests/tps/test_client_wipe.js new file mode 100644 index 0000000000..e0fb7a97ac --- /dev/null +++ b/services/sync/tests/tps/test_client_wipe.js @@ -0,0 +1,142 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +/* + * Bookmark lists + */ + +// the initial list of bookmarks to add to the browser +var bookmarks_initial = { + toolbar: [ + { uri: "http://www.google.com", title: "Google" }, + { + uri: "http://www.cnn.com", + title: "CNN", + changes: { + position: "Google", + }, + }, + { uri: "http://www.mozilla.com", title: "Mozilla" }, + { + uri: "http://www.firefox.com", + title: "Firefox", + changes: { + position: "Mozilla", + }, + }, + ], +}; + +var bookmarks_after_move = { + toolbar: [ + { uri: "http://www.cnn.com", title: "CNN" }, + { uri: "http://www.google.com", title: "Google" }, + { uri: "http://www.firefox.com", title: "Firefox" }, + { uri: "http://www.mozilla.com", title: "Mozilla" }, + ], +}; + +/* + * Password data + */ + +// Initial password data +var passwords_initial = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "secret", + usernameField: "uname", + passwordField: "pword", + changes: { + password: "SeCrEt$$$", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +// Password after first modify action has been performed +var passwords_after_change = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "SeCrEt$$$", + usernameField: "uname", + passwordField: "pword", + changes: { + username: "james", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +/* + * Prefs to use in the test + */ +var prefs1 = [ + { name: "browser.startup.homepage", value: "http://www.getfirefox.com" }, + { name: "browser.urlbar.maxRichResults", value: 20 }, + { name: "privacy.clearOnShutdown.siteSettings", value: true }, +]; + +var prefs2 = [ + { name: "browser.startup.homepage", value: "http://www.mozilla.com" }, + { name: "browser.urlbar.maxRichResults", value: 18 }, + { name: "privacy.clearOnShutdown.siteSettings", value: false }, +]; + +/* + * Test phases + */ + +// Add prefs,passwords and bookmarks to profile1 and sync. +Phase("phase1", [ + [Passwords.add, passwords_initial], + [Bookmarks.add, bookmarks_initial], + [Prefs.modify, prefs1], + [Prefs.verify, prefs1], + [Sync], +]); + +// Sync profile2 and verify same prefs,passwords and bookmarks are present. +Phase("phase2", [ + [Sync], + [Prefs.verify, prefs1], + [Passwords.verify, passwords_initial], + [Bookmarks.verify, bookmarks_initial], +]); + +// Using profile1, change some prefs,bookmarks and pwds, then do another sync with wipe-client. +// Verify that the cloud's settings are restored, and the recent local changes +// discarded. +Phase("phase3", [ + [Prefs.modify, prefs2], + [Passwords.modify, passwords_initial], + [Bookmarks.modify, bookmarks_initial], + [Prefs.verify, prefs2], + [Passwords.verify, passwords_after_change], + [Bookmarks.verify, bookmarks_after_move], + [Sync, SYNC_WIPE_CLIENT], + [Prefs.verify, prefs1], + [Passwords.verify, passwords_initial], + [Bookmarks.verify, bookmarks_initial], +]); diff --git a/services/sync/tests/tps/test_creditcards.js b/services/sync/tests/tps/test_creditcards.js new file mode 100644 index 0000000000..fea2b8a541 --- /dev/null +++ b/services/sync/tests/tps/test_creditcards.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* global Services */ +Services.prefs.setBoolPref("services.sync.engine.creditcards", true); + +EnableEngines(["creditcards"]); + +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", +}; + +const cc1 = [ + { + "cc-name": "John Doe", + "cc-number": "4716179744040592", + "cc-exp-month": 4, + "cc-exp-year": 2050, + "unknown-1": "an unknown field from another client", + changes: { + "cc-exp-year": 2051, + }, + }, +]; + +const cc1_after = [ + { + "cc-name": "John Doe", + "cc-number": "4716179744040592", + "cc-exp-month": 4, + "cc-exp-year": 2051, + "unknown-1": "an unknown field from another client", + }, +]; + +const cc2 = [ + { + "cc-name": "Timothy Berners-Lee", + "cc-number": "2221000374457678", + "cc-exp-month": 12, + "cc-exp-year": 2050, + }, +]; + +Phase("phase1", [[CreditCards.add, cc1], [Sync]]); + +Phase("phase2", [ + [Sync], + [CreditCards.verify, cc1], + [CreditCards.modify, cc1], + [CreditCards.add, cc2], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [CreditCards.verifyNot, cc1], + [CreditCards.verify, cc1_after], + [CreditCards.verify, cc2], +]); diff --git a/services/sync/tests/tps/test_existing_bookmarks.js b/services/sync/tests/tps/test_existing_bookmarks.js new file mode 100644 index 0000000000..cde41a96e6 --- /dev/null +++ b/services/sync/tests/tps/test_existing_bookmarks.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["bookmarks"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile2", + phase4: "profile1", +}; + +/* + * Bookmark lists + */ +var bookmarks_initial = { + menu: [ + { + uri: "http://www.google.com", + title: "Google", + changes: { + title: "google", + }, + }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + }, + { uri: "http://www.mozilla.com" }, + { + uri: "http://www.cnn.com", + description: "This is a description of the site a at www.cnn.com", + changes: { + description: "Global news", + }, + }, + ], +}; + +var bookmarks_after = { + menu: [ + { uri: "http://www.google.com", title: "google" }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + }, + { uri: "http://www.mozilla.com" }, + { uri: "http://www.cnn.com", description: "Global news" }, + ], +}; + +/* + * Test phases + */ + +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +Phase("phase2", [ + [Bookmarks.add, bookmarks_initial], + [Bookmarks.verify, bookmarks_initial], + [Sync], +]); + +Phase("phase3", [ + [Bookmarks.verify, bookmarks_initial], + [Bookmarks.modify, bookmarks_initial], + [Bookmarks.verify, bookmarks_after], + [Sync], +]); + +Phase("phase4", [[Sync], [Bookmarks.verify, bookmarks_after]]); diff --git a/services/sync/tests/tps/test_extstorage.js b/services/sync/tests/tps/test_extstorage.js new file mode 100644 index 0000000000..cf25187bc3 --- /dev/null +++ b/services/sync/tests/tps/test_extstorage.js @@ -0,0 +1,154 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["addons"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", + phase5: "profile1", +}; + +/* + * Test phases + */ + +Phase("phase1", [ + [ + ExtStorage.set, + "ext-1", + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": { + sk_1: "v1", + sk_2: "v2", + }, + }, + ], + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": { + sk_1: "v1", + sk_2: "v2", + }, + }, + ], + [Sync], +]); + +Phase("phase2", [ + [Sync], + [ + ExtStorage.set, + "ext-1", + { + "key-2": "value from profile 2", + }, + ], + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": "value from profile 2", + }, + ], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": "value from profile 2", + }, + ], + [ + ExtStorage.set, + "ext-1", + { + "key-2": "value from profile 1", + }, + ], + // exit without syncing. +]); + +Phase("phase4", [ + [Sync], + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": "value from profile 2", + }, + ], + [ + ExtStorage.set, + "ext-1", + { + "key-2": "second value from profile 2", + }, + ], + [Sync], +]); + +Phase("phase5", [ + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": "value from profile 1", + }, + ], + [Sync], + [ + ExtStorage.verify, + "ext-1", + null, + { + "key-1": { + sub_key_1: "value 1", + sub_key_2: "value 2", + }, + "key-2": "second value from profile 2", + }, + ], +]); diff --git a/services/sync/tests/tps/test_formdata.js b/services/sync/tests/tps/test_formdata.js new file mode 100644 index 0000000000..764b12342f --- /dev/null +++ b/services/sync/tests/tps/test_formdata.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["forms"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Form data asset lists: these define form values that are used in the tests. + */ + +var formdata1 = [ + { fieldname: "testing", value: "success", date: -1 }, + { fieldname: "testing", value: "failure", date: -2 }, + { fieldname: "username", value: "joe" }, +]; + +var formdata2 = [ + { fieldname: "testing", value: "success", date: -1 }, + { fieldname: "username", value: "joe" }, +]; + +var formdata_delete = [{ fieldname: "testing", value: "failure" }]; + +var formdata_new = [{ fieldname: "new-field", value: "new-value" }]; +/* + * Test phases + */ + +Phase("phase1", [ + [Formdata.add, formdata1], + [Formdata.verify, formdata1], + [Sync], +]); + +Phase("phase2", [[Sync], [Formdata.verify, formdata1]]); + +Phase("phase3", [ + [Sync], + [Formdata.delete, formdata_delete], + [Formdata.verifyNot, formdata_delete], + [Formdata.verify, formdata2], + // add new data after the first Sync, ensuring the tracker works. + [Formdata.add, formdata_new], + [Sync], +]); + +Phase("phase4", [ + [Sync], + [Formdata.verify, formdata2], + [Formdata.verify, formdata_new], + [Formdata.verifyNot, formdata_delete], +]); diff --git a/services/sync/tests/tps/test_history.js b/services/sync/tests/tps/test_history.js new file mode 100644 index 0000000000..e1df78c762 --- /dev/null +++ b/services/sync/tests/tps/test_history.js @@ -0,0 +1,129 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["history"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2" }; + +/* + * History asset lists: these define history entries that are used during + * the test + */ + +// the initial list of history items to add to the browser +var history1 = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.google.com/language_tools?hl=en", + title: "Language Tools", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -40 }, + ], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 1, date: -1 }, + { type: 1, date: -20 }, + { type: 2, date: -36 }, + ], + }, +]; + +// a list of items to delete from the history +var history_to_delete = [ + { uri: "http://www.cnn.com/" }, + { begin: -24, end: -1 }, + { host: "www.google.com" }, +]; + +// a list which reflects items that should be in the history after +// the above items are deleted +var history2 = [ + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -36 }, + ], + }, +]; + +// a list which includes history entries that should not be present +// after deletion of the history_to_delete entries +var history_not = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.google.com/language_tools?hl=en", + title: "Language Tools", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -40 }, + ], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: -1 }, + { type: 1, date: -20 }, + ], + }, +]; + +/* + * Test phases + * Note: there is no test phase in which deleted history entries are + * synced to other clients. This functionality is not supported by + * Sync, see bug 446517. + */ + +Phase("phase1", [[History.add, history1], [Sync]]); + +Phase("phase2", [ + [Sync], + [History.verify, history1], + [History.delete, history_to_delete], + [History.verify, history2], + [History.verifyNot, history_not], + [Sync], +]); diff --git a/services/sync/tests/tps/test_history_collision.js b/services/sync/tests/tps/test_history_collision.js new file mode 100644 index 0000000000..ea428d1051 --- /dev/null +++ b/services/sync/tests/tps/test_history_collision.js @@ -0,0 +1,98 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["history"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * History lists + */ + +// the initial list of history to add to the browser +var history1 = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [{ type: 1, date: 0 }], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -36 }, + ], + }, +]; + +// the history to delete +var history_to_delete = [ + { uri: "http://www.cnn.com/", title: "CNN" }, + { begin: -36, end: -1 }, +]; + +var history_not = [ + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, +]; + +var history_after_delete = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [{ type: 1, date: 0 }], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [{ type: 1, date: 0 }], + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[History.add, history1], [Sync]]); + +Phase("phase2", [[Sync], [History.add, history1], [Sync, SYNC_WIPE_REMOTE]]); + +Phase("phase3", [ + [Sync], + [History.verify, history1], + [History.delete, history_to_delete], + [History.verify, history_after_delete], + [History.verifyNot, history_not], + [Sync], +]); + +Phase("phase4", [ + [Sync], + [History.verify, history_after_delete], + [History.verifyNot, history_not], +]); diff --git a/services/sync/tests/tps/test_passwords.js b/services/sync/tests/tps/test_passwords.js new file mode 100644 index 0000000000..2dfd7de8dd --- /dev/null +++ b/services/sync/tests/tps/test_passwords.js @@ -0,0 +1,119 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["passwords"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Password asset lists: these define password entries that are used during + * the test + */ + +// initial password list to be loaded into the browser +var passwords_initial = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "SeCrEt123", + usernameField: "uname", + passwordField: "pword", + changes: { + password: "zippity-do-dah", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// expected state of passwords after the changes in the above list are applied +var passwords_after_first_update = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "zippity-do-dah", + usernameField: "uname", + passwordField: "pword", + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +var passwords_to_delete = [ + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +var passwords_absent = [ + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// expected state of passwords after the delete operation +var passwords_after_second_update = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "zippity-do-dah", + usernameField: "uname", + passwordField: "pword", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Passwords.add, passwords_initial], [Sync]]); + +Phase("phase2", [ + [Sync], + [Passwords.verify, passwords_initial], + [Passwords.modify, passwords_initial], + [Passwords.verify, passwords_after_first_update], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [Passwords.verify, passwords_after_first_update], + [Passwords.delete, passwords_to_delete], + [Passwords.verify, passwords_after_second_update], + [Passwords.verifyNot, passwords_absent], + [Sync], +]); + +Phase("phase4", [ + [Sync], + [Passwords.verify, passwords_after_second_update], + [Passwords.verifyNot, passwords_absent], +]); diff --git a/services/sync/tests/tps/test_prefs.js b/services/sync/tests/tps/test_prefs.js new file mode 100644 index 0000000000..3e26e947dd --- /dev/null +++ b/services/sync/tests/tps/test_prefs.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["prefs"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +var prefs1 = [ + { name: "browser.startup.homepage", value: "http://www.getfirefox.com" }, + { name: "browser.urlbar.maxRichResults", value: 20 }, + { name: "privacy.clearOnShutdown.siteSettings", value: true }, +]; + +var prefs2 = [ + { name: "browser.startup.homepage", value: "http://www.mozilla.com" }, + { name: "browser.urlbar.maxRichResults", value: 18 }, + { name: "privacy.clearOnShutdown.siteSettings", value: false }, +]; + +Phase("phase1", [[Prefs.modify, prefs1], [Prefs.verify, prefs1], [Sync]]); + +Phase("phase2", [ + [Sync], + [Prefs.verify, prefs1], + [Prefs.modify, prefs2], + [Prefs.verify, prefs2], + [Sync], +]); + +Phase("phase3", [[Sync], [Prefs.verify, prefs2]]); diff --git a/services/sync/tests/tps/test_privbrw_passwords.js b/services/sync/tests/tps/test_privbrw_passwords.js new file mode 100644 index 0000000000..dcde6c02b8 --- /dev/null +++ b/services/sync/tests/tps/test_privbrw_passwords.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ +EnableEngines(["passwords"]); + +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Password data + */ + +// Initial password data +var passwords_initial = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "secret", + usernameField: "uname", + passwordField: "pword", + changes: { + password: "SeCrEt$$$", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +// Password after first modify action has been performed +var passwords_after_first_change = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "SeCrEt$$$", + usernameField: "uname", + passwordField: "pword", + changes: { + username: "james", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +// Password after second modify action has been performed +var passwords_after_second_change = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "james", + password: "SeCrEt$$$", + usernameField: "uname", + passwordField: "pword", + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "jack", + password: "secretlogin", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Passwords.add, passwords_initial], [Sync]]); + +Phase("phase2", [ + [Sync], + [Passwords.verify, passwords_initial], + [Passwords.modify, passwords_initial], + [Passwords.verify, passwords_after_first_change], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [Windows.add, { private: true }], + [Passwords.verify, passwords_after_first_change], + [Passwords.modify, passwords_after_first_change], + [Passwords.verify, passwords_after_second_change], + [Sync], +]); + +Phase("phase4", [[Sync], [Passwords.verify, passwords_after_second_change]]); diff --git a/services/sync/tests/tps/test_privbrw_tabs.js b/services/sync/tests/tps/test_privbrw_tabs.js new file mode 100644 index 0000000000..c7c877677f --- /dev/null +++ b/services/sync/tests/tps/test_privbrw_tabs.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ +EnableEngines(["tabs"]); + +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Tabs data + */ + +var tabs1 = [ + { + uri: "data:text/html,<html><head><title>Firefox</title></head><body>Firefox</body></html>", + title: "Firefox", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Weave</title></head><body>Weave</body></html>", + title: "Weave", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Apple</title></head><body>Apple</body></html>", + title: "Apple", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>IRC</title></head><body>IRC</body></html>", + title: "IRC", + profile: "profile1", + }, +]; + +var tabs2 = [ + { + uri: "data:text/html,<html><head><title>Tinderbox</title></head><body>Tinderbox</body></html>", + title: "Tinderbox", + profile: "profile2", + }, + { + uri: "data:text/html,<html><head><title>Fox</title></head><body>Fox</body></html>", + title: "Fox", + profile: "profile2", + }, +]; + +var tabs3 = [ + { + uri: "data:text/html,<html><head><title>Jetpack</title></head><body>Jetpack</body></html>", + title: "Jetpack", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Selenium</title></head><body>Selenium</body></html>", + title: "Selenium", + profile: "profile1", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Tabs.add, tabs1], [Sync]]); + +Phase("phase2", [[Sync], [Tabs.verify, tabs1], [Tabs.add, tabs2], [Sync]]); + +Phase("phase3", [ + [Sync], + [Windows.add, { private: true }], + [Tabs.add, tabs3], + [Sync], +]); + +Phase("phase4", [[Sync], [Tabs.verifyNot, tabs3]]); diff --git a/services/sync/tests/tps/test_special_tabs.js b/services/sync/tests/tps/test_special_tabs.js new file mode 100644 index 0000000000..9e1eae88e3 --- /dev/null +++ b/services/sync/tests/tps/test_special_tabs.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 532173 - Dont sync tabs like about:* , weave firstrun etc + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ +EnableEngines(["tabs"]); + +var phases = { phase1: "profile1", phase2: "profile2" }; + +var tabs1 = [ + { + uri: "data:text/html,<html><head><title>Firefox</title></head><body>Firefox</body></html>", + title: "Firefox", + profile: "profile1", + }, + { uri: "about:robots", title: "About", profile: "profile1" }, + { uri: "about:credits", title: "Credits", profile: "profile1" }, + { + uri: "data:text/html,<html><head><title>Mozilla</title></head><body>Mozilla</body></html>", + title: "Mozilla", + profile: "profile1", + }, + { + uri: "http://www.mozilla.com/en-US/firefox/sync/firstrun.html", + title: "Firstrun", + profile: "profile1", + }, +]; + +var tabs2 = [ + { + uri: "data:text/html,<html><head><title>Firefox</title></head><body>Firefox</body></html>", + title: "Firefox", + profile: "profile1", + }, + { + uri: "data:text/html,<html><head><title>Mozilla</title></head><body>Mozilla</body></html>", + title: "Mozilla", + profile: "profile1", + }, +]; + +var tabs3 = [ + { + uri: "http://www.mozilla.com/en-US/firefox/sync/firstrun.html", + title: "Firstrun", + profile: "profile1", + }, + { uri: "about:robots", title: "About", profile: "profile1" }, + { uri: "about:credits", title: "Credits", profile: "profile1" }, +]; + +/* + * Test phases + */ +Phase("phase1", [[Tabs.add, tabs1], [Sync]]); + +Phase("phase2", [[Sync], [Tabs.verify, tabs2], [Tabs.verifyNot, tabs3]]); diff --git a/services/sync/tests/tps/test_sync.js b/services/sync/tests/tps/test_sync.js new file mode 100644 index 0000000000..3d14430c60 --- /dev/null +++ b/services/sync/tests/tps/test_sync.js @@ -0,0 +1,403 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in strict JSON format, as it will get parsed by the Python + * testrunner (no single quotes, extra comma's, etc). + */ + +var phases = { + phase1: "profile1", + phase2: "profile2", + phase3: "profile1", + phase4: "profile2", +}; + +/* + * Bookmark asset lists: these define bookmarks that are used during the test + */ + +// the initial list of bookmarks to be added to the browser +var bookmarks_initial = { + menu: [ + { + uri: "http://www.google.com", + tags: ["google", "computers", "internet", "www"], + changes: { + title: "Google", + tags: ["google", "computers", "misc"], + }, + }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + keyword: "bz", + changes: { + keyword: "bugzilla", + }, + }, + { folder: "foldera" }, + { uri: "http://www.mozilla.com" }, + { separator: true }, + { folder: "folderb" }, + ], + "menu/foldera": [ + { + uri: "http://www.yahoo.com", + title: "testing Yahoo", + changes: { + location: "menu/folderb", + }, + }, + { + uri: "http://www.cnn.com", + description: "This is a description of the site a at www.cnn.com", + changes: { + uri: "http://money.cnn.com", + description: "new description", + }, + }, + ], + "menu/folderb": [ + { + uri: "http://www.apple.com", + tags: ["apple", "mac"], + changes: { + uri: "http://www.apple.com/iphone/", + title: "iPhone", + location: "menu", + position: "Google", + tags: [], + }, + }, + ], + toolbar: [ + { + uri: "place:queryType=0&sort=8&maxResults=10&beginTimeRef=1&beginTime=0", + title: "Visited Today", + }, + ], +}; + +// the state of bookmarks after the first 'modify' action has been performed +// on them +var bookmarks_after_first_modify = { + menu: [ + { + uri: "http://www.apple.com/iphone/", + title: "iPhone", + before: "Google", + tags: [], + }, + { + uri: "http://www.google.com", + title: "Google", + tags: ["google", "computers", "misc"], + }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + keyword: "bugzilla", + }, + { folder: "foldera" }, + { uri: "http://www.mozilla.com" }, + { separator: true }, + { + folder: "folderb", + changes: { + location: "menu/foldera", + folder: "Folder B", + description: "folder description", + }, + }, + ], + "menu/foldera": [ + { + uri: "http://money.cnn.com", + title: "http://www.cnn.com", + description: "new description", + }, + ], + "menu/folderb": [{ uri: "http://www.yahoo.com", title: "testing Yahoo" }], + toolbar: [ + { + uri: "place:queryType=0&sort=8&maxResults=10&beginTimeRef=1&beginTime=0", + title: "Visited Today", + }, + ], +}; + +// a list of bookmarks to delete during a 'delete' action +var bookmarks_to_delete = { + menu: [ + { + uri: "http://www.google.com", + title: "Google", + tags: ["google", "computers", "misc"], + }, + ], +}; + +// the state of bookmarks after the second 'modify' action has been performed +// on them +var bookmarks_after_second_modify = { + menu: [ + { uri: "http://www.apple.com/iphone/", title: "iPhone" }, + { + uri: "http://bugzilla.mozilla.org/show_bug.cgi?id=%s", + title: "Bugzilla", + keyword: "bugzilla", + }, + { folder: "foldera" }, + { uri: "http://www.mozilla.com" }, + { separator: true }, + ], + "menu/foldera": [ + { + uri: "http://money.cnn.com", + title: "http://www.cnn.com", + description: "new description", + }, + { folder: "Folder B", description: "folder description" }, + ], + "menu/foldera/Folder B": [ + { uri: "http://www.yahoo.com", title: "testing Yahoo" }, + ], +}; + +// a list of bookmarks which should not be present after the last +// 'delete' and 'modify' actions +var bookmarks_absent = { + menu: [ + { uri: "http://www.google.com", title: "Google" }, + { folder: "folderb" }, + { folder: "Folder B" }, + ], +}; + +/* + * History asset lists: these define history entries that are used during + * the test + */ + +// the initial list of history items to add to the browser +var history_initial = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.google.com/language_tools?hl=en", + title: "Language Tools", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -40 }, + ], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 1, date: -1 }, + { type: 1, date: -20 }, + { type: 2, date: -36 }, + ], + }, +]; + +// a list of history entries to delete during a 'delete' action +var history_to_delete = [ + { uri: "http://www.cnn.com/" }, + { begin: -24, end: -1 }, + { host: "www.google.com" }, +]; + +// the expected history entries after the first 'delete' action +var history_after_delete = [ + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -36 }, + ], + }, +]; + +// history entries expected to not exist after a 'delete' action +var history_absent = [ + { + uri: "http://www.google.com/", + title: "Google", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -1 }, + ], + }, + { + uri: "http://www.cnn.com/", + title: "CNN", + visits: [ + { type: 1, date: -1 }, + { type: 2, date: -36 }, + ], + }, + { + uri: "http://www.google.com/language_tools?hl=en", + title: "Language Tools", + visits: [ + { type: 1, date: 0 }, + { type: 2, date: -40 }, + ], + }, + { + uri: "http://www.mozilla.com/", + title: "Mozilla", + visits: [ + { type: 1, date: -1 }, + { type: 1, date: -20 }, + ], + }, +]; + +/* + * Password asset lists: these define password entries that are used during + * the test + */ + +// the initial list of passwords to add to the browser +var passwords_initial = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "SeCrEt123", + usernameField: "uname", + passwordField: "pword", + changes: { + password: "zippity-do-dah", + }, + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// the expected state of passwords after the first 'modify' action +var passwords_after_first_modify = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "zippity-do-dah", + usernameField: "uname", + passwordField: "pword", + }, + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// a list of passwords to delete during a 'delete' action +var passwords_to_delete = [ + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// a list of passwords expected to be absent after 'delete' and 'modify' +// actions +var passwords_absent = [ + { + hostname: "http://www.example.com", + realm: "login", + username: "joe", + password: "secretlogin", + }, +]; + +// the expected state of passwords after the seconds 'modify' action +var passwords_after_second_modify = [ + { + hostname: "http://www.example.com", + submitURL: "http://login.example.com", + username: "joe", + password: "zippity-do-dah", + usernameField: "uname", + passwordField: "pword", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [ + [Bookmarks.add, bookmarks_initial], + [Passwords.add, passwords_initial], + [History.add, history_initial], + [Sync], +]); + +Phase("phase2", [ + [Sync], + [Bookmarks.verify, bookmarks_initial], + [Passwords.verify, passwords_initial], + [History.verify, history_initial], + [Bookmarks.modify, bookmarks_initial], + [Passwords.modify, passwords_initial], + [History.delete, history_to_delete], + [Bookmarks.verify, bookmarks_after_first_modify], + [Passwords.verify, passwords_after_first_modify], + [History.verify, history_after_delete], + [History.verifyNot, history_absent], + [Sync], +]); + +Phase("phase3", [ + [Sync], + [Bookmarks.verify, bookmarks_after_first_modify], + [Passwords.verify, passwords_after_first_modify], + [History.verify, history_after_delete], + [Bookmarks.modify, bookmarks_after_first_modify], + [Passwords.modify, passwords_after_first_modify], + [Bookmarks.delete, bookmarks_to_delete], + [Passwords.delete, passwords_to_delete], + [Bookmarks.verify, bookmarks_after_second_modify], + [Passwords.verify, passwords_after_second_modify], + [Bookmarks.verifyNot, bookmarks_absent], + [Passwords.verifyNot, passwords_absent], + [Sync], +]); + +Phase("phase4", [ + [Sync], + [Bookmarks.verify, bookmarks_after_second_modify], + [Passwords.verify, passwords_after_second_modify], + [Bookmarks.verifyNot, bookmarks_absent], + [Passwords.verifyNot, passwords_absent], + [History.verifyNot, history_absent], +]); diff --git a/services/sync/tests/tps/test_tabs.js b/services/sync/tests/tps/test_tabs.js new file mode 100644 index 0000000000..58e8d358cd --- /dev/null +++ b/services/sync/tests/tps/test_tabs.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +EnableEngines(["tabs"]); + +/* + * The list of phases mapped to their corresponding profiles. The object + * here must be in JSON format as it will get parsed by the Python + * testrunner. It is parsed by the YAML package, so it relatively flexible. + */ +var phases = { phase1: "profile1", phase2: "profile2", phase3: "profile1" }; + +/* + * Tab lists. + */ + +var tabs1 = [ + { uri: "https://www.mozilla.org/en-US/firefox/", profile: "profile1" }, + { + uri: "https://example.com/", + title: "Example Domain", + profile: "profile1", + }, +]; + +var tabs2 = [ + { uri: "https://www.mozilla.org/en-US/contribute/", profile: "profile2" }, + { + uri: "https://example.com/", + profile: "profile2", + }, +]; + +/* + * Test phases + */ + +Phase("phase1", [[Tabs.add, tabs1], [Sync]]); + +Phase("phase2", [[Sync], [Tabs.verify, tabs1], [Tabs.add, tabs2], [Sync]]); + +Phase("phase3", [[Sync], [Tabs.verify, tabs2]]); diff --git a/services/sync/tests/unit/addon1-search.json b/services/sync/tests/unit/addon1-search.json new file mode 100644 index 0000000000..55f8af8857 --- /dev/null +++ b/services/sync/tests/unit/addon1-search.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Non-Restartless Test Extension", + "type": "extension", + "guid": "addon1@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485, + "url": "http://127.0.0.1:8888/addon1.xpi" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/bootstrap1-search.json b/services/sync/tests/unit/bootstrap1-search.json new file mode 100644 index 0000000000..8cd1cf43ed --- /dev/null +++ b/services/sync/tests/unit/bootstrap1-search.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Restartless Test Extension", + "type": "extension", + "guid": "bootstrap1@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485, + "url": "http://127.0.0.1:8888/bootstrap1.xpi" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/head_appinfo.js b/services/sync/tests/unit/head_appinfo.js new file mode 100644 index 0000000000..79379ccea8 --- /dev/null +++ b/services/sync/tests/unit/head_appinfo.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +// Required to avoid failures. +do_get_profile(); + +// Init FormHistoryStartup and pretend we opened a profile. +var fhs = Cc["@mozilla.org/satchel/form-history-startup;1"].getService( + Ci.nsIObserver +); +fhs.observe(null, "profile-after-change", null); + +// An app is going to have some prefs set which xpcshell tests don't. +Services.prefs.setStringPref( + "identity.sync.tokenserver.uri", + "http://token-server" +); + +// Make sure to provide the right OS so crypto loads the right binaries +function getOS() { + switch (mozinfo.os) { + case "win": + return "WINNT"; + case "mac": + return "Darwin"; + default: + return "Linux"; + } +} + +const { updateAppInfo } = ChromeUtils.importESModule( + "resource://testing-common/AppInfo.sys.mjs" +); +updateAppInfo({ + name: "XPCShell", + ID: "xpcshell@tests.mozilla.org", + version: "1", + platformVersion: "", + OS: getOS(), +}); + +// Register resource aliases. Normally done in SyncComponents.manifest. +function addResourceAlias() { + const resProt = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + for (let s of ["common", "sync", "crypto"]) { + let uri = Services.io.newURI("resource://gre/modules/services-" + s + "/"); + resProt.setSubstitution("services-" + s, uri); + } +} +addResourceAlias(); diff --git a/services/sync/tests/unit/head_errorhandler_common.js b/services/sync/tests/unit/head_errorhandler_common.js new file mode 100644 index 0000000000..fcf44ec43b --- /dev/null +++ b/services/sync/tests/unit/head_errorhandler_common.js @@ -0,0 +1,195 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from head_appinfo.js */ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ +/* import-globals-from head_helpers.js */ +/* import-globals-from head_http_server.js */ + +// This file expects Service to be defined in the global scope when EHTestsCommon +// is used (from service.js). +/* global Service */ + +var { Changeset, EngineManager, Store, SyncEngine, Tracker, LegacyTracker } = + ChromeUtils.importESModule("resource://services-sync/engines.sys.mjs"); +var { + ABORT_SYNC_COMMAND, + CLIENT_NOT_CONFIGURED, + CREDENTIALS_CHANGED, + DEFAULT_DOWNLOAD_BATCH_SIZE, + DEFAULT_GUID_FETCH_BATCH_SIZE, + DEFAULT_KEYBUNDLE_NAME, + DEVICE_TYPE_DESKTOP, + DEVICE_TYPE_MOBILE, + ENGINE_APPLY_FAIL, + ENGINE_BATCH_INTERRUPTED, + ENGINE_DOWNLOAD_FAIL, + ENGINE_SUCCEEDED, + ENGINE_UNKNOWN_FAIL, + ENGINE_UPLOAD_FAIL, + HMAC_EVENT_INTERVAL, + IDLE_OBSERVER_BACK_DELAY, + LOGIN_FAILED, + LOGIN_FAILED_INVALID_PASSPHRASE, + LOGIN_FAILED_LOGIN_REJECTED, + LOGIN_FAILED_NETWORK_ERROR, + LOGIN_FAILED_NO_PASSPHRASE, + LOGIN_FAILED_NO_USERNAME, + LOGIN_FAILED_SERVER_ERROR, + LOGIN_SUCCEEDED, + MASTER_PASSWORD_LOCKED, + MASTER_PASSWORD_LOCKED_RETRY_INTERVAL, + MAXIMUM_BACKOFF_INTERVAL, + MAX_ERROR_COUNT_BEFORE_BACKOFF, + MAX_HISTORY_DOWNLOAD, + MAX_HISTORY_UPLOAD, + METARECORD_DOWNLOAD_FAIL, + MINIMUM_BACKOFF_INTERVAL, + MULTI_DEVICE_THRESHOLD, + NO_SYNC_NODE_FOUND, + NO_SYNC_NODE_INTERVAL, + OVER_QUOTA, + PREFS_BRANCH, + RESPONSE_OVER_QUOTA, + SCORE_INCREMENT_MEDIUM, + SCORE_INCREMENT_SMALL, + SCORE_INCREMENT_XLARGE, + SCORE_UPDATE_DELAY, + SERVER_MAINTENANCE, + SINGLE_USER_THRESHOLD, + SQLITE_MAX_VARIABLE_NUMBER, + STATUS_DISABLED, + STATUS_OK, + STORAGE_VERSION, + SYNC_FAILED, + SYNC_FAILED_PARTIAL, + SYNC_KEY_DECODED_LENGTH, + SYNC_KEY_ENCODED_LENGTH, + SYNC_SUCCEEDED, + URI_LENGTH_MAX, + VERSION_OUT_OF_DATE, + WEAVE_VERSION, + kFirefoxShuttingDown, + kFirstSyncChoiceNotMade, + kSyncBackoffNotMet, + kSyncMasterPasswordLocked, + kSyncNetworkOffline, + kSyncNotConfigured, + kSyncWeaveDisabled, +} = ChromeUtils.importESModule("resource://services-sync/constants.sys.mjs"); +var { BulkKeyBundle, SyncKeyBundle } = ChromeUtils.importESModule( + "resource://services-sync/keys.sys.mjs" +); + +// Common code for test_errorhandler_{1,2}.js -- pulled out to make it less +// monolithic and take less time to execute. +const EHTestsCommon = { + service_unavailable(request, response) { + let body = "Service Unavailable"; + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + response.setHeader("Retry-After", "42"); + response.bodyOutputStream.write(body, body.length); + }, + + async sync_httpd_setup() { + let clientsEngine = Service.clientsEngine; + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + let catapultEngine = Service.engineManager.get("catapult"); + let catapultSyncID = await catapultEngine.resetLocalSyncID(); + let global = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + clients: { version: clientsEngine.version, syncID: clientsSyncID }, + catapult: { version: catapultEngine.version, syncID: catapultSyncID }, + }, + }); + let clientsColl = new ServerCollection({}, true); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + let handler_401 = httpd_handler(401, "Unauthorized"); + return httpd_setup({ + // Normal server behaviour. + "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), + + // Credentials are wrong or node reallocated. + "/1.1/janedoe/storage/meta/global": handler_401, + "/1.1/janedoe/info/collections": handler_401, + + // Maintenance or overloaded (503 + Retry-After) at info/collections. + "/1.1/broken.info/info/collections": EHTestsCommon.service_unavailable, + + // Maintenance or overloaded (503 + Retry-After) at meta/global. + "/1.1/broken.meta/storage/meta/global": EHTestsCommon.service_unavailable, + "/1.1/broken.meta/info/collections": collectionsHelper.handler, + + // Maintenance or overloaded (503 + Retry-After) at crypto/keys. + "/1.1/broken.keys/storage/meta/global": upd("meta", global.handler()), + "/1.1/broken.keys/info/collections": collectionsHelper.handler, + "/1.1/broken.keys/storage/crypto/keys": EHTestsCommon.service_unavailable, + + // Maintenance or overloaded (503 + Retry-After) at wiping collection. + "/1.1/broken.wipe/info/collections": collectionsHelper.handler, + "/1.1/broken.wipe/storage/meta/global": upd("meta", global.handler()), + "/1.1/broken.wipe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/broken.wipe/storage": EHTestsCommon.service_unavailable, + "/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()), + "/1.1/broken.wipe/storage/catapult": EHTestsCommon.service_unavailable, + }); + }, + + CatapultEngine: (function () { + function CatapultEngine() { + SyncEngine.call(this, "Catapult", Service); + } + CatapultEngine.prototype = { + exception: null, // tests fill this in + async _sync() { + if (this.exception) { + throw this.exception; + } + }, + }; + Object.setPrototypeOf(CatapultEngine.prototype, SyncEngine.prototype); + + return CatapultEngine; + })(), + + async generateCredentialsChangedFailure() { + // Make sync fail due to changed credentials. We simply re-encrypt + // the keys with a different Sync Key, without changing the local one. + let newSyncKeyBundle = new BulkKeyBundle("crypto"); + await newSyncKeyBundle.generateRandom(); + let keys = Service.collectionKeys.asWBO(); + await keys.encrypt(newSyncKeyBundle); + return keys.upload(Service.resource(Service.cryptoKeysURL)); + }, + + async setUp(server) { + syncTestLogging(); + await configureIdentity({ username: "johndoe" }, server); + return EHTestsCommon.generateAndUploadKeys(); + }, + + async generateAndUploadKeys() { + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + let response = await serverKeys.upload( + Service.resource(Service.cryptoKeysURL) + ); + return response.success; + }, +}; diff --git a/services/sync/tests/unit/head_helpers.js b/services/sync/tests/unit/head_helpers.js new file mode 100644 index 0000000000..e79e55e57f --- /dev/null +++ b/services/sync/tests/unit/head_helpers.js @@ -0,0 +1,709 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from head_appinfo.js */ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ +/* import-globals-from head_errorhandler_common.js */ +/* import-globals-from head_http_server.js */ + +// This file expects Service to be defined in the global scope when EHTestsCommon +// is used (from service.js). +/* global Service */ + +var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); +var { Async } = ChromeUtils.importESModule( + "resource://services-common/async.sys.mjs" +); +var { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" +); +var { PlacesTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PlacesTestUtils.sys.mjs" +); +var { sinon } = ChromeUtils.importESModule( + "resource://testing-common/Sinon.sys.mjs" +); +var { SerializableSet, Svc, Utils, getChromeWindow } = + ChromeUtils.importESModule("resource://services-sync/util.sys.mjs"); +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { PlacesUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PlacesUtils.sys.mjs" +); +var { PlacesSyncUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PlacesSyncUtils.sys.mjs" +); +var { ObjectUtils } = ChromeUtils.importESModule( + "resource://gre/modules/ObjectUtils.sys.mjs" +); +var { + MockFxaStorageManager, + SyncTestingInfrastructure, + configureFxAccountIdentity, + configureIdentity, + encryptPayload, + getLoginTelemetryScalar, + makeFxAccountsInternalMock, + makeIdentityConfig, + promiseNamedTimer, + promiseZeroTimer, + sumHistogram, + syncTestLogging, + waitForZeroTimer, +} = ChromeUtils.importESModule( + "resource://testing-common/services/sync/utils.sys.mjs" +); +ChromeUtils.defineESModuleGetters(this, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", +}); + +add_setup(async function head_setup() { + // Initialize logging. This will sometimes be reset by a pref reset, + // so it's also called as part of SyncTestingInfrastructure(). + syncTestLogging(); + // If a test imports Service, make sure it is initialized first. + if (typeof Service !== "undefined") { + await Service.promiseInitialized; + } +}); + +ChromeUtils.defineLazyGetter(this, "SyncPingSchema", function () { + let { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" + ); + let { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" + ); + let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( + Ci.nsIFileInputStream + ); + let schema; + try { + let schemaFile = do_get_file("sync_ping_schema.json"); + stream.init(schemaFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); + + let bytes = NetUtil.readInputStream(stream, stream.available()); + schema = JSON.parse(new TextDecoder().decode(bytes)); + } finally { + stream.close(); + } + + // Allow tests to make whatever engines they want, this shouldn't cause + // validation failure. + schema.definitions.engine.properties.name = { type: "string" }; + return schema; +}); + +ChromeUtils.defineLazyGetter(this, "SyncPingValidator", function () { + const { JsonSchema } = ChromeUtils.importESModule( + "resource://gre/modules/JsonSchema.sys.mjs" + ); + return new JsonSchema.Validator(SyncPingSchema); +}); + +// This is needed for loadAddonTestFunctions(). +var gGlobalScope = this; + +function ExtensionsTestPath(path) { + if (path[0] != "/") { + throw Error("Path must begin with '/': " + path); + } + + return "../../../../toolkit/mozapps/extensions/test/xpcshell" + path; +} + +function webExtensionsTestPath(path) { + if (path[0] != "/") { + throw Error("Path must begin with '/': " + path); + } + + return "../../../../toolkit/components/extensions/test/xpcshell" + path; +} + +/** + * Loads the WebExtension test functions by importing its test file. + */ +function loadWebExtensionTestFunctions() { + /* import-globals-from ../../../../toolkit/components/extensions/test/xpcshell/head_sync.js */ + const path = webExtensionsTestPath("/head_sync.js"); + let file = do_get_file(path); + let uri = Services.io.newFileURI(file); + Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); +} + +/** + * Installs an add-on from an addonInstall + * + * @param install addonInstall instance to install + */ +async function installAddonFromInstall(install) { + await install.install(); + + Assert.notEqual(null, install.addon); + Assert.notEqual(null, install.addon.syncGUID); + + return install.addon; +} + +/** + * Convenience function to install an add-on from the extensions unit tests. + * + * @param file + * Add-on file to install. + * @param reconciler + * addons reconciler, if passed we will wait on the events to be + * processed before resolving + * @return addon object that was installed + */ +async function installAddon(file, reconciler = null) { + let install = await AddonManager.getInstallForFile(file); + Assert.notEqual(null, install); + const addon = await installAddonFromInstall(install); + if (reconciler) { + await reconciler.queueCaller.promiseCallsComplete(); + } + return addon; +} + +/** + * Convenience function to uninstall an add-on. + * + * @param addon + * Addon instance to uninstall + * @param reconciler + * addons reconciler, if passed we will wait on the events to be + * processed before resolving + */ +async function uninstallAddon(addon, reconciler = null) { + const uninstallPromise = new Promise(res => { + let listener = { + onUninstalled(uninstalled) { + if (uninstalled.id == addon.id) { + AddonManager.removeAddonListener(listener); + res(uninstalled); + } + }, + }; + AddonManager.addAddonListener(listener); + }); + addon.uninstall(); + await uninstallPromise; + if (reconciler) { + await reconciler.queueCaller.promiseCallsComplete(); + } +} + +async function generateNewKeys(collectionKeys, collections = null) { + let wbo = await collectionKeys.generateNewKeysWBO(collections); + let modified = new_timestamp(); + collectionKeys.setContents(wbo.cleartext, modified); +} + +// Helpers for testing open tabs. +// These reflect part of the internal structure of TabEngine, +// and stub part of Service.wm. + +function mockShouldSkipWindow(win) { + return win.closed || win.mockIsPrivate; +} + +function mockGetTabState(tab) { + return tab; +} + +function mockGetWindowEnumerator(urls) { + let elements = []; + + const numWindows = 1; + for (let w = 0; w < numWindows; ++w) { + let tabs = []; + let win = { + closed: false, + mockIsPrivate: false, + gBrowser: { + tabs, + }, + }; + elements.push(win); + + let lastAccessed = 2000; + for (let url of urls) { + tabs.push({ + linkedBrowser: { + currentURI: Services.io.newURI(url), + contentTitle: "title", + }, + lastAccessed, + }); + lastAccessed += 1000; + } + } + + // Always include a closed window and a private window. + elements.push({ + closed: true, + mockIsPrivate: false, + gBrowser: { + tabs: [], + }, + }); + + elements.push({ + closed: false, + mockIsPrivate: true, + gBrowser: { + tabs: [], + }, + }); + + return elements.values(); +} + +// Helper function to get the sync telemetry and add the typically used test +// engine names to its list of allowed engines. +function get_sync_test_telemetry() { + let { SyncTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" + ); + SyncTelemetry.tryRefreshDevices = function () {}; + let testEngines = ["rotary", "steam", "sterling", "catapult", "nineties"]; + for (let engineName of testEngines) { + SyncTelemetry.allowedEngines.add(engineName); + } + SyncTelemetry.submissionInterval = -1; + return SyncTelemetry; +} + +function assert_valid_ping(record) { + // Our JSON validator does not like `undefined` values, even though they will + // be skipped when we serialize to JSON. + record = JSON.parse(JSON.stringify(record)); + + // This is called as the test harness tears down due to shutdown. This + // will typically have no recorded syncs, and the validator complains about + // it. So ignore such records (but only ignore when *both* shutdown and + // no Syncs - either of them not being true might be an actual problem) + if (record && (record.why != "shutdown" || !!record.syncs.length)) { + const result = SyncPingValidator.validate(record); + if (!result.valid) { + if (result.errors.length) { + // validation failed - using a simple |deepEqual([], errors)| tends to + // truncate the validation errors in the output and doesn't show that + // the ping actually was - so be helpful. + info("telemetry ping validation failed"); + info("the ping data is: " + JSON.stringify(record, undefined, 2)); + info( + "the validation failures: " + + JSON.stringify(result.errors, undefined, 2) + ); + ok( + false, + "Sync telemetry ping validation failed - see output above for details" + ); + } + } + equal(record.version, 1); + record.syncs.forEach(p => { + lessOrEqual(p.when, Date.now()); + }); + } +} + +// Asserts that `ping` is a ping that doesn't contain any failure information +function assert_success_ping(ping) { + ok(!!ping); + assert_valid_ping(ping); + ping.syncs.forEach(record => { + ok(!record.failureReason, JSON.stringify(record.failureReason)); + equal(undefined, record.status); + greater(record.engines.length, 0); + for (let e of record.engines) { + ok(!e.failureReason); + equal(undefined, e.status); + if (e.validation) { + equal(undefined, e.validation.problems); + equal(undefined, e.validation.failureReason); + } + if (e.outgoing) { + for (let o of e.outgoing) { + equal(undefined, o.failed); + notEqual(undefined, o.sent); + } + } + if (e.incoming) { + equal(undefined, e.incoming.failed); + equal(undefined, e.incoming.newFailed); + notEqual(undefined, e.incoming.applied || e.incoming.reconciled); + } + } + }); +} + +// Hooks into telemetry to validate all pings after calling. +function validate_all_future_pings() { + let telem = get_sync_test_telemetry(); + telem.submit = assert_valid_ping; +} + +function wait_for_pings(expectedPings) { + return new Promise(resolve => { + let telem = get_sync_test_telemetry(); + let oldSubmit = telem.submit; + let pings = []; + telem.submit = function (record) { + pings.push(record); + if (pings.length == expectedPings) { + telem.submit = oldSubmit; + resolve(pings); + } + }; + }); +} + +async function wait_for_ping(callback, allowErrorPings, getFullPing = false) { + let pingsPromise = wait_for_pings(1); + await callback(); + let [record] = await pingsPromise; + if (allowErrorPings) { + assert_valid_ping(record); + } else { + assert_success_ping(record); + } + if (getFullPing) { + return record; + } + equal(record.syncs.length, 1); + return record.syncs[0]; +} + +// Perform a sync and validate all telemetry caused by the sync. If fnValidate +// is null, we just check the ping records success. If fnValidate is specified, +// then the sync must have recorded just a single sync, and that sync will be +// passed to the function to be checked. +async function sync_and_validate_telem( + fnValidate = null, + wantFullPing = false +) { + let numErrors = 0; + let telem = get_sync_test_telemetry(); + let oldSubmit = telem.submit; + try { + telem.submit = function (record) { + // This is called via an observer, so failures here don't cause the test + // to fail :( + try { + // All pings must be valid. + assert_valid_ping(record); + if (fnValidate) { + // for historical reasons most of these callbacks expect a "sync" + // record, not the entire ping. + if (wantFullPing) { + fnValidate(record); + } else { + Assert.equal(record.syncs.length, 1); + fnValidate(record.syncs[0]); + } + } else { + // no validation function means it must be a "success" ping. + assert_success_ping(record); + } + } catch (ex) { + print("Failure in ping validation callback", ex, "\n", ex.stack); + numErrors += 1; + } + }; + await Service.sync(); + Assert.ok(numErrors == 0, "There were telemetry validation errors"); + } finally { + telem.submit = oldSubmit; + } +} + +// Used for the (many) cases where we do a 'partial' sync, where only a single +// engine is actually synced, but we still want to ensure we're generating a +// valid ping. Returns a promise that resolves to the ping, or rejects with the +// thrown error after calling an optional callback. +async function sync_engine_and_validate_telem( + engine, + allowErrorPings, + onError, + wantFullPing = false +) { + let telem = get_sync_test_telemetry(); + let caughtError = null; + // Clear out status, so failures from previous syncs won't show up in the + // telemetry ping. + let { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" + ); + Status._engines = {}; + Status.partial = false; + // Ideally we'd clear these out like we do with engines, (probably via + // Status.resetSync()), but this causes *numerous* tests to fail, so we just + // assume that if no failureReason or engine failures are set, and the + // status properties are the same as they were initially, that it's just + // a leftover. + // This is only an issue since we're triggering the sync of just one engine, + // without doing any other parts of the sync. + let initialServiceStatus = Status._service; + let initialSyncStatus = Status._sync; + + let oldSubmit = telem.submit; + let submitPromise = new Promise((resolve, reject) => { + telem.submit = function (ping) { + telem.submit = oldSubmit; + ping.syncs.forEach(record => { + if (record && record.status) { + // did we see anything to lead us to believe that something bad actually happened + let realProblem = + record.failureReason || + record.engines.some(e => { + if (e.failureReason || e.status) { + return true; + } + if (e.outgoing && e.outgoing.some(o => o.failed > 0)) { + return true; + } + return e.incoming && e.incoming.failed; + }); + if (!realProblem) { + // no, so if the status is the same as it was initially, just assume + // that its leftover and that we can ignore it. + if (record.status.sync && record.status.sync == initialSyncStatus) { + delete record.status.sync; + } + if ( + record.status.service && + record.status.service == initialServiceStatus + ) { + delete record.status.service; + } + if (!record.status.sync && !record.status.service) { + delete record.status; + } + } + } + }); + if (allowErrorPings) { + assert_valid_ping(ping); + } else { + assert_success_ping(ping); + } + equal(ping.syncs.length, 1); + if (caughtError) { + if (onError) { + onError(ping.syncs[0], ping); + } + reject(caughtError); + } else if (wantFullPing) { + resolve(ping); + } else { + resolve(ping.syncs[0]); + } + }; + }); + // neuter the scheduler as it interacts badly with some of the tests - the + // engine being synced usually isn't the registered engine, so we see + // scored incremented and not removed, which schedules unexpected syncs. + let oldObserve = Service.scheduler.observe; + Service.scheduler.observe = () => {}; + try { + Svc.Obs.notify("weave:service:sync:start"); + try { + await engine.sync(); + } catch (e) { + caughtError = e; + } + if (caughtError) { + Svc.Obs.notify("weave:service:sync:error", caughtError); + } else { + Svc.Obs.notify("weave:service:sync:finish"); + } + } finally { + Service.scheduler.observe = oldObserve; + } + return submitPromise; +} + +// Returns a promise that resolves once the specified observer notification +// has fired. +function promiseOneObserver(topic, callback) { + return new Promise((resolve, reject) => { + let observer = function (subject, data) { + Svc.Obs.remove(topic, observer); + resolve({ subject, data }); + }; + Svc.Obs.add(topic, observer); + }); +} + +async function registerRotaryEngine() { + let { RotaryEngine } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/rotaryengine.sys.mjs" + ); + await Service.engineManager.clear(); + + await Service.engineManager.register(RotaryEngine); + let engine = Service.engineManager.get("rotary"); + let syncID = await engine.resetLocalSyncID(); + engine.enabled = true; + + return { engine, syncID, tracker: engine._tracker }; +} + +// Set the validation prefs to attempt validation every time to avoid non-determinism. +function enableValidationPrefs(engines = ["bookmarks"]) { + for (let engine of engines) { + Svc.PrefBranch.setIntPref(`engine.${engine}.validation.interval`, 0); + Svc.PrefBranch.setIntPref( + `engine.${engine}.validation.percentageChance`, + 100 + ); + Svc.PrefBranch.setIntPref(`engine.${engine}.validation.maxRecords`, -1); + Svc.PrefBranch.setBoolPref(`engine.${engine}.validation.enabled`, true); + } +} + +async function serverForEnginesWithKeys(users, engines, callback) { + // Generate and store a fake default key bundle to avoid resetting the client + // before the first sync. + let wbo = await Service.collectionKeys.generateNewKeysWBO(); + let modified = new_timestamp(); + Service.collectionKeys.setContents(wbo.cleartext, modified); + + let allEngines = [Service.clientsEngine].concat(engines); + + let globalEngines = {}; + for (let engine of allEngines) { + let syncID = await engine.resetLocalSyncID(); + globalEngines[engine.name] = { version: engine.version, syncID }; + } + + let contents = { + meta: { + global: { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: globalEngines, + }, + }, + crypto: { + keys: encryptPayload(wbo.cleartext), + }, + }; + for (let engine of allEngines) { + contents[engine.name] = {}; + } + + return serverForUsers(users, contents, callback); +} + +async function serverForFoo(engine, callback) { + // The bookmarks engine *always* tracks changes, meaning we might try + // and sync due to the bookmarks we ourselves create! Worse, because we + // do an engine sync only, there's no locking - so we end up with multiple + // syncs running. Neuter that by making the threshold very large. + Service.scheduler.syncThreshold = 10000000; + return serverForEnginesWithKeys({ foo: "password" }, engine, callback); +} + +// Places notifies history observers asynchronously, so `addVisits` might return +// before the tracker receives the notification. This helper registers an +// observer that resolves once the expected notification fires. +async function promiseVisit(expectedType, expectedURI) { + return new Promise(resolve => { + function done(type, uri) { + if (uri == expectedURI.spec && type == expectedType) { + PlacesObservers.removeListener( + ["page-visited", "page-removed"], + observer.handlePlacesEvents + ); + resolve(); + } + } + let observer = { + handlePlacesEvents(events) { + Assert.equal(events.length, 1); + + if (events[0].type === "page-visited") { + done("added", events[0].url); + } else if (events[0].type === "page-removed") { + Assert.ok(events[0].isRemovedFromStore); + done("removed", events[0].url); + } + }, + }; + PlacesObservers.addListener( + ["page-visited", "page-removed"], + observer.handlePlacesEvents + ); + }); +} + +async function addVisit( + suffix, + referrer = null, + transition = PlacesUtils.history.TRANSITION_LINK +) { + let uriString = "http://getfirefox.com/" + suffix; + let uri = CommonUtils.makeURI(uriString); + _("Adding visit for URI " + uriString); + + let visitAddedPromise = promiseVisit("added", uri); + await PlacesTestUtils.addVisits({ + uri, + visitDate: Date.now() * 1000, + transition, + referrer, + }); + await visitAddedPromise; + + return uri; +} + +function bookmarkNodesToInfos(nodes) { + return nodes.map(node => { + let info = { + guid: node.guid, + index: node.index, + }; + if (node.children) { + info.children = bookmarkNodesToInfos(node.children); + } + return info; + }); +} + +async function assertBookmarksTreeMatches(rootGuid, expected, message) { + let root = await PlacesUtils.promiseBookmarksTree(rootGuid, { + includeItemIds: true, + }); + let actual = bookmarkNodesToInfos(root.children); + + if (!ObjectUtils.deepEqual(actual, expected)) { + _(`Expected structure for ${rootGuid}`, JSON.stringify(expected)); + _(`Actual structure for ${rootGuid}`, JSON.stringify(actual)); + throw new Assert.constructor.AssertionError({ actual, expected, message }); + } +} + +function add_bookmark_test(task) { + const { BookmarksEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" + ); + + add_task(async function () { + _(`Running bookmarks test ${task.name}`); + let engine = new BookmarksEngine(Service); + await engine.initialize(); + await engine._resetClient(); + try { + await task(engine); + } finally { + await engine.finalize(); + } + }); +} diff --git a/services/sync/tests/unit/head_http_server.js b/services/sync/tests/unit/head_http_server.js new file mode 100644 index 0000000000..84dbb33951 --- /dev/null +++ b/services/sync/tests/unit/head_http_server.js @@ -0,0 +1,1265 @@ +/* import-globals-from head_appinfo.js */ +/* import-globals-from ../../../common/tests/unit/head_helpers.js */ +/* import-globals-from head_helpers.js */ + +var Cm = Components.manager; + +// Shared logging for all HTTP server functions. +var { Log } = ChromeUtils.importESModule("resource://gre/modules/Log.sys.mjs"); +var { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" +); +var { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +var { + MockFxaStorageManager, + SyncTestingInfrastructure, + configureFxAccountIdentity, + configureIdentity, + encryptPayload, + getLoginTelemetryScalar, + makeFxAccountsInternalMock, + makeIdentityConfig, + promiseNamedTimer, + promiseZeroTimer, + sumHistogram, + syncTestLogging, + waitForZeroTimer, +} = ChromeUtils.importESModule( + "resource://testing-common/services/sync/utils.sys.mjs" +); + +const SYNC_HTTP_LOGGER = "Sync.Test.Server"; + +// While the sync code itself uses 1.5, the tests hard-code 1.1, +// so we're sticking with 1.1 here. +const SYNC_API_VERSION = "1.1"; + +// Use the same method that record.js does, which mirrors the server. +// The server returns timestamps with 1/100 sec granularity. Note that this is +// subject to change: see Bug 650435. +function new_timestamp() { + return round_timestamp(Date.now()); +} + +// Rounds a millisecond timestamp `t` to seconds, with centisecond precision. +function round_timestamp(t) { + return Math.round(t / 10) / 100; +} + +function return_timestamp(request, response, timestamp) { + if (!timestamp) { + timestamp = new_timestamp(); + } + let body = "" + timestamp; + response.setHeader("X-Weave-Timestamp", body); + response.setStatusLine(request.httpVersion, 200, "OK"); + writeBytesToOutputStream(response.bodyOutputStream, body); + return timestamp; +} + +function has_hawk_header(req) { + return ( + req.hasHeader("Authorization") && + req.getHeader("Authorization").startsWith("Hawk") + ); +} + +function basic_auth_header(user, password) { + return "Basic " + btoa(user + ":" + CommonUtils.encodeUTF8(password)); +} + +function basic_auth_matches(req, user, password) { + if (!req.hasHeader("Authorization")) { + return false; + } + + let expected = basic_auth_header(user, CommonUtils.encodeUTF8(password)); + return req.getHeader("Authorization") == expected; +} + +function httpd_basic_auth_handler(body, metadata, response) { + if (basic_auth_matches(metadata, "guest", "guest")) { + response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } else { + body = "This path exists and is protected - failed"; + response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } + writeBytesToOutputStream(response.bodyOutputStream, body); +} + +/* + * Represent a WBO on the server + */ +function ServerWBO(id, initialPayload, modified) { + if (!id) { + throw new Error("No ID for ServerWBO!"); + } + this.id = id; + if (!initialPayload) { + return; + } + + if (typeof initialPayload == "object") { + initialPayload = JSON.stringify(initialPayload); + } + this.payload = initialPayload; + this.modified = modified || new_timestamp(); + this.sortindex = 0; +} +ServerWBO.prototype = { + get data() { + return JSON.parse(this.payload); + }, + + get() { + return { id: this.id, modified: this.modified, payload: this.payload }; + }, + + put(input) { + input = JSON.parse(input); + this.payload = input.payload; + this.modified = new_timestamp(); + this.sortindex = input.sortindex || 0; + }, + + delete() { + delete this.payload; + delete this.modified; + delete this.sortindex; + }, + + // This handler sets `newModified` on the response body if the collection + // timestamp has changed. This allows wrapper handlers to extract information + // that otherwise would exist only in the body stream. + handler() { + let self = this; + + return function (request, response) { + var statusCode = 200; + var status = "OK"; + var body; + + switch (request.method) { + case "GET": + if (self.payload) { + body = JSON.stringify(self.get()); + } else { + statusCode = 404; + status = "Not Found"; + body = "Not Found"; + } + break; + + case "PUT": + self.put(readBytesFromInputStream(request.bodyInputStream)); + body = JSON.stringify(self.modified); + response.setHeader("Content-Type", "application/json"); + response.newModified = self.modified; + break; + + case "DELETE": + self.delete(); + let ts = new_timestamp(); + body = JSON.stringify(ts); + response.setHeader("Content-Type", "application/json"); + response.newModified = ts; + break; + } + response.setHeader("X-Weave-Timestamp", "" + new_timestamp(), false); + response.setStatusLine(request.httpVersion, statusCode, status); + writeBytesToOutputStream(response.bodyOutputStream, body); + }; + }, + + /** + * Get the cleartext data stored in the payload. + * + * This isn't `get cleartext`, because `x.cleartext.blah = 3;` wouldn't work, + * which seems like a footgun. + */ + getCleartext() { + return JSON.parse(JSON.parse(this.payload).ciphertext); + }, + + /** + * Setter for getCleartext(), but lets you adjust the modified timestamp too. + * Returns this ServerWBO object. + */ + setCleartext(cleartext, modifiedTimestamp = this.modified) { + this.payload = JSON.stringify(encryptPayload(cleartext)); + this.modified = modifiedTimestamp; + return this; + }, +}; + +/** + * Represent a collection on the server. The '_wbos' attribute is a + * mapping of id -> ServerWBO objects. + * + * Note that if you want these records to be accessible individually, + * you need to register their handlers with the server separately, or use a + * containing HTTP server that will do so on your behalf. + * + * @param wbos + * An object mapping WBO IDs to ServerWBOs. + * @param acceptNew + * If true, POSTs to this collection URI will result in new WBOs being + * created and wired in on the fly. + * @param timestamp + * An optional timestamp value to initialize the modified time of the + * collection. This should be in the format returned by new_timestamp(). + * + * @return the new ServerCollection instance. + * + */ +function ServerCollection(wbos, acceptNew, timestamp) { + this._wbos = wbos || {}; + this.acceptNew = acceptNew || false; + + /* + * Track modified timestamp. + * We can't just use the timestamps of contained WBOs: an empty collection + * has a modified time. + */ + this.timestamp = timestamp || new_timestamp(); + this._log = Log.repository.getLogger(SYNC_HTTP_LOGGER); +} +ServerCollection.prototype = { + /** + * Convenience accessor for our WBO keys. + * Excludes deleted items, of course. + * + * @param filter + * A predicate function (applied to the ID and WBO) which dictates + * whether to include the WBO's ID in the output. + * + * @return an array of IDs. + */ + keys: function keys(filter) { + let ids = []; + for (let [id, wbo] of Object.entries(this._wbos)) { + if (wbo.payload && (!filter || filter(id, wbo))) { + ids.push(id); + } + } + return ids; + }, + + /** + * Convenience method to get an array of WBOs. + * Optionally provide a filter function. + * + * @param filter + * A predicate function, applied to the WBO, which dictates whether to + * include the WBO in the output. + * + * @return an array of ServerWBOs. + */ + wbos: function wbos(filter) { + let os = []; + for (let wbo of Object.values(this._wbos)) { + if (wbo.payload) { + os.push(wbo); + } + } + + if (filter) { + return os.filter(filter); + } + return os; + }, + + /** + * Convenience method to get an array of parsed ciphertexts. + * + * @return an array of the payloads of each stored WBO. + */ + payloads() { + return this.wbos().map(wbo => wbo.getCleartext()); + }, + + // Just for syntactic elegance. + wbo: function wbo(id) { + return this._wbos[id]; + }, + + payload: function payload(id) { + return this.wbo(id).payload; + }, + + cleartext(id) { + return this.wbo(id).getCleartext(); + }, + + /** + * Insert the provided WBO under its ID. + * + * @return the provided WBO. + */ + insertWBO: function insertWBO(wbo) { + this.timestamp = Math.max(this.timestamp, wbo.modified); + return (this._wbos[wbo.id] = wbo); + }, + + /** + * Update an existing WBO's cleartext using a callback function that modifies + * the record in place, or returns a new record. + */ + updateRecord(id, updateCallback, optTimestamp) { + let wbo = this.wbo(id); + if (!wbo) { + throw new Error("No record with provided ID"); + } + let curCleartext = wbo.getCleartext(); + // Allow update callback to either return a new cleartext, or modify in place. + let newCleartext = updateCallback(curCleartext) || curCleartext; + wbo.setCleartext(newCleartext, optTimestamp); + // It is already inserted, but we might need to update our timestamp based + // on it's `modified` value, if `optTimestamp` was provided. + return this.insertWBO(wbo); + }, + + /** + * Insert a record, which may either an object with a cleartext property, or + * the cleartext property itself. + */ + insertRecord(record, timestamp = Math.round(Date.now() / 10) / 100) { + if (typeof timestamp != "number") { + throw new TypeError("insertRecord: Timestamp is not a number."); + } + if (!record.id) { + throw new Error("Attempt to insert record with no id"); + } + // Allow providing either the cleartext directly, or the CryptoWrapper-like. + let cleartext = record.cleartext || record; + return this.insert(record.id, encryptPayload(cleartext), timestamp); + }, + + /** + * Insert the provided payload as part of a new ServerWBO with the provided + * ID. + * + * @param id + * The GUID for the WBO. + * @param payload + * The payload, as provided to the ServerWBO constructor. + * @param modified + * An optional modified time for the ServerWBO. + * + * @return the inserted WBO. + */ + insert: function insert(id, payload, modified) { + return this.insertWBO(new ServerWBO(id, payload, modified)); + }, + + /** + * Removes an object entirely from the collection. + * + * @param id + * (string) ID to remove. + */ + remove: function remove(id) { + delete this._wbos[id]; + }, + + _inResultSet(wbo, options) { + return ( + wbo.payload && + (!options.ids || options.ids.includes(wbo.id)) && + (!options.newer || wbo.modified > options.newer) && + (!options.older || wbo.modified < options.older) + ); + }, + + count(options) { + options = options || {}; + let c = 0; + for (let wbo of Object.values(this._wbos)) { + if (wbo.modified && this._inResultSet(wbo, options)) { + c++; + } + } + return c; + }, + + get(options, request) { + let data = []; + for (let wbo of Object.values(this._wbos)) { + if (wbo.modified && this._inResultSet(wbo, options)) { + data.push(wbo); + } + } + switch (options.sort) { + case "newest": + data.sort((a, b) => b.modified - a.modified); + break; + + case "oldest": + data.sort((a, b) => a.modified - b.modified); + break; + + case "index": + data.sort((a, b) => b.sortindex - a.sortindex); + break; + + default: + if (options.sort) { + this._log.error( + "Error: client requesting unknown sort order", + options.sort + ); + throw new Error("Unknown sort order"); + } + // If the client didn't request a sort order, shuffle the records + // to ensure that we don't accidentally depend on the default order. + TestUtils.shuffle(data); + } + if (options.full) { + data = data.map(wbo => wbo.get()); + let start = options.offset || 0; + if (options.limit) { + let numItemsPastOffset = data.length - start; + data = data.slice(start, start + options.limit); + // use options as a backchannel to set x-weave-next-offset + if (numItemsPastOffset > options.limit) { + options.nextOffset = start + options.limit; + } + } else if (start) { + data = data.slice(start); + } + + if (request && request.getHeader("accept") == "application/newlines") { + this._log.error( + "Error: client requesting application/newlines content" + ); + throw new Error( + "This server should not serve application/newlines content" + ); + } + + // Use options as a backchannel to report count. + options.recordCount = data.length; + } else { + data = data.map(wbo => wbo.id); + let start = options.offset || 0; + if (options.limit) { + data = data.slice(start, start + options.limit); + options.nextOffset = start + options.limit; + } else if (start) { + data = data.slice(start); + } + options.recordCount = data.length; + } + return JSON.stringify(data); + }, + + post(input) { + input = JSON.parse(input); + let success = []; + let failed = {}; + + // This will count records where we have an existing ServerWBO + // registered with us as successful and all other records as failed. + for (let key in input) { + let record = input[key]; + let wbo = this.wbo(record.id); + if (!wbo && this.acceptNew) { + this._log.debug( + "Creating WBO " + JSON.stringify(record.id) + " on the fly." + ); + wbo = new ServerWBO(record.id); + this.insertWBO(wbo); + } + if (wbo) { + wbo.payload = record.payload; + wbo.modified = new_timestamp(); + wbo.sortindex = record.sortindex || 0; + success.push(record.id); + } else { + failed[record.id] = "no wbo configured"; + } + } + return { modified: new_timestamp(), success, failed }; + }, + + delete(options) { + let deleted = []; + for (let wbo of Object.values(this._wbos)) { + if (this._inResultSet(wbo, options)) { + this._log.debug("Deleting " + JSON.stringify(wbo)); + deleted.push(wbo.id); + wbo.delete(); + } + } + return deleted; + }, + + // This handler sets `newModified` on the response body if the collection + // timestamp has changed. + handler() { + let self = this; + + return function (request, response) { + var statusCode = 200; + var status = "OK"; + var body; + + // Parse queryString + let options = {}; + for (let chunk of request.queryString.split("&")) { + if (!chunk) { + continue; + } + chunk = chunk.split("="); + if (chunk.length == 1) { + options[chunk[0]] = ""; + } else { + options[chunk[0]] = chunk[1]; + } + } + // The real servers return 400 if ids= is specified without a list of IDs. + if (options.hasOwnProperty("ids")) { + if (!options.ids) { + response.setStatusLine(request.httpVersion, "400", "Bad Request"); + body = "Bad Request"; + writeBytesToOutputStream(response.bodyOutputStream, body); + return; + } + options.ids = options.ids.split(","); + } + if (options.newer) { + options.newer = parseFloat(options.newer); + } + if (options.older) { + options.older = parseFloat(options.older); + } + if (options.limit) { + options.limit = parseInt(options.limit, 10); + } + if (options.offset) { + options.offset = parseInt(options.offset, 10); + } + + switch (request.method) { + case "GET": + body = self.get(options, request); + // see http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html + // for description of these headers. + let { recordCount: records, nextOffset } = options; + + self._log.info("Records: " + records + ", nextOffset: " + nextOffset); + if (records != null) { + response.setHeader("X-Weave-Records", "" + records); + } + if (nextOffset) { + response.setHeader("X-Weave-Next-Offset", "" + nextOffset); + } + response.setHeader("X-Last-Modified", "" + self.timestamp); + break; + + case "POST": + let res = self.post( + readBytesFromInputStream(request.bodyInputStream), + request + ); + body = JSON.stringify(res); + response.newModified = res.modified; + break; + + case "DELETE": + self._log.debug("Invoking ServerCollection.DELETE."); + let deleted = self.delete(options, request); + let ts = new_timestamp(); + body = JSON.stringify(ts); + response.newModified = ts; + response.deleted = deleted; + break; + } + response.setHeader("X-Weave-Timestamp", "" + new_timestamp(), false); + + // Update the collection timestamp to the appropriate modified time. + // This is either a value set by the handler, or the current time. + if (request.method != "GET") { + self.timestamp = + response.newModified >= 0 ? response.newModified : new_timestamp(); + } + response.setHeader("X-Last-Modified", "" + self.timestamp, false); + + response.setStatusLine(request.httpVersion, statusCode, status); + writeBytesToOutputStream(response.bodyOutputStream, body); + }; + }, +}; + +/* + * Test setup helpers. + */ +function sync_httpd_setup(handlers) { + handlers["/1.1/foo/storage/meta/global"] = new ServerWBO( + "global", + {} + ).handler(); + return httpd_setup(handlers); +} + +/* + * Track collection modified times. Return closures. + * + * XXX - DO NOT USE IN NEW TESTS + * + * This code has very limited and very hacky timestamp support - the test + * server now has more complete and correct support - using this helper + * may cause strangeness wrt timestamp headers and 412 responses. + */ +function track_collections_helper() { + /* + * Our tracking object. + */ + let collections = {}; + + /* + * Update the timestamp of a collection. + */ + function update_collection(coll, ts) { + _("Updating collection " + coll + " to " + ts); + let timestamp = ts || new_timestamp(); + collections[coll] = timestamp; + } + + /* + * Invoke a handler, updating the collection's modified timestamp unless + * it's a GET request. + */ + function with_updated_collection(coll, f) { + return function (request, response) { + f.call(this, request, response); + + // Update the collection timestamp to the appropriate modified time. + // This is either a value set by the handler, or the current time. + if (request.method != "GET") { + update_collection(coll, response.newModified); + } + }; + } + + /* + * Return the info/collections object. + */ + function info_collections(request, response) { + let body = "Error."; + switch (request.method) { + case "GET": + body = JSON.stringify(collections); + break; + default: + throw new Error("Non-GET on info_collections."); + } + + response.setHeader("Content-Type", "application/json"); + response.setHeader("X-Weave-Timestamp", "" + new_timestamp(), false); + response.setStatusLine(request.httpVersion, 200, "OK"); + writeBytesToOutputStream(response.bodyOutputStream, body); + } + + return { + collections, + handler: info_collections, + with_updated_collection, + update_collection, + }; +} + +// ===========================================================================// +// httpd.js-based Sync server. // +// ===========================================================================// + +/** + * In general, the preferred way of using SyncServer is to directly introspect + * it. Callbacks are available for operations which are hard to verify through + * introspection, such as deletions. + * + * One of the goals of this server is to provide enough hooks for test code to + * find out what it needs without monkeypatching. Use this object as your + * prototype, and override as appropriate. + */ +var SyncServerCallback = { + onCollectionDeleted: function onCollectionDeleted(user, collection) {}, + onItemDeleted: function onItemDeleted(user, collection, wboID) {}, + + /** + * Called at the top of every request. + * + * Allows the test to inspect the request. Hooks should be careful not to + * modify or change state of the request or they may impact future processing. + * The response is also passed so the callback can set headers etc - but care + * must be taken to not screw with the response body or headers that may + * conflict with normal operation of this server. + */ + onRequest: function onRequest(request, response) {}, +}; + +/** + * Construct a new test Sync server. Takes a callback object (e.g., + * SyncServerCallback) as input. + */ +function SyncServer(callback) { + this.callback = callback || Object.create(SyncServerCallback); + this.server = new HttpServer(); + this.started = false; + this.users = {}; + this._log = Log.repository.getLogger(SYNC_HTTP_LOGGER); + + // Install our own default handler. This allows us to mess around with the + // whole URL space. + let handler = this.server._handler; + handler._handleDefault = this.handleDefault.bind(this, handler); +} +SyncServer.prototype = { + server: null, // HttpServer. + users: null, // Map of username => {collections, password}. + + /** + * Start the SyncServer's underlying HTTP server. + * + * @param port + * The numeric port on which to start. -1 implies the default, a + * randomly chosen port. + * @param cb + * A callback function (of no arguments) which is invoked after + * startup. + */ + start: function start(port = -1, cb) { + if (this.started) { + this._log.warn("Warning: server already started on " + this.port); + return; + } + try { + this.server.start(port); + let i = this.server.identity; + this.port = i.primaryPort; + this.baseURI = + i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort + "/"; + this.started = true; + if (cb) { + cb(); + } + } catch (ex) { + _("=========================================="); + _("Got exception starting Sync HTTP server."); + _("Error: " + Log.exceptionStr(ex)); + _("Is there a process already listening on port " + port + "?"); + _("=========================================="); + do_throw(ex); + } + }, + + /** + * Stop the SyncServer's HTTP server. + * + * @param cb + * A callback function. Invoked after the server has been stopped. + * + */ + stop: function stop(cb) { + if (!this.started) { + this._log.warn( + "SyncServer: Warning: server not running. Can't stop me now!" + ); + return; + } + + this.server.stop(cb); + this.started = false; + }, + + /** + * Return a server timestamp for a record. + * The server returns timestamps with 1/100 sec granularity. Note that this is + * subject to change: see Bug 650435. + */ + timestamp: function timestamp() { + return new_timestamp(); + }, + + /** + * Create a new user, complete with an empty set of collections. + * + * @param username + * The username to use. An Error will be thrown if a user by that name + * already exists. + * @param password + * A password string. + * + * @return a user object, as would be returned by server.user(username). + */ + registerUser: function registerUser(username, password) { + if (username in this.users) { + throw new Error("User already exists."); + } + this.users[username] = { + password, + collections: {}, + }; + return this.user(username); + }, + + userExists: function userExists(username) { + return username in this.users; + }, + + getCollection: function getCollection(username, collection) { + return this.users[username].collections[collection]; + }, + + _insertCollection: function _insertCollection(collections, collection, wbos) { + let coll = new ServerCollection(wbos, true); + coll.collectionHandler = coll.handler(); + collections[collection] = coll; + return coll; + }, + + createCollection: function createCollection(username, collection, wbos) { + if (!(username in this.users)) { + throw new Error("Unknown user."); + } + let collections = this.users[username].collections; + if (collection in collections) { + throw new Error("Collection already exists."); + } + return this._insertCollection(collections, collection, wbos); + }, + + /** + * Accept a map like the following: + * { + * meta: {global: {version: 1, ...}}, + * crypto: {"keys": {}, foo: {bar: 2}}, + * bookmarks: {} + * } + * to cause collections and WBOs to be created. + * If a collection already exists, no error is raised. + * If a WBO already exists, it will be updated to the new contents. + */ + createContents: function createContents(username, collections) { + if (!(username in this.users)) { + throw new Error("Unknown user."); + } + let userCollections = this.users[username].collections; + for (let [id, contents] of Object.entries(collections)) { + let coll = + userCollections[id] || this._insertCollection(userCollections, id); + for (let [wboID, payload] of Object.entries(contents)) { + coll.insert(wboID, payload); + } + } + }, + + /** + * Insert a WBO in an existing collection. + */ + insertWBO: function insertWBO(username, collection, wbo) { + if (!(username in this.users)) { + throw new Error("Unknown user."); + } + let userCollections = this.users[username].collections; + if (!(collection in userCollections)) { + throw new Error("Unknown collection."); + } + userCollections[collection].insertWBO(wbo); + return wbo; + }, + + /** + * Delete all of the collections for the named user. + * + * @param username + * The name of the affected user. + * + * @return a timestamp. + */ + deleteCollections: function deleteCollections(username) { + if (!(username in this.users)) { + throw new Error("Unknown user."); + } + let userCollections = this.users[username].collections; + for (let name in userCollections) { + let coll = userCollections[name]; + this._log.trace("Bulk deleting " + name + " for " + username + "..."); + coll.delete({}); + } + this.users[username].collections = {}; + return this.timestamp(); + }, + + /** + * Simple accessor to allow collective binding and abbreviation of a bunch of + * methods. Yay! + * Use like this: + * + * let u = server.user("john"); + * u.collection("bookmarks").wbo("abcdefg").payload; // Etc. + * + * @return a proxy for the user data stored in this server. + */ + user: function user(username) { + let collection = this.getCollection.bind(this, username); + let createCollection = this.createCollection.bind(this, username); + let createContents = this.createContents.bind(this, username); + let modified = function (collectionName) { + return collection(collectionName).timestamp; + }; + let deleteCollections = this.deleteCollections.bind(this, username); + return { + collection, + createCollection, + createContents, + deleteCollections, + modified, + }; + }, + + /* + * Regular expressions for splitting up Sync request paths. + * Sync URLs are of the form: + * /$apipath/$version/$user/$further + * where $further is usually: + * storage/$collection/$wbo + * or + * storage/$collection + * or + * info/$op + * We assume for the sake of simplicity that $apipath is empty. + * + * N.B., we don't follow any kind of username spec here, because as far as I + * can tell there isn't one. See Bug 689671. Instead we follow the Python + * server code. + * + * Path: [all, version, username, first, rest] + * Storage: [all, collection?, id?] + */ + pathRE: + /^\/([0-9]+(?:\.[0-9]+)?)\/([-._a-zA-Z0-9]+)(?:\/([^\/]+)(?:\/(.+))?)?$/, + storageRE: /^([-_a-zA-Z0-9]+)(?:\/([-_a-zA-Z0-9]+)\/?)?$/, + + defaultHeaders: {}, + + /** + * HTTP response utility. + */ + respond: function respond(req, resp, code, status, body, headers) { + resp.setStatusLine(req.httpVersion, code, status); + if (!headers) { + headers = this.defaultHeaders; + } + for (let header in headers) { + let value = headers[header]; + resp.setHeader(header, value); + } + resp.setHeader("X-Weave-Timestamp", "" + this.timestamp(), false); + writeBytesToOutputStream(resp.bodyOutputStream, body); + }, + + /** + * This is invoked by the HttpServer. `this` is bound to the SyncServer; + * `handler` is the HttpServer's handler. + * + * TODO: need to use the correct Sync API response codes and errors here. + * TODO: Basic Auth. + * TODO: check username in path against username in BasicAuth. + */ + handleDefault: function handleDefault(handler, req, resp) { + try { + this._handleDefault(handler, req, resp); + } catch (e) { + if (e instanceof HttpError) { + this.respond(req, resp, e.code, e.description, "", {}); + } else { + throw e; + } + } + }, + + _handleDefault: function _handleDefault(handler, req, resp) { + this._log.debug( + "SyncServer: Handling request: " + req.method + " " + req.path + ); + + if (this.callback.onRequest) { + this.callback.onRequest(req, resp); + } + + let parts = this.pathRE.exec(req.path); + if (!parts) { + this._log.debug("SyncServer: Unexpected request: bad URL " + req.path); + throw HTTP_404; + } + + let [, version, username, first, rest] = parts; + // Doing a float compare of the version allows for us to pretend there was + // a node-reassignment - eg, we could re-assign from "1.1/user/" to + // "1.10/user" - this server will then still accept requests with the new + // URL while any code in sync itself which compares URLs will see a + // different URL. + if (parseFloat(version) != parseFloat(SYNC_API_VERSION)) { + this._log.debug("SyncServer: Unknown version."); + throw HTTP_404; + } + + if (!this.userExists(username)) { + this._log.debug("SyncServer: Unknown user."); + throw HTTP_401; + } + + // Hand off to the appropriate handler for this path component. + if (first in this.toplevelHandlers) { + let newHandler = this.toplevelHandlers[first]; + return newHandler.call( + this, + newHandler, + req, + resp, + version, + username, + rest + ); + } + this._log.debug("SyncServer: Unknown top-level " + first); + throw HTTP_404; + }, + + /** + * Compute the object that is returned for an info/collections request. + */ + infoCollections: function infoCollections(username) { + let responseObject = {}; + let colls = this.users[username].collections; + for (let coll in colls) { + responseObject[coll] = colls[coll].timestamp; + } + this._log.trace( + "SyncServer: info/collections returning " + JSON.stringify(responseObject) + ); + return responseObject; + }, + + /** + * Collection of the handler methods we use for top-level path components. + */ + toplevelHandlers: { + storage: function handleStorage( + handler, + req, + resp, + version, + username, + rest + ) { + let respond = this.respond.bind(this, req, resp); + if (!rest || !rest.length) { + this._log.debug( + "SyncServer: top-level storage " + req.method + " request." + ); + + // TODO: verify if this is spec-compliant. + if (req.method != "DELETE") { + respond(405, "Method Not Allowed", "[]", { Allow: "DELETE" }); + return undefined; + } + + // Delete all collections and track the timestamp for the response. + let timestamp = this.user(username).deleteCollections(); + + // Return timestamp and OK for deletion. + respond(200, "OK", JSON.stringify(timestamp)); + return undefined; + } + + let match = this.storageRE.exec(rest); + if (!match) { + this._log.warn("SyncServer: Unknown storage operation " + rest); + throw HTTP_404; + } + let [, collection, wboID] = match; + let coll = this.getCollection(username, collection); + + let checkXIUSFailure = () => { + if (req.hasHeader("x-if-unmodified-since")) { + let xius = parseFloat(req.getHeader("x-if-unmodified-since")); + // Sadly the way our tests are setup, we often end up with xius of + // zero (typically when syncing just one engine, so the date from + // info/collections isn't used) - so we allow that to work. + // Further, the Python server treats non-existing collections as + // having a timestamp of 0. + let collTimestamp = coll ? coll.timestamp : 0; + if (xius && xius < collTimestamp) { + this._log.info( + `x-if-unmodified-since mismatch - request wants ${xius} but our collection has ${collTimestamp}` + ); + respond(412, "precondition failed", "precondition failed"); + return true; + } + } + return false; + }; + + switch (req.method) { + case "GET": { + if (!coll) { + if (wboID) { + respond(404, "Not found", "Not found"); + return undefined; + } + // *cries inside*: - apparently the real sync server returned 200 + // here for some time, then returned 404 for some time (bug 687299), + // and now is back to 200 (bug 963332). + respond(200, "OK", "[]"); + return undefined; + } + if (!wboID) { + return coll.collectionHandler(req, resp); + } + let wbo = coll.wbo(wboID); + if (!wbo) { + respond(404, "Not found", "Not found"); + return undefined; + } + return wbo.handler()(req, resp); + } + case "DELETE": { + if (!coll) { + respond(200, "OK", "{}"); + return undefined; + } + if (checkXIUSFailure()) { + return undefined; + } + if (wboID) { + let wbo = coll.wbo(wboID); + if (wbo) { + wbo.delete(); + this.callback.onItemDeleted(username, collection, wboID); + } + respond(200, "OK", "{}"); + return undefined; + } + coll.collectionHandler(req, resp); + + // Spot if this is a DELETE for some IDs, and don't blow away the + // whole collection! + // + // We already handled deleting the WBOs by invoking the deleted + // collection's handler. However, in the case of + // + // DELETE storage/foobar + // + // we also need to remove foobar from the collections map. This + // clause tries to differentiate the above request from + // + // DELETE storage/foobar?ids=foo,baz + // + // and do the right thing. + // TODO: less hacky method. + if (-1 == req.queryString.indexOf("ids=")) { + // When you delete the entire collection, we drop it. + this._log.debug("Deleting entire collection."); + delete this.users[username].collections[collection]; + this.callback.onCollectionDeleted(username, collection); + } + + // Notify of item deletion. + let deleted = resp.deleted || []; + for (let i = 0; i < deleted.length; ++i) { + this.callback.onItemDeleted(username, collection, deleted[i]); + } + return undefined; + } + case "PUT": + // PUT and POST have slightly different XIUS semantics - for PUT, + // the check is against the item, whereas for POST it is against + // the collection. So first, a special-case for PUT. + if (req.hasHeader("x-if-unmodified-since")) { + let xius = parseFloat(req.getHeader("x-if-unmodified-since")); + // treat and xius of zero as if it wasn't specified - this happens + // in some of our tests for a new collection. + if (xius > 0) { + let wbo = coll.wbo(wboID); + if (xius < wbo.modified) { + this._log.info( + `x-if-unmodified-since mismatch - request wants ${xius} but wbo has ${wbo.modified}` + ); + respond(412, "precondition failed", "precondition failed"); + return undefined; + } + wbo.handler()(req, resp); + coll.timestamp = resp.newModified; + return resp; + } + } + // fall through to post. + case "POST": + if (checkXIUSFailure()) { + return undefined; + } + if (!coll) { + coll = this.createCollection(username, collection); + } + + if (wboID) { + let wbo = coll.wbo(wboID); + if (!wbo) { + this._log.trace( + "SyncServer: creating WBO " + collection + "/" + wboID + ); + wbo = coll.insert(wboID); + } + // Rather than instantiate each WBO's handler function, do it once + // per request. They get hit far less often than do collections. + wbo.handler()(req, resp); + coll.timestamp = resp.newModified; + return resp; + } + return coll.collectionHandler(req, resp); + default: + throw new Error("Request method " + req.method + " not implemented."); + } + }, + + info: function handleInfo(handler, req, resp, version, username, rest) { + switch (rest) { + case "collections": + let body = JSON.stringify(this.infoCollections(username)); + this.respond(req, resp, 200, "OK", body, { + "Content-Type": "application/json", + }); + return; + case "collection_usage": + case "collection_counts": + case "quota": + // TODO: implement additional info methods. + this.respond(req, resp, 200, "OK", "TODO"); + return; + default: + // TODO + this._log.warn("SyncServer: Unknown info operation " + rest); + throw HTTP_404; + } + }, + }, +}; + +/** + * Test helper. + */ +function serverForUsers(users, contents, callback) { + let server = new SyncServer(callback); + for (let [user, pass] of Object.entries(users)) { + server.registerUser(user, pass); + server.createContents(user, contents); + } + server.start(); + return server; +} diff --git a/services/sync/tests/unit/missing-sourceuri.json b/services/sync/tests/unit/missing-sourceuri.json new file mode 100644 index 0000000000..dcd487726a --- /dev/null +++ b/services/sync/tests/unit/missing-sourceuri.json @@ -0,0 +1,20 @@ +{ + "next": null, + "results": [ + { + "name": "Restartless Test Extension", + "type": "extension", + "guid": "missing-sourceuri@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485 + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/missing-xpi-search.json b/services/sync/tests/unit/missing-xpi-search.json new file mode 100644 index 0000000000..55f6432b29 --- /dev/null +++ b/services/sync/tests/unit/missing-xpi-search.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Non-Restartless Test Extension", + "type": "extension", + "guid": "missing-xpi@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485, + "url": "http://127.0.0.1:8888/THIS_DOES_NOT_EXIST.xpi" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/prefs_test_prefs_store.js b/services/sync/tests/unit/prefs_test_prefs_store.js new file mode 100644 index 0000000000..63851a6934 --- /dev/null +++ b/services/sync/tests/unit/prefs_test_prefs_store.js @@ -0,0 +1,47 @@ +// This is a "preferences" file used by test_prefs_store.js + +/* global pref, user_pref */ + +// The prefs that control what should be synced. +// Most of these are "default" prefs, so the value itself will not sync. +pref("services.sync.prefs.sync.testing.int", true); +pref("services.sync.prefs.sync.testing.string", true); +pref("services.sync.prefs.sync.testing.bool", true); +pref("services.sync.prefs.sync.testing.dont.change", true); +// This is a default pref, but has the special "sync-seen" pref. +pref("services.sync.prefs.sync.testing.seen", true); +pref("services.sync.prefs.sync-seen.testing.seen", false); + +// this one is a user pref, so it *will* sync. +user_pref("services.sync.prefs.sync.testing.turned.off", false); +pref("services.sync.prefs.sync.testing.nonexistent", true); +pref("services.sync.prefs.sync.testing.default", true); +pref("services.sync.prefs.sync.testing.synced.url", true); +// We shouldn't sync the URL, or the flag that says we should sync the pref +// (otherwise some other client might overwrite our local value). +user_pref("services.sync.prefs.sync.testing.unsynced.url", true); + +// The preference values - these are all user_prefs, otherwise their value +// will not be synced. +user_pref("testing.int", 123); +user_pref("testing.string", "ohai"); +user_pref("testing.bool", true); +user_pref("testing.dont.change", "Please don't change me."); +user_pref("testing.turned.off", "I won't get synced."); +user_pref("testing.not.turned.on", "I won't get synced either!"); +// Some url we don't want to sync +user_pref( + "testing.unsynced.url", + "moz-extension://d5d31b00-b944-4afb-bd3d-d0326551a0ae" +); +user_pref("testing.synced.url", "https://www.example.com"); + +// A pref that exists but still has the default value - will be synced with +// null as the value. +pref("testing.default", "I'm the default value"); + +// A pref that has the default value - it will start syncing as soon as +// we see a change, even if the change is to the default. +pref("testing.seen", "the value"); + +// A pref that shouldn't be synced diff --git a/services/sync/tests/unit/rewrite-search.json b/services/sync/tests/unit/rewrite-search.json new file mode 100644 index 0000000000..740b6f2c30 --- /dev/null +++ b/services/sync/tests/unit/rewrite-search.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "Rewrite Test Extension", + "type": "extension", + "guid": "rewrite@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 485, + "url": "http://127.0.0.1:8888/require.xpi?src=api" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/sync_ping_schema.json b/services/sync/tests/unit/sync_ping_schema.json new file mode 100644 index 0000000000..a9866e3550 --- /dev/null +++ b/services/sync/tests/unit/sync_ping_schema.json @@ -0,0 +1,262 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "schema for Sync pings, documentation avaliable in toolkit/components/telemetry/docs/sync-ping.rst", + "type": "object", + "additionalProperties": false, + "required": ["version", "syncs", "why", "uid"], + "properties": { + "version": { "type": "integer", "minimum": 0 }, + "os": { "type": "object" }, + "discarded": { "type": "integer", "minimum": 1 }, + "why": { "enum": ["shutdown", "schedule", "idchange"] }, + "uid": { + "type": "string", + "pattern": "^[0-9a-f]{32}$" + }, + "deviceID": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "devices": { + "type": "array", + "items": { "$ref": "#/definitions/device" } + }, + "sessionStartDate": { "type": "string" }, + "syncs": { + "type": "array", + "minItems": 0, + "items": { "$ref": "#/definitions/payload" } + }, + "syncNodeType": { + "type": "string" + }, + "events": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/event" } + }, + "migrations": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/migration" } + }, + "histograms": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "min": { "type": "integer" }, + "max": { "type": "integer" }, + "histogram_type": { "type": "integer" }, + "sum": { "type": "integer" }, + "ranges": { "type": "array" }, + "counts": { "type": "array" } + } + } + } + }, + "definitions": { + "payload": { + "type": "object", + "additionalProperties": false, + "required": ["when", "took"], + "properties": { + "didLogin": { "type": "boolean" }, + "when": { "type": "integer" }, + "status": { + "type": "object", + "anyOf": [{ "required": ["sync"] }, { "required": ["service"] }], + "additionalProperties": false, + "properties": { + "sync": { "type": "string" }, + "service": { "type": "string" } + } + }, + "why": { "type": "string" }, + "took": { "type": "integer", "minimum": -1 }, + "failureReason": { "$ref": "#/definitions/error" }, + "engines": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/engine" } + } + } + }, + "device": { + "required": ["id"], + "additionalProperties": false, + "type": "object", + "properties": { + "id": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "os": { "type": "string" }, + "version": { "type": "string" }, + "type": { "type": "string" }, + "syncID": { "type": "string", "pattern": "^[0-9a-f]{64}$" } + } + }, + "engine": { + "required": ["name"], + "additionalProperties": false, + "properties": { + "failureReason": { "$ref": "#/definitions/error" }, + "name": { "type": "string" }, + "took": { "type": "integer", "minimum": 1 }, + "status": { "type": "string" }, + "incoming": { + "type": "object", + "additionalProperties": false, + "anyOf": [{ "required": ["applied"] }, { "required": ["failed"] }], + "properties": { + "applied": { "type": "integer", "minimum": 1 }, + "failed": { "type": "integer", "minimum": 1 }, + "failedReasons": { + "type": "array", + "minItems": 1, + "$ref": "#/definitions/namedCount" + } + } + }, + "outgoing": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/definitions/outgoingBatch" } + }, + "steps": { + "type": "array", + "minItems": 1, + "$ref": "#/definitions/step" + }, + "validation": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { "required": ["checked"] }, + { "required": ["failureReason"] } + ], + "properties": { + "checked": { "type": "integer", "minimum": 0 }, + "failureReason": { "$ref": "#/definitions/error" }, + "took": { "type": "integer" }, + "version": { "type": "integer" }, + "problems": { + "type": "array", + "minItems": 1, + "$ref": "#/definitions/namedCount" + } + } + } + } + }, + "outgoingBatch": { + "type": "object", + "additionalProperties": false, + "anyOf": [{ "required": ["sent"] }, { "required": ["failed"] }], + "properties": { + "sent": { "type": "integer", "minimum": 1 }, + "failed": { "type": "integer", "minimum": 1 }, + "failedReasons": { + "type": "array", + "minItems": 1, + "$ref": "#/definitions/namedCount" + } + } + }, + "event": { + "type": "array", + "minItems": 4, + "maxItems": 6 + }, + "migration": { + "oneOf": [{ "$ref": "#/definitions/webextMigration" }] + }, + "webextMigration": { + "required": ["type"], + "properties": { + "type": { "enum": ["webext-storage"] }, + "entries": { "type": "integer" }, + "entriesSuccessful": { "type": "integer" }, + "extensions": { "type": "integer" }, + "extensionsSuccessful": { "type": "integer" }, + "openFailure": { "type": "boolean" } + } + }, + "error": { + "oneOf": [ + { "$ref": "#/definitions/httpError" }, + { "$ref": "#/definitions/nsError" }, + { "$ref": "#/definitions/shutdownError" }, + { "$ref": "#/definitions/authError" }, + { "$ref": "#/definitions/otherError" }, + { "$ref": "#/definitions/unexpectedError" }, + { "$ref": "#/definitions/sqlError" } + ] + }, + "httpError": { + "required": ["name", "code"], + "properties": { + "name": { "enum": ["httperror"] }, + "code": { "type": "integer" } + } + }, + "nsError": { + "required": ["name", "code"], + "properties": { + "name": { "enum": ["nserror"] }, + "code": { "type": "integer" } + } + }, + "shutdownError": { + "required": ["name"], + "properties": { + "name": { "enum": ["shutdownerror"] } + } + }, + "authError": { + "required": ["name"], + "properties": { + "name": { "enum": ["autherror"] }, + "from": { "enum": ["tokenserver", "fxaccounts", "hawkclient"] } + } + }, + "otherError": { + "required": ["name"], + "properties": { + "name": { "enum": ["othererror"] }, + "error": { "type": "string" } + } + }, + "unexpectedError": { + "required": ["name"], + "properties": { + "name": { "enum": ["unexpectederror"] }, + "error": { "type": "string" } + } + }, + "sqlError": { + "required": ["name"], + "properties": { + "name": { "enum": ["sqlerror"] }, + "code": { "type": "integer" } + } + }, + "step": { + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "took": { "type": "integer", "minimum": 1 }, + "counts": { + "type": "array", + "minItems": 1, + "$ref": "#/definitions/namedCount" + } + } + }, + "namedCount": { + "required": ["name", "count"], + "properties": { + "name": { "type": "string" }, + "count": { "type": "integer" } + } + } + } +} diff --git a/services/sync/tests/unit/systemaddon-search.json b/services/sync/tests/unit/systemaddon-search.json new file mode 100644 index 0000000000..a812714918 --- /dev/null +++ b/services/sync/tests/unit/systemaddon-search.json @@ -0,0 +1,21 @@ +{ + "next": null, + "results": [ + { + "name": "System Add-on Test", + "type": "extension", + "guid": "system1@tests.mozilla.org", + "current_version": { + "version": "1.0", + "files": [ + { + "platform": "all", + "size": 999, + "url": "http://127.0.0.1:8888/system.xpi" + } + ] + }, + "last_updated": "2011-09-05T20:42:09Z" + } + ] +} diff --git a/services/sync/tests/unit/test_412.js b/services/sync/tests/unit/test_412.js new file mode 100644 index 0000000000..de0a2c087e --- /dev/null +++ b/services/sync/tests/unit/test_412.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { RotaryEngine } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/rotaryengine.sys.mjs" +); + +add_task(async function test_412_not_treated_as_failure() { + await Service.engineManager.register(RotaryEngine); + let engine = Service.engineManager.get("rotary"); + + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + // add an item to the server to the first sync advances lastModified. + let collection = server.getCollection("foo", "rotary"); + let payload = encryptPayload({ + id: "existing", + something: "existing record", + }); + collection.insert("existing", payload); + + let promiseObserved = promiseOneObserver("weave:engine:sync:finish"); + try { + // Do sync. + _("initial sync to initialize the world"); + await Service.sync(); + + // create a new record that should be uploaded and arrange for our lastSync + // timestamp to be wrong so we get a 412. + engine._store.items = { new: "new record" }; + await engine._tracker.addChangedID("new", 0); + + let saw412 = false; + let _uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async () => { + let lastSync = await engine.getLastSync(); + await engine.setLastSync(lastSync - 2); + try { + await _uploadOutgoing.call(engine); + } catch (ex) { + saw412 = ex.status == 412; + throw ex; + } + }; + _("Second sync - expecting a 412"); + await Service.sync(); + await promiseObserved; + ok(saw412, "did see a 412 error"); + // But service status should be OK as the 412 shouldn't be treated as an error. + equal(Service.status.service, STATUS_OK); + } finally { + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_addon_utils.js b/services/sync/tests/unit/test_addon_utils.js new file mode 100644 index 0000000000..c039bee16c --- /dev/null +++ b/services/sync/tests/unit/test_addon_utils.js @@ -0,0 +1,156 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonUtils } = ChromeUtils.importESModule( + "resource://services-sync/addonutils.sys.mjs" +); + +const HTTP_PORT = 8888; +const SERVER_ADDRESS = "http://127.0.0.1:8888"; + +Services.prefs.setStringPref( + "extensions.getAddons.get.url", + SERVER_ADDRESS + "/search/guid:%IDS%" +); + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); +}); + +function createAndStartHTTPServer(port = HTTP_PORT) { + try { + let server = new HttpServer(); + + server.registerFile( + "/search/guid:missing-sourceuri%40tests.mozilla.org", + do_get_file("missing-sourceuri.json") + ); + + server.registerFile( + "/search/guid:rewrite%40tests.mozilla.org", + do_get_file("rewrite-search.json") + ); + + server.start(port); + + return server; + } catch (ex) { + _("Got exception starting HTTP server on port " + port); + _("Error: " + Log.exceptionStr(ex)); + do_throw(ex); + } + return null; /* not hit, but keeps eslint happy! */ +} + +function run_test() { + syncTestLogging(); + + run_next_test(); +} + +add_task(async function test_handle_empty_source_uri() { + _("Ensure that search results without a sourceURI are properly ignored."); + + let server = createAndStartHTTPServer(); + + const ID = "missing-sourceuri@tests.mozilla.org"; + + const result = await AddonUtils.installAddons([ + { id: ID, requireSecureURI: false }, + ]); + + Assert.ok("installedIDs" in result); + Assert.equal(0, result.installedIDs.length); + + Assert.ok("skipped" in result); + Assert.ok(result.skipped.includes(ID)); + + await promiseStopServer(server); +}); + +add_test(function test_ignore_untrusted_source_uris() { + _("Ensures that source URIs from insecure schemes are rejected."); + + const bad = [ + "http://example.com/foo.xpi", + "ftp://example.com/foo.xpi", + "silly://example.com/foo.xpi", + ]; + + const good = ["https://example.com/foo.xpi"]; + + for (let s of bad) { + let sourceURI = Services.io.newURI(s); + let addon = { sourceURI, name: "bad", id: "bad" }; + + let canInstall = AddonUtils.canInstallAddon(addon); + Assert.ok(!canInstall, "Correctly rejected a bad URL"); + } + + for (let s of good) { + let sourceURI = Services.io.newURI(s); + let addon = { sourceURI, name: "good", id: "good" }; + + let canInstall = AddonUtils.canInstallAddon(addon); + Assert.ok(canInstall, "Correctly accepted a good URL"); + } + run_next_test(); +}); + +add_task(async function test_source_uri_rewrite() { + _("Ensure that a 'src=api' query string is rewritten to 'src=sync'"); + + // This tests for conformance with bug 708134 so server-side metrics aren't + // skewed. + + // We resort to monkeypatching because of the API design. + let oldFunction = + Object.getPrototypeOf(AddonUtils).installAddonFromSearchResult; + + let installCalled = false; + Object.getPrototypeOf(AddonUtils).installAddonFromSearchResult = + async function testInstallAddon(addon, metadata) { + Assert.equal( + SERVER_ADDRESS + "/require.xpi?src=sync", + addon.sourceURI.spec + ); + + installCalled = true; + + const install = await AddonUtils.getInstallFromSearchResult(addon); + Assert.equal( + SERVER_ADDRESS + "/require.xpi?src=sync", + install.sourceURI.spec + ); + Assert.deepEqual( + install.installTelemetryInfo, + { source: "sync" }, + "Got the expected installTelemetryInfo" + ); + + return { id: addon.id, addon, install }; + }; + + let server = createAndStartHTTPServer(); + + let installOptions = { + id: "rewrite@tests.mozilla.org", + requireSecureURI: false, + }; + await AddonUtils.installAddons([installOptions]); + + Assert.ok(installCalled); + Object.getPrototypeOf(AddonUtils).installAddonFromSearchResult = oldFunction; + + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_addons_engine.js b/services/sync/tests/unit/test_addons_engine.js new file mode 100644 index 0000000000..d957ed8fd3 --- /dev/null +++ b/services/sync/tests/unit/test_addons_engine.js @@ -0,0 +1,277 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonManager } = ChromeUtils.importESModule( + "resource://gre/modules/AddonManager.sys.mjs" +); +const { CHANGE_INSTALLED } = ChromeUtils.importESModule( + "resource://services-sync/addonsreconciler.sys.mjs" +); +const { AddonsEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/addons.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +Services.prefs.setStringPref( + "extensions.getAddons.get.url", + "http://localhost:8888/search/guid:%IDS%" +); +Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false); + +let engine; +let syncID; +let reconciler; +let tracker; + +AddonTestUtils.init(this); + +const ADDON_ID = "addon1@tests.mozilla.org"; +const XPI = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + name: "Test 1", + description: "Test Description", + browser_specific_settings: { gecko: { id: ADDON_ID } }, + }, +}); + +async function resetReconciler() { + reconciler._addons = {}; + reconciler._changes = []; + + await reconciler.saveState(); + + await tracker.clearChangedIDs(); +} + +add_task(async function setup() { + AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" + ); + AddonTestUtils.overrideCertDB(); + await AddonTestUtils.promiseStartupManager(); + + await Service.engineManager.register(AddonsEngine); + engine = Service.engineManager.get("addons"); + syncID = await engine.resetLocalSyncID(); + reconciler = engine._reconciler; + tracker = engine._tracker; + + reconciler.startListening(); + + // Don't flush to disk in the middle of an event listener! + // This causes test hangs on WinXP. + reconciler._shouldPersist = false; + + await resetReconciler(); +}); + +// This is a basic sanity test for the unit test itself. If this breaks, the +// add-ons API likely changed upstream. +add_task(async function test_addon_install() { + _("Ensure basic add-on APIs work as expected."); + + let install = await AddonManager.getInstallForFile(XPI); + Assert.notEqual(install, null); + Assert.equal(install.type, "extension"); + Assert.equal(install.name, "Test 1"); + + await resetReconciler(); +}); + +add_task(async function test_find_dupe() { + _("Ensure the _findDupe() implementation is sane."); + + // This gets invoked at the top of sync, which is bypassed by this + // test, so we do it manually. + await engine._refreshReconcilerState(); + + let addon = await installAddon(XPI, reconciler); + + let record = { + id: Utils.makeGUID(), + addonID: ADDON_ID, + enabled: true, + applicationID: Services.appinfo.ID, + source: "amo", + }; + + let dupe = await engine._findDupe(record); + Assert.equal(addon.syncGUID, dupe); + + record.id = addon.syncGUID; + dupe = await engine._findDupe(record); + Assert.equal(null, dupe); + + await uninstallAddon(addon, reconciler); + await resetReconciler(); +}); + +add_task(async function test_get_changed_ids() { + let timerPrecision = Services.prefs.getBoolPref( + "privacy.reduceTimerPrecision" + ); + Services.prefs.setBoolPref("privacy.reduceTimerPrecision", false); + + registerCleanupFunction(function () { + Services.prefs.setBoolPref("privacy.reduceTimerPrecision", timerPrecision); + }); + + _("Ensure getChangedIDs() has the appropriate behavior."); + + _("Ensure getChangedIDs() returns an empty object by default."); + let changes = await engine.getChangedIDs(); + Assert.equal("object", typeof changes); + Assert.equal(0, Object.keys(changes).length); + + _("Ensure tracker changes are populated."); + let now = new Date(); + let changeTime = now.getTime() / 1000; + let guid1 = Utils.makeGUID(); + await tracker.addChangedID(guid1, changeTime); + + changes = await engine.getChangedIDs(); + Assert.equal("object", typeof changes); + Assert.equal(1, Object.keys(changes).length); + Assert.ok(guid1 in changes); + Assert.equal(changeTime, changes[guid1]); + + await tracker.clearChangedIDs(); + + _("Ensure reconciler changes are populated."); + let addon = await installAddon(XPI, reconciler); + await tracker.clearChangedIDs(); // Just in case. + changes = await engine.getChangedIDs(); + Assert.equal("object", typeof changes); + Assert.equal(1, Object.keys(changes).length); + Assert.ok(addon.syncGUID in changes); + _( + "Change time: " + changeTime + ", addon change: " + changes[addon.syncGUID] + ); + Assert.ok(changes[addon.syncGUID] >= changeTime); + + let oldTime = changes[addon.syncGUID]; + let guid2 = addon.syncGUID; + await uninstallAddon(addon, reconciler); + changes = await engine.getChangedIDs(); + Assert.equal(1, Object.keys(changes).length); + Assert.ok(guid2 in changes); + Assert.ok(changes[guid2] > oldTime); + + _("Ensure non-syncable add-ons aren't picked up by reconciler changes."); + reconciler._addons = {}; + reconciler._changes = []; + let record = { + id: "DUMMY", + guid: Utils.makeGUID(), + enabled: true, + installed: true, + modified: new Date(), + type: "UNSUPPORTED", + scope: 0, + foreignInstall: false, + }; + reconciler.addons.DUMMY = record; + await reconciler._addChange(record.modified, CHANGE_INSTALLED, record); + + changes = await engine.getChangedIDs(); + _(JSON.stringify(changes)); + Assert.equal(0, Object.keys(changes).length); + + await resetReconciler(); +}); + +add_task(async function test_disabled_install_semantics() { + _("Ensure that syncing a disabled add-on preserves proper state."); + + // This is essentially a test for bug 712542, which snuck into the original + // add-on sync drop. It ensures that when an add-on is installed that the + // disabled state and incoming syncGUID is preserved, even on the next sync. + const USER = "foo"; + const PASSWORD = "password"; + + let server = new SyncServer(); + server.start(); + await SyncTestingInfrastructure(server, USER, PASSWORD); + + await generateNewKeys(Service.collectionKeys); + + let contents = { + meta: { + global: { engines: { addons: { version: engine.version, syncID } } }, + }, + crypto: {}, + addons: {}, + }; + + server.registerUser(USER, "password"); + server.createContents(USER, contents); + + let amoServer = new HttpServer(); + amoServer.registerFile( + "/search/guid:addon1%40tests.mozilla.org", + do_get_file("addon1-search.json") + ); + + amoServer.registerFile("/addon1.xpi", XPI); + amoServer.start(8888); + + // Insert an existing record into the server. + let id = Utils.makeGUID(); + let now = Date.now() / 1000; + + let record = encryptPayload({ + id, + applicationID: Services.appinfo.ID, + addonID: ADDON_ID, + enabled: false, + deleted: false, + source: "amo", + }); + let wbo = new ServerWBO(id, record, now - 2); + server.insertWBO(USER, "addons", wbo); + + _("Performing sync of add-ons engine."); + await engine._sync(); + + // At this point the non-restartless extension should be staged for install. + + // Don't need this server any more. + await promiseStopServer(amoServer); + + // We ensure the reconciler has recorded the proper ID and enabled state. + let addon = reconciler.getAddonStateFromSyncGUID(id); + Assert.notEqual(null, addon); + Assert.equal(false, addon.enabled); + + // We fake an app restart and perform another sync, just to make sure things + // are sane. + await AddonTestUtils.promiseRestartManager(); + + let collection = server.getCollection(USER, "addons"); + engine.lastModified = collection.timestamp; + await engine._sync(); + + // The client should not upload a new record. The old record should be + // retained and unmodified. + Assert.equal(1, collection.count()); + + let payload = collection.payloads()[0]; + Assert.notEqual(null, collection.wbo(id)); + Assert.equal(ADDON_ID, payload.addonID); + Assert.ok(!payload.enabled); + + await promiseStopServer(server); +}); + +add_test(function cleanup() { + // There's an xpcom-shutdown hook for this, but let's give this a shot. + reconciler.stopListening(); + run_next_test(); +}); diff --git a/services/sync/tests/unit/test_addons_reconciler.js b/services/sync/tests/unit/test_addons_reconciler.js new file mode 100644 index 0000000000..c72b18b00e --- /dev/null +++ b/services/sync/tests/unit/test_addons_reconciler.js @@ -0,0 +1,209 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonsReconciler, CHANGE_INSTALLED, CHANGE_UNINSTALLED } = + ChromeUtils.importESModule( + "resource://services-sync/addonsreconciler.sys.mjs" + ); +const { AddonsEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/addons.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); +AddonTestUtils.overrideCertDB(); + +const ADDON_ID = "addon1@tests.mozilla.org"; +const XPI = AddonTestUtils.createTempWebExtensionFile({ + manifest: { + name: "Test 1", + description: "Test Description", + browser_specific_settings: { gecko: { id: ADDON_ID } }, + }, +}); + +function makeAddonsReconciler() { + const log = Service.engineManager.get("addons")._log; + const queueCaller = Async.asyncQueueCaller(log); + return new AddonsReconciler(queueCaller); +} + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); + Svc.PrefBranch.setBoolPref("engine.addons", true); + await Service.engineManager.register(AddonsEngine); +}); + +add_task(async function test_defaults() { + _("Ensure new objects have reasonable defaults."); + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + + Assert.ok(!reconciler._listening); + Assert.equal("object", typeof reconciler.addons); + Assert.equal(0, Object.keys(reconciler.addons).length); + Assert.equal(0, reconciler._changes.length); + Assert.equal(0, reconciler._listeners.length); +}); + +add_task(async function test_load_state_empty_file() { + _("Ensure loading from a missing file results in defaults being set."); + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + + let loaded = await reconciler.loadState(); + Assert.ok(!loaded); + + Assert.equal("object", typeof reconciler.addons); + Assert.equal(0, Object.keys(reconciler.addons).length); + Assert.equal(0, reconciler._changes.length); +}); + +add_task(async function test_install_detection() { + _("Ensure that add-on installation results in appropriate side-effects."); + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + reconciler.startListening(); + + let before = new Date(); + let addon = await installAddon(XPI); + let after = new Date(); + + Assert.equal(1, Object.keys(reconciler.addons).length); + Assert.ok(addon.id in reconciler.addons); + let record = reconciler.addons[ADDON_ID]; + + const KEYS = [ + "id", + "guid", + "enabled", + "installed", + "modified", + "type", + "scope", + "foreignInstall", + ]; + for (let key of KEYS) { + Assert.ok(key in record); + Assert.notEqual(null, record[key]); + } + + Assert.equal(addon.id, record.id); + Assert.equal(addon.syncGUID, record.guid); + Assert.ok(record.enabled); + Assert.ok(record.installed); + Assert.ok(record.modified >= before && record.modified <= after); + Assert.equal("extension", record.type); + Assert.ok(!record.foreignInstall); + + Assert.equal(1, reconciler._changes.length); + let change = reconciler._changes[0]; + Assert.ok(change[0] >= before && change[1] <= after); + Assert.equal(CHANGE_INSTALLED, change[1]); + Assert.equal(addon.id, change[2]); + + await uninstallAddon(addon); +}); + +add_task(async function test_uninstall_detection() { + _("Ensure that add-on uninstallation results in appropriate side-effects."); + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + reconciler.startListening(); + + reconciler._addons = {}; + reconciler._changes = []; + + let addon = await installAddon(XPI); + let id = addon.id; + + reconciler._changes = []; + await uninstallAddon(addon, reconciler); + + Assert.equal(1, Object.keys(reconciler.addons).length); + Assert.ok(id in reconciler.addons); + + let record = reconciler.addons[id]; + Assert.ok(!record.installed); + + Assert.equal(1, reconciler._changes.length); + let change = reconciler._changes[0]; + Assert.equal(CHANGE_UNINSTALLED, change[1]); + Assert.equal(id, change[2]); +}); + +add_task(async function test_load_state_future_version() { + _("Ensure loading a file from a future version results in no data loaded."); + + const FILENAME = "TEST_LOAD_STATE_FUTURE_VERSION"; + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + + // First we populate our new file. + let state = { version: 100, addons: { foo: {} }, changes: [[1, 1, "foo"]] }; + + // jsonSave() expects an object with ._log, so we give it a reconciler + // instance. + await Utils.jsonSave(FILENAME, reconciler, state); + + let loaded = await reconciler.loadState(FILENAME); + Assert.ok(!loaded); + + Assert.equal("object", typeof reconciler.addons); + Assert.equal(0, Object.keys(reconciler.addons).length); + Assert.equal(0, reconciler._changes.length); +}); + +add_task(async function test_prune_changes_before_date() { + _("Ensure that old changes are pruned properly."); + + let reconciler = makeAddonsReconciler(); + await reconciler.ensureStateLoaded(); + reconciler._changes = []; + + let now = new Date(); + const HOUR_MS = 1000 * 60 * 60; + + _("Ensure pruning an empty changes array works."); + reconciler.pruneChangesBeforeDate(now); + Assert.equal(0, reconciler._changes.length); + + let old = new Date(now.getTime() - HOUR_MS); + let young = new Date(now.getTime() - 1000); + reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]); + reconciler._changes.push([young, CHANGE_INSTALLED, "bar"]); + Assert.equal(2, reconciler._changes.length); + + _("Ensure pruning with an old time won't delete anything."); + let threshold = new Date(old.getTime() - 1); + reconciler.pruneChangesBeforeDate(threshold); + Assert.equal(2, reconciler._changes.length); + + _("Ensure pruning a single item works."); + threshold = new Date(young.getTime() - 1000); + reconciler.pruneChangesBeforeDate(threshold); + Assert.equal(1, reconciler._changes.length); + Assert.notEqual(undefined, reconciler._changes[0]); + Assert.equal(young, reconciler._changes[0][0]); + Assert.equal("bar", reconciler._changes[0][2]); + + _("Ensure pruning all changes works."); + reconciler._changes.push([old, CHANGE_INSTALLED, "foo"]); + reconciler.pruneChangesBeforeDate(now); + Assert.equal(0, reconciler._changes.length); +}); diff --git a/services/sync/tests/unit/test_addons_store.js b/services/sync/tests/unit/test_addons_store.js new file mode 100644 index 0000000000..a0a3ac6c69 --- /dev/null +++ b/services/sync/tests/unit/test_addons_store.js @@ -0,0 +1,750 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonsEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/addons.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" +); + +const HTTP_PORT = 8888; + +Services.prefs.setStringPref( + "extensions.getAddons.get.url", + "http://localhost:8888/search/guid:%IDS%" +); +// Note that all compat-override URLs currently 404, but that's OK - the main +// thing is to avoid us hitting the real AMO. +Services.prefs.setStringPref( + "extensions.getAddons.compatOverides.url", + "http://localhost:8888/compat-override/guid:%IDS%" +); +Services.prefs.setBoolPref("extensions.install.requireSecureOrigin", false); +Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false); + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); +AddonTestUtils.overrideCertDB(); + +Services.prefs.setBoolPref("extensions.experiments.enabled", true); + +const SYSTEM_ADDON_ID = "system1@tests.mozilla.org"; +add_task(async function setupSystemAddon() { + const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"]); + distroDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + AddonTestUtils.registerDirectory("XREAppFeat", distroDir); + + let xpi = await AddonTestUtils.createTempWebExtensionFile({ + manifest: { + browser_specific_settings: { gecko: { id: SYSTEM_ADDON_ID } }, + }, + }); + + xpi.copyTo(distroDir, `${SYSTEM_ADDON_ID}.xpi`); + + await AddonTestUtils.overrideBuiltIns({ system: [SYSTEM_ADDON_ID] }); + await AddonTestUtils.promiseStartupManager(); +}); + +const ID1 = "addon1@tests.mozilla.org"; +const ID2 = "addon2@tests.mozilla.org"; +const ID3 = "addon3@tests.mozilla.org"; + +const ADDONS = { + test_addon1: { + manifest: { + browser_specific_settings: { + gecko: { + id: ID1, + update_url: "http://example.com/data/test_install.json", + }, + }, + }, + }, + + test_addon2: { + manifest: { + browser_specific_settings: { gecko: { id: ID2 } }, + }, + }, + + test_addon3: { + manifest: { + browser_specific_settings: { + gecko: { + id: ID3, + strict_max_version: "0", + }, + }, + }, + }, +}; + +const SEARCH_RESULT = { + next: null, + results: [ + { + name: "Test Extension", + type: "extension", + guid: "addon1@tests.mozilla.org", + current_version: { + version: "1.0", + files: [ + { + platform: "all", + size: 485, + url: "http://localhost:8888/addon1.xpi", + }, + ], + }, + last_updated: "2018-10-27T04:12:00.826Z", + }, + ], +}; + +const MISSING_SEARCH_RESULT = { + next: null, + results: [ + { + name: "Test", + type: "extension", + guid: "missing-xpi@tests.mozilla.org", + current_version: { + version: "1.0", + files: [ + { + platform: "all", + size: 123, + url: "http://localhost:8888/THIS_DOES_NOT_EXIST.xpi", + }, + ], + }, + }, + ], +}; + +const XPIS = {}; +for (let [name, files] of Object.entries(ADDONS)) { + XPIS[name] = AddonTestUtils.createTempWebExtensionFile(files); +} + +let engine; +let store; +let reconciler; + +const proxyService = Cc[ + "@mozilla.org/network/protocol-proxy-service;1" +].getService(Ci.nsIProtocolProxyService); + +const proxyFilter = { + proxyInfo: proxyService.newProxyInfo( + "http", + "localhost", + HTTP_PORT, + "", + "", + 0, + 4096, + null + ), + + applyFilter(channel, defaultProxyInfo, callback) { + if (channel.URI.host === "example.com") { + callback.onProxyFilterResult(this.proxyInfo); + } else { + callback.onProxyFilterResult(defaultProxyInfo); + } + }, +}; + +proxyService.registerChannelFilter(proxyFilter, 0); +registerCleanupFunction(() => { + proxyService.unregisterChannelFilter(proxyFilter); +}); + +/** + * Create a AddonsRec for this application with the fields specified. + * + * @param id Sync GUID of record + * @param addonId ID of add-on + * @param enabled Boolean whether record is enabled + * @param deleted Boolean whether record was deleted + */ +function createRecordForThisApp(id, addonId, enabled, deleted) { + return { + id, + addonID: addonId, + enabled, + deleted: !!deleted, + applicationID: Services.appinfo.ID, + source: "amo", + }; +} + +function createAndStartHTTPServer(port) { + try { + let server = new HttpServer(); + + server.registerPathHandler( + "/search/guid:addon1%40tests.mozilla.org", + (req, resp) => { + resp.setHeader("Content-type", "application/json", true); + resp.write(JSON.stringify(SEARCH_RESULT)); + } + ); + server.registerPathHandler( + "/search/guid:missing-xpi%40tests.mozilla.org", + (req, resp) => { + resp.setHeader("Content-type", "application/json", true); + resp.write(JSON.stringify(MISSING_SEARCH_RESULT)); + } + ); + server.registerFile("/addon1.xpi", XPIS.test_addon1); + + server.start(port); + + return server; + } catch (ex) { + _("Got exception starting HTTP server on port " + port); + _("Error: " + Log.exceptionStr(ex)); + do_throw(ex); + } + return null; /* not hit, but keeps eslint happy! */ +} + +// A helper function to ensure that the reconciler's current view of the addon +// is the same as the addon itself. If it's not, then the reconciler missed a +// change, and is likely to re-upload the addon next sync because of the change +// it missed. +async function checkReconcilerUpToDate(addon) { + let stateBefore = Object.assign({}, store.reconciler.addons[addon.id]); + await store.reconciler.rectifyStateFromAddon(addon); + let stateAfter = store.reconciler.addons[addon.id]; + deepEqual(stateBefore, stateAfter); +} + +add_task(async function setup() { + await Service.engineManager.register(AddonsEngine); + engine = Service.engineManager.get("addons"); + store = engine._store; + reconciler = engine._reconciler; + + reconciler.startListening(); + + // Don't flush to disk in the middle of an event listener! + // This causes test hangs on WinXP. + reconciler._shouldPersist = false; +}); + +add_task(async function test_remove() { + _("Ensure removing add-ons from deleted records works."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + let record = createRecordForThisApp(addon.syncGUID, ID1, true, true); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + Assert.equal(null, countTelemetry.failedReasons); + Assert.equal(0, countTelemetry.incomingCounts.failed); + + let newAddon = await AddonManager.getAddonByID(ID1); + Assert.equal(null, newAddon); +}); + +add_task(async function test_apply_enabled() { + let countTelemetry = new SyncedRecordsTelemetry(); + _("Ensures that changes to the userEnabled flag apply."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + Assert.ok(addon.isActive); + Assert.ok(!addon.userDisabled); + + _("Ensure application of a disable record works as expected."); + let records = []; + records.push(createRecordForThisApp(addon.syncGUID, ID1, false, false)); + + let [failed] = await Promise.all([ + store.applyIncomingBatch(records, countTelemetry), + AddonTestUtils.promiseAddonEvent("onDisabled"), + ]); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + addon = await AddonManager.getAddonByID(ID1); + Assert.ok(addon.userDisabled); + await checkReconcilerUpToDate(addon); + records = []; + + _("Ensure enable record works as expected."); + records.push(createRecordForThisApp(addon.syncGUID, ID1, true, false)); + [failed] = await Promise.all([ + store.applyIncomingBatch(records, countTelemetry), + AddonTestUtils.promiseWebExtensionStartup(ID1), + ]); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + addon = await AddonManager.getAddonByID(ID1); + Assert.ok(!addon.userDisabled); + await checkReconcilerUpToDate(addon); + records = []; + + _("Ensure enabled state updates don't apply if the ignore pref is set."); + records.push(createRecordForThisApp(addon.syncGUID, ID1, false, false)); + Svc.PrefBranch.setBoolPref("addons.ignoreUserEnabledChanges", true); + failed = await store.applyIncomingBatch(records, countTelemetry); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + addon = await AddonManager.getAddonByID(ID1); + Assert.ok(!addon.userDisabled); + records = []; + + await uninstallAddon(addon, reconciler); + Svc.PrefBranch.clearUserPref("addons.ignoreUserEnabledChanges"); +}); + +add_task(async function test_apply_enabled_appDisabled() { + _( + "Ensures that changes to the userEnabled flag apply when the addon is appDisabled." + ); + + // this addon is appDisabled by default. + let addon = await installAddon(XPIS.test_addon3); + Assert.ok(addon.appDisabled); + Assert.ok(!addon.isActive); + Assert.ok(!addon.userDisabled); + + _("Ensure application of a disable record works as expected."); + store.reconciler.pruneChangesBeforeDate(Date.now() + 10); + store.reconciler._changes = []; + let records = []; + let countTelemetry = new SyncedRecordsTelemetry(); + records.push(createRecordForThisApp(addon.syncGUID, ID3, false, false)); + let failed = await store.applyIncomingBatch(records, countTelemetry); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + addon = await AddonManager.getAddonByID(ID3); + Assert.ok(addon.userDisabled); + await checkReconcilerUpToDate(addon); + records = []; + + _("Ensure enable record works as expected."); + records.push(createRecordForThisApp(addon.syncGUID, ID3, true, false)); + failed = await store.applyIncomingBatch(records, countTelemetry); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + addon = await AddonManager.getAddonByID(ID3); + Assert.ok(!addon.userDisabled); + await checkReconcilerUpToDate(addon); + records = []; + + await uninstallAddon(addon, reconciler); +}); + +add_task(async function test_ignore_different_appid() { + _( + "Ensure that incoming records with a different application ID are ignored." + ); + + // We test by creating a record that should result in an update. + let addon = await installAddon(XPIS.test_addon1, reconciler); + Assert.ok(!addon.userDisabled); + + let record = createRecordForThisApp(addon.syncGUID, ID1, false, false); + record.applicationID = "FAKE_ID"; + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + + let newAddon = await AddonManager.getAddonByID(ID1); + Assert.ok(!newAddon.userDisabled); + + await uninstallAddon(addon, reconciler); +}); + +add_task(async function test_ignore_unknown_source() { + _("Ensure incoming records with unknown source are ignored."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + + let record = createRecordForThisApp(addon.syncGUID, ID1, false, false); + record.source = "DUMMY_SOURCE"; + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + + let newAddon = await AddonManager.getAddonByID(ID1); + Assert.ok(!newAddon.userDisabled); + + await uninstallAddon(addon, reconciler); +}); + +add_task(async function test_apply_uninstall() { + _("Ensures that uninstalling an add-on from a record works."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + + let records = []; + let countTelemetry = new SyncedRecordsTelemetry(); + records.push(createRecordForThisApp(addon.syncGUID, ID1, true, true)); + let failed = await store.applyIncomingBatch(records, countTelemetry); + Assert.equal(0, failed.length); + Assert.equal(0, countTelemetry.incomingCounts.failed); + + addon = await AddonManager.getAddonByID(ID1); + Assert.equal(null, addon); +}); + +add_task(async function test_addon_syncability() { + _("Ensure isAddonSyncable functions properly."); + + Svc.PrefBranch.setStringPref( + "addons.trustedSourceHostnames", + "addons.mozilla.org,other.example.com" + ); + + Assert.ok(!(await store.isAddonSyncable(null))); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + Assert.ok(await store.isAddonSyncable(addon)); + + let dummy = {}; + const KEYS = [ + "id", + "syncGUID", + "type", + "scope", + "foreignInstall", + "isSyncable", + ]; + for (let k of KEYS) { + dummy[k] = addon[k]; + } + + Assert.ok(await store.isAddonSyncable(dummy)); + + dummy.type = "UNSUPPORTED"; + Assert.ok(!(await store.isAddonSyncable(dummy))); + dummy.type = addon.type; + + dummy.scope = 0; + Assert.ok(!(await store.isAddonSyncable(dummy))); + dummy.scope = addon.scope; + + dummy.isSyncable = false; + Assert.ok(!(await store.isAddonSyncable(dummy))); + dummy.isSyncable = addon.isSyncable; + + dummy.foreignInstall = true; + Assert.ok(!(await store.isAddonSyncable(dummy))); + dummy.foreignInstall = false; + + await uninstallAddon(addon, reconciler); + + Assert.ok(!store.isSourceURITrusted(null)); + + let trusted = [ + "https://addons.mozilla.org/foo", + "https://other.example.com/foo", + ]; + + let untrusted = [ + "http://addons.mozilla.org/foo", // non-https + "ftps://addons.mozilla.org/foo", // non-https + "https://untrusted.example.com/foo", // non-trusted hostname` + ]; + + for (let uri of trusted) { + Assert.ok(store.isSourceURITrusted(Services.io.newURI(uri))); + } + + for (let uri of untrusted) { + Assert.ok(!store.isSourceURITrusted(Services.io.newURI(uri))); + } + + Svc.PrefBranch.setStringPref("addons.trustedSourceHostnames", ""); + for (let uri of trusted) { + Assert.ok(!store.isSourceURITrusted(Services.io.newURI(uri))); + } + + Svc.PrefBranch.setStringPref( + "addons.trustedSourceHostnames", + "addons.mozilla.org" + ); + Assert.ok( + store.isSourceURITrusted( + Services.io.newURI("https://addons.mozilla.org/foo") + ) + ); + + Svc.PrefBranch.clearUserPref("addons.trustedSourceHostnames"); +}); + +add_task(async function test_get_all_ids() { + _("Ensures that getAllIDs() returns an appropriate set."); + + _("Installing two addons."); + // XXX - this test seems broken - at this point, before we've installed the + // addons below, store.getAllIDs() returns all addons installed by previous + // tests, even though those tests uninstalled the addon. + // So if any tests above ever add a new addon ID, they are going to need to + // be added here too. + // Assert.equal(0, Object.keys(store.getAllIDs()).length); + let addon1 = await installAddon(XPIS.test_addon1, reconciler); + let addon2 = await installAddon(XPIS.test_addon2, reconciler); + let addon3 = await installAddon(XPIS.test_addon3, reconciler); + + _("Ensure they're syncable."); + Assert.ok(await store.isAddonSyncable(addon1)); + Assert.ok(await store.isAddonSyncable(addon2)); + Assert.ok(await store.isAddonSyncable(addon3)); + + let ids = await store.getAllIDs(); + + Assert.equal("object", typeof ids); + Assert.equal(3, Object.keys(ids).length); + Assert.ok(addon1.syncGUID in ids); + Assert.ok(addon2.syncGUID in ids); + Assert.ok(addon3.syncGUID in ids); + + await uninstallAddon(addon1, reconciler); + await uninstallAddon(addon2, reconciler); + await uninstallAddon(addon3, reconciler); +}); + +add_task(async function test_change_item_id() { + _("Ensures that changeItemID() works properly."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + + let oldID = addon.syncGUID; + let newID = Utils.makeGUID(); + + await store.changeItemID(oldID, newID); + + let newAddon = await AddonManager.getAddonByID(ID1); + Assert.notEqual(null, newAddon); + Assert.equal(newID, newAddon.syncGUID); + + await uninstallAddon(newAddon, reconciler); +}); + +add_task(async function test_create() { + _("Ensure creating/installing an add-on from a record works."); + + let server = createAndStartHTTPServer(HTTP_PORT); + + let guid = Utils.makeGUID(); + let record = createRecordForThisApp(guid, ID1, true, false); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + + let newAddon = await AddonManager.getAddonByID(ID1); + Assert.notEqual(null, newAddon); + Assert.equal(guid, newAddon.syncGUID); + Assert.ok(!newAddon.userDisabled); + + await uninstallAddon(newAddon, reconciler); + + await promiseStopServer(server); +}); + +add_task(async function test_create_missing_search() { + _("Ensures that failed add-on searches are handled gracefully."); + + let server = createAndStartHTTPServer(HTTP_PORT); + + // The handler for this ID is not installed, so a search should 404. + const id = "missing@tests.mozilla.org"; + let guid = Utils.makeGUID(); + let record = createRecordForThisApp(guid, id, true, false); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(1, failed.length); + Assert.equal(guid, failed[0]); + Assert.equal( + countTelemetry.incomingCounts.failedReasons[0].name, + "GET <URL> failed (status 404)" + ); + Assert.equal(countTelemetry.incomingCounts.failedReasons[0].count, 1); + + let addon = await AddonManager.getAddonByID(id); + Assert.equal(null, addon); + + await promiseStopServer(server); +}); + +add_task(async function test_create_bad_install() { + _("Ensures that add-ons without a valid install are handled gracefully."); + + let server = createAndStartHTTPServer(HTTP_PORT); + + // The handler returns a search result but the XPI will 404. + const id = "missing-xpi@tests.mozilla.org"; + let guid = Utils.makeGUID(); + let record = createRecordForThisApp(guid, id, true, false); + let countTelemetry = new SyncedRecordsTelemetry(); + /* let failed = */ await store.applyIncomingBatch([record], countTelemetry); + // This addon had no source URI so was skipped - but it's not treated as + // failure. + // XXX - this test isn't testing what we thought it was. Previously the addon + // was not being installed due to requireSecureURL checking *before* we'd + // attempted to get the XPI. + // With requireSecureURL disabled we do see a download failure, but the addon + // *does* get added to |failed|. + // FTR: onDownloadFailed() is called with ERROR_NETWORK_FAILURE, so it's going + // to be tricky to distinguish a 404 from other transient network errors + // where we do want the addon to end up in |failed|. + // This is being tracked in bug 1284778. + // Assert.equal(0, failed.length); + + let addon = await AddonManager.getAddonByID(id); + Assert.equal(null, addon); + + await promiseStopServer(server); +}); + +add_task(async function test_ignore_system() { + _("Ensure we ignore system addons"); + // Our system addon should not appear in getAllIDs + await engine._refreshReconcilerState(); + let num = 0; + let ids = await store.getAllIDs(); + for (let guid in ids) { + num += 1; + let addon = reconciler.getAddonStateFromSyncGUID(guid); + Assert.notEqual(addon.id, SYSTEM_ADDON_ID); + } + Assert.greater(num, 1, "should have seen at least one."); +}); + +add_task(async function test_incoming_system() { + _("Ensure we handle incoming records that refer to a system addon"); + // eg, loop initially had a normal addon but it was then "promoted" to be a + // system addon but wanted to keep the same ID. The server record exists due + // to this. + + // before we start, ensure the system addon isn't disabled. + Assert.ok(!(await AddonManager.getAddonByID(SYSTEM_ADDON_ID).userDisabled)); + + // Now simulate an incoming record with the same ID as the system addon, + // but flagged as disabled - it should not be applied. + let server = createAndStartHTTPServer(HTTP_PORT); + // We make the incoming record flag the system addon as disabled - it should + // be ignored. + let guid = Utils.makeGUID(); + let record = createRecordForThisApp(guid, SYSTEM_ADDON_ID, false, false); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + + // The system addon should still not be userDisabled. + Assert.ok(!(await AddonManager.getAddonByID(SYSTEM_ADDON_ID).userDisabled)); + + await promiseStopServer(server); +}); + +add_task(async function test_wipe() { + _("Ensures that wiping causes add-ons to be uninstalled."); + + await installAddon(XPIS.test_addon1, reconciler); + + await store.wipe(); + + let addon = await AddonManager.getAddonByID(ID1); + Assert.equal(null, addon); +}); + +add_task(async function test_wipe_and_install() { + _("Ensure wipe followed by install works."); + + // This tests the reset sync flow where remote data is replaced by local. The + // receiving client will see a wipe followed by a record which should undo + // the wipe. + let installed = await installAddon(XPIS.test_addon1, reconciler); + + let record = createRecordForThisApp(installed.syncGUID, ID1, true, false); + + await store.wipe(); + + let deleted = await AddonManager.getAddonByID(ID1); + Assert.equal(null, deleted); + + // Re-applying the record can require re-fetching the XPI. + let server = createAndStartHTTPServer(HTTP_PORT); + + await store.applyIncoming(record); + + let fetched = await AddonManager.getAddonByID(record.addonID); + Assert.ok(!!fetched); + + // wipe again to we are left with a clean slate. + await store.wipe(); + + await promiseStopServer(server); +}); + +// STR for what this is testing: +// * Either: +// * Install then remove an addon, then delete addons.json from the profile +// or corrupt it (in which case the addon manager will remove it) +// * Install then remove an addon while addon caching is disabled, then +// re-enable addon caching. +// * Install the same addon in a different profile, sync it. +// * Sync this profile +// Before bug 1467904, the addon would fail to install because this profile +// has a copy of the addon in our addonsreconciler.json, but the addon manager +// does *not* have a copy in its cache, and repopulating that cache would not +// re-add it as the addon is no longer installed locally. +add_task(async function test_incoming_reconciled_but_not_cached() { + _( + "Ensure we handle incoming records our reconciler has but the addon cache does not" + ); + + // Make sure addon is not installed. + let addon = await AddonManager.getAddonByID(ID1); + Assert.equal(null, addon); + + Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false); + + addon = await installAddon(XPIS.test_addon1, reconciler); + Assert.notEqual(await AddonManager.getAddonByID(ID1), null); + await uninstallAddon(addon, reconciler); + + Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", true); + + // now pretend it is incoming. + let server = createAndStartHTTPServer(HTTP_PORT); + let guid = Utils.makeGUID(); + let record = createRecordForThisApp(guid, ID1, true, false); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch([record], countTelemetry); + Assert.equal(0, failed.length); + + Assert.notEqual(await AddonManager.getAddonByID(ID1), null); + + await promiseStopServer(server); +}); + +// NOTE: The test above must be the last test run due to the addon cache +// being trashed. It is probably possible to fix that by running, eg, +// AddonRespository.backgroundUpdateCheck() to rebuild the cache, but that +// requires implementing more AMO functionality in our test server + +add_task(async function cleanup() { + // There's an xpcom-shutdown hook for this, but let's give this a shot. + reconciler.stopListening(); +}); diff --git a/services/sync/tests/unit/test_addons_tracker.js b/services/sync/tests/unit/test_addons_tracker.js new file mode 100644 index 0000000000..f8473e4cfa --- /dev/null +++ b/services/sync/tests/unit/test_addons_tracker.js @@ -0,0 +1,174 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AddonsEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/addons.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); +AddonTestUtils.overrideCertDB(); + +Services.prefs.setBoolPref("extensions.experiments.enabled", true); + +Svc.PrefBranch.setBoolPref("engine.addons", true); + +let reconciler; +let tracker; + +const addon1ID = "addon1@tests.mozilla.org"; + +const ADDONS = { + test_addon1: { + manifest: { + browser_specific_settings: { gecko: { id: addon1ID } }, + }, + }, +}; + +const XPIS = {}; + +async function cleanup() { + tracker.stop(); + + tracker.resetScore(); + await tracker.clearChangedIDs(); + + reconciler._addons = {}; + reconciler._changes = []; + await reconciler.saveState(); +} + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); + for (let [name, data] of Object.entries(ADDONS)) { + XPIS[name] = AddonTestUtils.createTempWebExtensionFile(data); + } + await Service.engineManager.register(AddonsEngine); + let engine = Service.engineManager.get("addons"); + reconciler = engine._reconciler; + tracker = engine._tracker; + + await cleanup(); +}); + +add_task(async function test_empty() { + _("Verify the tracker is empty to start with."); + + Assert.equal(0, Object.keys(await tracker.getChangedIDs()).length); + Assert.equal(0, tracker.score); + + await cleanup(); +}); + +add_task(async function test_not_tracking() { + _("Ensures the tracker doesn't do anything when it isn't tracking."); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + await uninstallAddon(addon, reconciler); + + Assert.equal(0, Object.keys(await tracker.getChangedIDs()).length); + Assert.equal(0, tracker.score); + + await cleanup(); +}); + +add_task(async function test_track_install() { + _("Ensure that installing an add-on notifies tracker."); + + reconciler.startListening(); + + tracker.start(); + + Assert.equal(0, tracker.score); + let addon = await installAddon(XPIS.test_addon1, reconciler); + let changed = await tracker.getChangedIDs(); + + Assert.equal(1, Object.keys(changed).length); + Assert.ok(addon.syncGUID in changed); + Assert.equal(SCORE_INCREMENT_XLARGE, tracker.score); + + await uninstallAddon(addon, reconciler); + await cleanup(); +}); + +add_task(async function test_track_uninstall() { + _("Ensure that uninstalling an add-on notifies tracker."); + + reconciler.startListening(); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + let guid = addon.syncGUID; + Assert.equal(0, tracker.score); + + tracker.start(); + + await uninstallAddon(addon, reconciler); + let changed = await tracker.getChangedIDs(); + Assert.equal(1, Object.keys(changed).length); + Assert.ok(guid in changed); + Assert.equal(SCORE_INCREMENT_XLARGE, tracker.score); + + await cleanup(); +}); + +add_task(async function test_track_user_disable() { + _("Ensure that tracker sees disabling of add-on"); + + reconciler.startListening(); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + Assert.ok(!addon.userDisabled); + Assert.ok(!addon.appDisabled); + Assert.ok(addon.isActive); + + tracker.start(); + Assert.equal(0, tracker.score); + + _("Disabling add-on"); + await addon.disable(); + await reconciler.queueCaller.promiseCallsComplete(); + + let changed = await tracker.getChangedIDs(); + Assert.equal(1, Object.keys(changed).length); + Assert.ok(addon.syncGUID in changed); + Assert.equal(SCORE_INCREMENT_XLARGE, tracker.score); + + await uninstallAddon(addon, reconciler); + await cleanup(); +}); + +add_task(async function test_track_enable() { + _("Ensure that enabling a disabled add-on notifies tracker."); + + reconciler.startListening(); + + let addon = await installAddon(XPIS.test_addon1, reconciler); + await addon.disable(); + await Async.promiseYield(); + + Assert.equal(0, tracker.score); + + tracker.start(); + await addon.enable(); + await Async.promiseYield(); + await reconciler.queueCaller.promiseCallsComplete(); + + let changed = await tracker.getChangedIDs(); + Assert.equal(1, Object.keys(changed).length); + Assert.ok(addon.syncGUID in changed); + Assert.equal(SCORE_INCREMENT_XLARGE, tracker.score); + + await uninstallAddon(addon, reconciler); + await cleanup(); +}); diff --git a/services/sync/tests/unit/test_addons_validator.js b/services/sync/tests/unit/test_addons_validator.js new file mode 100644 index 0000000000..60f2f8bf43 --- /dev/null +++ b/services/sync/tests/unit/test_addons_validator.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AddonValidator } = ChromeUtils.importESModule( + "resource://services-sync/engines/addons.sys.mjs" +); + +function getDummyServerAndClient() { + return { + server: [ + { + id: "1111", + applicationID: Services.appinfo.ID, + addonID: "synced-addon@example.com", + enabled: true, + source: "amo", + understood: true, + }, + ], + client: [ + { + syncGUID: "1111", + id: "synced-addon@example.com", + type: "extension", + isSystem: false, + isSyncable: true, + }, + { + syncGUID: "2222", + id: "system-addon@example.com", + type: "extension", + isSystem: true, + isSyncable: false, + }, + { + // Plugins don't have a `syncedGUID`, but we don't sync them, so we + // shouldn't report them as client duplicates. + id: "some-plugin", + type: "plugin", + }, + { + id: "another-plugin", + type: "plugin", + }, + ], + }; +} + +add_task(async function test_valid() { + let { server, client } = getDummyServerAndClient(); + let validator = new AddonValidator({ + _findDupe(item) { + return null; + }, + isAddonSyncable(item) { + return item.type != "plugin"; + }, + }); + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + equal(clientRecords.length, 4); + equal(records.length, 1); + equal(deletedRecords.length, 0); + deepEqual(problemData, validator.emptyProblemData()); +}); diff --git a/services/sync/tests/unit/test_bookmark_batch_fail.js b/services/sync/tests/unit/test_bookmark_batch_fail.js new file mode 100644 index 0000000000..9644d730e4 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_batch_fail.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Making sure a failing sync reports a useful error"); +// `Service` is used as a global in head_helpers.js. +// eslint-disable-next-line no-unused-vars +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_bookmark_test(async function run_test(engine) { + await engine.initialize(); + engine._syncStartup = async function () { + throw new Error("FAIL!"); + }; + + try { + _("Try calling the sync that should throw right away"); + await engine._sync(); + do_throw("Should have failed sync!"); + } catch (ex) { + _("Making sure what we threw ended up as the exception:", ex); + Assert.equal(ex.message, "FAIL!"); + } +}); diff --git a/services/sync/tests/unit/test_bookmark_decline_undecline.js b/services/sync/tests/unit/test_bookmark_decline_undecline.js new file mode 100644 index 0000000000..12139dd163 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_decline_undecline.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +// A stored reference to the collection won't be valid after disabling. +function getBookmarkWBO(server, guid) { + let coll = server.user("foo").collection("bookmarks"); + if (!coll) { + return null; + } + return coll.wbo(guid); +} + +add_task(async function test_decline_undecline() { + let engine = Service.engineManager.get("bookmarks"); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + let { guid: bzGuid } = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://bugzilla.mozilla.org", + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + title: "bugzilla", + }); + + ok(!getBookmarkWBO(server, bzGuid), "Shouldn't have been uploaded yet"); + await Service.sync(); + ok(getBookmarkWBO(server, bzGuid), "Should be present on server"); + + engine.enabled = false; + await Service.sync(); + ok( + !getBookmarkWBO(server, bzGuid), + "Shouldn't be present on server anymore" + ); + + engine.enabled = true; + await Service.sync(); + ok(getBookmarkWBO(server, bzGuid), "Should be present on server again"); + } finally { + await PlacesSyncUtils.bookmarks.reset(); + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_bookmark_engine.js b/services/sync/tests/unit/test_bookmark_engine.js new file mode 100644 index 0000000000..6274a6b836 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_engine.js @@ -0,0 +1,1555 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { BookmarkHTMLUtils } = ChromeUtils.importESModule( + "resource://gre/modules/BookmarkHTMLUtils.sys.mjs" +); +const { BookmarkJSONUtils } = ChromeUtils.importESModule( + "resource://gre/modules/BookmarkJSONUtils.sys.mjs" +); +const { Bookmark, BookmarkFolder, BookmarksEngine, Livemark } = + ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" + ); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" +); + +var recordedEvents = []; + +function checkRecordedEvents(object, expected, message) { + // Ignore event telemetry from the merger. + let checkEvents = recordedEvents.filter(event => event.object == object); + deepEqual(checkEvents, expected, message); + // and clear the list so future checks are easier to write. + recordedEvents = []; +} + +async function fetchAllRecordIds() { + let db = await PlacesUtils.promiseDBConnection(); + let rows = await db.executeCached(` + WITH RECURSIVE + syncedItems(id, guid) AS ( + SELECT b.id, b.guid FROM moz_bookmarks b + WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____', + 'mobile______') + UNION ALL + SELECT b.id, b.guid FROM moz_bookmarks b + JOIN syncedItems s ON b.parent = s.id + ) + SELECT guid FROM syncedItems`); + let recordIds = new Set(); + for (let row of rows) { + let recordId = PlacesSyncUtils.bookmarks.guidToRecordId( + row.getResultByName("guid") + ); + recordIds.add(recordId); + } + return recordIds; +} + +async function cleanupEngine(engine) { + await engine.resetClient(); + await engine._store.wipe(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + // Note we don't finalize the engine here as add_bookmark_test() does. +} + +async function cleanup(engine, server) { + await promiseStopServer(server); + await cleanupEngine(engine); +} + +add_task(async function setup() { + await generateNewKeys(Service.collectionKeys); + await Service.engineManager.unregister("bookmarks"); + + Service.recordTelemetryEvent = (object, method, value, extra = undefined) => { + recordedEvents.push({ object, method, value, extra }); + }; +}); + +add_task(async function test_buffer_timeout() { + await Service.recordManager.clearCache(); + await PlacesSyncUtils.bookmarks.reset(); + let engine = new BookmarksEngine(Service); + engine._newWatchdog = function () { + // Return an already-aborted watchdog, so that we can abort merges + // immediately. + let watchdog = Async.watchdog(); + watchdog.controller.abort(); + return watchdog; + }; + await engine.initialize(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("bookmarks"); + + try { + info("Insert local bookmarks"); + await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.unfiledGuid, + children: [ + { + guid: "bookmarkAAAA", + url: "http://example.com/a", + title: "A", + }, + { + guid: "bookmarkBBBB", + url: "http://example.com/b", + title: "B", + }, + ], + }); + + info("Insert remote bookmarks"); + collection.insert( + "menu", + encryptPayload({ + id: "menu", + type: "folder", + parentid: "places", + title: "menu", + children: ["bookmarkCCCC", "bookmarkDDDD"], + }) + ); + collection.insert( + "bookmarkCCCC", + encryptPayload({ + id: "bookmarkCCCC", + type: "bookmark", + parentid: "menu", + bmkUri: "http://example.com/c", + title: "C", + }) + ); + collection.insert( + "bookmarkDDDD", + encryptPayload({ + id: "bookmarkDDDD", + type: "bookmark", + parentid: "menu", + bmkUri: "http://example.com/d", + title: "D", + }) + ); + + info("We expect this sync to fail"); + await Assert.rejects( + sync_engine_and_validate_telem(engine, true), + ex => ex.name == "InterruptedError" + ); + } finally { + await cleanup(engine, server); + await engine.finalize(); + } +}); + +add_bookmark_test(async function test_maintenance_after_failure(engine) { + _("Ensure we try to run maintenance if the engine fails to sync"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + let syncStartup = engine._syncStartup; + let syncError = new Error("Something is rotten in the state of Places"); + engine._syncStartup = function () { + throw syncError; + }; + + Services.prefs.clearUserPref("places.database.lastMaintenance"); + + _("Ensure the sync fails and we run maintenance"); + await Assert.rejects( + sync_engine_and_validate_telem(engine, true), + ex => ex == syncError + ); + checkRecordedEvents( + "maintenance", + [ + { + object: "maintenance", + method: "run", + value: "bookmarks", + extra: undefined, + }, + ], + "Should record event for first maintenance run" + ); + + _("Sync again, but ensure maintenance doesn't run"); + await Assert.rejects( + sync_engine_and_validate_telem(engine, true), + ex => ex == syncError + ); + checkRecordedEvents( + "maintenance", + [], + "Should not record event if maintenance didn't run" + ); + + _("Fast-forward last maintenance pref; ensure maintenance runs"); + Services.prefs.setIntPref( + "places.database.lastMaintenance", + Date.now() / 1000 - 14400 + ); + await Assert.rejects( + sync_engine_and_validate_telem(engine, true), + ex => ex == syncError + ); + checkRecordedEvents( + "maintenance", + [ + { + object: "maintenance", + method: "run", + value: "bookmarks", + extra: undefined, + }, + ], + "Should record event for second maintenance run" + ); + + _("Fix sync failure; ensure we report success after maintenance"); + engine._syncStartup = syncStartup; + await sync_engine_and_validate_telem(engine, false); + checkRecordedEvents( + "maintenance", + [ + { + object: "maintenance", + method: "fix", + value: "bookmarks", + extra: undefined, + }, + ], + "Should record event for successful sync after second maintenance" + ); + + await sync_engine_and_validate_telem(engine, false); + checkRecordedEvents( + "maintenance", + [], + "Should not record maintenance events after successful sync" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_bookmark_test(async function test_delete_invalid_roots_from_server(engine) { + _("Ensure that we delete the Places and Reading List roots from the server."); + + enableValidationPrefs(); + + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + engine._tracker.start(); + + try { + let placesRecord = await store.createRecord("places"); + collection.insert("places", encryptPayload(placesRecord.cleartext)); + + let listBmk = new Bookmark("bookmarks", Utils.makeGUID()); + listBmk.bmkUri = "https://example.com"; + listBmk.title = "Example reading list entry"; + listBmk.parentName = "Reading List"; + listBmk.parentid = "readinglist"; + collection.insert(listBmk.id, encryptPayload(listBmk.cleartext)); + + let readingList = new BookmarkFolder("bookmarks", "readinglist"); + readingList.title = "Reading List"; + readingList.children = [listBmk.id]; + readingList.parentName = ""; + readingList.parentid = "places"; + collection.insert("readinglist", encryptPayload(readingList.cleartext)); + + // Note that we don't insert a record for the toolbar, so the engine will + // report a parent-child disagreement, since Firefox's `parentid` is + // `toolbar`. + let newBmk = new Bookmark("bookmarks", Utils.makeGUID()); + newBmk.bmkUri = "http://getfirefox.com"; + newBmk.title = "Get Firefox!"; + newBmk.parentName = "Bookmarks Toolbar"; + newBmk.parentid = "toolbar"; + collection.insert(newBmk.id, encryptPayload(newBmk.cleartext)); + + deepEqual( + collection.keys().sort(), + ["places", "readinglist", listBmk.id, newBmk.id].sort(), + "Should store Places root, reading list items, and new bookmark on server" + ); + + let ping = await sync_engine_and_validate_telem(engine, true); + // In a real sync, the engine is named `bookmarks-buffered`. + // However, `sync_engine_and_validate_telem` simulates a sync where + // the engine isn't registered with the engine manager, so the recorder + // doesn't see its `overrideTelemetryName`. + let engineData = ping.engines.find(e => e.name == "bookmarks"); + ok(engineData.validation, "Bookmarks engine should always run validation"); + equal( + engineData.validation.checked, + 6, + "Bookmarks engine should validate all items" + ); + deepEqual( + engineData.validation.problems, + [ + { + name: "parentChildDisagreements", + count: 1, + }, + ], + "Bookmarks engine should report parent-child disagreement" + ); + deepEqual( + engineData.steps.map(step => step.name), + [ + "fetchLocalTree", + "fetchRemoteTree", + "merge", + "apply", + "notifyObservers", + "fetchLocalChangeRecords", + ], + "Bookmarks engine should report all merge steps" + ); + + deepEqual( + collection.keys().sort(), + ["menu", "mobile", "toolbar", "unfiled", newBmk.id].sort(), + "Should remove Places root and reading list items from server; upload local roots" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_bookmark_test(async function test_processIncoming_error_orderChildren( + engine +) { + _( + "Ensure that _orderChildren() is called even when _processIncoming() throws an error." + ); + + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + try { + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder 1", + }); + + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + let bmk2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + + let toolbar_record = await store.createRecord("toolbar"); + collection.insert("toolbar", encryptPayload(toolbar_record.cleartext)); + + let bmk1_record = await store.createRecord(bmk1.guid); + collection.insert(bmk1.guid, encryptPayload(bmk1_record.cleartext)); + + let bmk2_record = await store.createRecord(bmk2.guid); + collection.insert(bmk2.guid, encryptPayload(bmk2_record.cleartext)); + + // Create a server record for folder1 where we flip the order of + // the children. + let folder1_record = await store.createRecord(folder1.guid); + let folder1_payload = folder1_record.cleartext; + folder1_payload.children.reverse(); + collection.insert(folder1.guid, encryptPayload(folder1_payload)); + + // Create a bogus record that when synced down will provoke a + // network error which in turn provokes an exception in _processIncoming. + const BOGUS_GUID = "zzzzzzzzzzzz"; + let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!"); + bogus_record.get = function get() { + throw new Error("Sync this!"); + }; + + // Make the 10 minutes old so it will only be synced in the toFetch phase. + bogus_record.modified = new_timestamp() - 60 * 10; + await engine.setLastSync(new_timestamp() - 60); + engine.toFetch = new SerializableSet([BOGUS_GUID]); + + let error; + try { + await sync_engine_and_validate_telem(engine, true); + } catch (ex) { + error = ex; + } + ok(!!error); + + // Verify that the bookmark order has been applied. + folder1_record = await store.createRecord(folder1.guid); + let new_children = folder1_record.children; + Assert.deepEqual( + new_children.sort(), + [folder1_payload.children[0], folder1_payload.children[1]].sort() + ); + + let localChildIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + folder1.guid + ); + Assert.deepEqual(localChildIds.sort(), [bmk2.guid, bmk1.guid].sort()); + } finally { + await cleanup(engine, server); + } +}); + +add_bookmark_test(async function test_restorePromptsReupload(engine) { + await test_restoreOrImport(engine, { replace: true }); +}); + +add_bookmark_test(async function test_importPromptsReupload(engine) { + await test_restoreOrImport(engine, { replace: false }); +}); + +// Test a JSON restore or HTML import. Use JSON if `replace` is `true`, or +// HTML otherwise. +async function test_restoreOrImport(engine, { replace }) { + let verb = replace ? "restore" : "import"; + let verbing = replace ? "restoring" : "importing"; + let bookmarkUtils = replace ? BookmarkJSONUtils : BookmarkHTMLUtils; + + _(`Ensure that ${verbing} from a backup will reupload all records.`); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + engine._tracker.start(); // We skip usual startup... + + try { + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder 1", + }); + + _("Create a single record."); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + _(`Get Firefox!: ${bmk1.guid}`); + + let backupFilePath = PathUtils.join( + PathUtils.tempDir, + `t_b_e_${Date.now()}.json` + ); + + _("Make a backup."); + + await bookmarkUtils.exportToFile(backupFilePath); + + _("Create a different record and sync."); + let bmk2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + _(`Get Thunderbird!: ${bmk2.guid}`); + + await PlacesUtils.bookmarks.remove(bmk1.guid); + + let error; + try { + await sync_engine_and_validate_telem(engine, false); + } catch (ex) { + error = ex; + _("Got error: " + Log.exceptionStr(ex)); + } + Assert.ok(!error); + + _( + "Verify that there's only one bookmark on the server, and it's Thunderbird." + ); + // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu... + let wbos = collection.keys(function (id) { + return !["menu", "toolbar", "mobile", "unfiled", folder1.guid].includes( + id + ); + }); + Assert.equal(wbos.length, 1); + Assert.equal(wbos[0], bmk2.guid); + + _(`Now ${verb} from a backup.`); + await bookmarkUtils.importFromFile(backupFilePath, { replace }); + + // If `replace` is `true`, we'll wipe the server on the next sync. + let bookmarksCollection = server.user("foo").collection("bookmarks"); + _("Verify that we didn't wipe the server."); + Assert.ok(!!bookmarksCollection); + + _("Ensure we have the bookmarks we expect locally."); + let recordIds = await fetchAllRecordIds(); + _("GUIDs: " + JSON.stringify([...recordIds])); + + let bookmarkRecordIds = new Map(); + let count = 0; + for (let recordId of recordIds) { + count++; + let info = await PlacesUtils.bookmarks.fetch( + PlacesSyncUtils.bookmarks.recordIdToGuid(recordId) + ); + // Only one bookmark, so _all_ should be Firefox! + if (info.type == PlacesUtils.bookmarks.TYPE_BOOKMARK) { + _(`Found URI ${info.url.href} for record ID ${recordId}`); + bookmarkRecordIds.set(info.url.href, recordId); + } + } + Assert.ok(bookmarkRecordIds.has("http://getfirefox.com/")); + if (!replace) { + Assert.ok(bookmarkRecordIds.has("http://getthunderbird.com/")); + } + + _("Have the correct number of IDs locally, too."); + let expectedResults = [ + "menu", + "toolbar", + "mobile", + "unfiled", + folder1.guid, + bmk1.guid, + ]; + if (!replace) { + expectedResults.push("toolbar", folder1.guid, bmk2.guid); + } + Assert.equal(count, expectedResults.length); + + _("Sync again. This'll wipe bookmarks from the server."); + try { + await sync_engine_and_validate_telem(engine, false); + } catch (ex) { + error = ex; + _("Got error: " + Log.exceptionStr(ex)); + } + Assert.ok(!error); + + _("Verify that there's the right bookmarks on the server."); + // Of course, there's also the Bookmarks Toolbar and Bookmarks Menu... + let payloads = server.user("foo").collection("bookmarks").payloads(); + let bookmarkWBOs = payloads.filter(function (wbo) { + return wbo.type == "bookmark"; + }); + + let folderWBOs = payloads.filter(function (wbo) { + return ( + wbo.type == "folder" && + wbo.id != "menu" && + wbo.id != "toolbar" && + wbo.id != "unfiled" && + wbo.id != "mobile" && + wbo.parentid != "menu" + ); + }); + + let expectedFX = { + id: bookmarkRecordIds.get("http://getfirefox.com/"), + bmkUri: "http://getfirefox.com/", + title: "Get Firefox!", + }; + let expectedTB = { + id: bookmarkRecordIds.get("http://getthunderbird.com/"), + bmkUri: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }; + + let expectedBookmarks; + if (replace) { + expectedBookmarks = [expectedFX]; + } else { + expectedBookmarks = [expectedTB, expectedFX]; + } + + doCheckWBOs(bookmarkWBOs, expectedBookmarks); + + _("Our old friend Folder 1 is still in play."); + let expectedFolder1 = { title: "Folder 1" }; + + let expectedFolders; + if (replace) { + expectedFolders = [expectedFolder1]; + } else { + expectedFolders = [expectedFolder1, expectedFolder1]; + } + + doCheckWBOs(folderWBOs, expectedFolders); + } finally { + await cleanup(engine, server); + } +} + +function doCheckWBOs(WBOs, expected) { + Assert.equal(WBOs.length, expected.length); + for (let i = 0; i < expected.length; i++) { + let lhs = WBOs[i]; + let rhs = expected[i]; + if ("id" in rhs) { + Assert.equal(lhs.id, rhs.id); + } + if ("bmkUri" in rhs) { + Assert.equal(lhs.bmkUri, rhs.bmkUri); + } + if ("title" in rhs) { + Assert.equal(lhs.title, rhs.title); + } + } +} + +function FakeRecord(constructor, r) { + this.defaultCleartext = constructor.prototype.defaultCleartext; + constructor.call(this, "bookmarks", r.id); + for (let x in r) { + this[x] = r[x]; + } + // Borrow the constructor's conversion functions. + this.toSyncBookmark = constructor.prototype.toSyncBookmark; + this.cleartextToString = constructor.prototype.cleartextToString; +} + +// Bug 632287. +// (Note that `test_mismatched_folder_types()` in +// toolkit/components/places/tests/sync/test_bookmark_kinds.js is an exact +// copy of this test, so it's fine to remove it as part of bug 1449730) +add_task(async function test_mismatched_types() { + _( + "Ensure that handling a record that changes type causes deletion " + + "then re-adding." + ); + + let oldRecord = { + id: "l1nZZXfB8nC7", + type: "folder", + parentName: "Bookmarks Toolbar", + title: "Innerst i Sneglehode", + description: null, + parentid: "toolbar", + }; + + let newRecord = { + id: "l1nZZXfB8nC7", + type: "livemark", + siteUri: "http://sneglehode.wordpress.com/", + feedUri: "http://sneglehode.wordpress.com/feed/", + parentName: "Bookmarks Toolbar", + title: "Innerst i Sneglehode", + description: null, + children: [ + "HCRq40Rnxhrd", + "YeyWCV1RVsYw", + "GCceVZMhvMbP", + "sYi2hevdArlF", + "vjbZlPlSyGY8", + "UtjUhVyrpeG6", + "rVq8WMG2wfZI", + "Lx0tcy43ZKhZ", + "oT74WwV8_j4P", + "IztsItWVSo3-", + ], + parentid: "toolbar", + }; + + let engine = new BookmarksEngine(Service); + await engine.initialize(); + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + let oldR = new FakeRecord(BookmarkFolder, oldRecord); + let newR = new FakeRecord(Livemark, newRecord); + oldR.parentid = PlacesUtils.bookmarks.toolbarGuid; + newR.parentid = PlacesUtils.bookmarks.toolbarGuid; + + await store.applyIncoming(oldR); + await engine._apply(); + _("Applied old. It's a folder."); + let oldID = await PlacesTestUtils.promiseItemId(oldR.id); + _("Old ID: " + oldID); + let oldInfo = await PlacesUtils.bookmarks.fetch(oldR.id); + Assert.equal(oldInfo.type, PlacesUtils.bookmarks.TYPE_FOLDER); + + await store.applyIncoming(newR); + await engine._apply(); + } finally { + await cleanup(engine, server); + await engine.finalize(); + } +}); + +add_bookmark_test(async function test_misreconciled_root(engine) { + _("Ensure that we don't reconcile an arbitrary record with a root."); + + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + // Log real hard for this test. + store._log.trace = store._log.debug; + engine._log.trace = engine._log.debug; + + await engine._syncStartup(); + + // Let's find out where the toolbar is right now. + let toolbarBefore = await store.createRecord("toolbar", "bookmarks"); + let toolbarIDBefore = await PlacesTestUtils.promiseItemId( + PlacesUtils.bookmarks.toolbarGuid + ); + Assert.notEqual(-1, toolbarIDBefore); + + let parentRecordIDBefore = toolbarBefore.parentid; + let parentGUIDBefore = + PlacesSyncUtils.bookmarks.recordIdToGuid(parentRecordIDBefore); + let parentIDBefore = await PlacesTestUtils.promiseItemId(parentGUIDBefore); + Assert.equal("string", typeof parentGUIDBefore); + + _("Current parent: " + parentGUIDBefore + " (" + parentIDBefore + ")."); + + let to_apply = { + id: "zzzzzzzzzzzz", + type: "folder", + title: "Bookmarks Toolbar", + description: "Now you're for it.", + parentName: "", + parentid: "mobile", // Why not? + children: [], + }; + + let rec = new FakeRecord(BookmarkFolder, to_apply); + + _("Applying record."); + let countTelemetry = new SyncedRecordsTelemetry(); + await store.applyIncomingBatch([rec], countTelemetry); + + // Ensure that afterwards, toolbar is still there. + // As of 2012-12-05, this only passes because Places doesn't use "toolbar" as + // the real GUID, instead using a generated one. Sync does the translation. + let toolbarAfter = await store.createRecord("toolbar", "bookmarks"); + let parentRecordIDAfter = toolbarAfter.parentid; + let parentGUIDAfter = + PlacesSyncUtils.bookmarks.recordIdToGuid(parentRecordIDAfter); + let parentIDAfter = await PlacesTestUtils.promiseItemId(parentGUIDAfter); + Assert.equal( + await PlacesTestUtils.promiseItemGuid(toolbarIDBefore), + PlacesUtils.bookmarks.toolbarGuid + ); + Assert.equal(parentGUIDBefore, parentGUIDAfter); + Assert.equal(parentIDBefore, parentIDAfter); + + await cleanup(engine, server); +}); + +add_bookmark_test(async function test_invalid_url(engine) { + _("Ensure an incoming invalid bookmark URL causes an outgoing tombstone."); + + let server = await serverForFoo(engine); + let collection = server.user("foo").collection("bookmarks"); + + await SyncTestingInfrastructure(server); + await engine._syncStartup(); + + // check the URL really is invalid. + let url = "https://www.42registry.42/"; + Assert.throws(() => Services.io.newURI(url), /invalid/); + + let guid = "abcdefabcdef"; + + let toolbar = new BookmarkFolder("bookmarks", "toolbar"); + toolbar.title = "toolbar"; + toolbar.parentName = ""; + toolbar.parentid = "places"; + toolbar.children = [guid]; + collection.insert("toolbar", encryptPayload(toolbar.cleartext)); + + let item1 = new Bookmark("bookmarks", guid); + item1.bmkUri = "https://www.42registry.42/"; + item1.title = "invalid url"; + item1.parentName = "Bookmarks Toolbar"; + item1.parentid = "toolbar"; + item1.dateAdded = 1234; + collection.insert(guid, encryptPayload(item1.cleartext)); + + _("syncing."); + await sync_engine_and_validate_telem(engine, false); + + // We should find the record now exists on the server as a tombstone. + let updated = collection.cleartext(guid); + Assert.ok(updated.deleted, "record was deleted"); + + let local = await PlacesUtils.bookmarks.fetch(guid); + Assert.deepEqual(local, null, "no local bookmark exists"); + + await cleanup(engine, server); +}); + +add_bookmark_test(async function test_sync_dateAdded(engine) { + await Service.recordManager.clearCache(); + await PlacesSyncUtils.bookmarks.reset(); + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + // TODO: Avoid random orange (bug 1374599), this is only necessary + // intermittently - reset the last sync date so that we'll get all bookmarks. + await engine.setLastSync(1); + + engine._tracker.start(); // We skip usual startup... + + // Just matters that it's in the past, not how far. + let now = Date.now(); + let oneYearMS = 365 * 24 * 60 * 60 * 1000; + + try { + let toolbar = new BookmarkFolder("bookmarks", "toolbar"); + toolbar.title = "toolbar"; + toolbar.parentName = ""; + toolbar.parentid = "places"; + toolbar.children = [ + "abcdefabcdef", + "aaaaaaaaaaaa", + "bbbbbbbbbbbb", + "cccccccccccc", + "dddddddddddd", + "eeeeeeeeeeee", + ]; + collection.insert("toolbar", encryptPayload(toolbar.cleartext)); + + let item1GUID = "abcdefabcdef"; + let item1 = new Bookmark("bookmarks", item1GUID); + item1.bmkUri = "https://example.com"; + item1.title = "asdf"; + item1.parentName = "Bookmarks Toolbar"; + item1.parentid = "toolbar"; + item1.dateAdded = now - oneYearMS; + collection.insert(item1GUID, encryptPayload(item1.cleartext)); + + let item2GUID = "aaaaaaaaaaaa"; + let item2 = new Bookmark("bookmarks", item2GUID); + item2.bmkUri = "https://example.com/2"; + item2.title = "asdf2"; + item2.parentName = "Bookmarks Toolbar"; + item2.parentid = "toolbar"; + item2.dateAdded = now + oneYearMS; + const item2LastModified = now / 1000 - 100; + collection.insert( + item2GUID, + encryptPayload(item2.cleartext), + item2LastModified + ); + + let item3GUID = "bbbbbbbbbbbb"; + let item3 = new Bookmark("bookmarks", item3GUID); + item3.bmkUri = "https://example.com/3"; + item3.title = "asdf3"; + item3.parentName = "Bookmarks Toolbar"; + item3.parentid = "toolbar"; + // no dateAdded + collection.insert(item3GUID, encryptPayload(item3.cleartext)); + + let item4GUID = "cccccccccccc"; + let item4 = new Bookmark("bookmarks", item4GUID); + item4.bmkUri = "https://example.com/4"; + item4.title = "asdf4"; + item4.parentName = "Bookmarks Toolbar"; + item4.parentid = "toolbar"; + // no dateAdded, but lastModified in past + const item4LastModified = (now - oneYearMS) / 1000; + collection.insert( + item4GUID, + encryptPayload(item4.cleartext), + item4LastModified + ); + + let item5GUID = "dddddddddddd"; + let item5 = new Bookmark("bookmarks", item5GUID); + item5.bmkUri = "https://example.com/5"; + item5.title = "asdf5"; + item5.parentName = "Bookmarks Toolbar"; + item5.parentid = "toolbar"; + // no dateAdded, lastModified in (near) future. + const item5LastModified = (now + 60000) / 1000; + collection.insert( + item5GUID, + encryptPayload(item5.cleartext), + item5LastModified + ); + + let item6GUID = "eeeeeeeeeeee"; + let item6 = new Bookmark("bookmarks", item6GUID); + item6.bmkUri = "https://example.com/6"; + item6.title = "asdf6"; + item6.parentName = "Bookmarks Toolbar"; + item6.parentid = "toolbar"; + const item6LastModified = (now - oneYearMS) / 1000; + collection.insert( + item6GUID, + encryptPayload(item6.cleartext), + item6LastModified + ); + + await sync_engine_and_validate_telem(engine, false); + + let record1 = await store.createRecord(item1GUID); + let record2 = await store.createRecord(item2GUID); + + equal( + item1.dateAdded, + record1.dateAdded, + "dateAdded in past should be synced" + ); + equal( + record2.dateAdded, + item2LastModified * 1000, + "dateAdded in future should be ignored in favor of last modified" + ); + + let record3 = await store.createRecord(item3GUID); + + ok(record3.dateAdded); + // Make sure it's within 24 hours of the right timestamp... This is a little + // dodgey but we only really care that it's basically accurate and has the + // right day. + ok(Math.abs(Date.now() - record3.dateAdded) < 24 * 60 * 60 * 1000); + + let record4 = await store.createRecord(item4GUID); + equal( + record4.dateAdded, + item4LastModified * 1000, + "If no dateAdded is provided, lastModified should be used" + ); + + let record5 = await store.createRecord(item5GUID); + equal( + record5.dateAdded, + item5LastModified * 1000, + "If no dateAdded is provided, lastModified should be used (even if it's in the future)" + ); + + // Update item2 and try resyncing it. + item2.dateAdded = now - 100000; + collection.insert( + item2GUID, + encryptPayload(item2.cleartext), + now / 1000 - 50 + ); + + // Also, add a local bookmark and make sure its date added makes it up to the server + let bz = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://bugzilla.mozilla.org/", + title: "Bugzilla", + }); + + // last sync did a POST, which doesn't advance its lastModified value. + // Next sync of the engine doesn't hit info/collections, so lastModified + // remains stale. Setting it to null side-steps that. + engine.lastModified = null; + await sync_engine_and_validate_telem(engine, false); + + let newRecord2 = await store.createRecord(item2GUID); + equal( + newRecord2.dateAdded, + item2.dateAdded, + "dateAdded update should work for earlier date" + ); + + let bzWBO = collection.cleartext(bz.guid); + ok(bzWBO.dateAdded, "Locally added dateAdded lost"); + + let localRecord = await store.createRecord(bz.guid); + equal( + bzWBO.dateAdded, + localRecord.dateAdded, + "dateAdded should not change during upload" + ); + + item2.dateAdded += 10000; + collection.insert( + item2GUID, + encryptPayload(item2.cleartext), + now / 1000 - 10 + ); + + engine.lastModified = null; + await sync_engine_and_validate_telem(engine, false); + + let newerRecord2 = await store.createRecord(item2GUID); + equal( + newerRecord2.dateAdded, + newRecord2.dateAdded, + "dateAdded update should be ignored for later date if we know an earlier one " + ); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_buffer_hasDupe() { + await Service.recordManager.clearCache(); + await PlacesSyncUtils.bookmarks.reset(); + let engine = new BookmarksEngine(Service); + await engine.initialize(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("bookmarks"); + engine._tracker.start(); // We skip usual startup... + try { + let guid1 = Utils.makeGUID(); + let guid2 = Utils.makeGUID(); + await PlacesUtils.bookmarks.insert({ + guid: guid1, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "https://www.example.com", + title: "example.com", + }); + await PlacesUtils.bookmarks.insert({ + guid: guid2, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "https://www.example.com", + title: "example.com", + }); + + await sync_engine_and_validate_telem(engine, false); + // Make sure we set hasDupe on outgoing records + Assert.ok(collection.payloads().every(payload => payload.hasDupe)); + + await PlacesUtils.bookmarks.remove(guid1); + + await sync_engine_and_validate_telem(engine, false); + + let tombstone = JSON.parse( + JSON.parse(collection.payload(guid1)).ciphertext + ); + // We shouldn't set hasDupe on tombstones. + Assert.ok(tombstone.deleted); + Assert.ok(!tombstone.hasDupe); + + let record = JSON.parse(JSON.parse(collection.payload(guid2)).ciphertext); + // We should set hasDupe on weakly uploaded records. + Assert.ok(!record.deleted); + Assert.ok( + record.hasDupe, + "Bookmarks bookmark engine should set hasDupe for weakly uploaded records." + ); + + await sync_engine_and_validate_telem(engine, false); + } finally { + await cleanup(engine, server); + await engine.finalize(); + } +}); + +// Bug 890217. +add_bookmark_test(async function test_sync_imap_URLs(engine) { + await Service.recordManager.clearCache(); + await PlacesSyncUtils.bookmarks.reset(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + engine._tracker.start(); // We skip usual startup... + + try { + collection.insert( + "menu", + encryptPayload({ + id: "menu", + type: "folder", + parentid: "places", + title: "Bookmarks Menu", + children: ["bookmarkAAAA"], + }) + ); + collection.insert( + "bookmarkAAAA", + encryptPayload({ + id: "bookmarkAAAA", + type: "bookmark", + parentid: "menu", + bmkUri: + "imap://vs@eleven.vs.solnicky.cz:993/fetch%3EUID%3E/" + + "INBOX%3E56291?part=1.2&type=image/jpeg&filename=" + + "invalidazPrahy.jpg", + title: + "invalidazPrahy.jpg (JPEG Image, 1280x1024 pixels) - Scaled (71%)", + }) + ); + + await PlacesUtils.bookmarks.insert({ + guid: "bookmarkBBBB", + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: + "imap://eleven.vs.solnicky.cz:993/fetch%3EUID%3E/" + + "CURRENT%3E2433?part=1.2&type=text/html&filename=TomEdwards.html", + title: "TomEdwards.html", + }); + + await sync_engine_and_validate_telem(engine, false); + + let aInfo = await PlacesUtils.bookmarks.fetch("bookmarkAAAA"); + equal( + aInfo.url.href, + "imap://vs@eleven.vs.solnicky.cz:993/" + + "fetch%3EUID%3E/INBOX%3E56291?part=1.2&type=image/jpeg&filename=" + + "invalidazPrahy.jpg", + "Remote bookmark A with IMAP URL should exist locally" + ); + + let bPayload = collection.cleartext("bookmarkBBBB"); + equal( + bPayload.bmkUri, + "imap://eleven.vs.solnicky.cz:993/" + + "fetch%3EUID%3E/CURRENT%3E2433?part=1.2&type=text/html&filename=" + + "TomEdwards.html", + "Local bookmark B with IMAP URL should exist remotely" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_resume_buffer() { + await Service.recordManager.clearCache(); + let engine = new BookmarksEngine(Service); + await engine.initialize(); + await engine._store.wipe(); + await engine.resetClient(); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + + engine._tracker.start(); // We skip usual startup... + + const batchChunkSize = 50; + + engine._store._batchChunkSize = batchChunkSize; + try { + let children = []; + + let timestamp = round_timestamp(Date.now()); + // Add two chunks worth of records to the server + for (let i = 0; i < batchChunkSize * 2; ++i) { + let cleartext = { + id: Utils.makeGUID(), + type: "bookmark", + parentid: "toolbar", + title: `Bookmark ${i}`, + parentName: "Bookmarks Toolbar", + bmkUri: `https://example.com/${i}`, + }; + let wbo = collection.insert( + cleartext.id, + encryptPayload(cleartext), + timestamp + 10 * i + ); + // Something that is effectively random, but deterministic. + // (This is just to ensure we don't accidentally start using the + // sortindex again). + wbo.sortindex = 1000 + Math.round(Math.sin(i / 5) * 100); + children.push(cleartext.id); + } + + // Add the parent of those records, and ensure its timestamp is the most recent. + collection.insert( + "toolbar", + encryptPayload({ + id: "toolbar", + type: "folder", + parentid: "places", + title: "Bookmarks Toolbar", + children, + }), + timestamp + 10 * children.length + ); + + // Replace applyIncomingBatch with a custom one that calls the original, + // but forces it to throw on the 2nd chunk. + let origApplyIncomingBatch = engine._store.applyIncomingBatch; + engine._store.applyIncomingBatch = function (records) { + if (records.length > batchChunkSize) { + // Hacky way to make reading from the batchChunkSize'th record throw. + delete records[batchChunkSize]; + Object.defineProperty(records, batchChunkSize, { + get() { + throw new Error("D:"); + }, + }); + } + return origApplyIncomingBatch.call(this, records); + }; + + let caughtError; + _("We expect this to fail"); + try { + await sync_engine_and_validate_telem(engine, true); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError, "Expected engine.sync to throw"); + Assert.equal(caughtError.message, "D:"); + + // The buffer subtracts one second from the actual timestamp. + let lastSync = (await engine.getLastSync()) + 1; + // We poisoned the batchChunkSize'th record, so the last successfully + // applied record will be batchChunkSize - 1. + let expectedLastSync = timestamp + 10 * (batchChunkSize - 1); + Assert.equal(expectedLastSync, lastSync); + + engine._store.applyIncomingBatch = origApplyIncomingBatch; + + await sync_engine_and_validate_telem(engine, false); + + // Check that all the children made it onto the correct record. + let toolbarRecord = await engine._store.createRecord("toolbar"); + Assert.deepEqual(toolbarRecord.children.sort(), children.sort()); + } finally { + await cleanup(engine, server); + await engine.finalize(); + } +}); + +add_bookmark_test(async function test_livemarks(engine) { + _("Ensure we replace new and existing livemarks with tombstones"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("bookmarks"); + let now = Date.now(); + + try { + _("Insert existing livemark"); + let modifiedForA = now - 5 * 60 * 1000; + await PlacesUtils.bookmarks.insert({ + guid: "livemarkAAAA", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "A", + lastModified: new Date(modifiedForA), + dateAdded: new Date(modifiedForA), + source: PlacesUtils.bookmarks.SOURCE_SYNC, + }); + collection.insert( + "menu", + encryptPayload({ + id: "menu", + type: "folder", + parentName: "", + title: "menu", + children: ["livemarkAAAA"], + parentid: "places", + }), + round_timestamp(modifiedForA) + ); + collection.insert( + "livemarkAAAA", + encryptPayload({ + id: "livemarkAAAA", + type: "livemark", + feedUri: "http://example.com/a", + parentName: "menu", + title: "A", + parentid: "menu", + }), + round_timestamp(modifiedForA) + ); + + _("Insert remotely updated livemark"); + await PlacesUtils.bookmarks.insert({ + guid: "livemarkBBBB", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + title: "B", + lastModified: new Date(now), + dateAdded: new Date(now), + }); + collection.insert( + "toolbar", + encryptPayload({ + id: "toolbar", + type: "folder", + parentName: "", + title: "toolbar", + children: ["livemarkBBBB"], + parentid: "places", + }), + round_timestamp(now) + ); + collection.insert( + "livemarkBBBB", + encryptPayload({ + id: "livemarkBBBB", + type: "livemark", + feedUri: "http://example.com/b", + parentName: "toolbar", + title: "B", + parentid: "toolbar", + }), + round_timestamp(now) + ); + + _("Insert new remote livemark"); + collection.insert( + "unfiled", + encryptPayload({ + id: "unfiled", + type: "folder", + parentName: "", + title: "unfiled", + children: ["livemarkCCCC"], + parentid: "places", + }), + round_timestamp(now) + ); + collection.insert( + "livemarkCCCC", + encryptPayload({ + id: "livemarkCCCC", + type: "livemark", + feedUri: "http://example.com/c", + parentName: "unfiled", + title: "C", + parentid: "unfiled", + }), + round_timestamp(now) + ); + + _("Bump last sync time to ignore A"); + await engine.setLastSync(round_timestamp(now) - 60); + + _("Sync"); + await sync_engine_and_validate_telem(engine, false); + + deepEqual( + collection.keys().sort(), + [ + "livemarkAAAA", + "livemarkBBBB", + "livemarkCCCC", + "menu", + "mobile", + "toolbar", + "unfiled", + ], + "Should store original livemark A and tombstones for B and C on server" + ); + + let payloads = collection.payloads(); + + deepEqual( + payloads.find(payload => payload.id == "menu").children, + ["livemarkAAAA"], + "Should keep A in menu" + ); + ok( + !payloads.find(payload => payload.id == "livemarkAAAA").deleted, + "Should not upload tombstone for A" + ); + + deepEqual( + payloads.find(payload => payload.id == "toolbar").children, + [], + "Should remove B from toolbar" + ); + ok( + payloads.find(payload => payload.id == "livemarkBBBB").deleted, + "Should upload tombstone for B" + ); + + deepEqual( + payloads.find(payload => payload.id == "unfiled").children, + [], + "Should remove C from unfiled" + ); + ok( + payloads.find(payload => payload.id == "livemarkCCCC").deleted, + "Should replace C with tombstone" + ); + + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + children: [ + { + guid: "livemarkAAAA", + index: 0, + }, + ], + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "Should keep A and remove B locally" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_bookmark_test(async function test_unknown_fields(engine) { + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("bookmarks"); + try { + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder 1", + }); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + let bmk2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + let toolbar_record = await store.createRecord("toolbar"); + collection.insert("toolbar", encryptPayload(toolbar_record.cleartext)); + + let folder1_record_without_unknown_fields = await store.createRecord( + folder1.guid + ); + collection.insert( + folder1.guid, + encryptPayload(folder1_record_without_unknown_fields.cleartext) + ); + + // First bookmark record has an unknown string field + let bmk1_record = await store.createRecord(bmk1.guid); + console.log("bmk1_record: ", bmk1_record); + bmk1_record.cleartext.unknownStrField = + "an unknown field from another client"; + collection.insert(bmk1.guid, encryptPayload(bmk1_record.cleartext)); + + // Second bookmark record as an unknown object field + let bmk2_record = await store.createRecord(bmk2.guid); + bmk2_record.cleartext.unknownObjField = { + name: "an unknown object from another client", + }; + collection.insert(bmk2.guid, encryptPayload(bmk2_record.cleartext)); + + // Sync the two bookmarks + await sync_engine_and_validate_telem(engine, true); + + // Add a folder could also have an unknown field + let folder1_record = await store.createRecord(folder1.guid); + folder1_record.cleartext.unknownStrField = + "a folder could also have an unknown field!"; + collection.insert(folder1.guid, encryptPayload(folder1_record.cleartext)); + + // sync the new updates + await engine.setLastSync(1); + await sync_engine_and_validate_telem(engine, true); + + let payloads = collection.payloads(); + // Validate the server has the unknown fields at the top level (and now unknownFields) + let server_bmk1 = payloads.find(payload => payload.id == bmk1.guid); + deepEqual( + server_bmk1.unknownStrField, + "an unknown field from another client", + "unknown fields correctly on the record" + ); + Assert.equal(server_bmk1.unknownFields, null); + + // Check that the mirror table has unknown fields + let db = await PlacesUtils.promiseDBConnection(); + let rows = await db.executeCached( + ` + SELECT guid, title, unknownFields from items WHERE guid IN + (:bmk1, :bmk2, :folder1)`, + { bmk1: bmk1.guid, bmk2: bmk2.guid, folder1: folder1.guid } + ); + // We should have 3 rows that came from the server + Assert.equal(rows.length, 3); + + // Bookmark 1 - unknown string field + let remote_bmk1 = rows.find( + row => row.getResultByName("guid") == bmk1.guid + ); + Assert.equal(remote_bmk1.getResultByName("title"), "Get Firefox!"); + deepEqual(JSON.parse(remote_bmk1.getResultByName("unknownFields")), { + unknownStrField: "an unknown field from another client", + }); + + // Bookmark 2 - unknown object field + let remote_bmk2 = rows.find( + row => row.getResultByName("guid") == bmk2.guid + ); + Assert.equal(remote_bmk2.getResultByName("title"), "Get Thunderbird!"); + deepEqual(JSON.parse(remote_bmk2.getResultByName("unknownFields")), { + unknownObjField: { + name: "an unknown object from another client", + }, + }); + + // Folder with unknown field + + // check the server still has the unknown field + deepEqual( + payloads.find(payload => payload.id == folder1.guid).unknownStrField, + "a folder could also have an unknown field!", + "Server still has the unknown field" + ); + + let remote_folder = rows.find( + row => row.getResultByName("guid") == folder1.guid + ); + Assert.equal(remote_folder.getResultByName("title"), "Folder 1"); + deepEqual(JSON.parse(remote_folder.getResultByName("unknownFields")), { + unknownStrField: "a folder could also have an unknown field!", + }); + } finally { + await cleanup(engine, server); + } +}); diff --git a/services/sync/tests/unit/test_bookmark_order.js b/services/sync/tests/unit/test_bookmark_order.js new file mode 100644 index 0000000000..fc182b81ef --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_order.js @@ -0,0 +1,586 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_( + "Making sure after processing incoming bookmarks, they show up in the right order" +); +const { Bookmark, BookmarkFolder } = ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" +); +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +async function serverForFoo(engine) { + await generateNewKeys(Service.collectionKeys); + + let clientsEngine = Service.clientsEngine; + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + let engineSyncID = await engine.resetLocalSyncID(); + return serverForUsers( + { foo: "password" }, + { + meta: { + global: { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + clients: { + version: clientsEngine.version, + syncID: clientsSyncID, + }, + [engine.name]: { + version: engine.version, + syncID: engineSyncID, + }, + }, + }, + }, + crypto: { + keys: encryptPayload({ + id: "keys", + // Generate a fake default key bundle to avoid resetting the client + // before the first sync. + default: [ + await Weave.Crypto.generateRandomKey(), + await Weave.Crypto.generateRandomKey(), + ], + }), + }, + [engine.name]: {}, + } + ); +} + +async function resolveConflict( + engine, + collection, + timestamp, + buildTree, + message +) { + let guids = { + // These items don't exist on the server. + fx: Utils.makeGUID(), + nightly: Utils.makeGUID(), + support: Utils.makeGUID(), + customize: Utils.makeGUID(), + + // These exist on the server, but in a different order, and `res` + // has completely different children. + res: Utils.makeGUID(), + tb: Utils.makeGUID(), + + // These don't exist locally. + bz: Utils.makeGUID(), + irc: Utils.makeGUID(), + mdn: Utils.makeGUID(), + }; + + await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.menuGuid, + children: [ + { + guid: guids.fx, + title: "Get Firefox!", + url: "http://getfirefox.com/", + }, + { + guid: guids.res, + title: "Resources", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + children: [ + { + guid: guids.nightly, + title: "Nightly", + url: "https://nightly.mozilla.org/", + }, + { + guid: guids.support, + title: "Support", + url: "https://support.mozilla.org/", + }, + { + guid: guids.customize, + title: "Customize", + url: "https://mozilla.org/firefox/customize/", + }, + ], + }, + { + title: "Get Thunderbird!", + guid: guids.tb, + url: "http://getthunderbird.com/", + }, + ], + }); + + let serverRecords = [ + { + id: "menu", + type: "folder", + title: "Bookmarks Menu", + parentid: "places", + children: [guids.tb, guids.res], + }, + { + id: guids.tb, + type: "bookmark", + parentid: "menu", + bmkUri: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }, + { + id: guids.res, + type: "folder", + parentid: "menu", + title: "Resources", + children: [guids.irc, guids.bz, guids.mdn], + }, + { + id: guids.bz, + type: "bookmark", + parentid: guids.res, + bmkUri: "https://bugzilla.mozilla.org/", + title: "Bugzilla", + }, + { + id: guids.mdn, + type: "bookmark", + parentid: guids.res, + bmkUri: "https://developer.mozilla.org/", + title: "MDN", + }, + { + id: guids.irc, + type: "bookmark", + parentid: guids.res, + bmkUri: "ircs://irc.mozilla.org/nightly", + title: "IRC", + }, + ]; + for (let record of serverRecords) { + collection.insert(record.id, encryptPayload(record), timestamp); + } + + engine.lastModified = collection.timestamp; + await sync_engine_and_validate_telem(engine, false); + + let expectedTree = buildTree(guids); + await assertBookmarksTreeMatches( + PlacesUtils.bookmarks.menuGuid, + expectedTree, + message + ); +} + +async function get_engine() { + return Service.engineManager.get("bookmarks"); +} + +add_task(async function test_local_order_newer() { + let engine = await get_engine(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + let collection = server.user("foo").collection("bookmarks"); + let serverModified = Date.now() / 1000 - 120; + await resolveConflict( + engine, + collection, + serverModified, + guids => [ + { + guid: guids.fx, + index: 0, + }, + { + guid: guids.res, + index: 1, + children: [ + { + guid: guids.nightly, + index: 0, + }, + { + guid: guids.support, + index: 1, + }, + { + guid: guids.customize, + index: 2, + }, + { + guid: guids.irc, + index: 3, + }, + { + guid: guids.bz, + index: 4, + }, + { + guid: guids.mdn, + index: 5, + }, + ], + }, + { + guid: guids.tb, + index: 2, + }, + ], + "Should use local order as base if remote is older" + ); + } finally { + await engine.wipeClient(); + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_remote_order_newer() { + let engine = await get_engine(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + let collection = server.user("foo").collection("bookmarks"); + let serverModified = Date.now() / 1000 + 120; + await resolveConflict( + engine, + collection, + serverModified, + guids => [ + { + guid: guids.tb, + index: 0, + }, + { + guid: guids.res, + index: 1, + children: [ + { + guid: guids.irc, + index: 0, + }, + { + guid: guids.bz, + index: 1, + }, + { + guid: guids.mdn, + index: 2, + }, + { + guid: guids.nightly, + index: 3, + }, + { + guid: guids.support, + index: 4, + }, + { + guid: guids.customize, + index: 5, + }, + ], + }, + { + guid: guids.fx, + index: 2, + }, + ], + "Should use remote order as base if local is older" + ); + } finally { + await engine.wipeClient(); + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_bookmark_order() { + let engine = await get_engine(); + let store = engine._store; + _("Starting with a clean slate of no bookmarks"); + await store.wipe(); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + // Index 2 is the tags root. (Root indices depend on the order of the + // `CreateRoot` calls in `Database::CreateBookmarkRoots`). + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "clean slate" + ); + + function bookmark(name, parent) { + let bm = new Bookmark("http://weave.server/my-bookmark"); + bm.id = name; + bm.title = name; + bm.bmkUri = "http://uri/"; + bm.parentid = parent || "unfiled"; + bm.tags = []; + return bm; + } + + function folder(name, parent, children) { + let bmFolder = new BookmarkFolder("http://weave.server/my-bookmark-folder"); + bmFolder.id = name; + bmFolder.title = name; + bmFolder.parentid = parent || "unfiled"; + bmFolder.children = children; + return bmFolder; + } + + async function apply(records) { + for (record of records) { + await store.applyIncoming(record); + } + await engine._apply(); + } + let id10 = "10_aaaaaaaaa"; + _("basic add first bookmark"); + await apply([bookmark(id10, "")]); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + children: [ + { + guid: id10, + index: 0, + }, + ], + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "basic add first bookmark" + ); + let id20 = "20_aaaaaaaaa"; + _("basic append behind 10"); + await apply([bookmark(id20, "")]); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + children: [ + { + guid: id10, + index: 0, + }, + { + guid: id20, + index: 1, + }, + ], + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "basic append behind 10" + ); + + let id31 = "31_aaaaaaaaa"; + let id30 = "f30_aaaaaaaa"; + _("basic create in folder"); + let b31 = bookmark(id31, id30); + let f30 = folder(id30, "", [id31]); + await apply([b31, f30]); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + children: [ + { + guid: id10, + index: 0, + }, + { + guid: id20, + index: 1, + }, + { + guid: id30, + index: 2, + children: [ + { + guid: id31, + index: 0, + }, + ], + }, + ], + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "basic create in folder" + ); + + let id41 = "41_aaaaaaaaa"; + let id40 = "f40_aaaaaaaa"; + _("insert missing parent -> append to unfiled"); + await apply([bookmark(id41, id40)]); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + children: [ + { + guid: id10, + index: 0, + }, + { + guid: id20, + index: 1, + }, + { + guid: id30, + index: 2, + children: [ + { + guid: id31, + index: 0, + }, + ], + }, + { + guid: id41, + index: 3, + }, + ], + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "insert missing parent -> append to unfiled" + ); + + let id42 = "42_aaaaaaaaa"; + + _("insert another missing parent -> append"); + await apply([bookmark(id42, id40)]); + await assertBookmarksTreeMatches( + "", + [ + { + guid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }, + { + guid: PlacesUtils.bookmarks.toolbarGuid, + index: 1, + }, + { + guid: PlacesUtils.bookmarks.unfiledGuid, + index: 3, + children: [ + { + guid: id10, + index: 0, + }, + { + guid: id20, + index: 1, + }, + { + guid: id30, + index: 2, + children: [ + { + guid: id31, + index: 0, + }, + ], + }, + { + guid: id41, + index: 3, + }, + { + guid: id42, + index: 4, + }, + ], + }, + { + guid: PlacesUtils.bookmarks.mobileGuid, + index: 4, + }, + ], + "insert another missing parent -> append" + ); + + await engine.wipeClient(); + await Service.startOver(); + await engine.finalize(); +}); diff --git a/services/sync/tests/unit/test_bookmark_places_query_rewriting.js b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js new file mode 100644 index 0000000000..e8dbbb48b1 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_places_query_rewriting.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Rewrite place: URIs."); +const { BookmarkQuery, BookmarkFolder } = ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" +); +// `Service` is used as a global in head_helpers.js. +// eslint-disable-next-line no-unused-vars +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function makeTagRecord(id, uri) { + let tagRecord = new BookmarkQuery("bookmarks", id); + tagRecord.queryId = "MagicTags"; + tagRecord.parentName = "Bookmarks Toolbar"; + tagRecord.bmkUri = uri; + tagRecord.title = "tagtag"; + tagRecord.folderName = "bar"; + tagRecord.parentid = PlacesUtils.bookmarks.toolbarGuid; + return tagRecord; +} + +add_bookmark_test(async function run_test(engine) { + let store = engine._store; + + let toolbar = new BookmarkFolder("bookmarks", "toolbar"); + toolbar.parentid = "places"; + toolbar.children = ["abcdefabcdef"]; + + let uri = "place:folder=499&type=7&queryType=1"; + let tagRecord = makeTagRecord("abcdefabcdef", uri); + + _("Type: " + tagRecord.type); + _("Folder name: " + tagRecord.folderName); + await store.applyIncoming(toolbar); + await store.applyIncoming(tagRecord); + await engine._apply(); + + let insertedRecord = await store.createRecord("abcdefabcdef", "bookmarks"); + Assert.equal(insertedRecord.bmkUri, "place:tag=bar"); + + _("... but not if the type is wrong."); + let wrongTypeURI = "place:folder=499&type=2&queryType=1"; + let wrongTypeRecord = makeTagRecord("fedcbafedcba", wrongTypeURI); + await store.applyIncoming(wrongTypeRecord); + toolbar.children = ["fedcbafedcba"]; + await store.applyIncoming(toolbar); + let expected = wrongTypeURI; + await engine._apply(); + // the mirror appends a special param to these. + expected += "&excludeItems=1"; + + insertedRecord = await store.createRecord("fedcbafedcba", "bookmarks"); + Assert.equal(insertedRecord.bmkUri, expected); +}); diff --git a/services/sync/tests/unit/test_bookmark_record.js b/services/sync/tests/unit/test_bookmark_record.js new file mode 100644 index 0000000000..c261027ed9 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_record.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Bookmark, BookmarkQuery, PlacesItem } = ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function prepareBookmarkItem(collection, id) { + let b = new Bookmark(collection, id); + b.cleartext.stuff = "my payload here"; + return b; +} + +add_task(async function test_bookmark_record() { + await configureIdentity(); + + await generateNewKeys(Service.collectionKeys); + let keyBundle = Service.identity.syncKeyBundle; + + _("Creating a record"); + + let placesItem = new PlacesItem("bookmarks", "foo", "bookmark"); + let bookmarkItem = prepareBookmarkItem("bookmarks", "foo"); + + _("Checking getTypeObject"); + Assert.equal(placesItem.getTypeObject(placesItem.type), Bookmark); + Assert.equal(bookmarkItem.getTypeObject(bookmarkItem.type), Bookmark); + + await bookmarkItem.encrypt(keyBundle); + _("Ciphertext is " + bookmarkItem.ciphertext); + Assert.ok(bookmarkItem.ciphertext != null); + + _("Decrypting the record"); + + let payload = await bookmarkItem.decrypt(keyBundle); + Assert.equal(payload.stuff, "my payload here"); + Assert.equal(bookmarkItem.getTypeObject(bookmarkItem.type), Bookmark); + Assert.notEqual(payload, bookmarkItem.payload); // wrap.data.payload is the encrypted one +}); + +add_task(async function test_query_foldername() { + // Bug 1443388 + let checks = [ + ["foo", "foo"], + ["", undefined], + ]; + for (let [inVal, outVal] of checks) { + let bmk1 = new BookmarkQuery("bookmarks", Utils.makeGUID()); + bmk1.fromSyncBookmark({ + url: Services.io.newURI("https://example.com"), + folder: inVal, + }); + Assert.strictEqual(bmk1.folderName, outVal); + + // other direction + let bmk2 = new BookmarkQuery("bookmarks", Utils.makeGUID()); + bmk2.folderName = inVal; + let record = bmk2.toSyncBookmark(); + Assert.strictEqual(record.folder, outVal); + } +}); diff --git a/services/sync/tests/unit/test_bookmark_store.js b/services/sync/tests/unit/test_bookmark_store.js new file mode 100644 index 0000000000..2f4330ed2e --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_store.js @@ -0,0 +1,425 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Bookmark, BookmarkFolder, BookmarkQuery, PlacesItem } = + ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" + ); +// `Service` is used as a global in head_helpers.js. +// eslint-disable-next-line no-unused-vars +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const BookmarksToolbarTitle = "toolbar"; + +// apply some test records without going via a test server. +async function apply_records(engine, records) { + for (record of records) { + await engine._store.applyIncoming(record); + } + await engine._apply(); +} + +add_bookmark_test(async function test_ignore_specials(engine) { + _("Ensure that we can't delete bookmark roots."); + let store = engine._store; + + // Belt... + let record = new BookmarkFolder("bookmarks", "toolbar", "folder"); + record.deleted = true; + Assert.notEqual( + null, + await PlacesTestUtils.promiseItemId(PlacesUtils.bookmarks.toolbarGuid) + ); + + await apply_records(engine, [record]); + + // Ensure that the toolbar exists. + Assert.notEqual( + null, + await PlacesTestUtils.promiseItemId(PlacesUtils.bookmarks.toolbarGuid) + ); + + await apply_records(engine, [record]); + + Assert.notEqual( + null, + await PlacesTestUtils.promiseItemId(PlacesUtils.bookmarks.toolbarGuid) + ); + await store.wipe(); +}); + +add_bookmark_test(async function test_bookmark_create(engine) { + let store = engine._store; + + try { + _("Ensure the record isn't present yet."); + let item = await PlacesUtils.bookmarks.fetch({ + url: "http://getfirefox.com/", + }); + Assert.equal(null, item); + + _("Let's create a new record."); + let fxrecord = new Bookmark("bookmarks", "get-firefox1"); + fxrecord.bmkUri = "http://getfirefox.com/"; + fxrecord.title = "Get Firefox!"; + fxrecord.tags = ["firefox", "awesome", "browser"]; + fxrecord.keyword = "awesome"; + fxrecord.parentName = BookmarksToolbarTitle; + fxrecord.parentid = "toolbar"; + await apply_records(engine, [fxrecord]); + + _("Verify it has been created correctly."); + item = await PlacesUtils.bookmarks.fetch(fxrecord.id); + Assert.equal(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + Assert.equal(item.url.href, "http://getfirefox.com/"); + Assert.equal(item.title, fxrecord.title); + Assert.equal(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid); + let keyword = await PlacesUtils.keywords.fetch(fxrecord.keyword); + Assert.equal(keyword.url.href, "http://getfirefox.com/"); + + _( + "Have the store create a new record object. Verify that it has the same data." + ); + let newrecord = await store.createRecord(fxrecord.id); + Assert.ok(newrecord instanceof Bookmark); + for (let property of [ + "type", + "bmkUri", + "title", + "keyword", + "parentName", + "parentid", + ]) { + Assert.equal(newrecord[property], fxrecord[property]); + } + Assert.ok(Utils.deepEquals(newrecord.tags.sort(), fxrecord.tags.sort())); + + _("The calculated sort index is based on frecency data."); + Assert.ok(newrecord.sortindex >= 150); + + _("Create a record with some values missing."); + let tbrecord = new Bookmark("bookmarks", "thunderbird1"); + tbrecord.bmkUri = "http://getthunderbird.com/"; + tbrecord.parentName = BookmarksToolbarTitle; + tbrecord.parentid = "toolbar"; + await apply_records(engine, [tbrecord]); + + _("Verify it has been created correctly."); + item = await PlacesUtils.bookmarks.fetch(tbrecord.id); + Assert.equal(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK); + Assert.equal(item.url.href, "http://getthunderbird.com/"); + Assert.equal(item.title, ""); + Assert.equal(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid); + keyword = await PlacesUtils.keywords.fetch({ + url: "http://getthunderbird.com/", + }); + Assert.equal(null, keyword); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_bookmark_update(engine) { + let store = engine._store; + + try { + _("Create a bookmark whose values we'll change."); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + await PlacesUtils.keywords.insert({ + url: "http://getfirefox.com/", + keyword: "firefox", + }); + + _("Update the record with some null values."); + let record = await store.createRecord(bmk1.guid); + record.title = null; + record.keyword = null; + record.tags = null; + await apply_records(engine, [record]); + + _("Verify that the values have been cleared."); + let item = await PlacesUtils.bookmarks.fetch(bmk1.guid); + Assert.equal(item.title, ""); + let keyword = await PlacesUtils.keywords.fetch({ + url: "http://getfirefox.com/", + }); + Assert.equal(null, keyword); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_bookmark_createRecord(engine) { + let store = engine._store; + + try { + _("Create a bookmark without a title."); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com/", + }); + + _("Verify that the record is created accordingly."); + let record = await store.createRecord(bmk1.guid); + Assert.equal(record.title, ""); + Assert.equal(record.keyword, null); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_folder_create(engine) { + let store = engine._store; + + try { + _("Create a folder."); + let folder = new BookmarkFolder("bookmarks", "testfolder-1"); + folder.parentName = BookmarksToolbarTitle; + folder.parentid = "toolbar"; + folder.title = "Test Folder"; + await apply_records(engine, [folder]); + + _("Verify it has been created correctly."); + let item = await PlacesUtils.bookmarks.fetch(folder.id); + Assert.equal(item.type, PlacesUtils.bookmarks.TYPE_FOLDER); + Assert.equal(item.title, folder.title); + Assert.equal(item.parentGuid, PlacesUtils.bookmarks.toolbarGuid); + + _( + "Have the store create a new record object. Verify that it has the same data." + ); + let newrecord = await store.createRecord(folder.id); + Assert.ok(newrecord instanceof BookmarkFolder); + for (let property of ["title", "parentName", "parentid"]) { + Assert.equal(newrecord[property], folder[property]); + } + + _("Folders have high sort index to ensure they're synced first."); + Assert.equal(newrecord.sortindex, 1000000); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_folder_createRecord(engine) { + let store = engine._store; + + try { + _("Create a folder."); + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder1", + }); + + _("Create two bookmarks in that folder without assigning them GUIDs."); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + let bmk2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + + _("Create a record for the folder and verify basic properties."); + let record = await store.createRecord(folder1.guid); + Assert.ok(record instanceof BookmarkFolder); + Assert.equal(record.title, "Folder1"); + Assert.equal(record.parentid, "toolbar"); + Assert.equal(record.parentName, BookmarksToolbarTitle); + + _( + "Verify the folder's children. Ensures that the bookmarks were given GUIDs." + ); + Assert.deepEqual(record.children, [bmk1.guid, bmk2.guid]); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_deleted(engine) { + let store = engine._store; + + try { + _("Create a bookmark that will be deleted."); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + // The engine needs to think we've previously synced it. + await PlacesTestUtils.markBookmarksAsSynced(); + + _("Delete the bookmark through the store."); + let record = new PlacesItem("bookmarks", bmk1.guid); + record.deleted = true; + await apply_records(engine, [record]); + _("Ensure it has been deleted."); + let item = await PlacesUtils.bookmarks.fetch(bmk1.guid); + let newrec = await store.createRecord(bmk1.guid); + Assert.equal(null, item); + Assert.equal(newrec.deleted, true); + _("Verify that the keyword has been cleared."); + let keyword = await PlacesUtils.keywords.fetch({ + url: "http://getfirefox.com/", + }); + Assert.equal(null, keyword); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_move_folder(engine) { + let store = engine._store; + store._childrenToOrder = {}; // *sob* - only needed for legacy. + + try { + _("Create two folders and a bookmark in one of them."); + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder1", + }); + let folder2 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + title: "Folder2", + }); + let bmk = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + // add records to the store that represent the current state. + await apply_records(engine, [ + await store.createRecord(folder1.guid), + await store.createRecord(folder2.guid), + await store.createRecord(bmk.guid), + ]); + + _("Now simulate incoming records reparenting it."); + let bmkRecord = await store.createRecord(bmk.guid); + Assert.equal(bmkRecord.parentid, folder1.guid); + bmkRecord.parentid = folder2.guid; + + let folder1Record = await store.createRecord(folder1.guid); + Assert.deepEqual(folder1Record.children, [bmk.guid]); + folder1Record.children = []; + let folder2Record = await store.createRecord(folder2.guid); + Assert.deepEqual(folder2Record.children, []); + folder2Record.children = [bmk.guid]; + + await apply_records(engine, [bmkRecord, folder1Record, folder2Record]); + + _("Verify the new parent."); + let movedBmk = await PlacesUtils.bookmarks.fetch(bmk.guid); + Assert.equal(movedBmk.parentGuid, folder2.guid); + } finally { + _("Clean up."); + await store.wipe(); + } +}); + +add_bookmark_test(async function test_move_order(engine) { + let store = engine._store; + let tracker = engine._tracker; + + // Make sure the tracker is turned on. + tracker.start(); + try { + _("Create two bookmarks"); + let bmk1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + let bmk2 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + + _("Verify order."); + let childIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + "toolbar" + ); + Assert.deepEqual(childIds, [bmk1.guid, bmk2.guid]); + let toolbar = await store.createRecord("toolbar"); + Assert.deepEqual(toolbar.children, [bmk1.guid, bmk2.guid]); + + _("Move bookmarks around."); + store._childrenToOrder = {}; + toolbar.children = [bmk2.guid, bmk1.guid]; + await apply_records(engine, [ + toolbar, + await store.createRecord(bmk1.guid), + await store.createRecord(bmk2.guid), + ]); + delete store._childrenToOrder; + + _("Verify new order."); + let newChildIds = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + "toolbar" + ); + Assert.deepEqual(newChildIds, [bmk2.guid, bmk1.guid]); + } finally { + await tracker.stop(); + _("Clean up."); + await store.wipe(); + } +}); + +// Tests Bug 806460, in which query records arrive with empty folder +// names and missing bookmark URIs. +add_bookmark_test(async function test_empty_query_doesnt_die(engine) { + let record = new BookmarkQuery("bookmarks", "8xoDGqKrXf1P"); + record.folderName = ""; + record.queryId = ""; + record.parentName = "Toolbar"; + record.parentid = "toolbar"; + + // These should not throw. + await apply_records(engine, [record]); + + delete record.folderName; + await apply_records(engine, [record]); +}); + +add_bookmark_test(async function test_calculateIndex_for_invalid_url(engine) { + let store = engine._store; + + let folderIndex = await store._calculateIndex({ + type: "folder", + }); + equal(folderIndex, 1000000, "Should use high sort index for folders"); + + let toolbarIndex = await store._calculateIndex({ + parentid: "toolbar", + }); + equal(toolbarIndex, 150, "Should bump sort index for toolbar bookmarks"); + + let validURLIndex = await store._calculateIndex({ + bmkUri: "http://example.com/a", + }); + greaterOrEqual(validURLIndex, 0, "Should use frecency for index"); + + let invalidURLIndex = await store._calculateIndex({ + bmkUri: "!@#$%", + }); + equal(invalidURLIndex, 0, "Should not throw for invalid URLs"); +}); diff --git a/services/sync/tests/unit/test_bookmark_tracker.js b/services/sync/tests/unit/test_bookmark_tracker.js new file mode 100644 index 0000000000..9cfbb4de78 --- /dev/null +++ b/services/sync/tests/unit/test_bookmark_tracker.js @@ -0,0 +1,1275 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { PlacesTransactions } = ChromeUtils.importESModule( + "resource://gre/modules/PlacesTransactions.sys.mjs" +); + +let engine; +let store; +let tracker; + +const DAY_IN_MS = 24 * 60 * 60 * 1000; + +add_task(async function setup() { + await Service.engineManager.switchAlternatives(); + engine = Service.engineManager.get("bookmarks"); + store = engine._store; + tracker = engine._tracker; +}); + +// Test helpers. +async function verifyTrackerEmpty() { + await PlacesTestUtils.promiseAsyncUpdates(); + let changes = await tracker.getChangedIDs(); + deepEqual(changes, {}); + equal(tracker.score, 0); +} + +async function resetTracker() { + await PlacesTestUtils.markBookmarksAsSynced(); + tracker.resetScore(); +} + +async function cleanup() { + await engine.setLastSync(0); + await store.wipe(); + await resetTracker(); + await tracker.stop(); +} + +// startTracking is a signal that the test wants to notice things that happen +// after this is called (ie, things already tracked should be discarded.) +async function startTracking() { + engine._tracker.start(); + await PlacesTestUtils.markBookmarksAsSynced(); +} + +async function verifyTrackedItems(tracked) { + await PlacesTestUtils.promiseAsyncUpdates(); + let changedIDs = await tracker.getChangedIDs(); + let trackedIDs = new Set(Object.keys(changedIDs)); + for (let guid of tracked) { + ok(guid in changedIDs, `${guid} should be tracked`); + ok(changedIDs[guid].modified > 0, `${guid} should have a modified time`); + ok(changedIDs[guid].counter >= -1, `${guid} should have a change counter`); + trackedIDs.delete(guid); + } + equal( + trackedIDs.size, + 0, + `Unhandled tracked IDs: ${JSON.stringify(Array.from(trackedIDs))}` + ); +} + +async function verifyTrackedCount(expected) { + await PlacesTestUtils.promiseAsyncUpdates(); + let changedIDs = await tracker.getChangedIDs(); + do_check_attribute_count(changedIDs, expected); +} + +// A debugging helper that dumps the full bookmarks tree. +// Currently unused, but might come in handy +// eslint-disable-next-line no-unused-vars +async function dumpBookmarks() { + let columns = [ + "id", + "title", + "guid", + "syncStatus", + "syncChangeCounter", + "position", + ]; + return PlacesUtils.promiseDBConnection().then(connection => { + let all = []; + return connection + .executeCached( + `SELECT ${columns.join(", ")} FROM moz_bookmarks;`, + {}, + row => { + let repr = {}; + for (let column of columns) { + repr[column] = row.getResultByName(column); + } + all.push(repr); + } + ) + .then(() => { + dump("All bookmarks:\n"); + dump(JSON.stringify(all, undefined, 2)); + }); + }); +} + +add_task(async function test_tracking() { + _("Test starting and stopping the tracker"); + + // Remove existing tracking information for roots. + await startTracking(); + + let folder = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Test Folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + + // creating the folder should have made 2 changes - the folder itself and + // the parent of the folder. + await verifyTrackedCount(2); + // Reset the changes as the rest of the test doesn't want to see these. + await resetTracker(); + + function createBmk() { + return PlacesUtils.bookmarks.insert({ + parentGuid: folder.guid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + } + + try { + _("Tell the tracker to start tracking changes."); + await startTracking(); + await createBmk(); + // We expect two changed items because the containing folder + // changed as well (new child). + await verifyTrackedCount(2); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + + _("Notifying twice won't do any harm."); + await createBmk(); + await verifyTrackedCount(3); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_tracker_sql_batching() { + _( + "Test tracker does the correct thing when it is forced to batch SQL queries" + ); + + const SQLITE_MAX_VARIABLE_NUMBER = 999; + let numItems = SQLITE_MAX_VARIABLE_NUMBER * 2 + 10; + + await startTracking(); + + let children = []; + for (let i = 0; i < numItems; i++) { + children.push({ + url: "https://example.org/" + i, + title: "Sync Bookmark " + i, + }); + } + let inserted = await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.unfiledGuid, + children: [ + { + type: PlacesUtils.bookmarks.TYPE_FOLDER, + children, + }, + ], + }); + + Assert.equal(children.length, numItems); + Assert.equal(inserted.length, numItems + 1); + await verifyTrackedCount(numItems + 2); // The parent and grandparent are also tracked. + await resetTracker(); + + await PlacesUtils.bookmarks.remove(inserted[0]); + await verifyTrackedCount(numItems + 2); + + await cleanup(); +}); + +add_task(async function test_bookmarkAdded() { + _("Items inserted via the synchronous bookmarks API should be tracked"); + + try { + await startTracking(); + + _("Insert a folder using the sync API"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let syncFolder = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Sync Folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + await verifyTrackedItems(["menu", syncFolder.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + + await resetTracker(); + await startTracking(); + + _("Insert a bookmark using the sync API"); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let syncBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: syncFolder.guid, + url: "https://example.org/sync", + title: "Sync Bookmark", + }); + await verifyTrackedItems([syncFolder.guid, syncBmk.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_bookmarkAdded() { + _("Items inserted via the asynchronous bookmarks API should be tracked"); + + try { + await startTracking(); + + _("Insert a folder using the async API"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let asyncFolder = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Async Folder", + }); + await verifyTrackedItems(["menu", asyncFolder.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + + await resetTracker(); + await startTracking(); + + _("Insert a bookmark using the async API"); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let asyncBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: asyncFolder.guid, + url: "https://example.org/async", + title: "Async Bookmark", + }); + await verifyTrackedItems([asyncFolder.guid, asyncBmk.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + + await resetTracker(); + await startTracking(); + + _("Insert a separator using the async API"); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let asyncSep = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_SEPARATOR, + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: asyncFolder.index, + }); + await verifyTrackedItems(["menu", asyncSep.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemChanged() { + _("Items updated using the asynchronous bookmarks API should be tracked"); + + try { + await tracker.stop(); + + _("Insert a bookmark"); + let fxBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`Firefox GUID: ${fxBmk.guid}`); + + await startTracking(); + + _("Update the bookmark using the async API"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.update({ + guid: fxBmk.guid, + title: "Download Firefox", + url: "https://www.mozilla.org/firefox", + // PlacesUtils.bookmarks.update rejects last modified dates older than + // the added date. + lastModified: new Date(Date.now() + DAY_IN_MS), + }); + + await verifyTrackedItems([fxBmk.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemChanged_itemDates() { + _("Changes to item dates should be tracked"); + + try { + await tracker.stop(); + + _("Insert a bookmark"); + let fx_bm = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`Firefox GUID: ${fx_bm.guid}`); + + await startTracking(); + + _("Reset the bookmark's added date, should not be tracked"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let dateAdded = new Date(Date.now() - DAY_IN_MS); + await PlacesUtils.bookmarks.update({ + guid: fx_bm.guid, + dateAdded, + }); + await verifyTrackedCount(0); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges); + + await resetTracker(); + + _( + "Reset the bookmark's added date and another property, should be tracked" + ); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + dateAdded = new Date(); + await PlacesUtils.bookmarks.update({ + guid: fx_bm.guid, + dateAdded, + title: "test", + }); + await verifyTrackedItems([fx_bm.guid]); + Assert.equal(tracker.score, 2 * SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1); + + await resetTracker(); + + _("Set the bookmark's last modified date"); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + let fx_id = await PlacesTestUtils.promiseItemId(fx_bm.guid); + let dateModified = Date.now() * 1000; + PlacesUtils.bookmarks.setItemLastModified(fx_id, dateModified); + await verifyTrackedItems([fx_bm.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemTagged() { + _("Items tagged using the synchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Create a folder"); + let folder = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Parent", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + _("Folder ID: " + folder); + _("Folder GUID: " + folder.guid); + + _("Track changes to tags"); + let uri = CommonUtils.makeURI("http://getfirefox.com"); + let b = await PlacesUtils.bookmarks.insert({ + parentGuid: folder.guid, + url: uri, + title: "Get Firefox!", + }); + _("New item is " + b); + _("GUID: " + b.guid); + + await startTracking(); + + _("Tag the item"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + PlacesUtils.tagging.tagURI(uri, ["foo"]); + + // bookmark should be tracked, folder should not be. + await verifyTrackedItems([b.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 6); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemUntagged() { + _("Items untagged using the synchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert tagged bookmarks"); + let uri = CommonUtils.makeURI("http://getfirefox.com"); + let fx1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: uri, + title: "Get Firefox!", + }); + // Different parent and title; same URL. + let fx2 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: uri, + title: "Download Firefox", + }); + PlacesUtils.tagging.tagURI(uri, ["foo"]); + + await startTracking(); + + _("Remove the tag"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + PlacesUtils.tagging.untagURI(uri, ["foo"]); + + await verifyTrackedItems([fx1.guid, fx2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemUntagged() { + _("Items untagged using the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert tagged bookmarks"); + let fxBmk1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let fxBmk2 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com", + title: "Download Firefox", + }); + let tag = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.tagsGuid, + title: "some tag", + }); + let fxTag = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: tag.guid, + url: "http://getfirefox.com", + }); + + await startTracking(); + + _("Remove the tag using the async bookmarks API"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.remove(fxTag.guid); + + await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemTagged() { + _("Items tagged using the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert untagged bookmarks"); + let folder1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Folder 1", + }); + let fxBmk1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: folder1.guid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let folder2 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Folder 2", + }); + // Different parent and title; same URL. + let fxBmk2 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: folder2.guid, + url: "http://getfirefox.com", + title: "Download Firefox", + }); + + await startTracking(); + + // This will change once tags are moved into a separate table (bug 424160). + // We specifically test this case because Bookmarks.jsm updates tagged + // bookmarks and notifies observers. + _("Insert a tag using the async bookmarks API"); + let tag = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.tagsGuid, + title: "some tag", + }); + + _("Tag an item using the async bookmarks API"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: tag.guid, + url: "http://getfirefox.com", + }); + + await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemKeywordChanged() { + _("Keyword changes via the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert two bookmarks with the same URL"); + let fxBmk1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let fxBmk2 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com", + title: "Download Firefox", + }); + + await startTracking(); + + _("Add a keyword for both items"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.keywords.insert({ + keyword: "the_keyword", + url: "http://getfirefox.com", + postData: "postData", + }); + + await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemKeywordDeleted() { + _("Keyword deletions via the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert two bookmarks with the same URL and keywords"); + let fxBmk1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let fxBmk2 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com", + title: "Download Firefox", + }); + await PlacesUtils.keywords.insert({ + keyword: "the_keyword", + url: "http://getfirefox.com", + }); + + await startTracking(); + + _("Remove the keyword"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.keywords.remove("the_keyword"); + + await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_bookmarkAdded_filtered_root() { + _("Items outside the change roots should not be tracked"); + + try { + await startTracking(); + + _("Create a new root"); + let root = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.rootGuid, + title: "New root", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + _(`New root GUID: ${root.guid}`); + + _("Insert a bookmark underneath the new root"); + let untrackedBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: root.guid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + _(`New untracked bookmark GUID: ${untrackedBmk.guid}`); + + _("Insert a bookmark underneath the Places root"); + let rootBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.rootGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`New Places root bookmark GUID: ${rootBmk.guid}`); + + _("New root and bookmark should be ignored"); + await verifyTrackedItems([]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemDeleted_filtered_root() { + _("Deleted items outside the change roots should not be tracked"); + + try { + await tracker.stop(); + + _("Insert a bookmark underneath the Places root"); + let rootBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.rootGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`New Places root bookmark GUID: ${rootBmk.guid}`); + + await startTracking(); + + await PlacesUtils.bookmarks.remove(rootBmk); + + await verifyTrackedItems([]); + // We'll still increment the counter for the removed item. + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onPageAnnoChanged() { + _("Page annotations should not be tracked"); + + try { + await tracker.stop(); + + _("Insert a bookmark without an annotation"); + let pageURI = "http://getfirefox.com"; + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: pageURI, + title: "Get Firefox!", + }); + + await startTracking(); + + _("Add a page annotation"); + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([[PlacesUtils.CHARSET_ANNO, "UTF-16"]]), + }); + await verifyTrackedItems([]); + Assert.equal(tracker.score, 0); + await resetTracker(); + + _("Remove the page annotation"); + await PlacesUtils.history.update({ + url: pageURI, + annotations: new Map([[PlacesUtils.CHARSET_ANNO, null]]), + }); + await verifyTrackedItems([]); + Assert.equal(tracker.score, 0); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onFaviconChanged() { + _("Favicon changes should not be tracked"); + + try { + await tracker.stop(); + + let pageURI = CommonUtils.makeURI("http://getfirefox.com"); + let iconURI = CommonUtils.makeURI("http://getfirefox.com/icon"); + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: pageURI, + title: "Get Firefox!", + }); + + await PlacesTestUtils.addVisits(pageURI); + + await startTracking(); + + _("Favicon annotations should be ignored"); + let iconURL = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" + + "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg=="; + + PlacesUtils.favicons.replaceFaviconDataFromDataURL( + iconURI, + iconURL, + 0, + Services.scriptSecurityManager.getSystemPrincipal() + ); + + await new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + pageURI, + iconURI, + true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + (uri, dataLen, data, mimeType) => { + resolve(); + }, + Services.scriptSecurityManager.getSystemPrincipal() + ); + }); + await verifyTrackedItems([]); + Assert.equal(tracker.score, 0); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemMoved_moveToFolder() { + _("Items moved via `moveToFolder` should be tracked"); + + try { + await tracker.stop(); + + await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.menuGuid, + children: [ + { + guid: "bookmarkAAAA", + title: "A", + url: "http://example.com/a", + }, + { + guid: "bookmarkBBBB", + title: "B", + url: "http://example.com/b", + }, + { + guid: "bookmarkCCCC", + title: "C", + url: "http://example.com/c", + }, + { + guid: "bookmarkDDDD", + title: "D", + url: "http://example.com/d", + }, + ], + }); + await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.toolbarGuid, + children: [ + { + guid: "bookmarkEEEE", + title: "E", + url: "http://example.com/e", + }, + ], + }); + + await startTracking(); + + _("Move (A B D) to the toolbar"); + await PlacesUtils.bookmarks.moveToFolder( + ["bookmarkAAAA", "bookmarkBBBB", "bookmarkDDDD"], + PlacesUtils.bookmarks.toolbarGuid, + PlacesUtils.bookmarks.DEFAULT_INDEX + ); + + // Moving multiple bookmarks between two folders should track the old + // folder, new folder, and moved bookmarks. + await verifyTrackedItems([ + "menu", + "toolbar", + "bookmarkAAAA", + "bookmarkBBBB", + "bookmarkDDDD", + ]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + await resetTracker(); + + _("Reorder toolbar children: (D A B E)"); + await PlacesUtils.bookmarks.moveToFolder( + ["bookmarkDDDD", "bookmarkAAAA", "bookmarkBBBB"], + PlacesUtils.bookmarks.toolbarGuid, + 0 + ); + + // Reordering bookmarks in a folder should only track the folder, not the + // bookmarks. + await verifyTrackedItems(["toolbar"]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemMoved_update() { + _("Items moved via the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let tbBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + + await startTracking(); + + _("Repositioning a bookmark should track the folder"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.update({ + guid: tbBmk.guid, + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }); + await verifyTrackedItems(["menu"]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1); + await resetTracker(); + + _("Reparenting a bookmark should track both folders and the bookmark"); + totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.update({ + guid: tbBmk.guid, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + }); + await verifyTrackedItems(["menu", "toolbar", tbBmk.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 3); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemMoved_reorder() { + _("Items reordered via the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + _("Insert out-of-order bookmarks"); + let fxBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`Firefox GUID: ${fxBmk.guid}`); + + let tbBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + _(`Thunderbird GUID: ${tbBmk.guid}`); + + let mozBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://mozilla.org", + title: "Mozilla", + }); + _(`Mozilla GUID: ${mozBmk.guid}`); + + await startTracking(); + + _("Reorder bookmarks"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.menuGuid, [ + mozBmk.guid, + fxBmk.guid, + tbBmk.guid, + ]); + + // We only track the folder if we reorder its children, but we should + // bump the score for every changed item. + await verifyTrackedItems(["menu"]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemDeleted_removeFolderTransaction() { + _("Folders removed in a transaction should be tracked"); + + try { + await tracker.stop(); + + _("Create a folder with two children"); + let folder = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "Test folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + _(`Folder GUID: ${folder.guid}`); + let fx = await PlacesUtils.bookmarks.insert({ + parentGuid: folder.guid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`Firefox GUID: ${fx.guid}`); + let tb = await PlacesUtils.bookmarks.insert({ + parentGuid: folder.guid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + _(`Thunderbird GUID: ${tb.guid}`); + + await startTracking(); + + let txn = PlacesTransactions.Remove({ guid: folder.guid }); + // We haven't executed the transaction yet. + await verifyTrackerEmpty(); + + _("Execute the remove folder transaction"); + await txn.transact(); + await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + await resetTracker(); + + _("Undo the remove folder transaction"); + await PlacesTransactions.undo(); + + await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + await resetTracker(); + + _("Redo the transaction"); + await PlacesTransactions.redo(); + await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_treeMoved() { + _("Moving an entire tree of bookmarks should track the parents"); + + try { + // Create a couple of parent folders. + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + test: "First test folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + + // A second folder in the first. + let folder2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + title: "Second test folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + + // Create a couple of bookmarks in the second folder. + await PlacesUtils.bookmarks.insert({ + parentGuid: folder2.guid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + await PlacesUtils.bookmarks.insert({ + parentGuid: folder2.guid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + + await startTracking(); + + // Move folder 2 to be a sibling of folder1. + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.update({ + guid: folder2.guid, + parentGuid: PlacesUtils.bookmarks.menuGuid, + index: 0, + }); + + // the menu and both folders should be tracked, the children should not be. + await verifyTrackedItems(["menu", folder1.guid, folder2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 3); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemDeleted() { + _("Bookmarks deleted via the synchronous API should be tracked"); + + try { + await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let tb = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + + await startTracking(); + + // Delete the last item - the item and parent should be tracked. + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.remove(tb); + + await verifyTrackedItems(["menu", tb.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemDeleted() { + _("Bookmarks deleted via the asynchronous API should be tracked"); + + try { + await tracker.stop(); + + let fxBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + + await startTracking(); + + _("Delete the first item"); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.remove(fxBmk.guid); + + await verifyTrackedItems(["menu", fxBmk.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_async_onItemDeleted_eraseEverything() { + _("Erasing everything should track all deleted items"); + + try { + await tracker.stop(); + + let fxBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.mobileGuid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + _(`Firefox GUID: ${fxBmk.guid}`); + let tbBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.mobileGuid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + _(`Thunderbird GUID: ${tbBmk.guid}`); + let mozBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://mozilla.org", + title: "Mozilla", + }); + _(`Mozilla GUID: ${mozBmk.guid}`); + let mdnBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://developer.mozilla.org", + title: "MDN", + }); + _(`MDN GUID: ${mdnBmk.guid}`); + let bugsFolder = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + title: "Bugs", + }); + _(`Bugs folder GUID: ${bugsFolder.guid}`); + let bzBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: bugsFolder.guid, + url: "https://bugzilla.mozilla.org", + title: "Bugzilla", + }); + _(`Bugzilla GUID: ${bzBmk.guid}`); + let bugsChildFolder = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: bugsFolder.guid, + title: "Bugs child", + }); + _(`Bugs child GUID: ${bugsChildFolder.guid}`); + let bugsGrandChildBmk = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_BOOKMARK, + parentGuid: bugsChildFolder.guid, + url: "https://example.com", + title: "Bugs grandchild", + }); + _(`Bugs grandchild GUID: ${bugsGrandChildBmk.guid}`); + + await startTracking(); + // Simulate moving a synced item into a new folder. Deleting the folder + // should write a tombstone for the item, but not the folder. + await PlacesTestUtils.setBookmarkSyncFields({ + guid: bugsChildFolder.guid, + syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NEW, + }); + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.eraseEverything(); + + // bugsChildFolder's sync status is still "NEW", so it shouldn't be + // tracked. bugsGrandChildBmk is "NORMAL", so we *should* write a + // tombstone and track it. + await verifyTrackedItems([ + "menu", + mozBmk.guid, + mdnBmk.guid, + "toolbar", + bugsFolder.guid, + "mobile", + fxBmk.guid, + tbBmk.guid, + "unfiled", + bzBmk.guid, + bugsGrandChildBmk.guid, + ]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 8); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 11); + } finally { + _("Clean up."); + await cleanup(); + } +}); + +add_task(async function test_onItemDeleted_tree() { + _("Deleting a tree of bookmarks should track all items"); + + try { + // Create a couple of parent folders. + let folder1 = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + title: "First test folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + + // A second folder in the first. + let folder2 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + title: "Second test folder", + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + + // Create a couple of bookmarks in the second folder. + let fx = await PlacesUtils.bookmarks.insert({ + parentGuid: folder2.guid, + url: "http://getfirefox.com", + title: "Get Firefox!", + }); + let tb = await PlacesUtils.bookmarks.insert({ + parentGuid: folder2.guid, + url: "http://getthunderbird.com", + title: "Get Thunderbird!", + }); + + await startTracking(); + + // Delete folder2 - everything we created should be tracked. + let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges; + await PlacesUtils.bookmarks.remove(folder2); + + await verifyTrackedItems([fx.guid, tb.guid, folder1.guid, folder2.guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 4); + } finally { + _("Clean up."); + await cleanup(); + } +}); diff --git a/services/sync/tests/unit/test_bridged_engine.js b/services/sync/tests/unit/test_bridged_engine.js new file mode 100644 index 0000000000..25a81f8f69 --- /dev/null +++ b/services/sync/tests/unit/test_bridged_engine.js @@ -0,0 +1,248 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { BridgedEngine, BridgeWrapperXPCOM } = ChromeUtils.importESModule( + "resource://services-sync/bridged_engine.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +// Wraps an `object` in a proxy so that its methods are bound to it. This +// simulates how XPCOM class instances have all their methods bound. +function withBoundMethods(object) { + return new Proxy(object, { + get(target, key) { + let value = target[key]; + return typeof value == "function" ? value.bind(target) : value; + }, + }); +} + +add_task(async function test_interface() { + class TestBridge { + constructor() { + this.storageVersion = 2; + this.syncID = "syncID111111"; + this.clear(); + } + + clear() { + this.lastSyncMillis = 0; + this.wasSyncStarted = false; + this.incomingEnvelopes = []; + this.uploadedIDs = []; + this.wasSyncFinished = false; + this.wasReset = false; + this.wasWiped = false; + } + + // `mozIBridgedSyncEngine` methods. + + getLastSync(callback) { + CommonUtils.nextTick(() => callback.handleSuccess(this.lastSyncMillis)); + } + + setLastSync(millis, callback) { + this.lastSyncMillis = millis; + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + resetSyncId(callback) { + CommonUtils.nextTick(() => callback.handleSuccess(this.syncID)); + } + + ensureCurrentSyncId(newSyncId, callback) { + equal(newSyncId, this.syncID, "Local and new sync IDs should match"); + CommonUtils.nextTick(() => callback.handleSuccess(this.syncID)); + } + + syncStarted(callback) { + this.wasSyncStarted = true; + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + storeIncoming(envelopes, callback) { + this.incomingEnvelopes.push(...envelopes.map(r => JSON.parse(r))); + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + apply(callback) { + let outgoingEnvelopes = [ + { + id: "hanson", + data: { + plants: ["seed", "flower 💐", "rose"], + canYouTell: false, + }, + }, + { + id: "sheryl-crow", + data: { + today: "winding 🛣", + tomorrow: "winding 🛣", + }, + }, + ].map(cleartext => + JSON.stringify({ + id: cleartext.id, + payload: JSON.stringify(cleartext), + }) + ); + CommonUtils.nextTick(() => callback.handleSuccess(outgoingEnvelopes)); + } + + setUploaded(millis, ids, callback) { + this.uploadedIDs.push(...ids); + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + syncFinished(callback) { + this.wasSyncFinished = true; + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + reset(callback) { + this.clear(); + this.wasReset = true; + CommonUtils.nextTick(() => callback.handleSuccess()); + } + + wipe(callback) { + this.clear(); + this.wasWiped = true; + CommonUtils.nextTick(() => callback.handleSuccess()); + } + } + + let bridge = new TestBridge(); + let engine = new BridgedEngine("Nineties", Service); + engine._bridge = new BridgeWrapperXPCOM(withBoundMethods(bridge)); + engine.enabled = true; + + let server = await serverForFoo(engine); + try { + await SyncTestingInfrastructure(server); + + info("Add server records"); + let foo = server.user("foo"); + let collection = foo.collection("nineties"); + let now = new_timestamp(); + collection.insert( + "backstreet", + encryptPayload({ + id: "backstreet", + data: { + say: "I want it that way", + when: "never", + }, + }), + now + ); + collection.insert( + "tlc", + encryptPayload({ + id: "tlc", + data: { + forbidden: ["scrubs 🚫"], + numberAvailable: false, + }, + }), + now + 5 + ); + + info("Sync the engine"); + // Advance the last sync time to skip the Backstreet Boys... + bridge.lastSyncMillis = 1000 * (now + 2); + await sync_engine_and_validate_telem(engine, false); + + let metaGlobal = foo.collection("meta").wbo("global").get(); + deepEqual( + JSON.parse(metaGlobal.payload).engines.nineties, + { + version: 2, + syncID: "syncID111111", + }, + "Should write storage version and sync ID to m/g" + ); + + greater(bridge.lastSyncMillis, 0, "Should update last sync time"); + ok( + bridge.wasSyncStarted, + "Should have started sync before storing incoming" + ); + deepEqual( + bridge.incomingEnvelopes + .sort((a, b) => a.id.localeCompare(b.id)) + .map(({ payload, ...envelope }) => ({ + cleartextAsObject: JSON.parse(payload), + ...envelope, + })), + [ + { + id: "tlc", + modified: now + 5, + cleartextAsObject: { + id: "tlc", + data: { + forbidden: ["scrubs 🚫"], + numberAvailable: false, + }, + }, + }, + ], + "Should stage incoming records from server" + ); + deepEqual( + bridge.uploadedIDs.sort(), + ["hanson", "sheryl-crow"], + "Should mark new local records as uploaded" + ); + ok(bridge.wasSyncFinished, "Should have finished sync after uploading"); + + deepEqual( + collection.keys().sort(), + ["backstreet", "hanson", "sheryl-crow", "tlc"], + "Should have all records on server" + ); + let expectedRecords = [ + { + id: "sheryl-crow", + data: { + today: "winding 🛣", + tomorrow: "winding 🛣", + }, + }, + { + id: "hanson", + data: { + plants: ["seed", "flower 💐", "rose"], + canYouTell: false, + }, + }, + ]; + for (let expected of expectedRecords) { + let actual = collection.cleartext(expected.id); + deepEqual( + actual, + expected, + `Should upload record ${expected.id} from bridged engine` + ); + } + + await engine.resetClient(); + ok(bridge.wasReset, "Should reset local storage for bridge"); + + await engine.wipeClient(); + ok(bridge.wasWiped, "Should wipe local storage for bridge"); + + await engine.resetSyncID(); + ok( + !foo.collection("nineties"), + "Should delete server collection after resetting sync ID" + ); + } finally { + await promiseStopServer(server); + await engine.finalize(); + } +}); diff --git a/services/sync/tests/unit/test_clients_engine.js b/services/sync/tests/unit/test_clients_engine.js new file mode 100644 index 0000000000..d910a67503 --- /dev/null +++ b/services/sync/tests/unit/test_clients_engine.js @@ -0,0 +1,2108 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { ClientEngine, ClientsRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/clients.sys.mjs" +); +const { CryptoWrapper } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const MORE_THAN_CLIENTS_TTL_REFRESH = 691200; // 8 days +const LESS_THAN_CLIENTS_TTL_REFRESH = 86400; // 1 day + +let engine; + +/** + * Unpack the record with this ID, and verify that it has the same version that + * we should be putting into records. + */ +async function check_record_version(user, id) { + let payload = user.collection("clients").wbo(id).data; + + let rec = new CryptoWrapper(); + rec.id = id; + rec.collection = "clients"; + rec.ciphertext = payload.ciphertext; + rec.hmac = payload.hmac; + rec.IV = payload.IV; + + let cleartext = await rec.decrypt( + Service.collectionKeys.keyForCollection("clients") + ); + + _("Payload is " + JSON.stringify(cleartext)); + equal(Services.appinfo.version, cleartext.version); + equal(1, cleartext.protocols.length); + equal("1.5", cleartext.protocols[0]); +} + +// compare 2 different command arrays, taking into account that a flowID +// attribute must exist, be unique in the commands, but isn't specified in +// "expected" as the value isn't known. +function compareCommands(actual, expected, description) { + let tweakedActual = JSON.parse(JSON.stringify(actual)); + tweakedActual.map(elt => delete elt.flowID); + deepEqual(tweakedActual, expected, description); + // each item must have a unique flowID. + let allIDs = new Set(actual.map(elt => elt.flowID).filter(fid => !!fid)); + equal(allIDs.size, actual.length, "all items have unique IDs"); +} + +async function syncClientsEngine(server) { + engine._lastFxADevicesFetch = 0; + engine.lastModified = server.getCollection("foo", "clients").timestamp; + await engine._sync(); +} + +add_task(async function setup() { + engine = Service.clientsEngine; +}); + +async function cleanup() { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await engine._tracker.clearChangedIDs(); + await engine._resetClient(); + // un-cleanup the logs (the resetBranch will have reset their levels), since + // not all the tests use SyncTestingInfrastructure, and it's cheap. + syncTestLogging(); + // We don't finalize storage at cleanup, since we use the same clients engine + // instance across all tests. +} + +add_task(async function test_bad_hmac() { + _("Ensure that Clients engine deletes corrupt records."); + let deletedCollections = []; + let deletedItems = []; + let callback = { + onItemDeleted(username, coll, wboID) { + deletedItems.push(coll + "/" + wboID); + }, + onCollectionDeleted(username, coll) { + deletedCollections.push(coll); + }, + }; + Object.setPrototypeOf(callback, SyncServerCallback); + let server = await serverForFoo(engine, callback); + let user = server.user("foo"); + + function check_clients_count(expectedCount) { + let coll = user.collection("clients"); + + // Treat a non-existent collection as empty. + equal(expectedCount, coll ? coll.count() : 0); + } + + function check_client_deleted(id) { + let coll = user.collection("clients"); + let wbo = coll.wbo(id); + return !wbo || !wbo.payload; + } + + async function uploadNewKeys() { + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + ok( + (await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success + ); + } + + try { + await configureIdentity({ username: "foo" }, server); + await Service.login(); + + await generateNewKeys(Service.collectionKeys); + + _("First sync, client record is uploaded"); + equal(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + check_clients_count(0); + await syncClientsEngine(server); + check_clients_count(1); + ok(engine.lastRecordUpload > 0); + ok(!engine.isFirstSync); + + // Our uploaded record has a version. + await check_record_version(user, engine.localID); + + // Initial setup can wipe the server, so clean up. + deletedCollections = []; + deletedItems = []; + + _("Change our keys and our client ID, reupload keys."); + let oldLocalID = engine.localID; // Preserve to test for deletion! + engine.localID = Utils.makeGUID(); + await engine.resetClient(); + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + ok( + (await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success + ); + + _("Sync."); + await syncClientsEngine(server); + + _("Old record " + oldLocalID + " was deleted, new one uploaded."); + check_clients_count(1); + check_client_deleted(oldLocalID); + + _( + "Now change our keys but don't upload them. " + + "That means we get an HMAC error but redownload keys." + ); + Service.lastHMACEvent = 0; + engine.localID = Utils.makeGUID(); + await engine.resetClient(); + await generateNewKeys(Service.collectionKeys); + deletedCollections = []; + deletedItems = []; + check_clients_count(1); + await syncClientsEngine(server); + + _("Old record was not deleted, new one uploaded."); + equal(deletedCollections.length, 0); + equal(deletedItems.length, 0); + check_clients_count(2); + + _( + "Now try the scenario where our keys are wrong *and* there's a bad record." + ); + // Clean up and start fresh. + user.collection("clients")._wbos = {}; + Service.lastHMACEvent = 0; + engine.localID = Utils.makeGUID(); + await engine.resetClient(); + deletedCollections = []; + deletedItems = []; + check_clients_count(0); + + await uploadNewKeys(); + + // Sync once to upload a record. + await syncClientsEngine(server); + check_clients_count(1); + + // Generate and upload new keys, so the old client record is wrong. + await uploadNewKeys(); + + // Create a new client record and new keys. Now our keys are wrong, as well + // as the object on the server. We'll download the new keys and also delete + // the bad client record. + oldLocalID = engine.localID; // Preserve to test for deletion! + engine.localID = Utils.makeGUID(); + await engine.resetClient(); + await generateNewKeys(Service.collectionKeys); + let oldKey = Service.collectionKeys.keyForCollection(); + + equal(deletedCollections.length, 0); + equal(deletedItems.length, 0); + await syncClientsEngine(server); + equal(deletedItems.length, 1); + check_client_deleted(oldLocalID); + check_clients_count(1); + let newKey = Service.collectionKeys.keyForCollection(); + ok(!oldKey.equals(newKey)); + } finally { + await cleanup(); + await promiseStopServer(server); + } +}); + +add_task(async function test_properties() { + _("Test lastRecordUpload property"); + try { + equal( + Svc.PrefBranch.getPrefType("clients.lastRecordUpload"), + Ci.nsIPrefBranch.PREF_INVALID + ); + equal(engine.lastRecordUpload, 0); + + let now = Date.now(); + engine.lastRecordUpload = now / 1000; + equal(engine.lastRecordUpload, Math.floor(now / 1000)); + } finally { + await cleanup(); + } +}); + +add_task(async function test_full_sync() { + _("Ensure that Clients engine fetches all records for each sync."); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + let user = server.user("foo"); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let activeID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: activeID, + name: "Active client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + let deletedID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: deletedID, + name: "Client to delete", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + let store = engine._store; + + _("First sync. 2 records downloaded; our record uploaded."); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + ok(engine.lastRecordUpload > 0); + ok(!engine.isFirstSync); + deepEqual( + user.collection("clients").keys().sort(), + [activeID, deletedID, engine.localID].sort(), + "Our record should be uploaded on first sync" + ); + let ids = await store.getAllIDs(); + deepEqual( + Object.keys(ids).sort(), + [activeID, deletedID, engine.localID].sort(), + "Other clients should be downloaded on first sync" + ); + + _("Delete a record, then sync again"); + let collection = server.getCollection("foo", "clients"); + collection.remove(deletedID); + // Simulate a timestamp update in info/collections. + await syncClientsEngine(server); + + _("Record should be updated"); + ids = await store.getAllIDs(); + deepEqual( + Object.keys(ids).sort(), + [activeID, engine.localID].sort(), + "Deleted client should be removed on next sync" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_sync() { + _("Ensure that Clients engine uploads a new client record once a week."); + + let server = await serverForFoo(engine); + let user = server.user("foo"); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + function clientWBO() { + return user.collection("clients").wbo(engine.localID); + } + + try { + _("First sync. Client record is uploaded."); + equal(clientWBO(), undefined); + equal(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + ok(!!clientWBO().payload); + ok(engine.lastRecordUpload > 0); + ok(!engine.isFirstSync); + + _( + "Let's time travel more than a week back, new record should've been uploaded." + ); + engine.lastRecordUpload -= MORE_THAN_CLIENTS_TTL_REFRESH; + let lastweek = engine.lastRecordUpload; + clientWBO().payload = undefined; + await syncClientsEngine(server); + ok(!!clientWBO().payload); + ok(engine.lastRecordUpload > lastweek); + ok(!engine.isFirstSync); + + _("Remove client record."); + await engine.removeClientData(); + equal(clientWBO().payload, undefined); + + _("Time travel one day back, no record uploaded."); + engine.lastRecordUpload -= LESS_THAN_CLIENTS_TTL_REFRESH; + let yesterday = engine.lastRecordUpload; + await syncClientsEngine(server); + equal(clientWBO().payload, undefined); + equal(engine.lastRecordUpload, yesterday); + ok(!engine.isFirstSync); + } finally { + await cleanup(); + await promiseStopServer(server); + } +}); + +add_task(async function test_client_name_change() { + _("Ensure client name change incurs a client record update."); + + let tracker = engine._tracker; + + engine.localID; // Needed to increase the tracker changedIDs count. + let initialName = engine.localName; + + tracker.start(); + _("initial name: " + initialName); + + // Tracker already has data, so clear it. + await tracker.clearChangedIDs(); + + let initialScore = tracker.score; + + let changedIDs = await tracker.getChangedIDs(); + equal(Object.keys(changedIDs).length, 0); + + Services.prefs.setStringPref( + "identity.fxaccounts.account.device.name", + "new name" + ); + await tracker.asyncObserver.promiseObserversComplete(); + + _("new name: " + engine.localName); + notEqual(initialName, engine.localName); + changedIDs = await tracker.getChangedIDs(); + equal(Object.keys(changedIDs).length, 1); + ok(engine.localID in changedIDs); + ok(tracker.score > initialScore); + ok(tracker.score >= SCORE_INCREMENT_XLARGE); + + await tracker.stop(); + + await cleanup(); +}); + +add_task(async function test_fxa_device_id_change() { + _("Ensure an FxA device ID change incurs a client record update."); + + let tracker = engine._tracker; + + engine.localID; // Needed to increase the tracker changedIDs count. + + tracker.start(); + + // Tracker already has data, so clear it. + await tracker.clearChangedIDs(); + + let initialScore = tracker.score; + + let changedIDs = await tracker.getChangedIDs(); + equal(Object.keys(changedIDs).length, 0); + + Services.obs.notifyObservers(null, "fxaccounts:new_device_id"); + await tracker.asyncObserver.promiseObserversComplete(); + + changedIDs = await tracker.getChangedIDs(); + equal(Object.keys(changedIDs).length, 1); + ok(engine.localID in changedIDs); + ok(tracker.score > initialScore); + ok(tracker.score >= SINGLE_USER_THRESHOLD); + + await tracker.stop(); + + await cleanup(); +}); + +add_task(async function test_last_modified() { + _("Ensure that remote records have a sane serverLastModified attribute."); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + let user = server.user("foo"); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let activeID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: activeID, + name: "Active client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + let collection = user.collection("clients"); + + _("Sync to download the record"); + await syncClientsEngine(server); + + equal( + engine._store._remoteClients[activeID].serverLastModified, + now - 10, + "last modified in the local record is correctly the server last-modified" + ); + + _("Modify the record and re-upload it"); + // set a new name to make sure we really did upload. + engine._store._remoteClients[activeID].name = "New name"; + engine._modified.set(activeID, 0); + // The sync above also did a POST, so adjust our lastModified. + engine.lastModified = server.getCollection("foo", "clients").timestamp; + await engine._uploadOutgoing(); + + _("Local record should have updated timestamp"); + ok(engine._store._remoteClients[activeID].serverLastModified >= now); + + _("Record on the server should have new name but not serverLastModified"); + let payload = collection.cleartext(activeID); + equal(payload.name, "New name"); + equal(payload.serverLastModified, undefined); + } finally { + await cleanup(); + server.deleteCollections("foo"); + await promiseStopServer(server); + } +}); + +add_task(async function test_send_command() { + _("Verifies _sendCommandToClient puts commands in the outbound queue."); + + let store = engine._store; + let tracker = engine._tracker; + let remoteId = Utils.makeGUID(); + let rec = new ClientsRec("clients", remoteId); + + await store.create(rec); + await store.createRecord(remoteId, "clients"); + + let action = "testCommand"; + let args = ["foo", "bar"]; + let extra = { flowID: "flowy" }; + + await engine._sendCommandToClient(action, args, remoteId, extra); + + let newRecord = store._remoteClients[remoteId]; + let clientCommands = (await engine._readCommands())[remoteId]; + notEqual(newRecord, undefined); + equal(clientCommands.length, 1); + + let command = clientCommands[0]; + equal(command.command, action); + equal(command.args.length, 2); + deepEqual(command.args, args); + ok(command.flowID); + + const changes = await tracker.getChangedIDs(); + notEqual(changes[remoteId], undefined); + + await cleanup(); +}); + +// The browser UI might call _addClientCommand indirectly without awaiting on the returned promise. +// We need to make sure this doesn't result on commands not being saved. +add_task(async function test_add_client_command_race() { + let promises = []; + for (let i = 0; i < 100; i++) { + promises.push( + engine._addClientCommand(`client-${i}`, { command: "cmd", args: [] }) + ); + } + await Promise.all(promises); + + let localCommands = await engine._readCommands(); + for (let i = 0; i < 100; i++) { + equal(localCommands[`client-${i}`].length, 1); + } +}); + +add_task(async function test_command_validation() { + _("Verifies that command validation works properly."); + + let store = engine._store; + + let testCommands = [ + ["resetAll", [], true], + ["resetAll", ["foo"], false], + ["resetEngine", ["tabs"], true], + ["resetEngine", [], false], + ["wipeEngine", ["tabs"], true], + ["wipeEngine", [], false], + ["logout", [], true], + ["logout", ["foo"], false], + ["__UNKNOWN__", [], false], + ]; + + for (let [action, args, expectedResult] of testCommands) { + let remoteId = Utils.makeGUID(); + let rec = new ClientsRec("clients", remoteId); + + await store.create(rec); + await store.createRecord(remoteId, "clients"); + + await engine.sendCommand(action, args, remoteId); + + let newRecord = store._remoteClients[remoteId]; + notEqual(newRecord, undefined); + + let clientCommands = (await engine._readCommands())[remoteId]; + + if (expectedResult) { + _("Ensuring command is sent: " + action); + equal(clientCommands.length, 1); + + let command = clientCommands[0]; + equal(command.command, action); + deepEqual(command.args, args); + + notEqual(engine._tracker, undefined); + const changes = await engine._tracker.getChangedIDs(); + notEqual(changes[remoteId], undefined); + } else { + _("Ensuring command is scrubbed: " + action); + equal(clientCommands, undefined); + + if (store._tracker) { + equal(engine._tracker[remoteId], undefined); + } + } + } + await cleanup(); +}); + +add_task(async function test_command_duplication() { + _("Ensures duplicate commands are detected and not added"); + + let store = engine._store; + let remoteId = Utils.makeGUID(); + let rec = new ClientsRec("clients", remoteId); + await store.create(rec); + await store.createRecord(remoteId, "clients"); + + let action = "resetAll"; + let args = []; + + await engine.sendCommand(action, args, remoteId); + await engine.sendCommand(action, args, remoteId); + + let clientCommands = (await engine._readCommands())[remoteId]; + equal(clientCommands.length, 1); + + _("Check variant args length"); + await engine._saveCommands({}); + + action = "resetEngine"; + await engine.sendCommand(action, [{ x: "foo" }], remoteId); + await engine.sendCommand(action, [{ x: "bar" }], remoteId); + + _("Make sure we spot a real dupe argument."); + await engine.sendCommand(action, [{ x: "bar" }], remoteId); + + clientCommands = (await engine._readCommands())[remoteId]; + equal(clientCommands.length, 2); + + await cleanup(); +}); + +add_task(async function test_command_invalid_client() { + _("Ensures invalid client IDs are caught"); + + let id = Utils.makeGUID(); + let error; + + try { + await engine.sendCommand("wipeEngine", ["tabs"], id); + } catch (ex) { + error = ex; + } + + equal(error.message.indexOf("Unknown remote client ID: "), 0); + + await cleanup(); +}); + +add_task(async function test_process_incoming_commands() { + _("Ensures local commands are executed"); + + engine.localCommands = [{ command: "logout", args: [] }]; + + let ev = "weave:service:logout:finish"; + + let logoutPromise = new Promise(resolve => { + var handler = function () { + Svc.Obs.remove(ev, handler); + + resolve(); + }; + + Svc.Obs.add(ev, handler); + }); + + // logout command causes processIncomingCommands to return explicit false. + ok(!(await engine.processIncomingCommands())); + + await logoutPromise; + + await cleanup(); +}); + +add_task(async function test_filter_duplicate_names() { + _( + "Ensure that we exclude clients with identical names that haven't synced in a week." + ); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + let user = server.user("foo"); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + // Synced recently. + let recentID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: recentID, + name: "My Phone", + type: "mobile", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + // Dupe of our client, synced more than 1 week ago. + let dupeID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: dupeID, + name: engine.localName, + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 604820 + ); + + // Synced more than 1 week ago, but not a dupe. + let oldID = Utils.makeGUID(); + user.collection("clients").insertRecord( + { + id: oldID, + name: "My old desktop", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 604820 + ); + + try { + let store = engine._store; + + _("First sync"); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + ok(engine.lastRecordUpload > 0); + ok(!engine.isFirstSync); + deepEqual( + user.collection("clients").keys().sort(), + [recentID, dupeID, oldID, engine.localID].sort(), + "Our record should be uploaded on first sync" + ); + + let ids = await store.getAllIDs(); + deepEqual( + Object.keys(ids).sort(), + [recentID, dupeID, oldID, engine.localID].sort(), + "Duplicate ID should remain in getAllIDs" + ); + ok( + await engine._store.itemExists(dupeID), + "Dupe ID should be considered as existing for Sync methods." + ); + ok( + !engine.remoteClientExists(dupeID), + "Dupe ID should not be considered as existing for external methods." + ); + + // dupe desktop should not appear in .deviceTypes. + equal(engine.deviceTypes.get("desktop"), 2); + equal(engine.deviceTypes.get("mobile"), 1); + + // dupe desktop should not appear in stats + deepEqual(engine.stats, { + hasMobile: 1, + names: [engine.localName, "My Phone", "My old desktop"], + numClients: 3, + }); + + ok(engine.remoteClientExists(oldID), "non-dupe ID should exist."); + ok(!engine.remoteClientExists(dupeID), "dupe ID should not exist"); + equal( + engine.remoteClients.length, + 2, + "dupe should not be in remoteClients" + ); + + // Check that a subsequent Sync doesn't report anything as being processed. + let counts; + Svc.Obs.add("weave:engine:sync:applied", function observe(subject, data) { + Svc.Obs.remove("weave:engine:sync:applied", observe); + counts = subject; + }); + + await syncClientsEngine(server); + equal(counts.applied, 0); // We didn't report applying any records. + equal(counts.reconciled, 4); // We reported reconcilliation for all records + equal(counts.succeeded, 0); + equal(counts.failed, 0); + equal(counts.newFailed, 0); + + _("Broadcast logout to all clients"); + await engine.sendCommand("logout", []); + await syncClientsEngine(server); + + let collection = server.getCollection("foo", "clients"); + let recentPayload = collection.cleartext(recentID); + compareCommands( + recentPayload.commands, + [{ command: "logout", args: [] }], + "Should send commands to the recent client" + ); + + let oldPayload = collection.cleartext(oldID); + compareCommands( + oldPayload.commands, + [{ command: "logout", args: [] }], + "Should send commands to the week-old client" + ); + + let dupePayload = collection.cleartext(dupeID); + deepEqual( + dupePayload.commands, + [], + "Should not send commands to the dupe client" + ); + + _("Update the dupe client's modified time"); + collection.insertRecord( + { + id: dupeID, + name: engine.localName, + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + _("Second sync."); + await syncClientsEngine(server); + + ids = await store.getAllIDs(); + deepEqual( + Object.keys(ids).sort(), + [recentID, oldID, dupeID, engine.localID].sort(), + "Stale client synced, so it should no longer be marked as a dupe" + ); + + ok( + engine.remoteClientExists(dupeID), + "Dupe ID should appear as it synced." + ); + + // Recently synced dupe desktop should appear in .deviceTypes. + equal(engine.deviceTypes.get("desktop"), 3); + + // Recently synced dupe desktop should now appear in stats + deepEqual(engine.stats, { + hasMobile: 1, + names: [engine.localName, "My Phone", engine.localName, "My old desktop"], + numClients: 4, + }); + + ok( + engine.remoteClientExists(dupeID), + "recently synced dupe ID should now exist" + ); + equal( + engine.remoteClients.length, + 3, + "recently synced dupe should now be in remoteClients" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_command_sync() { + _("Ensure that commands are synced across clients."); + + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let user = server.user("foo"); + let remoteId = Utils.makeGUID(); + + function clientWBO(id) { + return user.collection("clients").wbo(id); + } + + _("Create remote client record"); + user.collection("clients").insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + try { + _("Syncing."); + await syncClientsEngine(server); + + _("Checking remote record was downloaded."); + let clientRecord = engine._store._remoteClients[remoteId]; + notEqual(clientRecord, undefined); + equal(clientRecord.commands.length, 0); + + _("Send a command to the remote client."); + await engine.sendCommand("wipeEngine", ["tabs"]); + let clientCommands = (await engine._readCommands())[remoteId]; + equal(clientCommands.length, 1); + await syncClientsEngine(server); + + _("Checking record was uploaded."); + notEqual(clientWBO(engine.localID).payload, undefined); + ok(engine.lastRecordUpload > 0); + ok(!engine.isFirstSync); + + notEqual(clientWBO(remoteId).payload, undefined); + + Svc.PrefBranch.setStringPref("client.GUID", remoteId); + await engine._resetClient(); + equal(engine.localID, remoteId); + _("Performing sync on resetted client."); + await syncClientsEngine(server); + notEqual(engine.localCommands, undefined); + equal(engine.localCommands.length, 1); + + let command = engine.localCommands[0]; + equal(command.command, "wipeEngine"); + equal(command.args.length, 1); + equal(command.args[0], "tabs"); + } finally { + await cleanup(); + + try { + let collection = server.getCollection("foo", "clients"); + collection.remove(remoteId); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_clients_not_in_fxa_list() { + _("Ensure that clients not in the FxA devices list are marked as stale."); + + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + let collection = server.getCollection("foo", "clients"); + + _("Create remote client records"); + collection.insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteId, + protocols: ["1.5"], + }); + + collection.insertRecord({ + id: remoteId2, + name: "Remote client 2", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteId2, + protocols: ["1.5"], + }); + + let fxAccounts = engine.fxAccounts; + engine.fxAccounts = { + notifyDevices() { + return Promise.resolve(true); + }, + device: { + getLocalId() { + return fxAccounts.device.getLocalId(); + }, + getLocalName() { + return fxAccounts.device.getLocalName(); + }, + getLocalType() { + return fxAccounts.device.getLocalType(); + }, + recentDeviceList: [{ id: remoteId }], + refreshDeviceList() { + return Promise.resolve(true); + }, + }, + _internal: { + now() { + return Date.now(); + }, + }, + }; + + try { + _("Syncing."); + await syncClientsEngine(server); + + ok(!engine._store._remoteClients[remoteId].stale); + ok(engine._store._remoteClients[remoteId2].stale); + } finally { + engine.fxAccounts = fxAccounts; + await cleanup(); + + try { + collection.remove(remoteId); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_dupe_device_ids() { + _( + "Ensure that we mark devices with duplicate fxaDeviceIds but older lastModified as stale." + ); + + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + let remoteDeviceId = Utils.makeGUID(); + + let collection = server.getCollection("foo", "clients"); + + _("Create remote client records"); + collection.insertRecord( + { + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteDeviceId, + protocols: ["1.5"], + }, + new_timestamp() - 3 + ); + collection.insertRecord({ + id: remoteId2, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteDeviceId, + protocols: ["1.5"], + }); + + let fxAccounts = engine.fxAccounts; + engine.fxAccounts = { + notifyDevices() { + return Promise.resolve(true); + }, + device: { + getLocalId() { + return fxAccounts.device.getLocalId(); + }, + getLocalName() { + return fxAccounts.device.getLocalName(); + }, + getLocalType() { + return fxAccounts.device.getLocalType(); + }, + recentDeviceList: [{ id: remoteDeviceId }], + refreshDeviceList() { + return Promise.resolve(true); + }, + }, + _internal: { + now() { + return Date.now(); + }, + }, + }; + + try { + _("Syncing."); + await syncClientsEngine(server); + + ok(engine._store._remoteClients[remoteId].stale); + ok(!engine._store._remoteClients[remoteId2].stale); + } finally { + engine.fxAccounts = fxAccounts; + await cleanup(); + + try { + collection.remove(remoteId); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_refresh_fxa_device_list() { + _("Ensure we refresh the fxa device list when we expect to."); + + await engine._store.wipe(); + engine._lastFxaDeviceRefresh = 0; + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let numRefreshes = 0; + let now = Date.now(); + let fxAccounts = engine.fxAccounts; + engine.fxAccounts = { + notifyDevices() { + return Promise.resolve(true); + }, + device: { + getLocalId() { + return fxAccounts.device.getLocalId(); + }, + getLocalName() { + return fxAccounts.device.getLocalName(); + }, + getLocalType() { + return fxAccounts.device.getLocalType(); + }, + recentDeviceList: [], + refreshDeviceList() { + numRefreshes += 1; + return Promise.resolve(true); + }, + }, + _internal: { + now() { + return now; + }, + }, + }; + + try { + _("Syncing."); + await syncClientsEngine(server); + Assert.equal(numRefreshes, 1, "first sync should refresh"); + now += 1000; // a second later. + await syncClientsEngine(server); + Assert.equal(numRefreshes, 1, "next sync should not refresh"); + now += 60 * 60 * 2 * 1000; // 2 hours later + await syncClientsEngine(server); + Assert.equal(numRefreshes, 2, "2 hours later should refresh"); + now += 1000; // a second later. + Assert.equal(numRefreshes, 2, "next sync should not refresh"); + } finally { + await cleanup(); + await promiseStopServer(server); + } +}); + +add_task(async function test_optional_client_fields() { + _("Ensure that we produce records with the fields added in Bug 1097222."); + + const SUPPORTED_PROTOCOL_VERSIONS = ["1.5"]; + let local = await engine._store.createRecord(engine.localID, "clients"); + equal(local.name, engine.localName); + equal(local.type, engine.localType); + equal(local.version, Services.appinfo.version); + deepEqual(local.protocols, SUPPORTED_PROTOCOL_VERSIONS); + + // Optional fields. + // Make sure they're what they ought to be... + equal(local.os, Services.appinfo.OS); + equal(local.appPackage, Services.appinfo.ID); + + // ... and also that they're non-empty. + ok(!!local.os); + ok(!!local.appPackage); + ok(!!local.application); + + // We don't currently populate device or formfactor. + // See Bug 1100722, Bug 1100723. + + await cleanup(); +}); + +add_task(async function test_merge_commands() { + _("Verifies local commands for remote clients are merged with the server's"); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let collection = server.getCollection("foo", "clients"); + + let desktopID = Utils.makeGUID(); + collection.insertRecord( + { + id: desktopID, + name: "Desktop client", + type: "desktop", + commands: [ + { + command: "wipeEngine", + args: ["history"], + flowID: Utils.makeGUID(), + }, + ], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + let mobileID = Utils.makeGUID(); + collection.insertRecord( + { + id: mobileID, + name: "Mobile client", + type: "mobile", + commands: [ + { + command: "logout", + args: [], + flowID: Utils.makeGUID(), + }, + ], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + _("First sync. 2 records downloaded."); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + + _("Broadcast logout to all clients"); + await engine.sendCommand("logout", []); + await syncClientsEngine(server); + + let desktopPayload = collection.cleartext(desktopID); + compareCommands( + desktopPayload.commands, + [ + { + command: "wipeEngine", + args: ["history"], + }, + { + command: "logout", + args: [], + }, + ], + "Should send the logout command to the desktop client" + ); + + let mobilePayload = collection.cleartext(mobileID); + compareCommands( + mobilePayload.commands, + [{ command: "logout", args: [] }], + "Should not send a duplicate logout to the mobile client" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_duplicate_remote_commands() { + _( + "Verifies local commands for remote clients are sent only once (bug 1289287)" + ); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let collection = server.getCollection("foo", "clients"); + + let desktopID = Utils.makeGUID(); + collection.insertRecord( + { + id: desktopID, + name: "Desktop client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + _("First sync. 1 record downloaded."); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + + _("Send command to client to wipe history engine"); + await engine.sendCommand("wipeEngine", ["history"]); + await syncClientsEngine(server); + + _( + "Simulate the desktop client consuming the command and syncing to the server" + ); + collection.insertRecord( + { + id: desktopID, + name: "Desktop client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + _("Send another command to the desktop client to wipe tabs engine"); + await engine.sendCommand("wipeEngine", ["tabs"], desktopID); + await syncClientsEngine(server); + + let desktopPayload = collection.cleartext(desktopID); + compareCommands( + desktopPayload.commands, + [ + { + command: "wipeEngine", + args: ["tabs"], + }, + ], + "Should only send the second command to the desktop client" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_upload_after_reboot() { + _("Multiple downloads, reboot, then upload (bug 1289287)"); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let collection = server.getCollection("foo", "clients"); + + let deviceBID = Utils.makeGUID(); + let deviceCID = Utils.makeGUID(); + collection.insertRecord( + { + id: deviceBID, + name: "Device B", + type: "desktop", + commands: [ + { + command: "wipeEngine", + args: ["history"], + flowID: Utils.makeGUID(), + }, + ], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + collection.insertRecord( + { + id: deviceCID, + name: "Device C", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + _("First sync. 2 records downloaded."); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + await syncClientsEngine(server); + + _("Send command to client to wipe tab engine"); + await engine.sendCommand("wipeEngine", ["tabs"], deviceBID); + + const oldUploadOutgoing = SyncEngine.prototype._uploadOutgoing; + SyncEngine.prototype._uploadOutgoing = async () => + engine._onRecordsWritten([], [deviceBID]); + await syncClientsEngine(server); + + let deviceBPayload = collection.cleartext(deviceBID); + compareCommands( + deviceBPayload.commands, + [ + { + command: "wipeEngine", + args: ["history"], + }, + ], + "Should be the same because the upload failed" + ); + + _("Simulate the client B consuming the command and syncing to the server"); + collection.insertRecord( + { + id: deviceBID, + name: "Device B", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + // Simulate reboot + SyncEngine.prototype._uploadOutgoing = oldUploadOutgoing; + engine = Service.clientsEngine = new ClientEngine(Service); + await engine.initialize(); + + await syncClientsEngine(server); + + deviceBPayload = collection.cleartext(deviceBID); + compareCommands( + deviceBPayload.commands, + [ + { + command: "wipeEngine", + args: ["tabs"], + }, + ], + "Should only had written our outgoing command" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_keep_cleared_commands_after_reboot() { + _( + "Download commands, fail upload, reboot, then apply new commands (bug 1289287)" + ); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let collection = server.getCollection("foo", "clients"); + + let deviceBID = Utils.makeGUID(); + let deviceCID = Utils.makeGUID(); + collection.insertRecord( + { + id: engine.localID, + name: "Device A", + type: "desktop", + commands: [ + { + command: "wipeEngine", + args: ["history"], + flowID: Utils.makeGUID(), + }, + { + command: "wipeEngine", + args: ["tabs"], + flowID: Utils.makeGUID(), + }, + ], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + collection.insertRecord( + { + id: deviceBID, + name: "Device B", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + collection.insertRecord( + { + id: deviceCID, + name: "Device C", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + _("First sync. Download remote and our record."); + strictEqual(engine.lastRecordUpload, 0); + ok(engine.isFirstSync); + + const oldUploadOutgoing = SyncEngine.prototype._uploadOutgoing; + SyncEngine.prototype._uploadOutgoing = async () => + engine._onRecordsWritten([], [deviceBID]); + let commandsProcessed = 0; + engine.service.wipeClient = _engine => { + commandsProcessed++; + }; + + await syncClientsEngine(server); + await engine.processIncomingCommands(); // Not called by the engine.sync(), gotta call it ourselves + equal(commandsProcessed, 2, "We processed 2 commands"); + + let localRemoteRecord = collection.cleartext(engine.localID); + compareCommands( + localRemoteRecord.commands, + [ + { + command: "wipeEngine", + args: ["history"], + }, + { + command: "wipeEngine", + args: ["tabs"], + }, + ], + "Should be the same because the upload failed" + ); + + // Another client sends a wipe command + collection.insertRecord( + { + id: engine.localID, + name: "Device A", + type: "desktop", + commands: [ + { + command: "wipeEngine", + args: ["history"], + flowID: Utils.makeGUID(), + }, + { + command: "wipeEngine", + args: ["tabs"], + flowID: Utils.makeGUID(), + }, + { + command: "wipeEngine", + args: ["bookmarks"], + flowID: Utils.makeGUID(), + }, + ], + version: "48", + protocols: ["1.5"], + }, + now - 5 + ); + + // Simulate reboot + SyncEngine.prototype._uploadOutgoing = oldUploadOutgoing; + engine = Service.clientsEngine = new ClientEngine(Service); + await engine.initialize(); + + commandsProcessed = 0; + engine.service.wipeClient = _engine => { + commandsProcessed++; + }; + await syncClientsEngine(server); + await engine.processIncomingCommands(); + equal( + commandsProcessed, + 1, + "We processed one command (the other were cleared)" + ); + + localRemoteRecord = collection.cleartext(deviceBID); + deepEqual(localRemoteRecord.commands, [], "Should be empty"); + } finally { + await cleanup(); + + // Reset service (remove mocks) + engine = Service.clientsEngine = new ClientEngine(Service); + await engine.initialize(); + await engine._resetClient(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_deleted_commands() { + _("Verifies commands for a deleted client are discarded"); + + let now = new_timestamp(); + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let collection = server.getCollection("foo", "clients"); + + let activeID = Utils.makeGUID(); + collection.insertRecord( + { + id: activeID, + name: "Active client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + let deletedID = Utils.makeGUID(); + collection.insertRecord( + { + id: deletedID, + name: "Client to delete", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }, + now - 10 + ); + + try { + _("First sync. 2 records downloaded."); + await syncClientsEngine(server); + + _("Delete a record on the server."); + collection.remove(deletedID); + + _("Broadcast a command to all clients"); + await engine.sendCommand("logout", []); + await syncClientsEngine(server); + + deepEqual( + collection.keys().sort(), + [activeID, engine.localID].sort(), + "Should not reupload deleted clients" + ); + + let activePayload = collection.cleartext(activeID); + compareCommands( + activePayload.commands, + [{ command: "logout", args: [] }], + "Should send the command to the active client" + ); + } finally { + await cleanup(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_command_sync() { + _("Notify other clients when writing their record."); + + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.getCollection("foo", "clients"); + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + + _("Create remote client record 1"); + collection.insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + _("Create remote client record 2"); + collection.insertRecord({ + id: remoteId2, + name: "Remote client 2", + type: "mobile", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + try { + equal(collection.count(), 2, "2 remote records written"); + await syncClientsEngine(server); + equal( + collection.count(), + 3, + "3 remote records written (+1 for the synced local record)" + ); + + await engine.sendCommand("wipeEngine", ["tabs"]); + await engine._tracker.addChangedID(engine.localID); + const getClientFxaDeviceId = sinon + .stub(engine, "getClientFxaDeviceId") + .callsFake(id => "fxa-" + id); + const engineMock = sinon.mock(engine); + let _notifyCollectionChanged = engineMock + .expects("_notifyCollectionChanged") + .withArgs(["fxa-" + remoteId, "fxa-" + remoteId2]); + _("Syncing."); + await syncClientsEngine(server); + _notifyCollectionChanged.verify(); + + engineMock.restore(); + getClientFxaDeviceId.restore(); + } finally { + await cleanup(); + await engine._tracker.clearChangedIDs(); + + try { + server.deleteCollections("foo"); + } finally { + await promiseStopServer(server); + } + } +}); + +add_task(async function ensureSameFlowIDs() { + let events = []; + let origRecordTelemetryEvent = Service.recordTelemetryEvent; + Service.recordTelemetryEvent = (object, method, value, extra) => { + events.push({ object, method, value, extra }); + }; + + let server = await serverForFoo(engine); + try { + // Setup 2 clients, send them a command, and ensure we get to events + // written, both with the same flowID. + await SyncTestingInfrastructure(server); + let collection = server.getCollection("foo", "clients"); + + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + + _("Create remote client record 1"); + collection.insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + _("Create remote client record 2"); + collection.insertRecord({ + id: remoteId2, + name: "Remote client 2", + type: "mobile", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + await syncClientsEngine(server); + await engine.sendCommand("wipeEngine", ["tabs"]); + await syncClientsEngine(server); + equal(events.length, 2); + // we don't know what the flowID is, but do know it should be the same. + equal(events[0].extra.flowID, events[1].extra.flowID); + // Wipe remote clients to ensure deduping doesn't prevent us from adding the command. + for (let client of Object.values(engine._store._remoteClients)) { + client.commands = []; + } + // check it's correctly used when we specify a flow ID + events.length = 0; + let flowID = Utils.makeGUID(); + await engine.sendCommand("wipeEngine", ["tabs"], null, { flowID }); + await syncClientsEngine(server); + equal(events.length, 2); + equal(events[0].extra.flowID, flowID); + equal(events[1].extra.flowID, flowID); + + // Wipe remote clients to ensure deduping doesn't prevent us from adding the command. + for (let client of Object.values(engine._store._remoteClients)) { + client.commands = []; + } + + // and that it works when something else is in "extra" + events.length = 0; + await engine.sendCommand("wipeEngine", ["tabs"], null, { + reason: "testing", + }); + await syncClientsEngine(server); + equal(events.length, 2); + equal(events[0].extra.flowID, events[1].extra.flowID); + equal(events[0].extra.reason, "testing"); + equal(events[1].extra.reason, "testing"); + // Wipe remote clients to ensure deduping doesn't prevent us from adding the command. + for (let client of Object.values(engine._store._remoteClients)) { + client.commands = []; + } + + // and when both are specified. + events.length = 0; + await engine.sendCommand("wipeEngine", ["tabs"], null, { + reason: "testing", + flowID, + }); + await syncClientsEngine(server); + equal(events.length, 2); + equal(events[0].extra.flowID, flowID); + equal(events[1].extra.flowID, flowID); + equal(events[0].extra.reason, "testing"); + equal(events[1].extra.reason, "testing"); + // Wipe remote clients to ensure deduping doesn't prevent us from adding the command. + for (let client of Object.values(engine._store._remoteClients)) { + client.commands = []; + } + } finally { + Service.recordTelemetryEvent = origRecordTelemetryEvent; + cleanup(); + await promiseStopServer(server); + } +}); + +add_task(async function test_duplicate_commands_telemetry() { + let events = []; + let origRecordTelemetryEvent = Service.recordTelemetryEvent; + Service.recordTelemetryEvent = (object, method, value, extra) => { + events.push({ object, method, value, extra }); + }; + + let server = await serverForFoo(engine); + try { + await SyncTestingInfrastructure(server); + let collection = server.getCollection("foo", "clients"); + + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + + _("Create remote client record 1"); + collection.insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + _("Create remote client record 2"); + collection.insertRecord({ + id: remoteId2, + name: "Remote client 2", + type: "mobile", + commands: [], + version: "48", + protocols: ["1.5"], + }); + + await syncClientsEngine(server); + // Make sure deduping works before syncing + await engine.sendCommand("wipeEngine", ["history"], remoteId); + await engine.sendCommand("wipeEngine", ["history"], remoteId); + equal(events.length, 1); + await syncClientsEngine(server); + // And after syncing. + await engine.sendCommand("wipeEngine", ["history"], remoteId); + equal(events.length, 1); + // Ensure we aren't deduping commands to different clients + await engine.sendCommand("wipeEngine", ["history"], remoteId2); + equal(events.length, 2); + } finally { + Service.recordTelemetryEvent = origRecordTelemetryEvent; + cleanup(); + await promiseStopServer(server); + } +}); + +add_task(async function test_other_clients_notified_on_first_sync() { + _( + "Ensure that other clients are notified when we upload our client record for the first time." + ); + + await engine.resetLastSync(); + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + const fxAccounts = engine.fxAccounts; + let calls = 0; + engine.fxAccounts = { + device: { + getLocalId() { + return fxAccounts.device.getLocalId(); + }, + getLocalName() { + return fxAccounts.device.getLocalName(); + }, + getLocalType() { + return fxAccounts.device.getLocalType(); + }, + }, + notifyDevices() { + calls++; + return Promise.resolve(true); + }, + _internal: { + now() { + return Date.now(); + }, + }, + }; + + try { + engine.lastRecordUpload = 0; + _("First sync, should notify other clients"); + await syncClientsEngine(server); + equal(calls, 1); + + _("Second sync, should not notify other clients"); + await syncClientsEngine(server); + equal(calls, 1); + } finally { + engine.fxAccounts = fxAccounts; + cleanup(); + await promiseStopServer(server); + } +}); + +add_task( + async function device_disconnected_notification_updates_known_stale_clients() { + const spyUpdate = sinon.spy(engine, "updateKnownStaleClients"); + + Services.obs.notifyObservers( + null, + "fxaccounts:device_disconnected", + JSON.stringify({ isLocalDevice: false }) + ); + ok(spyUpdate.calledOnce, "updateKnownStaleClients should be called"); + spyUpdate.resetHistory(); + + Services.obs.notifyObservers( + null, + "fxaccounts:device_disconnected", + JSON.stringify({ isLocalDevice: true }) + ); + ok(spyUpdate.notCalled, "updateKnownStaleClients should not be called"); + + spyUpdate.restore(); + } +); + +add_task(async function update_known_stale_clients() { + const makeFakeClient = id => ({ id, fxaDeviceId: `fxa-${id}` }); + const clients = [ + makeFakeClient("one"), + makeFakeClient("two"), + makeFakeClient("three"), + ]; + const stubRemoteClients = sinon + .stub(engine._store, "_remoteClients") + .get(() => { + return clients; + }); + const stubFetchFxADevices = sinon + .stub(engine, "_fetchFxADevices") + .callsFake(() => { + engine._knownStaleFxADeviceIds = ["fxa-one", "fxa-two"]; + }); + + engine._knownStaleFxADeviceIds = null; + await engine.updateKnownStaleClients(); + ok(clients[0].stale); + ok(clients[1].stale); + ok(!clients[2].stale); + + stubRemoteClients.restore(); + stubFetchFxADevices.restore(); +}); + +add_task(async function test_create_record_command_limit() { + await engine._store.wipe(); + await generateNewKeys(Service.collectionKeys); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + const fakeLimit = 4 * 1024; + + let maxSizeStub = sinon + .stub(Service, "getMemcacheMaxRecordPayloadSize") + .callsFake(() => fakeLimit); + + let user = server.user("foo"); + let remoteId = Utils.makeGUID(); + + _("Create remote client record"); + user.collection("clients").insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "57", + protocols: ["1.5"], + }); + + try { + _("Initial sync."); + await syncClientsEngine(server); + + _("Send a fairly sane number of commands."); + + for (let i = 0; i < 5; ++i) { + await engine.sendCommand("wipeEngine", [`history: ${i}`], remoteId); + } + + await syncClientsEngine(server); + + _("Make sure they all fit and weren't dropped."); + let parsedServerRecord = user.collection("clients").cleartext(remoteId); + + equal(parsedServerRecord.commands.length, 5); + + await engine.sendCommand("wipeEngine", ["history"], remoteId); + + _("Send a not-sane number of commands."); + // Much higher than the maximum number of commands we could actually fit. + for (let i = 0; i < 500; ++i) { + await engine.sendCommand("wipeEngine", [`tabs: ${i}`], remoteId); + } + + await syncClientsEngine(server); + + _("Ensure we didn't overflow the server limit."); + let wbo = user.collection("clients").wbo(remoteId); + less(wbo.payload.length, fakeLimit); + + _( + "And that the data we uploaded is both sane json and containing some commands." + ); + let remoteCommands = wbo.getCleartext().commands; + greater(remoteCommands.length, 2); + let firstCommand = remoteCommands[0]; + _( + "The first command should still be present, since it had a high priority" + ); + equal(firstCommand.command, "wipeEngine"); + _("And the last command in the list should be the last command we sent."); + let lastCommand = remoteCommands[remoteCommands.length - 1]; + equal(lastCommand.command, "wipeEngine"); + deepEqual(lastCommand.args, ["tabs: 499"]); + } finally { + maxSizeStub.restore(); + await cleanup(); + try { + let collection = server.getCollection("foo", "clients"); + collection.remove(remoteId); + } finally { + await promiseStopServer(server); + } + } +}); diff --git a/services/sync/tests/unit/test_clients_escape.js b/services/sync/tests/unit/test_clients_escape.js new file mode 100644 index 0000000000..53ec7fd7a9 --- /dev/null +++ b/services/sync/tests/unit/test_clients_escape.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_clients_escape() { + _("Set up test fixtures."); + + await configureIdentity(); + let keyBundle = Service.identity.syncKeyBundle; + + let engine = Service.clientsEngine; + + try { + _("Test that serializing client records results in uploadable ascii"); + engine.localID = "ascii"; + engine.localName = "wéävê"; + + _("Make sure we have the expected record"); + let record = await engine._createRecord("ascii"); + Assert.equal(record.id, "ascii"); + Assert.equal(record.name, "wéävê"); + + _("Encrypting record..."); + await record.encrypt(keyBundle); + _("Encrypted."); + + let serialized = JSON.stringify(record); + let checkCount = 0; + _("Checking for all ASCII:", serialized); + for (let ch of serialized) { + let code = ch.charCodeAt(0); + _("Checking asciiness of '", ch, "'=", code); + Assert.ok(code < 128); + checkCount++; + } + + _("Processed", checkCount, "characters out of", serialized.length); + Assert.equal(checkCount, serialized.length); + + _("Making sure the record still looks like it did before"); + await record.decrypt(keyBundle); + Assert.equal(record.id, "ascii"); + Assert.equal(record.name, "wéävê"); + + _("Sanity check that creating the record also gives the same"); + record = await engine._createRecord("ascii"); + Assert.equal(record.id, "ascii"); + Assert.equal(record.name, "wéävê"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); diff --git a/services/sync/tests/unit/test_collection_getBatched.js b/services/sync/tests/unit/test_collection_getBatched.js new file mode 100644 index 0000000000..f5425abe92 --- /dev/null +++ b/services/sync/tests/unit/test_collection_getBatched.js @@ -0,0 +1,187 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Collection, WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function recordRange(lim, offset, total) { + let res = []; + for (let i = offset; i < Math.min(lim + offset, total); ++i) { + res.push({ id: String(i), payload: "test:" + i }); + } + return res; +} + +function get_test_collection_info({ + totalRecords, + batchSize, + lastModified, + throwAfter = Infinity, + interruptedAfter = Infinity, +}) { + let coll = new Collection("http://example.com/test/", WBORecord, Service); + coll.full = true; + let requests = []; + let responses = []; + coll.get = async function () { + let limit = +this.limit; + let offset = 0; + if (this.offset) { + equal(this.offset.slice(0, 6), "foobar"); + offset = +this.offset.slice(6); + } + requests.push({ + limit, + offset, + spec: this.spec, + headers: Object.assign({}, this.headers), + }); + if (--throwAfter === 0) { + throw new Error("Some Network Error"); + } + let body = recordRange(limit, offset, totalRecords); + let response = { + obj: body, + success: true, + status: 200, + headers: {}, + }; + if (--interruptedAfter === 0) { + response.success = false; + response.status = 412; + response.body = ""; + } else if (offset + limit < totalRecords) { + // Ensure we're treating this as an opaque string, since the docs say + // it might not be numeric. + response.headers["x-weave-next-offset"] = "foobar" + (offset + batchSize); + } + response.headers["x-last-modified"] = lastModified; + responses.push(response); + return response; + }; + return { responses, requests, coll }; +} + +add_task(async function test_success() { + const totalRecords = 11; + const batchSize = 2; + const lastModified = "111111"; + let { responses, requests, coll } = get_test_collection_info({ + totalRecords, + batchSize, + lastModified, + }); + let { response, records } = await coll.getBatched(batchSize); + + equal(requests.length, Math.ceil(totalRecords / batchSize)); + + equal(records.length, totalRecords); + checkRecordsOrder(records); + + // ensure we're returning the last response + equal(responses[responses.length - 1], response); + + // check first separately since its a bit of a special case + ok(!requests[0].headers["x-if-unmodified-since"]); + ok(!requests[0].offset); + equal(requests[0].limit, batchSize); + let expectedOffset = 2; + for (let i = 1; i < requests.length; ++i) { + let req = requests[i]; + equal(req.headers["x-if-unmodified-since"], lastModified); + equal(req.limit, batchSize); + if (i !== requests.length - 1) { + equal(req.offset, expectedOffset); + } + + expectedOffset += batchSize; + } + + // ensure we cleaned up anything that would break further + // use of this collection. + ok(!coll._headers["x-if-unmodified-since"]); + ok(!coll.offset); + ok(!coll.limit || coll.limit == Infinity); +}); + +add_task(async function test_total_limit() { + _("getBatched respects the (initial) value of the limit property"); + const totalRecords = 100; + const recordLimit = 11; + const batchSize = 2; + const lastModified = "111111"; + let { requests, coll } = get_test_collection_info({ + totalRecords, + batchSize, + lastModified, + }); + coll.limit = recordLimit; + let { records } = await coll.getBatched(batchSize); + checkRecordsOrder(records); + + equal(requests.length, Math.ceil(recordLimit / batchSize)); + equal(records.length, recordLimit); + + for (let i = 0; i < requests.length; ++i) { + let req = requests[i]; + if (i !== requests.length - 1) { + equal(req.limit, batchSize); + } else { + equal(req.limit, recordLimit % batchSize); + } + } + + equal(coll._limit, recordLimit); +}); + +add_task(async function test_412() { + _("We shouldn't record records if we get a 412 in the middle of a batch"); + const totalRecords = 11; + const batchSize = 2; + const lastModified = "111111"; + let { responses, requests, coll } = get_test_collection_info({ + totalRecords, + batchSize, + lastModified, + interruptedAfter: 3, + }); + let { response, records } = await coll.getBatched(batchSize); + + equal(requests.length, 3); + equal(records.length, 0); // we should not get any records + + // ensure we're returning the last response + equal(responses[responses.length - 1], response); + + ok(!response.success); + equal(response.status, 412); +}); + +add_task(async function test_get_throws() { + _("getBatched() should throw if a get() throws"); + const totalRecords = 11; + const batchSize = 2; + const lastModified = "111111"; + let { requests, coll } = get_test_collection_info({ + totalRecords, + batchSize, + lastModified, + throwAfter: 3, + }); + + await Assert.rejects(coll.getBatched(batchSize), /Some Network Error/); + + equal(requests.length, 3); +}); + +function checkRecordsOrder(records) { + ok(!!records.length); + for (let i = 0; i < records.length; i++) { + equal(records[i].id, String(i)); + equal(records[i].payload, "test:" + i); + } +} diff --git a/services/sync/tests/unit/test_collections_recovery.js b/services/sync/tests/unit/test_collections_recovery.js new file mode 100644 index 0000000000..fd923bc272 --- /dev/null +++ b/services/sync/tests/unit/test_collections_recovery.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Verify that we wipe the server if we have to regenerate keys. +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_missing_crypto_collection() { + enableValidationPrefs(); + + let johnHelper = track_collections_helper(); + let johnU = johnHelper.with_updated_collection; + let johnColls = johnHelper.collections; + + let empty = false; + function maybe_empty(handler) { + return function (request, response) { + if (empty) { + let body = "{}"; + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); + } else { + handler(request, response); + } + }; + } + + let handlers = { + "/1.1/johndoe/info/collections": maybe_empty(johnHelper.handler), + "/1.1/johndoe/storage/crypto/keys": johnU( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": johnU( + "meta", + new ServerWBO("global").handler() + ), + }; + let collections = [ + "clients", + "bookmarks", + "forms", + "history", + "passwords", + "prefs", + "tabs", + ]; + // Disable addon sync because AddonManager won't be initialized here. + await Service.engineManager.unregister("addons"); + await Service.engineManager.unregister("extension-storage"); + + for (let coll of collections) { + handlers["/1.1/johndoe/storage/" + coll] = johnU( + coll, + new ServerCollection({}, true).handler() + ); + } + let server = httpd_setup(handlers); + await configureIdentity({ username: "johndoe" }, server); + + try { + let fresh = 0; + let orig = Service._freshStart; + Service._freshStart = async function () { + _("Called _freshStart."); + await orig.call(Service); + fresh++; + }; + + _("Startup, no meta/global: freshStart called once."); + await sync_and_validate_telem(); + Assert.equal(fresh, 1); + fresh = 0; + + _("Regular sync: no need to freshStart."); + await Service.sync(); + Assert.equal(fresh, 0); + + _("Simulate a bad info/collections."); + delete johnColls.crypto; + await sync_and_validate_telem(); + Assert.equal(fresh, 1); + fresh = 0; + + _("Regular sync: no need to freshStart."); + await sync_and_validate_telem(); + Assert.equal(fresh, 0); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_corrupt_keys.js b/services/sync/tests/unit/test_corrupt_keys.js new file mode 100644 index 0000000000..06d7335985 --- /dev/null +++ b/services/sync/tests/unit/test_corrupt_keys.js @@ -0,0 +1,248 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { HistoryEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); +const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_locally_changed_keys() { + enableValidationPrefs(); + + let hmacErrorCount = 0; + function counting(f) { + return async function () { + hmacErrorCount++; + return f.call(this); + }; + } + + Service.handleHMACEvent = counting(Service.handleHMACEvent); + + let server = new SyncServer(); + let johndoe = server.registerUser("johndoe", "password"); + johndoe.createContents({ + meta: {}, + crypto: {}, + clients: {}, + }); + server.start(); + + try { + Svc.PrefBranch.setStringPref("registerEngines", "Tab"); + + await configureIdentity({ username: "johndoe" }, server); + // We aren't doing a .login yet, so fudge the cluster URL. + Service.clusterURL = Service.identity._token.endpoint; + + await Service.engineManager.register(HistoryEngine); + // Disable addon sync because AddonManager won't be initialized here. + await Service.engineManager.unregister("addons"); + await Service.engineManager.unregister("extension-storage"); + + async function corrupt_local_keys() { + Service.collectionKeys._default.keyPair = [ + await Weave.Crypto.generateRandomKey(), + await Weave.Crypto.generateRandomKey(), + ]; + } + + _("Setting meta."); + + // Bump version on the server. + let m = new WBORecord("meta", "global"); + m.payload = { + syncID: "foooooooooooooooooooooooooo", + storageVersion: STORAGE_VERSION, + }; + await m.upload(Service.resource(Service.metaURL)); + + _( + "New meta/global: " + + JSON.stringify(johndoe.collection("meta").wbo("global")) + ); + + // Upload keys. + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + Assert.ok( + (await serverKeys.upload(Service.resource(Service.cryptoKeysURL))).success + ); + + // Check that login works. + Assert.ok(await Service.login()); + Assert.ok(Service.isLoggedIn); + + // Sync should upload records. + await sync_and_validate_telem(); + + // Tabs exist. + _("Tabs modified: " + johndoe.modified("tabs")); + Assert.ok(johndoe.modified("tabs") > 0); + + // Let's create some server side history records. + let liveKeys = Service.collectionKeys.keyForCollection("history"); + _("Keys now: " + liveKeys.keyPair); + let visitType = Ci.nsINavHistoryService.TRANSITION_LINK; + let history = johndoe.createCollection("history"); + for (let i = 0; i < 5; i++) { + let id = "record-no--" + i; + let modified = Date.now() / 1000 - 60 * (i + 10); + + let w = new CryptoWrapper("history", "id"); + w.cleartext = { + id, + histUri: "http://foo/bar?" + id, + title: id, + sortindex: i, + visits: [{ date: (modified - 5) * 1000000, type: visitType }], + deleted: false, + }; + await w.encrypt(liveKeys); + + let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac }; + history.insert(id, payload, modified); + } + + history.timestamp = Date.now() / 1000; + let old_key_time = johndoe.modified("crypto"); + _("Old key time: " + old_key_time); + + // Check that we can decrypt one. + let rec = new CryptoWrapper("history", "record-no--0"); + await rec.fetch( + Service.resource(Service.storageURL + "history/record-no--0") + ); + _(JSON.stringify(rec)); + Assert.ok(!!(await rec.decrypt(liveKeys))); + + Assert.equal(hmacErrorCount, 0); + + // Fill local key cache with bad data. + await corrupt_local_keys(); + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + + Assert.equal(hmacErrorCount, 0); + + _("HMAC error count: " + hmacErrorCount); + // Now syncing should succeed, after one HMAC error. + await sync_and_validate_telem(ping => { + Assert.equal( + ping.engines.find(e => e.name == "history").incoming.applied, + 5 + ); + }); + + Assert.equal(hmacErrorCount, 1); + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + + // And look! We downloaded history! + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--0") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--1") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--2") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--3") + ); + Assert.ok( + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--4") + ); + Assert.equal(hmacErrorCount, 1); + + _("Busting some new server values."); + // Now what happens if we corrupt the HMAC on the server? + for (let i = 5; i < 10; i++) { + let id = "record-no--" + i; + let modified = 1 + Date.now() / 1000; + + let w = new CryptoWrapper("history", "id"); + w.cleartext = { + id, + histUri: "http://foo/bar?" + id, + title: id, + sortindex: i, + visits: [{ date: (modified - 5) * 1000000, type: visitType }], + deleted: false, + }; + await w.encrypt(Service.collectionKeys.keyForCollection("history")); + w.hmac = w.hmac.toUpperCase(); + + let payload = { ciphertext: w.ciphertext, IV: w.IV, hmac: w.hmac }; + history.insert(id, payload, modified); + } + history.timestamp = Date.now() / 1000; + + _("Server key time hasn't changed."); + Assert.equal(johndoe.modified("crypto"), old_key_time); + + _("Resetting HMAC error timer."); + Service.lastHMACEvent = 0; + + _("Syncing..."); + await sync_and_validate_telem(ping => { + Assert.equal( + ping.engines.find(e => e.name == "history").incoming.failed, + 5 + ); + }); + + _( + "Keys now: " + Service.collectionKeys.keyForCollection("history").keyPair + ); + _( + "Server keys have been updated, and we skipped over 5 more HMAC errors without adjusting history." + ); + Assert.ok(johndoe.modified("crypto") > old_key_time); + Assert.equal(hmacErrorCount, 6); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--5") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--6") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--7") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--8") + ); + Assert.equal( + false, + await PlacesUtils.history.hasVisits("http://foo/bar?record-no--9") + ); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); + +function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + validate_all_future_pings(); + + run_next_test(); +} diff --git a/services/sync/tests/unit/test_declined.js b/services/sync/tests/unit/test_declined.js new file mode 100644 index 0000000000..af7f8eb8c5 --- /dev/null +++ b/services/sync/tests/unit/test_declined.js @@ -0,0 +1,194 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { DeclinedEngines } = ChromeUtils.importESModule( + "resource://services-sync/stages/declined.sys.mjs" +); +const { EngineSynchronizer } = ChromeUtils.importESModule( + "resource://services-sync/stages/enginesync.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Observers } = ChromeUtils.importESModule( + "resource://services-common/observers.sys.mjs" +); + +function PetrolEngine() {} +PetrolEngine.prototype.name = "petrol"; + +function DieselEngine() {} +DieselEngine.prototype.name = "diesel"; + +function DummyEngine() {} +DummyEngine.prototype.name = "dummy"; + +function ActualEngine() {} +ActualEngine.prototype.name = "actual"; +Object.setPrototypeOf(ActualEngine.prototype, SyncEngine.prototype); + +function getEngineManager() { + let manager = new EngineManager(Service); + Service.engineManager = manager; + manager._engines = { + petrol: new PetrolEngine(), + diesel: new DieselEngine(), + dummy: new DummyEngine(), + actual: new ActualEngine(), + }; + return manager; +} + +/** + * 'Fetch' a meta/global record that doesn't mention declined. + * + * Push it into the EngineSynchronizer to set enabled; verify that those are + * correct. + * + * Then push it into DeclinedEngines to set declined; verify that none are + * declined, and a notification is sent for our locally disabled-but-not- + * declined engines. + */ +add_task(async function testOldMeta() { + let meta = { + payload: { + engines: { + petrol: 1, + diesel: 2, + nonlocal: 3, // Enabled but not supported. + }, + }, + }; + + _("Record: " + JSON.stringify(meta)); + + let manager = getEngineManager(); + + // Update enabled from meta/global. + let engineSync = new EngineSynchronizer(Service); + await engineSync._updateEnabledFromMeta(meta, 3, manager); + + Assert.ok(manager._engines.petrol.enabled, "'petrol' locally enabled."); + Assert.ok(manager._engines.diesel.enabled, "'diesel' locally enabled."); + Assert.ok( + !("nonlocal" in manager._engines), + "We don't know anything about the 'nonlocal' engine." + ); + Assert.ok(!manager._engines.actual.enabled, "'actual' not locally enabled."); + Assert.ok(!manager.isDeclined("actual"), "'actual' not declined, though."); + + let declinedEngines = new DeclinedEngines(Service); + + function onNotDeclined(subject, topic, data) { + Observers.remove("weave:engines:notdeclined", onNotDeclined); + Assert.ok( + subject.undecided.has("actual"), + "EngineManager observed that 'actual' was undecided." + ); + + let declined = manager.getDeclined(); + _("Declined: " + JSON.stringify(declined)); + + Assert.ok(!meta.changed, "No need to upload a new meta/global."); + run_next_test(); + } + + Observers.add("weave:engines:notdeclined", onNotDeclined); + + declinedEngines.updateDeclined(meta, manager); +}); + +/** + * 'Fetch' a meta/global that declines an engine we don't + * recognize. Ensure that we track that declined engine along + * with any we locally declined, and that the meta/global + * record is marked as changed and includes all declined + * engines. + */ +add_task(async function testDeclinedMeta() { + let meta = { + payload: { + engines: { + petrol: 1, + diesel: 2, + nonlocal: 3, // Enabled but not supported. + }, + declined: ["nonexistent"], // Declined and not supported. + }, + }; + + _("Record: " + JSON.stringify(meta)); + + let manager = getEngineManager(); + manager._engines.petrol.enabled = true; + manager._engines.diesel.enabled = true; + manager._engines.dummy.enabled = true; + manager._engines.actual.enabled = false; // Disabled but not declined. + + manager.decline(["localdecline"]); // Declined and not supported. + + let declinedEngines = new DeclinedEngines(Service); + + function onNotDeclined(subject, topic, data) { + Observers.remove("weave:engines:notdeclined", onNotDeclined); + Assert.ok( + subject.undecided.has("actual"), + "EngineManager observed that 'actual' was undecided." + ); + + let declined = manager.getDeclined(); + _("Declined: " + JSON.stringify(declined)); + + Assert.equal( + declined.indexOf("actual"), + -1, + "'actual' is locally disabled, but not marked as declined." + ); + + Assert.equal( + declined.indexOf("clients"), + -1, + "'clients' is enabled and not remotely declined." + ); + Assert.equal( + declined.indexOf("petrol"), + -1, + "'petrol' is enabled and not remotely declined." + ); + Assert.equal( + declined.indexOf("diesel"), + -1, + "'diesel' is enabled and not remotely declined." + ); + Assert.equal( + declined.indexOf("dummy"), + -1, + "'dummy' is enabled and not remotely declined." + ); + + Assert.ok( + 0 <= declined.indexOf("nonexistent"), + "'nonexistent' was declined on the server." + ); + + Assert.ok( + 0 <= declined.indexOf("localdecline"), + "'localdecline' was declined locally." + ); + + // The meta/global is modified, too. + Assert.ok( + 0 <= meta.payload.declined.indexOf("nonexistent"), + "meta/global's declined contains 'nonexistent'." + ); + Assert.ok( + 0 <= meta.payload.declined.indexOf("localdecline"), + "meta/global's declined contains 'localdecline'." + ); + Assert.strictEqual(true, meta.changed, "meta/global was changed."); + } + + Observers.add("weave:engines:notdeclined", onNotDeclined); + + declinedEngines.updateDeclined(meta, manager); +}); diff --git a/services/sync/tests/unit/test_disconnect_shutdown.js b/services/sync/tests/unit/test_disconnect_shutdown.js new file mode 100644 index 0000000000..0606c93a7d --- /dev/null +++ b/services/sync/tests/unit/test_disconnect_shutdown.js @@ -0,0 +1,101 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { SyncDisconnect, SyncDisconnectInternal } = ChromeUtils.importESModule( + "resource://services-sync/SyncDisconnect.sys.mjs" +); +const { AsyncShutdown } = ChromeUtils.importESModule( + "resource://gre/modules/AsyncShutdown.sys.mjs" +); +const { PREF_LAST_FXA_USER } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); + +add_task(async function test_shutdown_blocker() { + let spySignout = sinon.stub( + SyncDisconnectInternal, + "doSyncAndAccountDisconnect" + ); + + // We don't need to check for the lock regularly as we end up aborting the wait. + SyncDisconnectInternal.lockRetryInterval = 1000; + // Force the retry count to a very large value - this test should never + // abort due to the retry count and we want the test to fail (aka timeout) + // should our abort code not work. + SyncDisconnectInternal.lockRetryCount = 10000; + // mock the "browser" sanitize function - it should not be called by + // this test. + let spyBrowser = sinon.stub(SyncDisconnectInternal, "doSanitizeBrowserData"); + // mock Sync + let mockEngine1 = { + enabled: true, + name: "Test Engine 1", + wipeClient: sinon.spy(), + }; + let mockEngine2 = { + enabled: false, + name: "Test Engine 2", + wipeClient: sinon.spy(), + }; + + // This weave mock never gives up the lock. + let Weave = { + Service: { + enabled: true, + lock: () => false, // so we never get the lock. + unlock: sinon.spy(), + + engineManager: { + getAll: sinon.stub().returns([mockEngine1, mockEngine2]), + }, + errorHandler: { + resetFileLog: sinon.spy(), + }, + }, + }; + let weaveStub = sinon.stub(SyncDisconnectInternal, "getWeave"); + weaveStub.returns(Weave); + + Services.prefs.setStringPref(PREF_LAST_FXA_USER, "dGVzdEBleGFtcGxlLmNvbQ=="); + + let promiseDisconnected = SyncDisconnect.disconnect(true); + + // Pretend we hit the shutdown blocker. + info("simulating quitApplicationGranted"); + Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); + AsyncShutdown.quitApplicationGranted._trigger(); + Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); + + info("waiting for disconnect to complete"); + await promiseDisconnected; + + Assert.ok( + !Services.prefs.prefHasUserValue(PREF_LAST_FXA_USER), + "Should have reset different user warning pref" + ); + Assert.equal( + Weave.Service.unlock.callCount, + 0, + "should not have unlocked at the end" + ); + Assert.ok(!Weave.Service.enabled, "Weave should be and remain disabled"); + Assert.equal( + Weave.Service.errorHandler.resetFileLog.callCount, + 1, + "should have reset the log" + ); + Assert.equal( + mockEngine1.wipeClient.callCount, + 1, + "enabled engine should have been wiped" + ); + Assert.equal( + mockEngine2.wipeClient.callCount, + 0, + "disabled engine should not have been wiped" + ); + Assert.equal(spyBrowser.callCount, 1, "should not sanitize the browser"); + Assert.equal(spySignout.callCount, 1, "should have signed out of FxA"); +}); diff --git a/services/sync/tests/unit/test_engine.js b/services/sync/tests/unit/test_engine.js new file mode 100644 index 0000000000..31a08d5bc9 --- /dev/null +++ b/services/sync/tests/unit/test_engine.js @@ -0,0 +1,246 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Observers } = ChromeUtils.importESModule( + "resource://services-common/observers.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function SteamStore(engine) { + Store.call(this, "Steam", engine); + this.wasWiped = false; +} +SteamStore.prototype = { + async wipe() { + this.wasWiped = true; + }, +}; +Object.setPrototypeOf(SteamStore.prototype, Store.prototype); + +function SteamTracker(name, engine) { + LegacyTracker.call(this, name || "Steam", engine); +} +Object.setPrototypeOf(SteamTracker.prototype, LegacyTracker.prototype); + +function SteamEngine(name, service) { + SyncEngine.call(this, name, service); + this.wasReset = false; + this.wasSynced = false; +} +SteamEngine.prototype = { + _storeObj: SteamStore, + _trackerObj: SteamTracker, + + async _resetClient() { + this.wasReset = true; + }, + + async _sync() { + this.wasSynced = true; + }, +}; +Object.setPrototypeOf(SteamEngine.prototype, SyncEngine.prototype); + +var engineObserver = { + topics: [], + + observe(subject, topic, data) { + Assert.equal(data, "steam"); + this.topics.push(topic); + }, + + reset() { + this.topics = []; + }, +}; +Observers.add("weave:engine:reset-client:start", engineObserver); +Observers.add("weave:engine:reset-client:finish", engineObserver); +Observers.add("weave:engine:wipe-client:start", engineObserver); +Observers.add("weave:engine:wipe-client:finish", engineObserver); +Observers.add("weave:engine:sync:start", engineObserver); +Observers.add("weave:engine:sync:finish", engineObserver); + +async function cleanup(engine) { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + engine.wasReset = false; + engine.wasSynced = false; + engineObserver.reset(); + await engine._tracker.clearChangedIDs(); + await engine.finalize(); +} + +add_task(async function test_members() { + _("Engine object members"); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + Assert.equal(engine.Name, "Steam"); + Assert.equal(engine.prefName, "steam"); + Assert.ok(engine._store instanceof SteamStore); + Assert.ok(engine._tracker instanceof SteamTracker); +}); + +add_task(async function test_score() { + _("Engine.score corresponds to tracker.score and is readonly"); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + Assert.equal(engine.score, 0); + engine._tracker.score += 5; + Assert.equal(engine.score, 5); + + try { + engine.score = 10; + } catch (ex) { + // Setting an attribute that has a getter produces an error in + // Firefox <= 3.6 and is ignored in later versions. Either way, + // the attribute's value won't change. + } + Assert.equal(engine.score, 5); +}); + +add_task(async function test_resetClient() { + _("Engine.resetClient calls _resetClient"); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + Assert.ok(!engine.wasReset); + + await engine.resetClient(); + Assert.ok(engine.wasReset); + Assert.equal(engineObserver.topics[0], "weave:engine:reset-client:start"); + Assert.equal(engineObserver.topics[1], "weave:engine:reset-client:finish"); + + await cleanup(engine); +}); + +add_task(async function test_invalidChangedIDs() { + _("Test that invalid changed IDs on disk don't end up live."); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + let tracker = engine._tracker; + + await tracker._beforeSave(); + await IOUtils.writeUTF8(tracker._storage.path, "5", { + tmpPath: tracker._storage.path + ".tmp", + }); + + ok(!tracker._storage.dataReady); + const changes = await tracker.getChangedIDs(); + changes.placeholder = true; + deepEqual( + changes, + { placeholder: true }, + "Accessing changed IDs should load changes from disk as a side effect" + ); + ok(tracker._storage.dataReady); + + Assert.ok(changes.placeholder); + await cleanup(engine); +}); + +add_task(async function test_wipeClient() { + _("Engine.wipeClient calls resetClient, wipes store, clears changed IDs"); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + Assert.ok(!engine.wasReset); + Assert.ok(!engine._store.wasWiped); + Assert.ok(await engine._tracker.addChangedID("a-changed-id")); + let changes = await engine._tracker.getChangedIDs(); + Assert.ok("a-changed-id" in changes); + + await engine.wipeClient(); + Assert.ok(engine.wasReset); + Assert.ok(engine._store.wasWiped); + changes = await engine._tracker.getChangedIDs(); + Assert.equal(JSON.stringify(changes), "{}"); + Assert.equal(engineObserver.topics[0], "weave:engine:wipe-client:start"); + Assert.equal(engineObserver.topics[1], "weave:engine:reset-client:start"); + Assert.equal(engineObserver.topics[2], "weave:engine:reset-client:finish"); + Assert.equal(engineObserver.topics[3], "weave:engine:wipe-client:finish"); + + await cleanup(engine); +}); + +add_task(async function test_enabled() { + _("Engine.enabled corresponds to preference"); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + try { + Assert.ok(!engine.enabled); + Svc.PrefBranch.setBoolPref("engine.steam", true); + Assert.ok(engine.enabled); + + engine.enabled = false; + Assert.ok(!Svc.PrefBranch.getBoolPref("engine.steam")); + } finally { + await cleanup(engine); + } +}); + +add_task(async function test_sync() { + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + try { + _("Engine.sync doesn't call _sync if it's not enabled"); + Assert.ok(!engine.enabled); + Assert.ok(!engine.wasSynced); + await engine.sync(); + + Assert.ok(!engine.wasSynced); + + _("Engine.sync calls _sync if it's enabled"); + engine.enabled = true; + + await engine.sync(); + Assert.ok(engine.wasSynced); + Assert.equal(engineObserver.topics[0], "weave:engine:sync:start"); + Assert.equal(engineObserver.topics[1], "weave:engine:sync:finish"); + } finally { + await cleanup(engine); + } +}); + +add_task(async function test_disabled_no_track() { + _("When an engine is disabled, its tracker is not tracking."); + let engine = new SteamEngine("Steam", Service); + await engine.initialize(); + let tracker = engine._tracker; + Assert.equal(engine, tracker.engine); + + Assert.ok(!engine.enabled); + Assert.ok(!tracker._isTracking); + let changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + Assert.ok(!tracker.engineIsEnabled()); + Assert.ok(!tracker._isTracking); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + let promisePrefChangeHandled = Promise.withResolvers(); + const origMethod = tracker.onEngineEnabledChanged; + tracker.onEngineEnabledChanged = async (...args) => { + await origMethod.apply(tracker, args); + promisePrefChangeHandled.resolve(); + }; + + engine.enabled = true; // Also enables the tracker automatically. + await promisePrefChangeHandled.promise; + Assert.ok(tracker._isTracking); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + await tracker.addChangedID("abcdefghijkl"); + changes = await tracker.getChangedIDs(); + Assert.ok(0 < changes.abcdefghijkl); + promisePrefChangeHandled = Promise.withResolvers(); + Svc.PrefBranch.setBoolPref("engine." + engine.prefName, false); + await promisePrefChangeHandled.promise; + Assert.ok(!tracker._isTracking); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + await cleanup(engine); +}); diff --git a/services/sync/tests/unit/test_engine_abort.js b/services/sync/tests/unit/test_engine_abort.js new file mode 100644 index 0000000000..f9bbf9d338 --- /dev/null +++ b/services/sync/tests/unit/test_engine_abort.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { RotaryEngine } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/rotaryengine.sys.mjs" +); + +add_task(async function test_processIncoming_abort() { + _( + "An abort exception, raised in applyIncoming, will abort _processIncoming." + ); + let engine = new RotaryEngine(Service); + + let collection = new ServerCollection(); + let id = Utils.makeGUID(); + let payload = encryptPayload({ id, denomination: "Record No. " + id }); + collection.insert(id, payload); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + _("Create some server data."); + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + _("Fake applyIncoming to abort."); + engine._store.applyIncoming = async function (record) { + let ex = { + code: SyncEngine.prototype.eEngineAbortApplyIncoming, + cause: "Nooo", + }; + _("Throwing: " + JSON.stringify(ex)); + throw ex; + }; + + _("Trying _processIncoming. It will throw after aborting."); + let err; + try { + await engine._syncStartup(); + await engine._processIncoming(); + } catch (ex) { + err = ex; + } + + Assert.equal(err, "Nooo"); + err = undefined; + + _("Trying engine.sync(). It will abort without error."); + try { + // This will quietly fail. + await engine.sync(); + } catch (ex) { + err = ex; + } + + Assert.equal(err, undefined); + + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + + await engine._tracker.clearChangedIDs(); + await engine.finalize(); +}); diff --git a/services/sync/tests/unit/test_engine_changes_during_sync.js b/services/sync/tests/unit/test_engine_changes_during_sync.js new file mode 100644 index 0000000000..891bea41ec --- /dev/null +++ b/services/sync/tests/unit/test_engine_changes_during_sync.js @@ -0,0 +1,611 @@ +const { FormHistory } = ChromeUtils.importESModule( + "resource://gre/modules/FormHistory.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Bookmark, BookmarkFolder, BookmarkQuery } = ChromeUtils.importESModule( + "resource://services-sync/engines/bookmarks.sys.mjs" +); +const { HistoryRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); +const { FormRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/forms.sys.mjs" +); +const { LoginRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/passwords.sys.mjs" +); +const { PrefRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/prefs.sys.mjs" +); + +const LoginInfo = Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" +); + +/** + * We don't test the clients or tabs engines because neither has conflict + * resolution logic. The clients engine syncs twice per global sync, and + * custom conflict resolution logic for commands that doesn't use + * timestamps. Tabs doesn't have conflict resolution at all, since it's + * read-only. + */ + +async function assertChildGuids(folderGuid, expectedChildGuids, message) { + let tree = await PlacesUtils.promiseBookmarksTree(folderGuid); + let childGuids = tree.children.map(child => child.guid); + deepEqual(childGuids, expectedChildGuids, message); +} + +async function cleanup(engine, server) { + await engine._tracker.stop(); + await engine._store.wipe(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + await promiseStopServer(server); +} + +add_task(async function test_history_change_during_sync() { + _("Ensure that we don't bump the score when applying history records."); + + enableValidationPrefs(); + + let engine = Service.engineManager.get("history"); + let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("history"); + + // Override `uploadOutgoing` to insert a record while we're applying + // changes. The tracker should ignore this change. + let uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async function () { + engine._uploadOutgoing = uploadOutgoing; + try { + await uploadOutgoing.call(this); + } finally { + _("Inserting local history visit"); + await addVisit("during_sync"); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + }; + + engine._tracker.start(); + + try { + let remoteRec = new HistoryRec("history", "UrOOuzE5QM-e"); + remoteRec.histUri = "http://getfirefox.com/"; + remoteRec.title = "Get Firefox!"; + remoteRec.visits = [ + { + date: PlacesUtils.toPRTime(Date.now()), + type: PlacesUtils.history.TRANSITION_TYPED, + }, + ]; + collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext)); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score for visits added during sync" + ); + + equal( + collection.count(), + 1, + "New local visit should not exist on server after first sync" + ); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score during second history sync" + ); + + equal( + collection.count(), + 2, + "New local visit should exist on server after second sync" + ); + } finally { + engine._uploadOutgoing = uploadOutgoing; + await cleanup(engine, server); + } +}); + +add_task(async function test_passwords_change_during_sync() { + _("Ensure that we don't bump the score when applying passwords."); + + enableValidationPrefs(); + + let engine = Service.engineManager.get("passwords"); + let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + let uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async function () { + engine._uploadOutgoing = uploadOutgoing; + try { + await uploadOutgoing.call(this); + } finally { + _("Inserting local password"); + let login = new LoginInfo( + "https://example.com", + "", + null, + "username", + "password", + "", + "" + ); + await Services.logins.addLoginAsync(login); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + }; + + engine._tracker.start(); + + try { + let remoteRec = new LoginRec( + "passwords", + "{765e3d6e-071d-d640-a83d-81a7eb62d3ed}" + ); + remoteRec.formSubmitURL = ""; + remoteRec.httpRealm = ""; + remoteRec.hostname = "https://mozilla.org"; + remoteRec.username = "username"; + remoteRec.password = "sekrit"; + remoteRec.timeCreated = Date.now(); + remoteRec.timePasswordChanged = Date.now(); + collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext)); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score for passwords added during first sync" + ); + + equal( + collection.count(), + 1, + "New local password should not exist on server after first sync" + ); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score during second passwords sync" + ); + + equal( + collection.count(), + 2, + "New local password should exist on server after second sync" + ); + } finally { + engine._uploadOutgoing = uploadOutgoing; + await cleanup(engine, server); + } +}); + +add_task(async function test_prefs_change_during_sync() { + _("Ensure that we don't bump the score when applying prefs."); + + const TEST_PREF = "test.duringSync"; + // create a "control pref" for the pref we sync. + Services.prefs.setBoolPref("services.sync.prefs.sync.test.duringSync", true); + + enableValidationPrefs(); + + let engine = Service.engineManager.get("prefs"); + let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("prefs"); + + let uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async function () { + engine._uploadOutgoing = uploadOutgoing; + try { + await uploadOutgoing.call(this); + } finally { + _("Updating local pref value"); + // Change the value of a synced pref. + Services.prefs.setStringPref(TEST_PREF, "hello"); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + }; + + engine._tracker.start(); + + try { + // All synced prefs are stored in a single record, so we'll only ever + // have one record on the server. This test just checks that we don't + // track or upload prefs changed during the sync. + let guid = CommonUtils.encodeBase64URL(Services.appinfo.ID); + let remoteRec = new PrefRec("prefs", guid); + remoteRec.value = { + [TEST_PREF]: "world", + }; + collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext)); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score for prefs added during first sync" + ); + let payloads = collection.payloads(); + equal( + payloads.length, + 1, + "Should not upload multiple prefs records after first sync" + ); + equal( + payloads[0].value[TEST_PREF], + "world", + "Should not upload pref value changed during first sync" + ); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score during second prefs sync" + ); + payloads = collection.payloads(); + equal( + payloads.length, + 1, + "Should not upload multiple prefs records after second sync" + ); + equal( + payloads[0].value[TEST_PREF], + "hello", + "Should upload changed pref value during second sync" + ); + } finally { + engine._uploadOutgoing = uploadOutgoing; + await cleanup(engine, server); + Services.prefs.clearUserPref(TEST_PREF); + } +}); + +add_task(async function test_forms_change_during_sync() { + _("Ensure that we don't bump the score when applying form records."); + + enableValidationPrefs(); + + let engine = Service.engineManager.get("forms"); + let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("forms"); + + let uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async function () { + engine._uploadOutgoing = uploadOutgoing; + try { + await uploadOutgoing.call(this); + } finally { + _("Inserting local form history entry"); + await FormHistory.update([ + { + op: "add", + fieldname: "favoriteDrink", + value: "cocoa", + }, + ]); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + }; + + engine._tracker.start(); + + try { + // Add an existing remote form history entry. We shouldn't bump the score when + // we apply this record. + let remoteRec = new FormRec("forms", "Tl9dHgmJSR6FkyxS"); + remoteRec.name = "name"; + remoteRec.value = "alice"; + collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext)); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score for forms added during first sync" + ); + + equal( + collection.count(), + 1, + "New local form should not exist on server after first sync" + ); + + await sync_engine_and_validate_telem(engine, true); + strictEqual( + Service.scheduler.globalScore, + 0, + "Should not bump global score during second forms sync" + ); + + equal( + collection.count(), + 2, + "New local form should exist on server after second sync" + ); + } finally { + engine._uploadOutgoing = uploadOutgoing; + await cleanup(engine, server); + } +}); + +add_task(async function test_bookmark_change_during_sync() { + _("Ensure that we track bookmark changes made during a sync."); + + enableValidationPrefs(); + let schedulerProto = Object.getPrototypeOf(Service.scheduler); + let syncThresholdDescriptor = Object.getOwnPropertyDescriptor( + schedulerProto, + "syncThreshold" + ); + Object.defineProperty(Service.scheduler, "syncThreshold", { + // Trigger resync if any changes exist, rather than deciding based on the + // normal sync threshold. + get: () => 0, + }); + + let engine = Service.engineManager.get("bookmarks"); + let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]); + await SyncTestingInfrastructure(server); + + // Already-tracked bookmarks that shouldn't be uploaded during the first sync. + let bzBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.menuGuid, + url: "https://bugzilla.mozilla.org/", + title: "Bugzilla", + }); + _(`Bugzilla GUID: ${bzBmk.guid}`); + + await PlacesTestUtils.setBookmarkSyncFields({ + guid: bzBmk.guid, + syncChangeCounter: 0, + syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL, + }); + + let collection = server.user("foo").collection("bookmarks"); + + let bmk3; // New child of Folder 1, created locally during sync. + + let uploadOutgoing = engine._uploadOutgoing; + engine._uploadOutgoing = async function () { + engine._uploadOutgoing = uploadOutgoing; + try { + await uploadOutgoing.call(this); + } finally { + _("Inserting bookmark into local store"); + bmk3 = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "https://mozilla.org/", + title: "Mozilla", + }); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + }; + + // New bookmarks that should be uploaded during the first sync. + let folder1 = await PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + title: "Folder 1", + }); + _(`Folder GUID: ${folder1.guid}`); + + let tbBmk = await PlacesUtils.bookmarks.insert({ + parentGuid: folder1.guid, + url: "http://getthunderbird.com/", + title: "Get Thunderbird!", + }); + _(`Thunderbird GUID: ${tbBmk.guid}`); + + engine._tracker.start(); + + try { + let bmk2_guid = "get-firefox1"; // New child of Folder 1, created remotely. + let folder2_guid = "folder2-1111"; // New folder, created remotely. + let tagQuery_guid = "tag-query111"; // New tag query child of Folder 2, created remotely. + let bmk4_guid = "example-org1"; // New tagged child of Folder 2, created remotely. + { + // An existing record changed on the server that should not trigger + // another sync when applied. + let remoteBzBmk = new Bookmark("bookmarks", bzBmk.guid); + remoteBzBmk.bmkUri = "https://bugzilla.mozilla.org/"; + remoteBzBmk.description = "New description"; + remoteBzBmk.title = "Bugzilla"; + remoteBzBmk.tags = ["new", "tags"]; + remoteBzBmk.parentName = "Bookmarks Menu"; + remoteBzBmk.parentid = "menu"; + collection.insert(bzBmk.guid, encryptPayload(remoteBzBmk.cleartext)); + + let remoteFolder = new BookmarkFolder("bookmarks", folder2_guid); + remoteFolder.title = "Folder 2"; + remoteFolder.children = [bmk4_guid, tagQuery_guid]; + remoteFolder.parentName = "Bookmarks Menu"; + remoteFolder.parentid = "menu"; + collection.insert(folder2_guid, encryptPayload(remoteFolder.cleartext)); + + let remoteFxBmk = new Bookmark("bookmarks", bmk2_guid); + remoteFxBmk.bmkUri = "http://getfirefox.com/"; + remoteFxBmk.description = "Firefox is awesome."; + remoteFxBmk.title = "Get Firefox!"; + remoteFxBmk.tags = ["firefox", "awesome", "browser"]; + remoteFxBmk.keyword = "awesome"; + remoteFxBmk.parentName = "Folder 1"; + remoteFxBmk.parentid = folder1.guid; + collection.insert(bmk2_guid, encryptPayload(remoteFxBmk.cleartext)); + + // A tag query referencing a nonexistent tag folder, which we should + // create locally when applying the record. + let remoteTagQuery = new BookmarkQuery("bookmarks", tagQuery_guid); + remoteTagQuery.bmkUri = "place:type=7&folder=999"; + remoteTagQuery.title = "Taggy tags"; + remoteTagQuery.folderName = "taggy"; + remoteTagQuery.parentName = "Folder 2"; + remoteTagQuery.parentid = folder2_guid; + collection.insert( + tagQuery_guid, + encryptPayload(remoteTagQuery.cleartext) + ); + + // A bookmark that should appear in the results for the tag query. + let remoteTaggedBmk = new Bookmark("bookmarks", bmk4_guid); + remoteTaggedBmk.bmkUri = "https://example.org/"; + remoteTaggedBmk.title = "Tagged bookmark"; + remoteTaggedBmk.tags = ["taggy"]; + remoteTaggedBmk.parentName = "Folder 2"; + remoteTaggedBmk.parentid = folder2_guid; + collection.insert(bmk4_guid, encryptPayload(remoteTaggedBmk.cleartext)); + + collection.insert( + "toolbar", + encryptPayload({ + id: "toolbar", + type: "folder", + title: "toolbar", + children: [folder1.guid], + parentName: "places", + parentid: "places", + }) + ); + + collection.insert( + "menu", + encryptPayload({ + id: "menu", + type: "folder", + title: "menu", + children: [bzBmk.guid, folder2_guid], + parentName: "places", + parentid: "places", + }) + ); + + collection.insert( + folder1.guid, + encryptPayload({ + id: folder1.guid, + type: "folder", + title: "Folder 1", + children: [bmk2_guid], + parentName: "toolbar", + parentid: "toolbar", + }) + ); + } + + await assertChildGuids( + folder1.guid, + [tbBmk.guid], + "Folder should have 1 child before first sync" + ); + + let pingsPromise = wait_for_pings(2); + + let changes = await PlacesSyncUtils.bookmarks.pullChanges(); + deepEqual( + Object.keys(changes).sort(), + [folder1.guid, tbBmk.guid, "menu", "mobile", "toolbar", "unfiled"].sort(), + "Should track bookmark and folder created before first sync" + ); + + // Unlike the tests above, we can't use `sync_engine_and_validate_telem` + // because the bookmarks engine will automatically schedule a follow-up + // sync for us. + _("Perform first sync and immediate follow-up sync"); + Service.sync({ engines: ["bookmarks"] }); + + let pings = await pingsPromise; + equal(pings.length, 2, "Should submit two pings"); + ok( + pings.every(p => { + assert_success_ping(p); + return p.syncs.length == 1; + }), + "Should submit 1 sync per ping" + ); + + strictEqual( + Service.scheduler.globalScore, + 0, + "Should reset global score after follow-up sync" + ); + ok(bmk3, "Should insert bookmark during first sync to simulate change"); + ok( + collection.wbo(bmk3.guid), + "Changed bookmark should be uploaded after follow-up sync" + ); + + let bmk2 = await PlacesUtils.bookmarks.fetch({ + guid: bmk2_guid, + }); + ok(bmk2, "Remote bookmark should be applied during first sync"); + { + // We only check child GUIDs, and not their order, because the exact + // order is an implementation detail. + let folder1Children = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + folder1.guid + ); + deepEqual( + folder1Children.sort(), + [bmk2_guid, tbBmk.guid, bmk3.guid].sort(), + "Folder 1 should have 3 children after first sync" + ); + } + await assertChildGuids( + folder2_guid, + [bmk4_guid, tagQuery_guid], + "Folder 2 should have 2 children after first sync" + ); + let taggedURIs = []; + await PlacesUtils.bookmarks.fetch({ tags: ["taggy"] }, b => + taggedURIs.push(b.url) + ); + equal(taggedURIs.length, 1, "Should have 1 tagged URI"); + equal( + taggedURIs[0].href, + "https://example.org/", + "Synced tagged bookmark should appear in tagged URI list" + ); + + changes = await PlacesSyncUtils.bookmarks.pullChanges(); + deepEqual( + changes, + {}, + "Should have already uploaded changes in follow-up sync" + ); + + // First ping won't include validation data, since we've changed bookmarks + // and `canValidate` will indicate it can't proceed. + let engineData = pings.map(p => { + return p.syncs[0].engines.find(e => e.name == "bookmarks-buffered"); + }); + ok(engineData[0].validation, "Engine should validate after first sync"); + ok(engineData[1].validation, "Engine should validate after second sync"); + } finally { + Object.defineProperty( + schedulerProto, + "syncThreshold", + syncThresholdDescriptor + ); + engine._uploadOutgoing = uploadOutgoing; + await cleanup(engine, server); + } +}); diff --git a/services/sync/tests/unit/test_enginemanager.js b/services/sync/tests/unit/test_enginemanager.js new file mode 100644 index 0000000000..3e366be54f --- /dev/null +++ b/services/sync/tests/unit/test_enginemanager.js @@ -0,0 +1,232 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function PetrolEngine() {} +PetrolEngine.prototype.name = "petrol"; +PetrolEngine.prototype.finalize = async function () {}; + +function DieselEngine() {} +DieselEngine.prototype.name = "diesel"; +DieselEngine.prototype.finalize = async function () {}; + +function DummyEngine() {} +DummyEngine.prototype.name = "dummy"; +DummyEngine.prototype.finalize = async function () {}; + +class ActualEngine extends SyncEngine { + constructor(service) { + super("Actual", service); + } +} + +add_task(async function test_basics() { + _("We start out with a clean slate"); + + let manager = new EngineManager(Service); + + let engines = await manager.getAll(); + Assert.equal(engines.length, 0); + Assert.equal(await manager.get("dummy"), undefined); + + _("Register an engine"); + await manager.register(DummyEngine); + let dummy = await manager.get("dummy"); + Assert.ok(dummy instanceof DummyEngine); + + engines = await manager.getAll(); + Assert.equal(engines.length, 1); + Assert.equal(engines[0], dummy); + + _("Register an already registered engine is ignored"); + await manager.register(DummyEngine); + Assert.equal(await manager.get("dummy"), dummy); + + _("Register multiple engines in one go"); + await manager.register([PetrolEngine, DieselEngine]); + let petrol = await manager.get("petrol"); + let diesel = await manager.get("diesel"); + Assert.ok(petrol instanceof PetrolEngine); + Assert.ok(diesel instanceof DieselEngine); + + engines = await manager.getAll(); + Assert.equal(engines.length, 3); + Assert.notEqual(engines.indexOf(petrol), -1); + Assert.notEqual(engines.indexOf(diesel), -1); + + _("Retrieve multiple engines in one go"); + engines = await manager.get(["dummy", "diesel"]); + Assert.equal(engines.length, 2); + Assert.notEqual(engines.indexOf(dummy), -1); + Assert.notEqual(engines.indexOf(diesel), -1); + + _("getEnabled() only returns enabled engines"); + engines = await manager.getEnabled(); + Assert.equal(engines.length, 0); + + petrol.enabled = true; + engines = await manager.getEnabled(); + Assert.equal(engines.length, 1); + Assert.equal(engines[0], petrol); + + dummy.enabled = true; + diesel.enabled = true; + engines = await manager.getEnabled(); + Assert.equal(engines.length, 3); + + _("getEnabled() returns enabled engines in sorted order"); + petrol.syncPriority = 1; + dummy.syncPriority = 2; + diesel.syncPriority = 3; + + engines = await manager.getEnabled(); + + Assert.deepEqual(engines, [petrol, dummy, diesel]); + + _("Changing the priorities should change the order in getEnabled()"); + + dummy.syncPriority = 4; + + engines = await manager.getEnabled(); + + Assert.deepEqual(engines, [petrol, diesel, dummy]); + + _("Unregister an engine by name"); + await manager.unregister("dummy"); + Assert.equal(await manager.get("dummy"), undefined); + engines = await manager.getAll(); + Assert.equal(engines.length, 2); + Assert.equal(engines.indexOf(dummy), -1); + + _("Unregister an engine by value"); + // manager.unregister() checks for instanceof Engine, so let's make one: + await manager.register(ActualEngine); + let actual = await manager.get("actual"); + Assert.ok(actual instanceof ActualEngine); + Assert.ok(actual instanceof SyncEngine); + + await manager.unregister(actual); + Assert.equal(await manager.get("actual"), undefined); +}); + +class AutoEngine { + constructor(type) { + this.name = "automobile"; + this.type = type; + this.initializeCalled = false; + this.finalizeCalled = false; + this.isActive = false; + } + + async initialize() { + Assert.ok(!this.initializeCalled); + Assert.equal(AutoEngine.current, undefined); + this.initializeCalled = true; + this.isActive = true; + AutoEngine.current = this; + } + + async finalize() { + Assert.equal(AutoEngine.current, this); + Assert.ok(!this.finalizeCalled); + Assert.ok(this.isActive); + this.finalizeCalled = true; + this.isActive = false; + AutoEngine.current = undefined; + } +} + +class GasolineEngine extends AutoEngine { + constructor() { + super("gasoline"); + } +} + +class ElectricEngine extends AutoEngine { + constructor() { + super("electric"); + } +} + +add_task(async function test_alternates() { + let manager = new EngineManager(Service); + let engines = await manager.getAll(); + Assert.equal(engines.length, 0); + + const prefName = "services.sync.engines.automobile.electric"; + Services.prefs.clearUserPref(prefName); + + await manager.registerAlternatives( + "automobile", + prefName, + ElectricEngine, + GasolineEngine + ); + + let gasEngine = manager.get("automobile"); + Assert.equal(gasEngine.type, "gasoline"); + + Assert.ok(gasEngine.isActive); + Assert.ok(gasEngine.initializeCalled); + Assert.ok(!gasEngine.finalizeCalled); + Assert.equal(AutoEngine.current, gasEngine); + + _("Check that setting the controlling pref to false makes no difference"); + Services.prefs.setBoolPref(prefName, false); + Assert.equal(manager.get("automobile"), gasEngine); + Assert.ok(gasEngine.isActive); + Assert.ok(gasEngine.initializeCalled); + Assert.ok(!gasEngine.finalizeCalled); + + _("Even after the call to switchAlternatives"); + await manager.switchAlternatives(); + Assert.equal(manager.get("automobile"), gasEngine); + Assert.ok(gasEngine.isActive); + Assert.ok(gasEngine.initializeCalled); + Assert.ok(!gasEngine.finalizeCalled); + + _("Set the pref to true, we still shouldn't switch yet"); + Services.prefs.setBoolPref(prefName, true); + Assert.equal(manager.get("automobile"), gasEngine); + Assert.ok(gasEngine.isActive); + Assert.ok(gasEngine.initializeCalled); + Assert.ok(!gasEngine.finalizeCalled); + + _("Now we expect to switch from gas to electric"); + await manager.switchAlternatives(); + let elecEngine = manager.get("automobile"); + Assert.equal(elecEngine.type, "electric"); + Assert.ok(elecEngine.isActive); + Assert.ok(elecEngine.initializeCalled); + Assert.ok(!elecEngine.finalizeCalled); + Assert.equal(AutoEngine.current, elecEngine); + + Assert.ok(!gasEngine.isActive); + Assert.ok(gasEngine.finalizeCalled); + + _("Switch back, and ensure we get a new instance that got initialized again"); + Services.prefs.setBoolPref(prefName, false); + await manager.switchAlternatives(); + + // First make sure we deactivated the electric engine as we should + Assert.ok(!elecEngine.isActive); + Assert.ok(elecEngine.initializeCalled); + Assert.ok(elecEngine.finalizeCalled); + + let newGasEngine = manager.get("automobile"); + Assert.notEqual(newGasEngine, gasEngine); + Assert.equal(newGasEngine.type, "gasoline"); + + Assert.ok(newGasEngine.isActive); + Assert.ok(newGasEngine.initializeCalled); + Assert.ok(!newGasEngine.finalizeCalled); + + _("Make sure unregister removes the alt info too"); + await manager.unregister("automobile"); + Assert.equal(manager.get("automobile"), null); + Assert.ok(newGasEngine.finalizeCalled); + Assert.deepEqual(Object.keys(manager._altEngineInfo), []); +}); diff --git a/services/sync/tests/unit/test_errorhandler_1.js b/services/sync/tests/unit/test_errorhandler_1.js new file mode 100644 index 0000000000..2d52b93a02 --- /dev/null +++ b/services/sync/tests/unit/test_errorhandler_1.js @@ -0,0 +1,341 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); + +const fakeServer = new SyncServer(); +fakeServer.start(); +const fakeServerUrl = "http://localhost:" + fakeServer.port; + +registerCleanupFunction(function () { + return promiseStopServer(fakeServer).finally(() => { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + }); +}); + +let engine; +add_task(async function setup() { + await Service.engineManager.clear(); + await Service.engineManager.register(EHTestsCommon.CatapultEngine); + engine = Service.engineManager.get("catapult"); +}); + +async function clean() { + let promiseLogReset = promiseOneObserver("weave:service:reset-file-log"); + await Service.startOver(); + await promiseLogReset; + Status.resetSync(); + Status.resetBackoff(); + // Move log levels back to trace (startOver will have reversed this), sicne + syncTestLogging(); +} + +add_task(async function test_401_logout() { + enableValidationPrefs(); + + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + // By calling sync, we ensure we're logged in. + await sync_and_validate_telem(); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.ok(Service.isLoggedIn); + + let promiseErrors = new Promise(res => { + Svc.Obs.add("weave:service:sync:error", onSyncError); + function onSyncError() { + _("Got weave:service:sync:error in first sync."); + Svc.Obs.remove("weave:service:sync:error", onSyncError); + + // Wait for the automatic next sync. + Svc.Obs.add("weave:service:login:error", onLoginError); + function onLoginError() { + _("Got weave:service:login:error in second sync."); + Svc.Obs.remove("weave:service:login:error", onLoginError); + res(); + } + } + }); + + // Make sync fail due to login rejected. + await configureIdentity({ username: "janedoe" }, server); + Service._updateCachedURLs(); + + _("Starting first sync."); + await sync_and_validate_telem(ping => { + deepEqual(ping.failureReason, { name: "httperror", code: 401 }); + }); + _("First sync done."); + + await promiseErrors; + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); + Assert.ok(!Service.isLoggedIn); + + // Clean up. + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_credentials_changed_logout() { + enableValidationPrefs(); + + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + // By calling sync, we ensure we're logged in. + await sync_and_validate_telem(); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.ok(Service.isLoggedIn); + + await EHTestsCommon.generateCredentialsChangedFailure(); + + await sync_and_validate_telem(ping => { + equal(ping.status.sync, CREDENTIALS_CHANGED); + deepEqual(ping.failureReason, { + name: "unexpectederror", + error: "Error: Aborting sync, remote setup failed", + }); + }); + + Assert.equal(Status.sync, CREDENTIALS_CHANGED); + Assert.ok(!Service.isLoggedIn); + + // Clean up. + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_login_non_network_error() { + enableValidationPrefs(); + + // Test non-network errors are reported + // when calling sync + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + Service.identity._syncKeyBundle = null; + + await Service.sync(); + Assert.equal(Status.login, LOGIN_FAILED_NO_PASSPHRASE); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_sync_non_network_error() { + enableValidationPrefs(); + + // Test non-network errors are reported + // when calling sync + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + // By calling sync, we ensure we're logged in. + await Service.sync(); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.ok(Service.isLoggedIn); + + await EHTestsCommon.generateCredentialsChangedFailure(); + + await sync_and_validate_telem(ping => { + equal(ping.status.sync, CREDENTIALS_CHANGED); + deepEqual(ping.failureReason, { + name: "unexpectederror", + error: "Error: Aborting sync, remote setup failed", + }); + }); + + Assert.equal(Status.sync, CREDENTIALS_CHANGED); + // If we clean this tick, telemetry won't get the right error + await Async.promiseYield(); + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_login_sync_network_error() { + enableValidationPrefs(); + + // Test network errors are reported when calling sync. + await configureIdentity({ username: "broken.wipe" }); + Service.clusterURL = fakeServerUrl; + + await Service.sync(); + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); + + await clean(); +}); + +add_task(async function test_sync_network_error() { + enableValidationPrefs(); + + // Test network errors are reported when calling sync. + Services.io.offline = true; + + await Service.sync(); + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + + Services.io.offline = false; + await clean(); +}); + +add_task(async function test_login_non_network_error() { + enableValidationPrefs(); + + // Test non-network errors are reported + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + Service.identity._syncKeyBundle = null; + + await Service.sync(); + Assert.equal(Status.login, LOGIN_FAILED_NO_PASSPHRASE); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_sync_non_network_error() { + enableValidationPrefs(); + + // Test non-network errors are reported + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + // By calling sync, we ensure we're logged in. + await Service.sync(); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.ok(Service.isLoggedIn); + + await EHTestsCommon.generateCredentialsChangedFailure(); + + await Service.sync(); + Assert.equal(Status.sync, CREDENTIALS_CHANGED); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_login_network_error() { + enableValidationPrefs(); + + await configureIdentity({ username: "johndoe" }); + Service.clusterURL = fakeServerUrl; + + // Test network errors are not reported. + + await Service.sync(); + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); + + Services.io.offline = false; + await clean(); +}); + +add_task(async function test_sync_network_error() { + enableValidationPrefs(); + + // Test network errors are not reported. + Services.io.offline = true; + + await Service.sync(); + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + + Services.io.offline = false; + await clean(); +}); + +add_task(async function test_sync_server_maintenance_error() { + enableValidationPrefs(); + + // Test server maintenance errors are not reported. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + const BACKOFF = 42; + engine.enabled = true; + engine.exception = { status: 503, headers: { "retry-after": BACKOFF } }; + + Assert.equal(Status.service, STATUS_OK); + + await sync_and_validate_telem(ping => { + equal(ping.status.sync, SERVER_MAINTENANCE); + deepEqual(ping.engines.find(e => e.failureReason).failureReason, { + name: "httperror", + code: 503, + }); + }); + + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + Assert.equal(Status.sync, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_info_collections_login_server_maintenance_error() { + enableValidationPrefs(); + + // Test info/collections server maintenance errors are not reported. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.info" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + await Service.sync(); + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_meta_global_login_server_maintenance_error() { + enableValidationPrefs(); + + // Test meta/global server maintenance errors are not reported. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.meta" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + await Service.sync(); + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_errorhandler_2.js b/services/sync/tests/unit/test_errorhandler_2.js new file mode 100644 index 0000000000..5cab4d832d --- /dev/null +++ b/services/sync/tests/unit/test_errorhandler_2.js @@ -0,0 +1,550 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +const fakeServer = new SyncServer(); +fakeServer.start(); + +registerCleanupFunction(function () { + return promiseStopServer(fakeServer).finally(() => { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + }); +}); + +const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]); +logsdir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +function removeLogFiles() { + let entries = logsdir.directoryEntries; + while (entries.hasMoreElements()) { + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + logfile.remove(false); + } +} + +function getLogFiles() { + let result = []; + let entries = logsdir.directoryEntries; + while (entries.hasMoreElements()) { + result.push(entries.getNext().QueryInterface(Ci.nsIFile)); + } + return result; +} + +let engine; +add_task(async function setup() { + await Service.engineManager.clear(); + await Service.engineManager.register(EHTestsCommon.CatapultEngine); + engine = Service.engineManager.get("catapult"); +}); + +async function clean() { + let promiseLogReset = promiseOneObserver("weave:service:reset-file-log"); + await Service.startOver(); + await promiseLogReset; + Status.resetSync(); + Status.resetBackoff(); + removeLogFiles(); + // Move log levels back to trace (startOver will have reversed this), sicne + syncTestLogging(); +} + +add_task(async function test_crypto_keys_login_server_maintenance_error() { + enableValidationPrefs(); + + Status.resetSync(); + // Test crypto/keys server maintenance errors are not reported. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.keys" }, server); + + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); +}); + +add_task(async function test_lastSync_not_updated_on_complete_failure() { + enableValidationPrefs(); + + // Test info/collections prolonged server maintenance errors are reported. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "johndoe" }, server); + + // Do an initial sync that we expect to be successful. + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await sync_and_validate_telem(); + await promiseObserved; + + Assert.equal(Status.service, STATUS_OK); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + let lastSync = Svc.PrefBranch.getStringPref("lastSync"); + + Assert.ok(lastSync); + + // Report server maintenance on info/collections requests + server.registerPathHandler( + "/1.1/johndoe/info/collections", + EHTestsCommon.service_unavailable + ); + + promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await sync_and_validate_telem(() => {}); + await promiseObserved; + + Assert.equal(Status.sync, SERVER_MAINTENANCE); + Assert.equal(Status.service, SYNC_FAILED); + + // We shouldn't update lastSync on complete failure. + Assert.equal(lastSync, Svc.PrefBranch.getStringPref("lastSync")); + + await clean(); + await promiseStopServer(server); +}); + +add_task( + async function test_sync_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + const BACKOFF = 42; + engine.enabled = true; + engine.exception = { status: 503, headers: { "retry-after": BACKOFF } }; + + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + Assert.equal(Status.sync, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_info_collections_login_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test info/collections server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.info" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_meta_global_login_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test meta/global server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.meta" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + await EHTestsCommon.setUp(server); + + await configureIdentity({ username: "broken.keys" }, server); + // Force re-download of keys + Service.collectionKeys.clear(); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + await configureIdentity({ username: "broken.keys" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test crypto/keys server maintenance errors are reported + // when calling syncAndReportErrors. + let server = await EHTestsCommon.sync_httpd_setup(); + + // Start off with an empty account, do not upload a key. + await configureIdentity({ username: "broken.wipe" }, server); + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, LOGIN_FAILED); + Assert.equal(Status.login, SERVER_MAINTENANCE); + + await clean(); + await promiseStopServer(server); + } +); + +add_task( + async function test_wipeRemote_syncAndReportErrors_server_maintenance_error() { + enableValidationPrefs(); + + // Test that we report prolonged server maintenance errors that occur whilst + // wiping all remote devices. + let server = await EHTestsCommon.sync_httpd_setup(); + + await configureIdentity({ username: "broken.wipe" }, server); + await EHTestsCommon.generateAndUploadKeys(); + + engine.exception = null; + engine.enabled = true; + + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.service, STATUS_OK); + + Svc.PrefBranch.setStringPref("firstSync", "wipeRemote"); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + await Service.sync(); + await promiseObserved; + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Status.service, SYNC_FAILED); + Assert.equal(Status.sync, SERVER_MAINTENANCE); + Assert.equal(Svc.PrefBranch.getStringPref("firstSync"), "wipeRemote"); + + await clean(); + await promiseStopServer(server); + } +); + +add_task(async function test_sync_engine_generic_fail() { + enableValidationPrefs(); + + equal(getLogFiles().length, 0); + + let server = await EHTestsCommon.sync_httpd_setup(); + engine.enabled = true; + engine.sync = async function sync() { + Svc.Obs.notify("weave:engine:sync:error", ENGINE_UNKNOWN_FAIL, "catapult"); + }; + let lastSync = Svc.PrefBranch.getStringPref("lastSync", null); + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + Assert.equal(Status.engines.catapult, undefined); + + let promiseObserved = new Promise(res => { + Svc.Obs.add("weave:engine:sync:finish", function onEngineFinish() { + Svc.Obs.remove("weave:engine:sync:finish", onEngineFinish); + + log.info("Adding reset-file-log observer."); + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + res(); + }); + }); + }); + + Assert.ok(await EHTestsCommon.setUp(server)); + await sync_and_validate_telem(ping => { + deepEqual(ping.status.service, SYNC_FAILED_PARTIAL); + deepEqual(ping.engines.find(e => e.status).status, ENGINE_UNKNOWN_FAIL); + }); + + await promiseObserved; + + _("Status.engines: " + JSON.stringify(Status.engines)); + Assert.equal(Status.engines.catapult, ENGINE_UNKNOWN_FAIL); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + // lastSync should update on partial failure. + Assert.notEqual(lastSync, Svc.PrefBranch.getStringPref("lastSync")); + + // Test Error log was written on SYNC_FAILED_PARTIAL. + let logFiles = getLogFiles(); + equal(logFiles.length, 1); + Assert.ok( + logFiles[0].leafName.startsWith("error-sync-"), + logFiles[0].leafName + ); + + await clean(); + + await promiseStopServer(server); +}); + +add_task(async function test_logs_on_sync_error() { + enableValidationPrefs(); + + _( + "Ensure that an error is still logged when weave:service:sync:error " + + "is notified, despite shouldReportError returning false." + ); + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + log.info("TESTING"); + + // Ensure that we report no error. + Status.login = MASTER_PASSWORD_LOCKED; + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + Svc.Obs.notify("weave:service:sync:error", {}); + await promiseObserved; + + // Test that error log was written. + let logFiles = getLogFiles(); + equal(logFiles.length, 1); + Assert.ok( + logFiles[0].leafName.startsWith("error-sync-"), + logFiles[0].leafName + ); + + await clean(); +}); + +add_task(async function test_logs_on_login_error() { + enableValidationPrefs(); + + _( + "Ensure that an error is still logged when weave:service:login:error " + + "is notified, despite shouldReportError returning false." + ); + + let log = Log.repository.getLogger("Sync.ErrorHandler"); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + log.info("TESTING"); + + // Ensure that we report no error. + Status.login = MASTER_PASSWORD_LOCKED; + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + Svc.Obs.notify("weave:service:login:error", {}); + await promiseObserved; + + // Test that error log was written. + let logFiles = getLogFiles(); + equal(logFiles.length, 1); + Assert.ok( + logFiles[0].leafName.startsWith("error-sync-"), + logFiles[0].leafName + ); + + await clean(); +}); + +// This test should be the last one since it monkeypatches the engine object +// and we should only have one engine object throughout the file (bug 629664). +add_task(async function test_engine_applyFailed() { + enableValidationPrefs(); + + let server = await EHTestsCommon.sync_httpd_setup(); + + engine.enabled = true; + delete engine.exception; + engine.sync = async function sync() { + Svc.Obs.notify("weave:engine:sync:applied", { newFailed: 1 }, "catapult"); + }; + + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + let promiseObserved = promiseOneObserver("weave:service:reset-file-log"); + + Assert.equal(Status.engines.catapult, undefined); + Assert.ok(await EHTestsCommon.setUp(server)); + await Service.sync(); + await promiseObserved; + + Assert.equal(Status.engines.catapult, ENGINE_APPLY_FAIL); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + // Test Error log was written on SYNC_FAILED_PARTIAL. + let logFiles = getLogFiles(); + equal(logFiles.length, 1); + Assert.ok( + logFiles[0].leafName.startsWith("error-sync-"), + logFiles[0].leafName + ); + + await clean(); + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_errorhandler_filelog.js b/services/sync/tests/unit/test_errorhandler_filelog.js new file mode 100644 index 0000000000..66260b3f59 --- /dev/null +++ b/services/sync/tests/unit/test_errorhandler_filelog.js @@ -0,0 +1,473 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// `Service` is used as a global in head_helpers.js. +// eslint-disable-next-line no-unused-vars +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { logManager } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"]); +logsdir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); + +// Delay to wait before cleanup, to allow files to age. +// This is so large because the file timestamp granularity is per-second, and +// so otherwise we can end up with all of our files -- the ones we want to +// keep, and the ones we want to clean up -- having the same modified time. +const CLEANUP_DELAY = 2000; +const DELAY_BUFFER = 500; // Buffer for timers on different OS platforms. + +function run_test() { + validate_all_future_pings(); + run_next_test(); +} + +add_test(function test_noOutput() { + // Ensure that the log appender won't print anything. + logManager._fileAppender.level = Log.Level.Fatal + 1; + + // Clear log output from startup. + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", false); + Svc.Obs.notify("weave:service:sync:finish"); + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogOuter() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogOuter); + // Clear again without having issued any output. + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", true); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLogInner() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLogInner); + + logManager._fileAppender.level = Log.Level.Trace; + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + + // Fake a successful sync. + Svc.Obs.notify("weave:service:sync:finish"); + }); +}); + +add_test(function test_logOnSuccess_false() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", false); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + log.info("this won't show up"); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + // No log file was written. + Assert.ok(!logsdir.directoryEntries.hasMoreElements()); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + + // Fake a successful sync. + Svc.Obs.notify("weave:service:sync:finish"); +}); + +function readFile(file, callback) { + NetUtil.asyncFetch( + { + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true, + }, + function (inputStream, statusCode, request) { + let data = NetUtil.readInputStreamToString( + inputStream, + inputStream.available() + ); + callback(statusCode, data); + } + ); +} + +add_test(function test_logOnSuccess_true() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", true); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + const MESSAGE = "this WILL show up"; + log.info(MESSAGE); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Exactly one log file was written. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.equal(logfile.leafName.slice(-4), ".txt"); + Assert.ok(logfile.leafName.startsWith("success-sync-"), logfile.leafName); + Assert.ok(!entries.hasMoreElements()); + + // Ensure the log message was actually written to file. + readFile(logfile, function (error, data) { + Assert.ok(Components.isSuccessCode(error)); + Assert.notEqual(data.indexOf(MESSAGE), -1); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + }); + + // Fake a successful sync. + Svc.Obs.notify("weave:service:sync:finish"); +}); + +add_test(function test_sync_error_logOnError_false() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", false); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + log.info("this won't show up"); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + // No log file was written. + Assert.ok(!logsdir.directoryEntries.hasMoreElements()); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + + // Fake an unsuccessful sync. + Svc.Obs.notify("weave:service:sync:error"); +}); + +add_test(function test_sync_error_logOnError_true() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + const MESSAGE = "this WILL show up"; + log.info(MESSAGE); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Exactly one log file was written. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.equal(logfile.leafName.slice(-4), ".txt"); + Assert.ok(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + Assert.ok(!entries.hasMoreElements()); + + // Ensure the log message was actually written to file. + readFile(logfile, function (error, data) { + Assert.ok(Components.isSuccessCode(error)); + Assert.notEqual(data.indexOf(MESSAGE), -1); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + }); + + // Fake an unsuccessful sync. + Svc.Obs.notify("weave:service:sync:error"); +}); + +add_test(function test_login_error_logOnError_false() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", false); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + log.info("this won't show up"); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + // No log file was written. + Assert.ok(!logsdir.directoryEntries.hasMoreElements()); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + + // Fake an unsuccessful login. + Svc.Obs.notify("weave:service:login:error"); +}); + +add_test(function test_login_error_logOnError_true() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + const MESSAGE = "this WILL show up"; + log.info(MESSAGE); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Exactly one log file was written. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.equal(logfile.leafName.slice(-4), ".txt"); + Assert.ok(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + Assert.ok(!entries.hasMoreElements()); + + // Ensure the log message was actually written to file. + readFile(logfile, function (error, data) { + Assert.ok(Components.isSuccessCode(error)); + Assert.notEqual(data.indexOf(MESSAGE), -1); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + }); + + // Fake an unsuccessful login. + Svc.Obs.notify("weave:service:login:error"); +}); + +add_test(function test_noNewFailed_noErrorLog() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", false); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + // No log file was written. + Assert.ok(!logsdir.directoryEntries.hasMoreElements()); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + // failed is nonzero and newFailed is zero -- shouldn't write a log. + let count = { + applied: 8, + succeeded: 4, + failed: 5, + newFailed: 0, + reconciled: 4, + }; + Svc.Obs.notify("weave:engine:sync:applied", count, "foobar-engine"); + Svc.Obs.notify("weave:service:sync:finish"); +}); + +add_test(function test_newFailed_errorLog() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnSuccess", false); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + const MESSAGE = "this WILL show up 2"; + log.info(MESSAGE); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + // Exactly one log file was written. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.equal(logfile.leafName.slice(-4), ".txt"); + Assert.ok(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + Assert.ok(!entries.hasMoreElements()); + + // Ensure the log message was actually written to file. + readFile(logfile, function (error, data) { + Assert.ok(Components.isSuccessCode(error)); + Assert.notEqual(data.indexOf(MESSAGE), -1); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + }); + // newFailed is nonzero -- should write a log. + let count = { + applied: 8, + succeeded: 4, + failed: 5, + newFailed: 4, + reconciled: 4, + }; + + Svc.Obs.notify("weave:engine:sync:applied", count, "foobar-engine"); + Svc.Obs.notify("weave:service:sync:finish"); +}); + +add_test(function test_errorLog_dumpAddons() { + Svc.PrefBranch.setStringPref("log.logger", "Trace"); + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() { + Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog); + + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.equal(logfile.leafName.slice(-4), ".txt"); + Assert.ok(logfile.leafName.startsWith("error-sync-"), logfile.leafName); + Assert.ok(!entries.hasMoreElements()); + + // Ensure we logged some addon list (which is probably empty) + readFile(logfile, function (error, data) { + Assert.ok(Components.isSuccessCode(error)); + Assert.notEqual(data.indexOf("Addons installed"), -1); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + }); + }); + + // Fake an unsuccessful sync. + Svc.Obs.notify("weave:service:sync:error"); +}); + +// Check that error log files are deleted above an age threshold. +add_test(async function test_logErrorCleanup_age() { + _("Beginning test_logErrorCleanup_age."); + let maxAge = CLEANUP_DELAY / 1000; + let oldLogs = []; + let numLogs = 10; + let errString = "some error log\n"; + + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + Svc.PrefBranch.setIntPref("log.appender.file.maxErrorAge", maxAge); + + _("Making some files."); + const logsDir = PathUtils.join(PathUtils.profileDir, "weave", "logs"); + await IOUtils.makeDirectory(logsDir); + for (let i = 0; i < numLogs; i++) { + let now = Date.now(); + let filename = "error-sync-" + now + "" + i + ".txt"; + let newLog = new FileUtils.File(PathUtils.join(logsDir, filename)); + let foStream = FileUtils.openFileOutputStream(newLog); + foStream.write(errString, errString.length); + foStream.close(); + _(" > Created " + filename); + oldLogs.push(newLog.leafName); + } + + Svc.Obs.add( + "services-tests:common:log-manager:cleanup-logs", + function onCleanupLogs() { + Svc.Obs.remove( + "services-tests:common:log-manager:cleanup-logs", + onCleanupLogs + ); + + // Only the newest created log file remains. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + let logfile = entries.getNext().QueryInterface(Ci.nsIFile); + Assert.ok( + oldLogs.every(function (e) { + return e != logfile.leafName; + }) + ); + Assert.ok(!entries.hasMoreElements()); + + // Clean up. + try { + logfile.remove(false); + } catch (ex) { + dump("Couldn't delete file: " + ex.message + "\n"); + // Stupid Windows box. + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + run_next_test(); + } + ); + + let delay = CLEANUP_DELAY + DELAY_BUFFER; + + _("Cleaning up logs after " + delay + "msec."); + CommonUtils.namedTimer( + function onTimer() { + Svc.Obs.notify("weave:service:sync:error"); + }, + delay, + this, + "cleanup-timer" + ); +}); + +add_task(async function test_remove_log_on_startOver() { + Svc.PrefBranch.setBoolPref("log.appender.file.logOnError", true); + + let log = Log.repository.getLogger("Sync.Test.FileLog"); + const MESSAGE = "this WILL show up"; + log.info(MESSAGE); + + let promiseLogWritten = promiseOneObserver("weave:service:reset-file-log"); + // Fake an unsuccessful sync. + Svc.Obs.notify("weave:service:sync:error"); + + await promiseLogWritten; + // Should have at least 1 log file. + let entries = logsdir.directoryEntries; + Assert.ok(entries.hasMoreElements()); + + // Fake a reset. + let promiseRemoved = promiseOneObserver("weave:service:remove-file-log"); + Svc.Obs.notify("weave:service:start-over:finish"); + await promiseRemoved; + + // should be no files left. + Assert.ok(!logsdir.directoryEntries.hasMoreElements()); +}); diff --git a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js new file mode 100644 index 0000000000..d73d548cc7 --- /dev/null +++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js @@ -0,0 +1,294 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); +const { FakeCryptoService } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/fakeservices.sys.mjs" +); + +var engineManager = Service.engineManager; + +function CatapultEngine() { + SyncEngine.call(this, "Catapult", Service); +} +CatapultEngine.prototype = { + exception: null, // tests fill this in + async _sync() { + throw this.exception; + }, +}; +Object.setPrototypeOf(CatapultEngine.prototype, SyncEngine.prototype); + +async function sync_httpd_setup() { + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + let catapultEngine = engineManager.get("catapult"); + let syncID = await catapultEngine.resetLocalSyncID(); + let engines = { catapult: { version: catapultEngine.version, syncID } }; + + // Track these using the collections helper, which keeps modified times + // up-to-date. + let clientsColl = new ServerCollection({}, true); + let keysWBO = new ServerWBO("keys"); + let globalWBO = new ServerWBO("global", { + storageVersion: STORAGE_VERSION, + syncID: Utils.makeGUID(), + engines, + }); + + let handlers = { + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/meta/global": upd("meta", globalWBO.handler()), + "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + }; + return httpd_setup(handlers); +} + +async function setUp(server) { + await configureIdentity({ username: "johndoe" }, server); + new FakeCryptoService(); + syncTestLogging(); +} + +async function generateAndUploadKeys(server) { + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + let res = Service.resource( + server.baseURI + "/1.1/johndoe/storage/crypto/keys" + ); + return (await serverKeys.upload(res)).success; +} + +add_task(async function setup() { + await engineManager.clear(); + validate_all_future_pings(); + await engineManager.register(CatapultEngine); +}); + +add_task(async function test_backoff500() { + enableValidationPrefs(); + + _("Test: HTTP 500 sets backoff status."); + let server = await sync_httpd_setup(); + await setUp(server); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = { status: 500 }; + + try { + Assert.ok(!Status.enforceBackoff); + + // Forcibly create and upload keys here -- otherwise we don't get to the 500! + Assert.ok(await generateAndUploadKeys(server)); + + await Service.login(); + await Service.sync(); + Assert.ok(Status.enforceBackoff); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + } finally { + Status.resetBackoff(); + await Service.startOver(); + } + await promiseStopServer(server); +}); + +add_task(async function test_backoff503() { + enableValidationPrefs(); + + _( + "Test: HTTP 503 with Retry-After header leads to backoff notification and sets backoff status." + ); + let server = await sync_httpd_setup(); + await setUp(server); + + const BACKOFF = 42; + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = { status: 503, headers: { "retry-after": BACKOFF } }; + + let backoffInterval; + Svc.Obs.add("weave:service:backoff:interval", function (subject) { + backoffInterval = subject; + }); + + try { + Assert.ok(!Status.enforceBackoff); + + Assert.ok(await generateAndUploadKeys(server)); + + await Service.login(); + await Service.sync(); + + Assert.ok(Status.enforceBackoff); + Assert.equal(backoffInterval, BACKOFF); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + Assert.equal(Status.sync, SERVER_MAINTENANCE); + } finally { + Status.resetBackoff(); + Status.resetSync(); + await Service.startOver(); + } + await promiseStopServer(server); +}); + +add_task(async function test_overQuota() { + enableValidationPrefs(); + + _("Test: HTTP 400 with body error code 14 means over quota."); + let server = await sync_httpd_setup(); + await setUp(server); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = { + status: 400, + toString() { + return "14"; + }, + }; + + try { + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await generateAndUploadKeys(server)); + + await Service.login(); + await Service.sync(); + + Assert.equal(Status.sync, OVER_QUOTA); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + } finally { + Status.resetSync(); + await Service.startOver(); + } + await promiseStopServer(server); +}); + +add_task(async function test_service_networkError() { + enableValidationPrefs(); + + _( + "Test: Connection refused error from Service.sync() leads to the right status code." + ); + let server = await sync_httpd_setup(); + await setUp(server); + await promiseStopServer(server); + // Provoke connection refused. + Service.clusterURL = "http://localhost:12345/"; + + try { + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Service._loggedIn = true; + await Service.sync(); + + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + Assert.equal(Status.service, SYNC_FAILED); + } finally { + Status.resetSync(); + await Service.startOver(); + } +}); + +add_task(async function test_service_offline() { + enableValidationPrefs(); + + _( + "Test: Wanting to sync in offline mode leads to the right status code but does not increment the ignorable error count." + ); + let server = await sync_httpd_setup(); + await setUp(server); + + await promiseStopServer(server); + Services.io.offline = true; + Services.prefs.setBoolPref("network.dns.offline-localhost", false); + + try { + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Service._loggedIn = true; + await Service.sync(); + + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + Assert.equal(Status.service, SYNC_FAILED); + } finally { + Status.resetSync(); + await Service.startOver(); + } + Services.io.offline = false; + Services.prefs.clearUserPref("network.dns.offline-localhost"); +}); + +add_task(async function test_engine_networkError() { + enableValidationPrefs(); + + _( + "Test: Network related exceptions from engine.sync() lead to the right status code." + ); + let server = await sync_httpd_setup(); + await setUp(server); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + engine.exception = Components.Exception( + "NS_ERROR_UNKNOWN_HOST", + Cr.NS_ERROR_UNKNOWN_HOST + ); + + try { + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await generateAndUploadKeys(server)); + + await Service.login(); + await Service.sync(); + + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + } finally { + Status.resetSync(); + await Service.startOver(); + } + await promiseStopServer(server); +}); + +add_task(async function test_resource_timeout() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + let engine = engineManager.get("catapult"); + engine.enabled = true; + // Resource throws this when it encounters a timeout. + engine.exception = Components.Exception( + "Aborting due to channel inactivity.", + Cr.NS_ERROR_NET_TIMEOUT + ); + + try { + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await generateAndUploadKeys(server)); + + await Service.login(); + await Service.sync(); + + Assert.equal(Status.sync, LOGIN_FAILED_NETWORK_ERROR); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + } finally { + Status.resetSync(); + await Service.startOver(); + } + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_extension_storage_engine.js b/services/sync/tests/unit/test_extension_storage_engine.js new file mode 100644 index 0000000000..a061812aca --- /dev/null +++ b/services/sync/tests/unit/test_extension_storage_engine.js @@ -0,0 +1,275 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + Service: "resource://services-sync/service.sys.mjs", + extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs", +}); + +const { ExtensionStorageEngineBridge, ExtensionStorageEngineKinto } = + ChromeUtils.importESModule( + "resource://services-sync/engines/extension-storage.sys.mjs" + ); + +const { BridgeWrapperXPCOM } = ChromeUtils.importESModule( + "resource://services-sync/bridged_engine.sys.mjs" +); + +Services.prefs.setStringPref("webextensions.storage.sync.log.level", "debug"); + +add_task(async function test_switching_between_kinto_and_bridged() { + function assertUsingKinto(message) { + let kintoEngine = Service.engineManager.get("extension-storage"); + Assert.ok(kintoEngine instanceof ExtensionStorageEngineKinto, message); + } + function assertUsingBridged(message) { + let bridgedEngine = Service.engineManager.get("extension-storage"); + Assert.ok(bridgedEngine instanceof ExtensionStorageEngineBridge, message); + } + + let isUsingKinto = Services.prefs.getBoolPref( + "webextensions.storage.sync.kinto", + false + ); + if (isUsingKinto) { + assertUsingKinto("Should use Kinto engine before flipping pref"); + } else { + assertUsingBridged("Should use bridged engine before flipping pref"); + } + + _("Flip pref"); + Services.prefs.setBoolPref("webextensions.storage.sync.kinto", !isUsingKinto); + await Service.engineManager.switchAlternatives(); + + if (isUsingKinto) { + assertUsingBridged("Should use bridged engine after flipping pref"); + } else { + assertUsingKinto("Should use Kinto engine after flipping pref"); + } + + _("Clean up"); + Services.prefs.clearUserPref("webextensions.storage.sync.kinto"); + await Service.engineManager.switchAlternatives(); +}); + +add_task(async function test_enable() { + const PREF = "services.sync.engine.extension-storage.force"; + + let addonsEngine = Service.engineManager.get("addons"); + let extensionStorageEngine = Service.engineManager.get("extension-storage"); + + try { + Assert.ok( + addonsEngine.enabled, + "Add-ons engine should be enabled by default" + ); + Assert.ok( + extensionStorageEngine.enabled, + "Extension storage engine should be enabled by default" + ); + + addonsEngine.enabled = false; + Assert.ok( + !extensionStorageEngine.enabled, + "Disabling add-ons should disable extension storage" + ); + + extensionStorageEngine.enabled = true; + Assert.ok( + !extensionStorageEngine.enabled, + "Enabling extension storage without override pref shouldn't work" + ); + + Services.prefs.setBoolPref(PREF, true); + Assert.ok( + extensionStorageEngine.enabled, + "Setting override pref should enable extension storage" + ); + + extensionStorageEngine.enabled = false; + Assert.ok( + !extensionStorageEngine.enabled, + "Disabling extension storage engine with override pref should work" + ); + + extensionStorageEngine.enabled = true; + Assert.ok( + extensionStorageEngine.enabled, + "Enabling extension storage with override pref should work" + ); + } finally { + addonsEngine.enabled = true; + Services.prefs.clearUserPref(PREF); + } +}); + +add_task(async function test_notifyPendingChanges() { + let engine = new ExtensionStorageEngineBridge(Service); + + let extension = { id: "ext-1" }; + let expectedChange = { + a: "b", + c: "d", + }; + + let lastSync = 0; + let syncID = Utils.makeGUID(); + let error = null; + engine.component = { + QueryInterface: ChromeUtils.generateQI([ + "mozIBridgedSyncEngine", + "mozIExtensionStorageArea", + "mozISyncedExtensionStorageArea", + ]), + ensureCurrentSyncId(id, callback) { + if (syncID != id) { + syncID = id; + lastSync = 0; + } + callback.handleSuccess(id); + }, + resetSyncId(callback) { + callback.handleSuccess(syncID); + }, + syncStarted(callback) { + callback.handleSuccess(); + }, + getLastSync(callback) { + callback.handleSuccess(lastSync); + }, + setLastSync(lastSyncMillis, callback) { + lastSync = lastSyncMillis; + callback.handleSuccess(); + }, + apply(callback) { + callback.handleSuccess([]); + }, + fetchPendingSyncChanges(callback) { + if (error) { + callback.handleError(Cr.NS_ERROR_FAILURE, error.message); + } else { + callback.onChanged(extension.id, JSON.stringify(expectedChange)); + callback.handleSuccess(); + } + }, + setUploaded(modified, ids, callback) { + callback.handleSuccess(); + }, + syncFinished(callback) { + callback.handleSuccess(); + }, + takeMigrationInfo(callback) { + callback.handleSuccess(null); + }, + }; + + engine._bridge = new BridgeWrapperXPCOM(engine.component); + + let server = await serverForFoo(engine); + + let actualChanges = []; + let listener = changes => actualChanges.push(changes); + extensionStorageSync.addOnChangedListener(extension, listener); + + try { + await SyncTestingInfrastructure(server); + + info("Sync engine; notify about changes"); + await sync_engine_and_validate_telem(engine, false); + deepEqual( + actualChanges, + [expectedChange], + "Should notify about changes during sync" + ); + + error = new Error("oops!"); + actualChanges = []; + await sync_engine_and_validate_telem(engine, false); + deepEqual( + actualChanges, + [], + "Should finish syncing even if notifying about changes fails" + ); + } finally { + extensionStorageSync.removeOnChangedListener(extension, listener); + await promiseStopServer(server); + await engine.finalize(); + } +}); + +// It's difficult to know what to test - there's already tests for the bridged +// engine etc - so we just try and check that this engine conforms to the +// mozIBridgedSyncEngine interface guarantees. +add_task(async function test_engine() { + // Forcibly set the bridged engine in the engine manager. the reason we do + // this, unlike the other tests where we just create the engine, is so that + // telemetry can get at the engine's `overrideTelemetryName`, which it gets + // through the engine manager. + await Service.engineManager.unregister("extension-storage"); + await Service.engineManager.register(ExtensionStorageEngineBridge); + let engine = Service.engineManager.get("extension-storage"); + Assert.equal(engine.version, 1); + + Assert.deepEqual(await engine.getSyncID(), null); + await engine.resetLocalSyncID(); + Assert.notEqual(await engine.getSyncID(), null); + + Assert.equal(await engine.getLastSync(), 0); + // lastSync is seconds on this side of the world, but milli-seconds on the other. + await engine.setLastSync(1234.567); + // should have 2 digit precision. + Assert.equal(await engine.getLastSync(), 1234.57); + await engine.setLastSync(0); + + // Set some data. + await extensionStorageSync.set({ id: "ext-2" }, { ext_2_key: "ext_2_value" }); + // Now do a sync with out regular test server. + let server = await serverForFoo(engine); + try { + await SyncTestingInfrastructure(server); + + info("Add server records"); + let foo = server.user("foo"); + let collection = foo.collection("extension-storage"); + let now = new_timestamp(); + + collection.insert( + "fakeguid0000", + encryptPayload({ + id: "fakeguid0000", + extId: "ext-1", + data: JSON.stringify({ foo: "bar" }), + }), + now + ); + + info("Sync the engine"); + + let ping = await sync_engine_and_validate_telem(engine, false); + Assert.ok(ping.engines.find(e => e.name == "rust-webext-storage")); + Assert.equal( + ping.engines.find(e => e.name == "extension-storage"), + null + ); + + // We should have applied the data from the existing collection record. + Assert.deepEqual(await extensionStorageSync.get({ id: "ext-1" }, null), { + foo: "bar", + }); + + // should now be 2 records on the server. + let payloads = collection.payloads(); + Assert.equal(payloads.length, 2); + // find the new one we wrote. + let newPayload = + payloads[0].id == "fakeguid0000" ? payloads[1] : payloads[0]; + Assert.equal(newPayload.data, `{"ext_2_key":"ext_2_value"}`); + // should have updated the timestamp. + greater(await engine.getLastSync(), 0, "Should update last sync time"); + } finally { + await promiseStopServer(server); + await engine.finalize(); + } +}); diff --git a/services/sync/tests/unit/test_extension_storage_engine_kinto.js b/services/sync/tests/unit/test_extension_storage_engine_kinto.js new file mode 100644 index 0000000000..b074fe376c --- /dev/null +++ b/services/sync/tests/unit/test_extension_storage_engine_kinto.js @@ -0,0 +1,136 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("webextensions.storage.sync.kinto", true); + +const { ExtensionStorageEngineKinto: ExtensionStorageEngine } = + ChromeUtils.importESModule( + "resource://services-sync/engines/extension-storage.sys.mjs" + ); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { extensionStorageSyncKinto: extensionStorageSync } = + ChromeUtils.importESModule( + "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs" + ); + +let engine; + +function mock(options) { + let calls = []; + let ret = function () { + calls.push(arguments); + return options.returns; + }; + let proto = { + get calls() { + return calls; + }, + }; + Object.setPrototypeOf(proto, Function.prototype); + Object.setPrototypeOf(ret, proto); + return ret; +} + +function setSkipChance(v) { + Services.prefs.setIntPref( + "services.sync.extension-storage.skipPercentageChance", + v + ); +} + +add_task(async function setup() { + await Service.engineManager.register(ExtensionStorageEngine); + engine = Service.engineManager.get("extension-storage"); + do_get_profile(); // so we can use FxAccounts + loadWebExtensionTestFunctions(); + setSkipChance(0); +}); + +add_task(async function test_calling_sync_calls__sync() { + let oldSync = ExtensionStorageEngine.prototype._sync; + let syncMock = (ExtensionStorageEngine.prototype._sync = mock({ + returns: true, + })); + try { + // I wanted to call the main sync entry point for the entire + // package, but that fails because it tries to sync ClientEngine + // first, which fails. + await engine.sync(); + } finally { + ExtensionStorageEngine.prototype._sync = oldSync; + } + equal(syncMock.calls.length, 1); +}); + +add_task(async function test_sync_skip() { + try { + // Do a few times to ensure we aren't getting "lucky" WRT Math.random() + for (let i = 0; i < 10; ++i) { + setSkipChance(100); + engine._tracker._score = 0; + ok( + !engine.shouldSkipSync("user"), + "Should allow explicitly requested syncs" + ); + ok(!engine.shouldSkipSync("startup"), "Should allow startup syncs"); + ok( + engine.shouldSkipSync("schedule"), + "Should skip scheduled syncs if skipProbability is 100" + ); + engine._tracker._score = MULTI_DEVICE_THRESHOLD; + ok( + !engine.shouldSkipSync("schedule"), + "should allow scheduled syncs if tracker score is high" + ); + engine._tracker._score = 0; + setSkipChance(0); + ok( + !engine.shouldSkipSync("schedule"), + "Should allow scheduled syncs if probability is 0" + ); + } + } finally { + engine._tracker._score = 0; + setSkipChance(0); + } +}); + +add_task(async function test_calling_wipeClient_calls_clearAll() { + let oldClearAll = extensionStorageSync.clearAll; + let clearMock = (extensionStorageSync.clearAll = mock({ + returns: Promise.resolve(), + })); + try { + await engine.wipeClient(); + } finally { + extensionStorageSync.clearAll = oldClearAll; + } + equal(clearMock.calls.length, 1); +}); + +add_task(async function test_calling_sync_calls_ext_storage_sync() { + const extension = { id: "my-extension" }; + let oldSync = extensionStorageSync.syncAll; + let syncMock = (extensionStorageSync.syncAll = mock({ + returns: Promise.resolve(), + })); + try { + await withSyncContext(async function (context) { + // Set something so that everyone knows that we're using storage.sync + await extensionStorageSync.set(extension, { a: "b" }, context); + let ping = await sync_engine_and_validate_telem(engine, false); + Assert.ok(ping.engines.find(e => e.name == "extension-storage")); + Assert.equal( + ping.engines.find(e => e.name == "rust-webext-storage"), + null + ); + }); + } finally { + extensionStorageSync.syncAll = oldSync; + } + Assert.ok(syncMock.calls.length >= 1); +}); diff --git a/services/sync/tests/unit/test_extension_storage_migration_telem.js b/services/sync/tests/unit/test_extension_storage_migration_telem.js new file mode 100644 index 0000000000..a4b4c95f55 --- /dev/null +++ b/services/sync/tests/unit/test_extension_storage_migration_telem.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Import the rust-based and kinto-based implementations. Not great to grab +// these as they're somewhat private, but we want to run the pings through our +// validation machinery which is here in the sync test code. +const { extensionStorageSync: rustImpl } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionStorageSync.sys.mjs" +); +const { extensionStorageSyncKinto: kintoImpl } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs" +); + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { ExtensionStorageEngineBridge } = ChromeUtils.importESModule( + "resource://services-sync/engines/extension-storage.sys.mjs" +); + +Services.prefs.setBoolPref("webextensions.storage.sync.kinto", false); +Services.prefs.setStringPref("webextensions.storage.sync.log.level", "debug"); + +// It's tricky to force error cases here (the databases are opened with +// exclusive locks) and that part of the code has coverage in the vendored +// application-services webext-storage crate. So this just tests that the +// migration data ends up in the ping, and exactly once. +add_task(async function test_sync_migration_telem() { + // Set some stuff using the kinto-based impl prior to fully setting up sync. + let e1 = { id: "test@mozilla.com" }; + let c1 = { extension: e1, callOnClose() {} }; + + let e2 = { id: "test-2@mozilla.com" }; + let c2 = { extension: e2, callOnClose() {} }; + await kintoImpl.set(e1, { foo: "bar" }, c1); + await kintoImpl.set(e1, { baz: "quux" }, c1); + await kintoImpl.set(e2, { second: "2nd" }, c2); + + Assert.deepEqual(await rustImpl.get(e1, "foo", c1), { foo: "bar" }); + Assert.deepEqual(await rustImpl.get(e1, "baz", c1), { baz: "quux" }); + Assert.deepEqual(await rustImpl.get(e2, null, c2), { second: "2nd" }); + + // Explicitly unregister first. It's very possible this isn't needed for this + // case, however it's fairly harmless, we hope to uplift this patch to beta, + // and earlier today we had beta-only problems caused by this (bug 1629116) + await Service.engineManager.unregister("extension-storage"); + await Service.engineManager.register(ExtensionStorageEngineBridge); + let engine = Service.engineManager.get("extension-storage"); + let server = await serverForFoo(engine, undefined); + try { + await SyncTestingInfrastructure(server); + await Service.engineManager.switchAlternatives(); + + _("First sync"); + let ping = await sync_engine_and_validate_telem(engine, false, null, true); + Assert.deepEqual(ping.migrations, [ + { + type: "webext-storage", + entries: 3, + entriesSuccessful: 3, + extensions: 2, + extensionsSuccessful: 2, + openFailure: false, + }, + ]); + + // force another sync + await engine.setLastSync(0); + _("Second sync"); + + ping = await sync_engine_and_validate_telem(engine, false, null, true); + Assert.deepEqual(ping.migrations, undefined); + } finally { + await kintoImpl.clear(e1, c1); + await kintoImpl.clear(e2, c2); + await rustImpl.clear(e1, c1); + await rustImpl.clear(e2, c2); + await promiseStopServer(server); + await engine.finalize(); + } +}); diff --git a/services/sync/tests/unit/test_extension_storage_tracker_kinto.js b/services/sync/tests/unit/test_extension_storage_tracker_kinto.js new file mode 100644 index 0000000000..2de56ae400 --- /dev/null +++ b/services/sync/tests/unit/test_extension_storage_tracker_kinto.js @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("webextensions.storage.sync.kinto", true); + +const { ExtensionStorageEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/extension-storage.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { extensionStorageSyncKinto: extensionStorageSync } = + ChromeUtils.importESModule( + "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs" + ); + +let engine; + +add_task(async function setup() { + await Service.engineManager.register(ExtensionStorageEngine); + engine = Service.engineManager.get("extension-storage"); + do_get_profile(); // so we can use FxAccounts + loadWebExtensionTestFunctions(); +}); + +add_task(async function test_changing_extension_storage_changes_score() { + const tracker = engine._tracker; + const extension = { id: "my-extension-id" }; + tracker.start(); + await withSyncContext(async function (context) { + await extensionStorageSync.set(extension, { a: "b" }, context); + }); + Assert.equal(tracker.score, SCORE_INCREMENT_MEDIUM); + + tracker.resetScore(); + await withSyncContext(async function (context) { + await extensionStorageSync.remove(extension, "a", context); + }); + Assert.equal(tracker.score, SCORE_INCREMENT_MEDIUM); + + await tracker.stop(); +}); diff --git a/services/sync/tests/unit/test_form_validator.js b/services/sync/tests/unit/test_form_validator.js new file mode 100644 index 0000000000..58ea8b855b --- /dev/null +++ b/services/sync/tests/unit/test_form_validator.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { FormValidator } = ChromeUtils.importESModule( + "resource://services-sync/engines/forms.sys.mjs" +); + +function getDummyServerAndClient() { + return { + server: [ + { + id: "11111", + guid: "11111", + name: "foo", + fieldname: "foo", + value: "bar", + }, + { + id: "22222", + guid: "22222", + name: "foo2", + fieldname: "foo2", + value: "bar2", + }, + { + id: "33333", + guid: "33333", + name: "foo3", + fieldname: "foo3", + value: "bar3", + }, + ], + client: [ + { + id: "11111", + guid: "11111", + name: "foo", + fieldname: "foo", + value: "bar", + }, + { + id: "22222", + guid: "22222", + name: "foo2", + fieldname: "foo2", + value: "bar2", + }, + { + id: "33333", + guid: "33333", + name: "foo3", + fieldname: "foo3", + value: "bar3", + }, + ], + }; +} + +add_task(async function test_valid() { + let { server, client } = getDummyServerAndClient(); + let validator = new FormValidator(); + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + equal(clientRecords.length, 3); + equal(records.length, 3); + equal(deletedRecords.length, 0); + deepEqual(problemData, validator.emptyProblemData()); +}); + +add_task(async function test_formValidatorIgnoresMissingClients() { + // Since history form records are not deleted from the server, the + // |FormValidator| shouldn't set the |missingClient| flag in |problemData|. + let { server, client } = getDummyServerAndClient(); + client.pop(); + + let validator = new FormValidator(); + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + + equal(clientRecords.length, 2); + equal(records.length, 3); + equal(deletedRecords.length, 0); + + let expected = validator.emptyProblemData(); + deepEqual(problemData, expected); +}); diff --git a/services/sync/tests/unit/test_forms_store.js b/services/sync/tests/unit/test_forms_store.js new file mode 100644 index 0000000000..716487865f --- /dev/null +++ b/services/sync/tests/unit/test_forms_store.js @@ -0,0 +1,176 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_( + "Make sure the form store follows the Store api and correctly accesses the backend form storage" +); +const { FormEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/forms.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" +); + +add_task(async function run_test() { + let engine = new FormEngine(Service); + await engine.initialize(); + let store = engine._store; + + async function applyEnsureNoFailures(records) { + let countTelemetry = new SyncedRecordsTelemetry(); + Assert.equal( + (await store.applyIncomingBatch(records, countTelemetry)).length, + 0 + ); + } + + _("Remove any existing entries"); + await store.wipe(); + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Add a form entry"); + await applyEnsureNoFailures([ + { + id: Utils.makeGUID(), + name: "name!!", + value: "value??", + }, + ]); + + _("Should have 1 entry now"); + let id = ""; + for (let _id in await store.getAllIDs()) { + if (id == "") { + id = _id; + } else { + do_throw("Should have only gotten one!"); + } + } + Assert.ok(store.itemExists(id)); + + _("Should be able to find this entry as a dupe"); + Assert.equal( + await engine._findDupe({ name: "name!!", value: "value??" }), + id + ); + + let rec = await store.createRecord(id); + _("Got record for id", id, rec); + Assert.equal(rec.name, "name!!"); + Assert.equal(rec.value, "value??"); + + _("Create a non-existent id for delete"); + Assert.ok((await store.createRecord("deleted!!")).deleted); + + _("Try updating.. doesn't do anything yet"); + await store.update({}); + + _("Remove all entries"); + await store.wipe(); + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Add another entry"); + await applyEnsureNoFailures([ + { + id: Utils.makeGUID(), + name: "another", + value: "entry", + }, + ]); + id = ""; + for (let _id in await store.getAllIDs()) { + if (id == "") { + id = _id; + } else { + do_throw("Should have only gotten one!"); + } + } + + _("Change the id of the new entry to something else"); + await store.changeItemID(id, "newid"); + + _("Make sure it's there"); + Assert.ok(store.itemExists("newid")); + + _("Remove the entry"); + await store.remove({ + id: "newid", + }); + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Removing the entry again shouldn't matter"); + await store.remove({ + id: "newid", + }); + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Add another entry to delete using applyIncomingBatch"); + let toDelete = { + id: Utils.makeGUID(), + name: "todelete", + value: "entry", + }; + await applyEnsureNoFailures([toDelete]); + id = ""; + for (let _id in await store.getAllIDs()) { + if (id == "") { + id = _id; + } else { + do_throw("Should have only gotten one!"); + } + } + Assert.ok(store.itemExists(id)); + // mark entry as deleted + toDelete.id = id; + toDelete.deleted = true; + await applyEnsureNoFailures([toDelete]); + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Add an entry to wipe"); + await applyEnsureNoFailures([ + { + id: Utils.makeGUID(), + name: "towipe", + value: "entry", + }, + ]); + + await store.wipe(); + + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + + _("Ensure we work if formfill is disabled."); + Services.prefs.setBoolPref("browser.formfill.enable", false); + try { + // a search + if ((await store.getAllIDs()).length) { + do_throw("Shouldn't get any ids!"); + } + // an update. + await applyEnsureNoFailures([ + { + id: Utils.makeGUID(), + name: "some", + value: "entry", + }, + ]); + } finally { + Services.prefs.clearUserPref("browser.formfill.enable"); + await store.wipe(); + } +}); diff --git a/services/sync/tests/unit/test_forms_tracker.js b/services/sync/tests/unit/test_forms_tracker.js new file mode 100644 index 0000000000..aee74381ad --- /dev/null +++ b/services/sync/tests/unit/test_forms_tracker.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { FormEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/forms.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function run_test() { + _("Verify we've got an empty tracker to work with."); + let engine = new FormEngine(Service); + await engine.initialize(); + let tracker = engine._tracker; + + let changes = await tracker.getChangedIDs(); + do_check_empty(changes); + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + async function addEntry(name, value) { + await engine._store.create({ name, value }); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + async function removeEntry(name, value) { + let guid = await engine._findDupe({ name, value }); + await engine._store.remove({ id: guid }); + await engine._tracker.asyncObserver.promiseObserversComplete(); + } + + try { + _("Create an entry. Won't show because we haven't started tracking yet"); + await addEntry("name", "John Doe"); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + _("Tell the tracker to start tracking changes."); + tracker.start(); + await removeEntry("name", "John Doe"); + await addEntry("email", "john@doe.com"); + changes = await tracker.getChangedIDs(); + do_check_attribute_count(changes, 2); + + _("Notifying twice won't do any harm."); + tracker.start(); + await addEntry("address", "Memory Lane"); + changes = await tracker.getChangedIDs(); + do_check_attribute_count(changes, 3); + + _("Check that ignoreAll is respected"); + await tracker.clearChangedIDs(); + tracker.score = 0; + tracker.ignoreAll = true; + await addEntry("username", "johndoe123"); + await addEntry("favoritecolor", "green"); + await removeEntry("name", "John Doe"); + tracker.ignoreAll = false; + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + equal(tracker.score, 0); + + _("Let's stop tracking again."); + await tracker.clearChangedIDs(); + await tracker.stop(); + await removeEntry("address", "Memory Lane"); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + + _("Notifying twice won't do any harm."); + await tracker.stop(); + await removeEntry("email", "john@doe.com"); + changes = await tracker.getChangedIDs(); + do_check_empty(changes); + } finally { + _("Clean up."); + await engine._store.wipe(); + } +}); diff --git a/services/sync/tests/unit/test_fxa_node_reassignment.js b/services/sync/tests/unit/test_fxa_node_reassignment.js new file mode 100644 index 0000000000..0b25df0183 --- /dev/null +++ b/services/sync/tests/unit/test_fxa_node_reassignment.js @@ -0,0 +1,399 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_("Test that node reassignment happens correctly using the FxA identity mgr."); +// The node-reassignment logic is quite different for FxA than for the legacy +// provider. In particular, there's no special request necessary for +// reassignment - it comes from the token server - so we need to ensure the +// Fxa cluster manager grabs a new token. + +const { RESTRequest } = ChromeUtils.importESModule( + "resource://services-common/rest.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); +const { SyncAuthManager } = ChromeUtils.importESModule( + "resource://services-sync/sync_auth.sys.mjs" +); + +add_task(async function setup() { + // Disables all built-in engines. Important for avoiding errors thrown by the + // add-ons engine. + await Service.engineManager.clear(); + + // Setup the sync auth manager. + Status.__authManager = Service.identity = new SyncAuthManager(); +}); + +// API-compatible with SyncServer handler. Bind `handler` to something to use +// as a ServerCollection handler. +function handleReassign(handler, req, resp) { + resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); + resp.setHeader("Content-Type", "application/json"); + let reassignBody = JSON.stringify({ error: "401inator in place" }); + resp.bodyOutputStream.write(reassignBody, reassignBody.length); +} + +var numTokenRequests = 0; + +function prepareServer(cbAfterTokenFetch) { + syncTestLogging(); + let config = makeIdentityConfig({ username: "johndoe" }); + // A server callback to ensure we don't accidentally hit the wrong endpoint + // after a node reassignment. + let callback = { + onRequest(req, resp) { + let full = `${req.scheme}://${req.host}:${req.port}${req.path}`; + let expected = config.fxaccount.token.endpoint; + Assert.ok( + full.startsWith(expected), + `request made to ${full}, expected ${expected}` + ); + }, + }; + Object.setPrototypeOf(callback, SyncServerCallback); + let server = new SyncServer(callback); + server.registerUser("johndoe"); + server.start(); + + // Set the token endpoint for the initial token request that's done implicitly + // via configureIdentity. + config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe/"; + // And future token fetches will do magic around numReassigns. + let numReassigns = 0; + return configureIdentity(config).then(() => { + Service.identity._tokenServerClient = { + getTokenUsingOAuth() { + return new Promise(res => { + // Build a new URL with trailing zeros for the SYNC_VERSION part - this + // will still be seen as equivalent by the test server, but different + // by sync itself. + numReassigns += 1; + let trailingZeros = new Array(numReassigns + 1).join("0"); + let token = config.fxaccount.token; + token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe"; + token.uid = config.username; + _(`test server saw token fetch - endpoint now ${token.endpoint}`); + numTokenRequests += 1; + res(token); + if (cbAfterTokenFetch) { + cbAfterTokenFetch(); + } + }); + }, + }; + return server; + }); +} + +function getReassigned() { + try { + return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_UNEXPECTED) { + do_throw( + "Got exception retrieving lastSyncReassigned: " + Log.exceptionStr(ex) + ); + } + } + return false; +} + +/** + * Make a test request to `url`, then watch the result of two syncs + * to ensure that a node request was made. + * Runs `between` between the two. This can be used to undo deliberate failure + * setup, detach observers, etc. + */ +async function syncAndExpectNodeReassignment( + server, + firstNotification, + between, + secondNotification, + url +) { + _("Starting syncAndExpectNodeReassignment\n"); + let deferred = Promise.withResolvers(); + async function onwards() { + let numTokenRequestsBefore; + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + Assert.equal(Service.clusterURL, ""); + + // Track whether we fetched a new token. + numTokenRequestsBefore = numTokenRequests; + + // Allow for tests to clean up error conditions. + between(); + } + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Second sync nextTick."); + Assert.equal( + numTokenRequests, + numTokenRequestsBefore + 1, + "fetched a new token" + ); + Service.startOver().then(() => { + server.stop(deferred.resolve); + }); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + await Service.sync(); + } + + // Make sure that we really do get a 401 (but we can only do that if we are + // already logged in, as the login process is what sets up the URLs) + if (Service.isLoggedIn) { + _("Making request to " + url + " which should 401"); + let request = new RESTRequest(url); + await request.get(); + Assert.equal(request.response.status, 401); + CommonUtils.nextTick(onwards); + } else { + _("Skipping preliminary validation check for a 401 as we aren't logged in"); + CommonUtils.nextTick(onwards); + } + await deferred.promise; +} + +// Check that when we sync we don't request a new token by default - our +// test setup has configured the client with a valid token, and that token +// should be used to form the cluster URL. +add_task(async function test_single_token_fetch() { + enableValidationPrefs(); + + _("Test a normal sync only fetches 1 token"); + + let numTokenFetches = 0; + + function afterTokenFetch() { + numTokenFetches++; + } + + // Set the cluster URL to an "old" version - this is to ensure we don't + // use that old cached version for the first sync but prefer the value + // we got from the token (and as above, we are also checking we don't grab + // a new token). If the test actually attempts to connect to this URL + // it will crash. + Service.clusterURL = "http://example.com/"; + + let server = await prepareServer(afterTokenFetch); + + Assert.ok(!Service.isLoggedIn, "not already logged in"); + await Service.sync(); + Assert.equal(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); + Assert.equal(numTokenFetches, 0, "didn't fetch a new token"); + // A bit hacky, but given we know how prepareServer works we can deduce + // that clusterURL we expect. + let expectedClusterURL = server.baseURI + "1.1/johndoe/"; + Assert.equal(Service.clusterURL, expectedClusterURL); + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_momentary_401_engine() { + enableValidationPrefs(); + + _("Test a failure for engine URLs that's resolved by reassignment."); + let server = await prepareServer(); + let john = server.user("johndoe"); + + _("Enabling the Rotary engine."); + let { engine, syncID, tracker } = await registerRotaryEngine(); + + // We need the server to be correctly set up prior to experimenting. Do this + // through a sync. + let global = { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + rotary: { version: engine.version, syncID }, + }; + john.createCollection("meta").insert("global", global); + + _("First sync to prepare server contents."); + await Service.sync(); + + _("Setting up Rotary collection to 401."); + let rotary = john.createCollection("rotary"); + let oldHandler = rotary.collectionHandler; + rotary.collectionHandler = handleReassign.bind(this, undefined); + + // We want to verify that the clusterURL pref has been cleared after a 401 + // inside a sync. Flag the Rotary engine to need syncing. + john.collection("rotary").timestamp += 1000; + + function between() { + _("Undoing test changes."); + rotary.collectionHandler = oldHandler; + + function onLoginStart() { + // lastSyncReassigned shouldn't be cleared until a sync has succeeded. + _("Ensuring that lastSyncReassigned is still set at next sync start."); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + Assert.ok(getReassigned()); + } + + _("Adding observer that lastSyncReassigned is still set on login."); + Svc.Obs.add("weave:service:login:start", onLoginStart); + } + + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:finish", + between, + "weave:service:sync:finish", + Service.storageURL + "rotary" + ); + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); + +// This test ends up being a failing info fetch *after we're already logged in*. +add_task(async function test_momentary_401_info_collections_loggedin() { + enableValidationPrefs(); + + _( + "Test a failure for info/collections after login that's resolved by reassignment." + ); + let server = await prepareServer(); + + _("First sync to prepare server contents."); + await Service.sync(); + + _("Arrange for info/collections to return a 401."); + let oldHandler = server.toplevelHandlers.info; + server.toplevelHandlers.info = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.info = oldHandler; + } + + Assert.ok(Service.isLoggedIn, "already logged in"); + + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.infoURL + ); +}); + +// This test ends up being a failing info fetch *before we're logged in*. +// In this case we expect to recover during the login phase - so the first +// sync succeeds. +add_task(async function test_momentary_401_info_collections_loggedout() { + enableValidationPrefs(); + + _( + "Test a failure for info/collections before login that's resolved by reassignment." + ); + + let oldHandler; + let sawTokenFetch = false; + + function afterTokenFetch() { + // After a single token fetch, we undo our evil handleReassign hack, so + // the next /info request returns the collection instead of a 401 + server.toplevelHandlers.info = oldHandler; + sawTokenFetch = true; + } + + let server = await prepareServer(afterTokenFetch); + + // Return a 401 for the next /info request - it will be reset immediately + // after a new token is fetched. + oldHandler = server.toplevelHandlers.info; + server.toplevelHandlers.info = handleReassign; + + Assert.ok(!Service.isLoggedIn, "not already logged in"); + + await Service.sync(); + Assert.equal(Status.sync, SYNC_SUCCEEDED, "sync succeeded"); + // sync was successful - check we grabbed a new token. + Assert.ok(sawTokenFetch, "a new token was fetched by this test."); + // and we are done. + await Service.startOver(); + await promiseStopServer(server); +}); + +// This test ends up being a failing meta/global fetch *after we're already logged in*. +add_task(async function test_momentary_401_storage_loggedin() { + enableValidationPrefs(); + + _( + "Test a failure for any storage URL after login that's resolved by" + + "reassignment." + ); + let server = await prepareServer(); + + _("First sync to prepare server contents."); + await Service.sync(); + + _("Arrange for meta/global to return a 401."); + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + Assert.ok(Service.isLoggedIn, "already logged in"); + + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global" + ); +}); + +// This test ends up being a failing meta/global fetch *before we've logged in*. +add_task(async function test_momentary_401_storage_loggedout() { + enableValidationPrefs(); + + _( + "Test a failure for any storage URL before login, not just engine parts. " + + "Resolved by reassignment." + ); + let server = await prepareServer(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + Assert.ok(!Service.isLoggedIn, "already logged in"); + + await syncAndExpectNodeReassignment( + server, + "weave:service:login:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global" + ); +}); diff --git a/services/sync/tests/unit/test_fxa_service_cluster.js b/services/sync/tests/unit/test_fxa_service_cluster.js new file mode 100644 index 0000000000..0203d01ef5 --- /dev/null +++ b/services/sync/tests/unit/test_fxa_service_cluster.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { initializeIdentityWithTokenServerResponse } = + ChromeUtils.importESModule( + "resource://testing-common/services/sync/fxa_utils.sys.mjs" + ); + +add_task(async function test_findCluster() { + _("Test FxA _findCluster()"); + + _("_findCluster() throws on 500 errors."); + initializeIdentityWithTokenServerResponse({ + status: 500, + headers: [], + body: "", + }); + + await Assert.rejects( + Service.identity._findCluster(), + /TokenServerClientServerError/ + ); + + _("_findCluster() returns null on authentication errors."); + initializeIdentityWithTokenServerResponse({ + status: 401, + headers: { "content-type": "application/json" }, + body: "{}", + }); + + let cluster = await Service.identity._findCluster(); + Assert.strictEqual(cluster, null); + + _("_findCluster() works with correct tokenserver response."); + let endpoint = "http://example.com/something"; + initializeIdentityWithTokenServerResponse({ + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + api_endpoint: endpoint, + duration: 300, + id: "id", + key: "key", + uid: "uid", + }), + }); + + cluster = await Service.identity._findCluster(); + // The cluster manager ensures a trailing "/" + Assert.strictEqual(cluster, endpoint + "/"); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } +}); diff --git a/services/sync/tests/unit/test_history_engine.js b/services/sync/tests/unit/test_history_engine.js new file mode 100644 index 0000000000..9cca379b0b --- /dev/null +++ b/services/sync/tests/unit/test_history_engine.js @@ -0,0 +1,429 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { HistoryEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); + +// Use only for rawAddVisit. +XPCOMUtils.defineLazyServiceGetter( + this, + "asyncHistory", + "@mozilla.org/browser/history;1", + "mozIAsyncHistory" +); +async function rawAddVisit(id, uri, visitPRTime, transitionType) { + return new Promise((resolve, reject) => { + let results = []; + let handler = { + handleResult(result) { + results.push(result); + }, + handleError(resultCode, placeInfo) { + do_throw(`updatePlaces gave error ${resultCode}!`); + }, + handleCompletion(count) { + resolve({ results, count }); + }, + }; + asyncHistory.updatePlaces( + [ + { + guid: id, + uri: typeof uri == "string" ? CommonUtils.makeURI(uri) : uri, + visits: [{ visitDate: visitPRTime, transitionType }], + }, + ], + handler + ); + }); +} + +add_task(async function test_history_download_limit() { + let engine = new HistoryEngine(Service); + await engine.initialize(); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let lastSync = new_timestamp(); + + let collection = server.user("foo").collection("history"); + for (let i = 0; i < 15; i++) { + let id = "place" + i.toString(10).padStart(7, "0"); + let wbo = new ServerWBO( + id, + encryptPayload({ + id, + histUri: "http://example.com/" + i, + title: "Page " + i, + visits: [ + { + date: Date.now() * 1000, + type: PlacesUtils.history.TRANSITIONS.TYPED, + }, + { + date: Date.now() * 1000, + type: PlacesUtils.history.TRANSITIONS.LINK, + }, + ], + }), + lastSync + 1 + i + ); + wbo.sortindex = 15 - i; + collection.insertWBO(wbo); + } + + // We have 15 records on the server since the last sync, but our download + // limit is 5 records at a time. We should eventually fetch all 15. + await engine.setLastSync(lastSync); + engine.downloadBatchSize = 4; + engine.downloadLimit = 5; + + // Don't actually fetch any backlogged records, so that we can inspect + // the backlog between syncs. + engine.guidFetchBatchSize = 0; + + let ping = await sync_engine_and_validate_telem(engine, false); + deepEqual(ping.engines[0].incoming, { applied: 5 }); + + let backlogAfterFirstSync = Array.from(engine.toFetch).sort(); + deepEqual(backlogAfterFirstSync, [ + "place0000000", + "place0000001", + "place0000002", + "place0000003", + "place0000004", + "place0000005", + "place0000006", + "place0000007", + "place0000008", + "place0000009", + ]); + + // We should have fast-forwarded the last sync time. + equal(await engine.getLastSync(), lastSync + 15); + + engine.lastModified = collection.modified; + ping = await sync_engine_and_validate_telem(engine, false); + ok(!ping.engines[0].incoming); + + // After the second sync, our backlog still contains the same GUIDs: we + // weren't able to make progress on fetching them, since our + // `guidFetchBatchSize` is 0. + let backlogAfterSecondSync = Array.from(engine.toFetch).sort(); + deepEqual(backlogAfterFirstSync, backlogAfterSecondSync); + + // Now add a newer record to the server. + let newWBO = new ServerWBO( + "placeAAAAAAA", + encryptPayload({ + id: "placeAAAAAAA", + histUri: "http://example.com/a", + title: "New Page A", + visits: [ + { + date: Date.now() * 1000, + type: PlacesUtils.history.TRANSITIONS.TYPED, + }, + ], + }), + lastSync + 20 + ); + newWBO.sortindex = -1; + collection.insertWBO(newWBO); + + engine.lastModified = collection.modified; + ping = await sync_engine_and_validate_telem(engine, false); + deepEqual(ping.engines[0].incoming, { applied: 1 }); + + // Our backlog should remain the same. + let backlogAfterThirdSync = Array.from(engine.toFetch).sort(); + deepEqual(backlogAfterSecondSync, backlogAfterThirdSync); + + equal(await engine.getLastSync(), lastSync + 20); + + // Bump the fetch batch size to let the backlog make progress. We should + // make 3 requests to fetch 5 backlogged GUIDs. + engine.guidFetchBatchSize = 2; + + engine.lastModified = collection.modified; + ping = await sync_engine_and_validate_telem(engine, false); + deepEqual(ping.engines[0].incoming, { applied: 5 }); + + deepEqual(Array.from(engine.toFetch).sort(), [ + "place0000005", + "place0000006", + "place0000007", + "place0000008", + "place0000009", + ]); + + // Sync again to clear out the backlog. + engine.lastModified = collection.modified; + ping = await sync_engine_and_validate_telem(engine, false); + deepEqual(ping.engines[0].incoming, { applied: 5 }); + + deepEqual(Array.from(engine.toFetch), []); + + await engine.wipeClient(); + await engine.finalize(); +}); + +add_task(async function test_history_visit_roundtrip() { + let engine = new HistoryEngine(Service); + await engine.initialize(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + engine._tracker.start(); + + let id = "aaaaaaaaaaaa"; + let oneHourMS = 60 * 60 * 1000; + // Insert a visit with a non-round microsecond timestamp (e.g. it's not evenly + // divisible by 1000). This will typically be the case for visits that occur + // during normal navigation. + let time = (Date.now() - oneHourMS) * 1000 + 555; + // We use the low level history api since it lets us provide microseconds + let { count } = await rawAddVisit( + id, + "https://www.example.com", + time, + PlacesUtils.history.TRANSITIONS.TYPED + ); + equal(count, 1); + // Check that it was inserted and that we didn't round on the insert. + let visits = await PlacesSyncUtils.history.fetchVisitsForURL( + "https://www.example.com" + ); + equal(visits.length, 1); + equal(visits[0].date, time); + + let collection = server.user("foo").collection("history"); + + // Sync the visit up to the server. + await sync_engine_and_validate_telem(engine, false); + + collection.updateRecord( + id, + cleartext => { + // Double-check that we didn't round the visit's timestamp to the nearest + // millisecond when uploading. + equal(cleartext.visits[0].date, time); + // Add a remote visit so that we get past the deepEquals check in reconcile + // (otherwise the history engine will skip applying this record). The + // contents of this visit don't matter, beyond the fact that it needs to + // exist. + cleartext.visits.push({ + date: (Date.now() - oneHourMS / 2) * 1000, + type: PlacesUtils.history.TRANSITIONS.LINK, + }); + }, + new_timestamp() + 10 + ); + + // Force a remote sync. + await engine.setLastSync(new_timestamp() - 30); + await sync_engine_and_validate_telem(engine, false); + + // Make sure that we didn't duplicate the visit when inserting. (Prior to bug + // 1423395, we would insert a duplicate visit, where the timestamp was + // effectively `Math.round(microsecondTimestamp / 1000) * 1000`.) + visits = await PlacesSyncUtils.history.fetchVisitsForURL( + "https://www.example.com" + ); + equal(visits.length, 2); + + await engine.wipeClient(); + await engine.finalize(); +}); + +add_task(async function test_history_visit_dedupe_old() { + let engine = new HistoryEngine(Service); + await engine.initialize(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let initialVisits = Array.from({ length: 25 }, (_, index) => ({ + transition: PlacesUtils.history.TRANSITION_LINK, + date: new Date(Date.UTC(2017, 10, 1 + index)), + })); + initialVisits.push({ + transition: PlacesUtils.history.TRANSITION_LINK, + date: new Date(), + }); + await PlacesUtils.history.insert({ + url: "https://www.example.com", + visits: initialVisits, + }); + + let recentVisits = await PlacesSyncUtils.history.fetchVisitsForURL( + "https://www.example.com" + ); + equal(recentVisits.length, 20); + let { visits: allVisits, guid } = await PlacesUtils.history.fetch( + "https://www.example.com", + { + includeVisits: true, + } + ); + equal(allVisits.length, 26); + + let collection = server.user("foo").collection("history"); + + await sync_engine_and_validate_telem(engine, false); + + collection.updateRecord( + guid, + data => { + data.visits.push( + // Add a couple remote visit equivalent to some old visits we have already + { + date: Date.UTC(2017, 10, 1) * 1000, // Nov 1, 2017 + type: PlacesUtils.history.TRANSITIONS.LINK, + }, + { + date: Date.UTC(2017, 10, 2) * 1000, // Nov 2, 2017 + type: PlacesUtils.history.TRANSITIONS.LINK, + }, + // Add a couple new visits to make sure we are still applying them. + { + date: Date.UTC(2017, 11, 4) * 1000, // Dec 4, 2017 + type: PlacesUtils.history.TRANSITIONS.LINK, + }, + { + date: Date.UTC(2017, 11, 5) * 1000, // Dec 5, 2017 + type: PlacesUtils.history.TRANSITIONS.LINK, + } + ); + }, + new_timestamp() + 10 + ); + + await engine.setLastSync(new_timestamp() - 30); + await sync_engine_and_validate_telem(engine, false); + + allVisits = ( + await PlacesUtils.history.fetch("https://www.example.com", { + includeVisits: true, + }) + ).visits; + + equal(allVisits.length, 28); + ok( + allVisits.find(x => x.date.getTime() === Date.UTC(2017, 11, 4)), + "Should contain the Dec. 4th visit" + ); + ok( + allVisits.find(x => x.date.getTime() === Date.UTC(2017, 11, 5)), + "Should contain the Dec. 5th visit" + ); + + await engine.wipeClient(); + await engine.finalize(); +}); + +add_task(async function test_history_unknown_fields() { + let engine = new HistoryEngine(Service); + await engine.initialize(); + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + engine._tracker.start(); + + let id = "aaaaaaaaaaaa"; + let oneHourMS = 60 * 60 * 1000; + // Insert a visit with a non-round microsecond timestamp (e.g. it's not evenly + // divisible by 1000). This will typically be the case for visits that occur + // during normal navigation. + let time = (Date.now() - oneHourMS) * 1000 + 555; + // We use the low level history api since it lets us provide microseconds + let { count } = await rawAddVisit( + id, + "https://www.example.com", + time, + PlacesUtils.history.TRANSITIONS.TYPED + ); + equal(count, 1); + + let collection = server.user("foo").collection("history"); + + // Sync the visit up to the server. + await sync_engine_and_validate_telem(engine, false); + + collection.updateRecord( + id, + cleartext => { + equal(cleartext.visits[0].date, time); + + // Add unknown fields to an instance of a visit + cleartext.visits.push({ + date: (Date.now() - oneHourMS / 2) * 1000, + type: PlacesUtils.history.TRANSITIONS.LINK, + unknownVisitField: "an unknown field could show up in a visit!", + }); + cleartext.title = "A page title"; + // Add unknown fields to the payload for this URL + cleartext.unknownStrField = "an unknown str field"; + cleartext.unknownObjField = { newField: "a field within an object" }; + }, + new_timestamp() + 10 + ); + + // Force a remote sync. + await engine.setLastSync(new_timestamp() - 30); + await sync_engine_and_validate_telem(engine, false); + + // Add a new visit to ensure we're actually putting things back on the server + let newTime = (Date.now() - oneHourMS) * 1000 + 555; + await rawAddVisit( + id, + "https://www.example.com", + newTime, + PlacesUtils.history.TRANSITIONS.LINK + ); + + // Sync again + await engine.setLastSync(new_timestamp() - 30); + await sync_engine_and_validate_telem(engine, false); + + let placeInfo = await PlacesSyncUtils.history.fetchURLInfoForGuid(id); + + // Found the place we're looking for + Assert.equal(placeInfo.title, "A page title"); + Assert.equal(placeInfo.url, "https://www.example.com/"); + + // It correctly returns any unknownFields that might've been + // stored in the moz_places_extra table + deepEqual(JSON.parse(placeInfo.unknownFields), { + unknownStrField: "an unknown str field", + unknownObjField: { newField: "a field within an object" }, + }); + + // Getting visits via SyncUtils also will return unknownFields + // via the moz_historyvisits_extra table + let visits = await PlacesSyncUtils.history.fetchVisitsForURL( + "https://www.example.com" + ); + equal(visits.length, 3); + + // fetchVisitsForURL is a sync method that gets called during upload + // so unknown field should already be at the top-level + deepEqual( + visits[0].unknownVisitField, + "an unknown field could show up in a visit!" + ); + + // Remote history record should have the fields back at the top level + let remotePlace = collection.payloads().find(rec => rec.id === id); + deepEqual(remotePlace.unknownStrField, "an unknown str field"); + deepEqual(remotePlace.unknownObjField, { + newField: "a field within an object", + }); + + await engine.wipeClient(); + await engine.finalize(); +}); diff --git a/services/sync/tests/unit/test_history_store.js b/services/sync/tests/unit/test_history_store.js new file mode 100644 index 0000000000..07aee0dd01 --- /dev/null +++ b/services/sync/tests/unit/test_history_store.js @@ -0,0 +1,570 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { HistoryEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" +); + +const TIMESTAMP1 = (Date.now() - 103406528) * 1000; +const TIMESTAMP2 = (Date.now() - 6592903) * 1000; +const TIMESTAMP3 = (Date.now() - 123894) * 1000; + +function promiseOnVisitObserved() { + return new Promise(res => { + let listener = new PlacesWeakCallbackWrapper(events => { + PlacesObservers.removeListener(["page-visited"], listener); + res(); + }); + PlacesObservers.addListener(["page-visited"], listener); + }); +} + +function isDateApproximately(actual, expected, skewMillis = 1000) { + let lowerBound = expected - skewMillis; + let upperBound = expected + skewMillis; + return actual >= lowerBound && actual <= upperBound; +} + +let engine, store, fxuri, fxguid, tburi, tbguid; + +async function applyEnsureNoFailures(records) { + let countTelemetry = new SyncedRecordsTelemetry(); + Assert.equal( + (await store.applyIncomingBatch(records, countTelemetry)).length, + 0 + ); +} + +add_task(async function setup() { + engine = new HistoryEngine(Service); + await engine.initialize(); + store = engine._store; +}); + +add_task(async function test_store() { + _("Verify that we've got an empty store to work with."); + do_check_empty(await store.getAllIDs()); + + _("Let's create an entry in the database."); + fxuri = CommonUtils.makeURI("http://getfirefox.com/"); + + await PlacesTestUtils.addVisits({ + uri: fxuri, + title: "Get Firefox!", + visitDate: TIMESTAMP1, + }); + _("Verify that the entry exists."); + let ids = Object.keys(await store.getAllIDs()); + Assert.equal(ids.length, 1); + fxguid = ids[0]; + Assert.ok(await store.itemExists(fxguid)); + + _("If we query a non-existent record, it's marked as deleted."); + let record = await store.createRecord("non-existent"); + Assert.ok(record.deleted); + + _("Verify createRecord() returns a complete record."); + record = await store.createRecord(fxguid); + Assert.equal(record.histUri, fxuri.spec); + Assert.equal(record.title, "Get Firefox!"); + Assert.equal(record.visits.length, 1); + Assert.equal(record.visits[0].date, TIMESTAMP1); + Assert.equal(record.visits[0].type, Ci.nsINavHistoryService.TRANSITION_LINK); + + _("Let's modify the record and have the store update the database."); + let secondvisit = { + date: TIMESTAMP2, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }; + let onVisitObserved = promiseOnVisitObserved(); + let updatedRec = await store.createRecord(fxguid); + updatedRec.cleartext.title = "Hol Dir Firefox!"; + updatedRec.cleartext.visits.push(secondvisit); + await applyEnsureNoFailures([updatedRec]); + await onVisitObserved; + let queryres = await PlacesUtils.history.fetch(fxuri.spec, { + includeVisits: true, + }); + Assert.equal(queryres.title, "Hol Dir Firefox!"); + Assert.deepEqual(queryres.visits, [ + { + date: new Date(TIMESTAMP2 / 1000), + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + date: new Date(TIMESTAMP1 / 1000), + transition: Ci.nsINavHistoryService.TRANSITION_LINK, + }, + ]); + await PlacesUtils.history.clear(); +}); + +add_task(async function test_store_create() { + _("Create a brand new record through the store."); + tbguid = Utils.makeGUID(); + tburi = CommonUtils.makeURI("http://getthunderbird.com"); + let onVisitObserved = promiseOnVisitObserved(); + let record = await store.createRecord(tbguid); + record.cleartext = { + id: tbguid, + histUri: tburi.spec, + title: "The bird is the word!", + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_TYPED }, + ], + }; + await applyEnsureNoFailures([record]); + await onVisitObserved; + Assert.ok(await store.itemExists(tbguid)); + do_check_attribute_count(await store.getAllIDs(), 1); + let queryres = await PlacesUtils.history.fetch(tburi.spec, { + includeVisits: true, + }); + Assert.equal(queryres.title, "The bird is the word!"); + Assert.deepEqual(queryres.visits, [ + { + date: new Date(TIMESTAMP3 / 1000), + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + ]); + await PlacesUtils.history.clear(); +}); + +add_task(async function test_null_title() { + _( + "Make sure we handle a null title gracefully (it can happen in some cases, e.g. for resource:// URLs)" + ); + let resguid = Utils.makeGUID(); + let resuri = CommonUtils.makeURI("unknown://title"); + let record = await store.createRecord(resguid); + record.cleartext = { + id: resguid, + histUri: resuri.spec, + title: null, + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_TYPED }, + ], + }; + await applyEnsureNoFailures([record]); + do_check_attribute_count(await store.getAllIDs(), 1); + + let queryres = await PlacesUtils.history.fetch(resuri.spec, { + includeVisits: true, + }); + Assert.equal(queryres.title, ""); + Assert.deepEqual(queryres.visits, [ + { + date: new Date(TIMESTAMP3 / 1000), + transition: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + ]); + await PlacesUtils.history.clear(); +}); + +add_task(async function test_invalid_records() { + _("Make sure we handle invalid URLs in places databases gracefully."); + await PlacesUtils.withConnectionWrapper( + "test_invalid_record", + async function (db) { + await db.execute( + "INSERT INTO moz_places " + + "(url, url_hash, title, rev_host, visit_count, last_visit_date) " + + "VALUES ('invalid-uri', hash('invalid-uri'), 'Invalid URI', '.', 1, " + + TIMESTAMP3 + + ")" + ); + // Add the corresponding visit to retain database coherence. + await db.execute( + "INSERT INTO moz_historyvisits " + + "(place_id, visit_date, visit_type, session) " + + "VALUES ((SELECT id FROM moz_places WHERE url_hash = hash('invalid-uri') AND url = 'invalid-uri'), " + + TIMESTAMP3 + + ", " + + Ci.nsINavHistoryService.TRANSITION_TYPED + + ", 1)" + ); + } + ); + do_check_attribute_count(await store.getAllIDs(), 1); + + _("Make sure we report records with invalid URIs."); + let invalid_uri_guid = Utils.makeGUID(); + let countTelemetry = new SyncedRecordsTelemetry(); + let failed = await store.applyIncomingBatch( + [ + { + id: invalid_uri_guid, + histUri: ":::::::::::::::", + title: "Doesn't have a valid URI", + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_EMBED }, + ], + }, + ], + countTelemetry + ); + Assert.equal(failed.length, 1); + Assert.equal(failed[0], invalid_uri_guid); + Assert.equal( + countTelemetry.incomingCounts.failedReasons[0].name, + "<URL> is not a valid URL." + ); + Assert.equal(countTelemetry.incomingCounts.failedReasons[0].count, 1); + + _("Make sure we handle records with invalid GUIDs gracefully (ignore)."); + await applyEnsureNoFailures([ + { + id: "invalid", + histUri: "http://invalid.guid/", + title: "Doesn't have a valid GUID", + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_EMBED }, + ], + }, + ]); + + _( + "Make sure we handle records with invalid visit codes or visit dates, gracefully ignoring those visits." + ); + let no_date_visit_guid = Utils.makeGUID(); + let no_type_visit_guid = Utils.makeGUID(); + let invalid_type_visit_guid = Utils.makeGUID(); + let non_integer_visit_guid = Utils.makeGUID(); + countTelemetry = new SyncedRecordsTelemetry(); + failed = await store.applyIncomingBatch( + [ + { + id: no_date_visit_guid, + histUri: "http://no.date.visit/", + title: "Visit has no date", + visits: [{ type: Ci.nsINavHistoryService.TRANSITION_EMBED }], + }, + { + id: no_type_visit_guid, + histUri: "http://no.type.visit/", + title: "Visit has no type", + visits: [{ date: TIMESTAMP3 }], + }, + { + id: invalid_type_visit_guid, + histUri: "http://invalid.type.visit/", + title: "Visit has invalid type", + visits: [ + { + date: TIMESTAMP3, + type: Ci.nsINavHistoryService.TRANSITION_LINK - 1, + }, + ], + }, + { + id: non_integer_visit_guid, + histUri: "http://non.integer.visit/", + title: "Visit has non-integer date", + visits: [ + { date: 1234.567, type: Ci.nsINavHistoryService.TRANSITION_EMBED }, + ], + }, + ], + countTelemetry + ); + Assert.equal(failed.length, 0); + + // Make sure we can apply tombstones (both valid and invalid) + countTelemetry = new SyncedRecordsTelemetry(); + failed = await store.applyIncomingBatch( + [ + { id: no_date_visit_guid, deleted: true }, + { id: "not-a-valid-guid", deleted: true }, + ], + countTelemetry + ); + Assert.deepEqual(failed, ["not-a-valid-guid"]); + Assert.equal( + countTelemetry.incomingCounts.failedReasons[0].name, + "<URL> is not a valid URL." + ); + + _("Make sure we handle records with javascript: URLs gracefully."); + await applyEnsureNoFailures( + [ + { + id: Utils.makeGUID(), + histUri: "javascript:''", + title: "javascript:''", + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_EMBED }, + ], + }, + ], + countTelemetry + ); + + _("Make sure we handle records without any visits gracefully."); + await applyEnsureNoFailures([ + { + id: Utils.makeGUID(), + histUri: "http://getfirebug.com", + title: "Get Firebug!", + visits: [], + }, + ]); +}); + +add_task(async function test_unknowingly_invalid_records() { + _("Make sure we handle rejection of records by places gracefully."); + let oldCAU = store._canAddURI; + store._canAddURI = () => true; + try { + _("Make sure that when places rejects this record we record it as failed"); + let guid = Utils.makeGUID(); + let countTelemetry = new SyncedRecordsTelemetry(); + let invalidRecord = await store.createRecord(guid); + invalidRecord.cleartext = { + id: guid, + histUri: "javascript:''", + title: "javascript:''", + visits: [ + { + date: TIMESTAMP3, + type: Ci.nsINavHistoryService.TRANSITION_EMBED, + }, + ], + }; + let result = await store.applyIncomingBatch( + [invalidRecord], + countTelemetry + ); + deepEqual(result, [guid]); + } finally { + store._canAddURI = oldCAU; + } +}); + +add_task(async function test_clamp_visit_dates() { + let futureVisitTime = Date.now() + 5 * 60 * 1000; + let recentVisitTime = Date.now() - 5 * 60 * 1000; + + let recordA = await store.createRecord("visitAAAAAAA"); + recordA.cleartext = { + id: "visitAAAAAAA", + histUri: "http://example.com/a", + title: "A", + visits: [ + { + date: "invalidDate", + type: Ci.nsINavHistoryService.TRANSITION_LINK, + }, + ], + }; + let recordB = await store.createRecord("visitBBBBBBB"); + recordB.cleartext = { + id: "visitBBBBBBB", + histUri: "http://example.com/b", + title: "B", + visits: [ + { + date: 100, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + date: 250, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + date: recentVisitTime * 1000, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + ], + }; + let recordC = await store.createRecord("visitCCCCCCC"); + recordC.cleartext = { + id: "visitCCCCCCC", + histUri: "http://example.com/c", + title: "D", + visits: [ + { + date: futureVisitTime * 1000, + type: Ci.nsINavHistoryService.TRANSITION_BOOKMARK, + }, + ], + }; + let recordD = await store.createRecord("visitDDDDDDD"); + recordD.cleartext = { + id: "visitDDDDDDD", + histUri: "http://example.com/d", + title: "D", + visits: [ + { + date: recentVisitTime * 1000, + type: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, + }, + ], + }; + await applyEnsureNoFailures([recordA, recordB, recordC, recordD]); + + let visitsForA = await PlacesSyncUtils.history.fetchVisitsForURL( + "http://example.com/a" + ); + deepEqual(visitsForA, [], "Should ignore visits with invalid dates"); + + let visitsForB = await PlacesSyncUtils.history.fetchVisitsForURL( + "http://example.com/b" + ); + deepEqual( + visitsForB, + [ + { + date: recentVisitTime * 1000, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + { + // We should clamp visit dates older than original Mosaic release. + date: PlacesSyncUtils.bookmarks.EARLIEST_BOOKMARK_TIMESTAMP * 1000, + type: Ci.nsINavHistoryService.TRANSITION_TYPED, + }, + ], + "Should record clamped visit and valid visit for B" + ); + + let visitsForC = await PlacesSyncUtils.history.fetchVisitsForURL( + "http://example.com/c" + ); + equal(visitsForC.length, 1, "Should record clamped future visit for C"); + let visitDateForC = PlacesUtils.toDate(visitsForC[0].date); + ok( + isDateApproximately(visitDateForC, Date.now()), + "Should clamp future visit date for C to now" + ); + + let visitsForD = await PlacesSyncUtils.history.fetchVisitsForURL( + "http://example.com/d" + ); + deepEqual( + visitsForD, + [ + { + date: recentVisitTime * 1000, + type: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD, + }, + ], + "Should not clamp valid visit dates" + ); +}); + +add_task(async function test_remove() { + _("Remove an existent record and a non-existent from the store."); + await applyEnsureNoFailures([ + { id: fxguid, deleted: true }, + { id: Utils.makeGUID(), deleted: true }, + ]); + Assert.equal(false, await store.itemExists(fxguid)); + let queryres = await PlacesUtils.history.fetch(fxuri.spec, { + includeVisits: true, + }); + Assert.equal(null, queryres); + + _("Make sure wipe works."); + await store.wipe(); + do_check_empty(await store.getAllIDs()); + queryres = await PlacesUtils.history.fetch(fxuri.spec, { + includeVisits: true, + }); + Assert.equal(null, queryres); + queryres = await PlacesUtils.history.fetch(tburi.spec, { + includeVisits: true, + }); + Assert.equal(null, queryres); +}); + +add_task(async function test_chunking() { + let mvpi = store.MAX_VISITS_PER_INSERT; + store.MAX_VISITS_PER_INSERT = 3; + let checkChunks = function (input, expected) { + let chunks = Array.from(store._generateChunks(input)); + deepEqual(chunks, expected); + }; + try { + checkChunks([{ visits: ["x"] }], [[{ visits: ["x"] }]]); + + // 3 should still be one chunk. + checkChunks([{ visits: ["x", "x", "x"] }], [[{ visits: ["x", "x", "x"] }]]); + + // 4 should still be one chunk as we don't split individual records. + checkChunks( + [{ visits: ["x", "x", "x", "x"] }], + [[{ visits: ["x", "x", "x", "x"] }]] + ); + + // 4 in the first and 1 in the second should be 2 chunks. + checkChunks( + [{ visits: ["x", "x", "x", "x"] }, { visits: ["x"] }], + // expected + [[{ visits: ["x", "x", "x", "x"] }], [{ visits: ["x"] }]] + ); + + // we put multiple records into chunks + checkChunks( + [ + { visits: ["x", "x"] }, + { visits: ["x"] }, + { visits: ["x"] }, + { visits: ["x", "x"] }, + { visits: ["x", "x", "x", "x"] }, + ], + // expected + [ + [{ visits: ["x", "x"] }, { visits: ["x"] }], + [{ visits: ["x"] }, { visits: ["x", "x"] }], + [{ visits: ["x", "x", "x", "x"] }], + ] + ); + } finally { + store.MAX_VISITS_PER_INSERT = mvpi; + } +}); + +add_task(async function test_getAllIDs_filters_file_uris() { + let uri = CommonUtils.makeURI("file:///Users/eoger/tps/config.json"); + let visitAddedPromise = promiseVisit("added", uri); + await PlacesTestUtils.addVisits({ + uri, + visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_LINK, + }); + await visitAddedPromise; + + do_check_attribute_count(await store.getAllIDs(), 0); + + await PlacesUtils.history.clear(); +}); + +add_task(async function test_applyIncomingBatch_filters_file_uris() { + const guid = Utils.makeGUID(); + let uri = CommonUtils.makeURI("file:///Users/eoger/tps/config.json"); + await applyEnsureNoFailures([ + { + id: guid, + histUri: uri.spec, + title: "TPS CONFIG", + visits: [ + { date: TIMESTAMP3, type: Ci.nsINavHistoryService.TRANSITION_TYPED }, + ], + }, + ]); + Assert.equal(false, await store.itemExists(guid)); + let queryres = await PlacesUtils.history.fetch(uri.spec, { + includeVisits: true, + }); + Assert.equal(null, queryres); +}); + +add_task(async function cleanup() { + _("Clean up."); + await PlacesUtils.history.clear(); +}); diff --git a/services/sync/tests/unit/test_history_tracker.js b/services/sync/tests/unit/test_history_tracker.js new file mode 100644 index 0000000000..6f351d6984 --- /dev/null +++ b/services/sync/tests/unit/test_history_tracker.js @@ -0,0 +1,251 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { PlacesDBUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PlacesDBUtils.sys.mjs" +); +const { HistoryEngine } = ChromeUtils.importESModule( + "resource://services-sync/engines/history.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let engine; +let tracker; + +add_task(async function setup() { + await Service.engineManager.clear(); + await Service.engineManager.register(HistoryEngine); + engine = Service.engineManager.get("history"); + tracker = engine._tracker; +}); + +async function verifyTrackerEmpty() { + let changes = await engine.pullNewChanges(); + do_check_empty(changes); + equal(tracker.score, 0); +} + +async function verifyTrackedCount(expected) { + let changes = await engine.pullNewChanges(); + do_check_attribute_count(changes, expected); +} + +async function verifyTrackedItems(tracked) { + let changes = await engine.pullNewChanges(); + let trackedIDs = new Set(Object.keys(changes)); + for (let guid of tracked) { + ok(guid in changes, `${guid} should be tracked`); + ok(changes[guid] > 0, `${guid} should have a modified time`); + trackedIDs.delete(guid); + } + equal( + trackedIDs.size, + 0, + `Unhandled tracked IDs: ${JSON.stringify(Array.from(trackedIDs))}` + ); +} + +async function resetTracker() { + await tracker.clearChangedIDs(); + tracker.resetScore(); +} + +async function cleanup() { + await PlacesUtils.history.clear(); + await resetTracker(); + await tracker.stop(); +} + +add_task(async function test_empty() { + _("Verify we've got an empty, disabled tracker to work with."); + await verifyTrackerEmpty(); + Assert.ok(!tracker._isTracking); + + await cleanup(); +}); + +add_task(async function test_not_tracking() { + _("Create history item. Won't show because we haven't started tracking yet"); + await addVisit("not_tracking"); + await verifyTrackerEmpty(); + + await cleanup(); +}); + +add_task(async function test_start_tracking() { + _("Add hook for save completion."); + let savePromise = new Promise((resolve, reject) => { + let save = tracker._storage._save; + tracker._storage._save = async function () { + try { + await save.call(this); + resolve(); + } catch (ex) { + reject(ex); + } finally { + tracker._storage._save = save; + } + }; + }); + + _("Tell the tracker to start tracking changes."); + tracker.start(); + let scorePromise = promiseOneObserver("weave:engine:score:updated"); + await addVisit("start_tracking"); + await scorePromise; + + _("Score updated in test_start_tracking."); + await verifyTrackedCount(1); + Assert.equal(tracker.score, SCORE_INCREMENT_SMALL); + + await savePromise; + + _("changedIDs written to disk. Proceeding."); + await cleanup(); +}); + +add_task(async function test_start_tracking_twice() { + _("Verifying preconditions."); + tracker.start(); + await addVisit("start_tracking_twice1"); + await verifyTrackedCount(1); + Assert.equal(tracker.score, SCORE_INCREMENT_SMALL); + + _("Notifying twice won't do any harm."); + tracker.start(); + let scorePromise = promiseOneObserver("weave:engine:score:updated"); + await addVisit("start_tracking_twice2"); + await scorePromise; + + _("Score updated in test_start_tracking_twice."); + await verifyTrackedCount(2); + Assert.equal(tracker.score, 2 * SCORE_INCREMENT_SMALL); + + await cleanup(); +}); + +add_task(async function test_track_delete() { + _("Deletions are tracked."); + + // This isn't present because we weren't tracking when it was visited. + await addVisit("track_delete"); + let uri = CommonUtils.makeURI("http://getfirefox.com/track_delete"); + let guid = await engine._store.GUIDForUri(uri.spec); + await verifyTrackerEmpty(); + + tracker.start(); + let visitRemovedPromise = promiseVisit("removed", uri); + let scorePromise = promiseOneObserver("weave:engine:score:updated"); + await PlacesUtils.history.remove(uri); + await Promise.all([scorePromise, visitRemovedPromise]); + + await verifyTrackedItems([guid]); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + + await cleanup(); +}); + +add_task(async function test_dont_track_expiration() { + _("Expirations are not tracked."); + let uriToRemove = await addVisit("to_remove"); + let guidToRemove = await engine._store.GUIDForUri(uriToRemove.spec); + + await resetTracker(); + await verifyTrackerEmpty(); + + tracker.start(); + let visitRemovedPromise = promiseVisit("removed", uriToRemove); + let scorePromise = promiseOneObserver("weave:engine:score:updated"); + + // Observe expiration. + Services.obs.addObserver(function onExpiration(aSubject, aTopic, aData) { + Services.obs.removeObserver(onExpiration, aTopic); + // Remove the remaining page to update its score. + PlacesUtils.history.remove(uriToRemove); + }, PlacesUtils.TOPIC_EXPIRATION_FINISHED); + + // Force expiration of 1 entry. + Services.prefs.setIntPref("places.history.expiration.max_pages", 0); + Cc["@mozilla.org/places/expiration;1"] + .getService(Ci.nsIObserver) + .observe(null, "places-debug-start-expiration", 1); + + await Promise.all([scorePromise, visitRemovedPromise]); + await verifyTrackedItems([guidToRemove]); + + await cleanup(); +}); + +add_task(async function test_stop_tracking() { + _("Let's stop tracking again."); + await tracker.stop(); + await addVisit("stop_tracking"); + await verifyTrackerEmpty(); + + await cleanup(); +}); + +add_task(async function test_stop_tracking_twice() { + await tracker.stop(); + await addVisit("stop_tracking_twice1"); + + _("Notifying twice won't do any harm."); + await tracker.stop(); + await addVisit("stop_tracking_twice2"); + await verifyTrackerEmpty(); + + await cleanup(); +}); + +add_task(async function test_filter_file_uris() { + tracker.start(); + + let uri = CommonUtils.makeURI("file:///Users/eoger/tps/config.json"); + let visitAddedPromise = promiseVisit("added", uri); + await PlacesTestUtils.addVisits({ + uri, + visitDate: Date.now() * 1000, + transition: PlacesUtils.history.TRANSITION_LINK, + }); + await visitAddedPromise; + + await verifyTrackerEmpty(); + await tracker.stop(); + await cleanup(); +}); + +add_task(async function test_filter_hidden() { + tracker.start(); + + _("Add visit; should be hidden by the redirect"); + let hiddenURI = await addVisit("hidden"); + let hiddenGUID = await engine._store.GUIDForUri(hiddenURI.spec); + _(`Hidden visit GUID: ${hiddenGUID}`); + + _("Add redirect visit; should be tracked"); + let trackedURI = await addVisit( + "redirect", + hiddenURI.spec, + PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT + ); + let trackedGUID = await engine._store.GUIDForUri(trackedURI.spec); + _(`Tracked visit GUID: ${trackedGUID}`); + + _("Add visit for framed link; should be ignored"); + let embedURI = await addVisit( + "framed_link", + null, + PlacesUtils.history.TRANSITION_FRAMED_LINK + ); + let embedGUID = await engine._store.GUIDForUri(embedURI.spec); + _(`Framed link visit GUID: ${embedGUID}`); + + _("Run Places maintenance to mark redirect visit as hidden"); + await PlacesDBUtils.maintenanceOnIdle(); + + await verifyTrackedItems([trackedGUID]); + + await cleanup(); +}); diff --git a/services/sync/tests/unit/test_hmac_error.js b/services/sync/tests/unit/test_hmac_error.js new file mode 100644 index 0000000000..26dbc12dea --- /dev/null +++ b/services/sync/tests/unit/test_hmac_error.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +// Track HMAC error counts. +var hmacErrorCount = 0; +(function () { + let hHE = Service.handleHMACEvent; + Service.handleHMACEvent = async function () { + hmacErrorCount++; + return hHE.call(Service); + }; +})(); + +async function shared_setup() { + enableValidationPrefs(); + syncTestLogging(); + + hmacErrorCount = 0; + + let clientsEngine = Service.clientsEngine; + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + + // Make sure RotaryEngine is the only one we sync. + let { engine, syncID, tracker } = await registerRotaryEngine(); + await engine.setLastSync(123); // Needs to be non-zero so that tracker is queried. + engine._store.items = { + flying: "LNER Class A3 4472", + scotsman: "Flying Scotsman", + }; + await tracker.addChangedID("scotsman", 0); + Assert.equal(1, Service.engineManager.getEnabled().length); + + let engines = { + rotary: { version: engine.version, syncID }, + clients: { version: clientsEngine.version, syncID: clientsSyncID }, + }; + + // Common server objects. + let global = new ServerWBO("global", { engines }); + let keysWBO = new ServerWBO("keys"); + let rotaryColl = new ServerCollection({}, true); + let clientsColl = new ServerCollection({}, true); + + return [engine, rotaryColl, clientsColl, keysWBO, global, tracker]; +} + +add_task(async function hmac_error_during_404() { + _("Attempt to replicate the HMAC error setup."); + let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] = + await shared_setup(); + + // Hand out 404s for crypto/keys. + let keysHandler = keysWBO.handler(); + let key404Counter = 0; + let keys404Handler = function (request, response) { + if (key404Counter > 0) { + let body = "Not Found"; + response.setStatusLine(request.httpVersion, 404, body); + response.bodyOutputStream.write(body, body.length); + key404Counter--; + return; + } + keysHandler(request, response); + }; + + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + let handlers = { + "/1.1/foo/info/collections": collectionsHelper.handler, + "/1.1/foo/storage/meta/global": upd("meta", global.handler()), + "/1.1/foo/storage/crypto/keys": upd("crypto", keys404Handler), + "/1.1/foo/storage/clients": upd("clients", clientsColl.handler()), + "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()), + }; + + let server = sync_httpd_setup(handlers); + // Do not instantiate SyncTestingInfrastructure; we need real crypto. + await configureIdentity({ username: "foo" }, server); + await Service.login(); + + try { + _("Syncing."); + await sync_and_validate_telem(); + + _( + "Partially resetting client, as if after a restart, and forcing redownload." + ); + Service.collectionKeys.clear(); + await engine.setLastSync(0); // So that we redownload records. + key404Counter = 1; + _("---------------------------"); + await sync_and_validate_telem(); + _("---------------------------"); + + // Two rotary items, one client record... no errors. + Assert.equal(hmacErrorCount, 0); + } finally { + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + await promiseStopServer(server); + } +}); + +add_task(async function hmac_error_during_node_reassignment() { + _("Attempt to replicate an HMAC error during node reassignment."); + let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] = + await shared_setup(); + + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + // We'll provide a 401 mid-way through the sync. This function + // simulates shifting to a node which has no data. + function on401() { + _("Deleting server data..."); + global.delete(); + rotaryColl.delete(); + keysWBO.delete(); + clientsColl.delete(); + delete collectionsHelper.collections.rotary; + delete collectionsHelper.collections.crypto; + delete collectionsHelper.collections.clients; + _("Deleted server data."); + } + + let should401 = false; + function upd401(coll, handler) { + return function (request, response) { + if (should401 && request.method != "DELETE") { + on401(); + should401 = false; + let body = '"reassigned!"'; + response.setStatusLine(request.httpVersion, 401, "Node reassignment."); + response.bodyOutputStream.write(body, body.length); + return; + } + handler(request, response); + }; + } + + let handlers = { + "/1.1/foo/info/collections": collectionsHelper.handler, + "/1.1/foo/storage/meta/global": upd("meta", global.handler()), + "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/foo/storage/clients": upd401("clients", clientsColl.handler()), + "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()), + }; + + let server = sync_httpd_setup(handlers); + // Do not instantiate SyncTestingInfrastructure; we need real crypto. + await configureIdentity({ username: "foo" }, server); + + _("Syncing."); + // First hit of clients will 401. This will happen after meta/global and + // keys -- i.e., in the middle of the sync, but before RotaryEngine. + should401 = true; + + // Use observers to perform actions when our sync finishes. + // This allows us to observe the automatic next-tick sync that occurs after + // an abort. + function onSyncError() { + do_throw("Should not get a sync error!"); + } + let onSyncFinished = function () {}; + let obs = { + observe: function observe(subject, topic, data) { + switch (topic) { + case "weave:service:sync:error": + onSyncError(); + break; + case "weave:service:sync:finish": + onSyncFinished(); + break; + } + }, + }; + + Svc.Obs.add("weave:service:sync:finish", obs); + Svc.Obs.add("weave:service:sync:error", obs); + + // This kicks off the actual test. Split into a function here to allow this + // source file to broadly follow actual execution order. + async function onwards() { + _("== Invoking first sync."); + await Service.sync(); + _("We should not simultaneously have data but no keys on the server."); + let hasData = rotaryColl.wbo("flying") || rotaryColl.wbo("scotsman"); + let hasKeys = keysWBO.modified; + + _("We correctly handle 401s by aborting the sync and starting again."); + Assert.ok(!hasData == !hasKeys); + + _("Be prepared for the second (automatic) sync..."); + } + + _("Make sure that syncing again causes recovery."); + let callbacksPromise = new Promise(resolve => { + onSyncFinished = function () { + _("== First sync done."); + _("---------------------------"); + onSyncFinished = function () { + _("== Second (automatic) sync done."); + let hasData = rotaryColl.wbo("flying") || rotaryColl.wbo("scotsman"); + let hasKeys = keysWBO.modified; + Assert.ok(!hasData == !hasKeys); + + // Kick off another sync. Can't just call it, because we're inside the + // lock... + (async () => { + await Async.promiseYield(); + _("Now a fresh sync will get no HMAC errors."); + _( + "Partially resetting client, as if after a restart, and forcing redownload." + ); + Service.collectionKeys.clear(); + await engine.setLastSync(0); + hmacErrorCount = 0; + + onSyncFinished = async function () { + // Two rotary items, one client record... no errors. + Assert.equal(hmacErrorCount, 0); + + Svc.Obs.remove("weave:service:sync:finish", obs); + Svc.Obs.remove("weave:service:sync:error", obs); + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + server.stop(resolve); + }; + + Service.sync(); + })().catch(console.error); + }; + }; + }); + await onwards(); + await callbacksPromise; +}); diff --git a/services/sync/tests/unit/test_httpd_sync_server.js b/services/sync/tests/unit/test_httpd_sync_server.js new file mode 100644 index 0000000000..6ac8ff5e04 --- /dev/null +++ b/services/sync/tests/unit/test_httpd_sync_server.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_test(function test_creation() { + // Explicit callback for this one. + let server = new SyncServer(Object.create(SyncServerCallback)); + Assert.ok(!!server); // Just so we have a check. + server.start(null, function () { + _("Started on " + server.port); + server.stop(run_next_test); + }); +}); + +add_test(function test_url_parsing() { + let server = new SyncServer(); + + // Check that we can parse a WBO URI. + let parts = server.pathRE.exec("/1.1/johnsmith/storage/crypto/keys"); + let [all, version, username, first, rest] = parts; + Assert.equal(all, "/1.1/johnsmith/storage/crypto/keys"); + Assert.equal(version, "1.1"); + Assert.equal(username, "johnsmith"); + Assert.equal(first, "storage"); + Assert.equal(rest, "crypto/keys"); + Assert.equal(null, server.pathRE.exec("/nothing/else")); + + // Check that we can parse a collection URI. + parts = server.pathRE.exec("/1.1/johnsmith/storage/crypto"); + [all, version, username, first, rest] = parts; + Assert.equal(all, "/1.1/johnsmith/storage/crypto"); + Assert.equal(version, "1.1"); + Assert.equal(username, "johnsmith"); + Assert.equal(first, "storage"); + Assert.equal(rest, "crypto"); + + // We don't allow trailing slash on storage URI. + parts = server.pathRE.exec("/1.1/johnsmith/storage/"); + Assert.equal(parts, undefined); + + // storage alone is a valid request. + parts = server.pathRE.exec("/1.1/johnsmith/storage"); + [all, version, username, first, rest] = parts; + Assert.equal(all, "/1.1/johnsmith/storage"); + Assert.equal(version, "1.1"); + Assert.equal(username, "johnsmith"); + Assert.equal(first, "storage"); + Assert.equal(rest, undefined); + + parts = server.storageRE.exec("storage"); + let collection; + [all, , collection] = parts; + Assert.equal(all, "storage"); + Assert.equal(collection, undefined); + + run_next_test(); +}); + +const { RESTRequest } = ChromeUtils.importESModule( + "resource://services-common/rest.sys.mjs" +); +function localRequest(server, path) { + _("localRequest: " + path); + let url = server.baseURI.substr(0, server.baseURI.length - 1) + path; + _("url: " + url); + return new RESTRequest(url); +} + +add_task(async function test_basic_http() { + let server = new SyncServer(); + server.registerUser("john", "password"); + Assert.ok(server.userExists("john")); + server.start(); + _("Started on " + server.port); + + let req = localRequest(server, "/1.1/john/storage/crypto/keys"); + _("req is " + req); + // Shouldn't reject, beyond that we don't care. + await req.get(); + + await promiseStopServer(server); +}); + +add_task(async function test_info_collections() { + let server = new SyncServer(Object.create(SyncServerCallback)); + function responseHasCorrectHeaders(r) { + Assert.equal(r.status, 200); + Assert.equal(r.headers["content-type"], "application/json"); + Assert.ok("x-weave-timestamp" in r.headers); + } + + server.registerUser("john", "password"); + server.start(); + + let req = localRequest(server, "/1.1/john/info/collections"); + await req.get(); + responseHasCorrectHeaders(req.response); + Assert.equal(req.response.body, "{}"); + + let putReq = localRequest(server, "/1.1/john/storage/crypto/keys"); + let payload = JSON.stringify({ foo: "bar" }); + let putResp = await putReq.put(payload); + + responseHasCorrectHeaders(putResp); + + let putResponseBody = putResp.body; + _("PUT response body: " + JSON.stringify(putResponseBody)); + + // When we PUT something to crypto/keys, "crypto" appears in the response. + req = localRequest(server, "/1.1/john/info/collections"); + + await req.get(); + responseHasCorrectHeaders(req.response); + let expectedColl = server.getCollection("john", "crypto"); + Assert.ok(!!expectedColl); + let modified = expectedColl.timestamp; + Assert.ok(modified > 0); + Assert.equal(putResponseBody, modified); + Assert.equal(JSON.parse(req.response.body).crypto, modified); + + await promiseStopServer(server); +}); + +add_task(async function test_storage_request() { + let keysURL = "/1.1/john/storage/crypto/keys?foo=bar"; + let foosURL = "/1.1/john/storage/crypto/foos"; + let storageURL = "/1.1/john/storage"; + + let server = new SyncServer(); + let creation = server.timestamp(); + server.registerUser("john", "password"); + + server.createContents("john", { + crypto: { foos: { foo: "bar" } }, + }); + let coll = server.user("john").collection("crypto"); + Assert.ok(!!coll); + + _("We're tracking timestamps."); + Assert.ok(coll.timestamp >= creation); + + async function retrieveWBONotExists() { + let req = localRequest(server, keysURL); + let response = await req.get(); + _("Body is " + response.body); + _("Modified is " + response.newModified); + Assert.equal(response.status, 404); + Assert.equal(response.body, "Not found"); + } + + async function retrieveWBOExists() { + let req = localRequest(server, foosURL); + let response = await req.get(); + _("Body is " + response.body); + _("Modified is " + response.newModified); + let parsedBody = JSON.parse(response.body); + Assert.equal(parsedBody.id, "foos"); + Assert.equal(parsedBody.modified, coll.wbo("foos").modified); + Assert.equal(JSON.parse(parsedBody.payload).foo, "bar"); + } + + async function deleteWBONotExists() { + let req = localRequest(server, keysURL); + server.callback.onItemDeleted = function (username, collection, wboID) { + do_throw("onItemDeleted should not have been called."); + }; + + let response = await req.delete(); + + _("Body is " + response.body); + _("Modified is " + response.newModified); + Assert.equal(response.status, 200); + delete server.callback.onItemDeleted; + } + + async function deleteWBOExists() { + let req = localRequest(server, foosURL); + server.callback.onItemDeleted = function (username, collection, wboID) { + _("onItemDeleted called for " + collection + "/" + wboID); + delete server.callback.onItemDeleted; + Assert.equal(username, "john"); + Assert.equal(collection, "crypto"); + Assert.equal(wboID, "foos"); + }; + await req.delete(); + _("Body is " + req.response.body); + _("Modified is " + req.response.newModified); + Assert.equal(req.response.status, 200); + } + + async function deleteStorage() { + _("Testing DELETE on /storage."); + let now = server.timestamp(); + _("Timestamp: " + now); + let req = localRequest(server, storageURL); + await req.delete(); + + _("Body is " + req.response.body); + _("Modified is " + req.response.newModified); + let parsedBody = JSON.parse(req.response.body); + Assert.ok(parsedBody >= now); + do_check_empty(server.users.john.collections); + } + + async function getStorageFails() { + _("Testing that GET on /storage fails."); + let req = localRequest(server, storageURL); + await req.get(); + Assert.equal(req.response.status, 405); + Assert.equal(req.response.headers.allow, "DELETE"); + } + + async function getMissingCollectionWBO() { + _("Testing that fetching a WBO from an on-existent collection 404s."); + let req = localRequest(server, storageURL + "/foobar/baz"); + await req.get(); + Assert.equal(req.response.status, 404); + } + + server.start(null); + + await retrieveWBONotExists(); + await retrieveWBOExists(); + await deleteWBOExists(); + await deleteWBONotExists(); + await getStorageFails(); + await getMissingCollectionWBO(); + await deleteStorage(); + + await promiseStopServer(server); +}); + +add_task(async function test_x_weave_records() { + let server = new SyncServer(); + server.registerUser("john", "password"); + + server.createContents("john", { + crypto: { foos: { foo: "bar" }, bars: { foo: "baz" } }, + }); + server.start(); + + let wbo = localRequest(server, "/1.1/john/storage/crypto/foos"); + await wbo.get(); + Assert.equal(false, "x-weave-records" in wbo.response.headers); + let col = localRequest(server, "/1.1/john/storage/crypto"); + await col.get(); + // Collection fetches do. + Assert.equal(col.response.headers["x-weave-records"], "2"); + + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_interval_triggers.js b/services/sync/tests/unit/test_interval_triggers.js new file mode 100644 index 0000000000..6f2821ec45 --- /dev/null +++ b/services/sync/tests/unit/test_interval_triggers.js @@ -0,0 +1,472 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +Svc.PrefBranch.setStringPref("registerEngines", ""); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let scheduler; +let clientsEngine; + +async function sync_httpd_setup() { + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + let global = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + clients: { version: clientsEngine.version, syncID: clientsSyncID }, + }, + }); + let clientsColl = new ServerCollection({}, true); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + return httpd_setup({ + "/1.1/johndoe/storage/meta/global": upd("meta", global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), + }); +} + +async function setUp(server) { + syncTestLogging(); + await configureIdentity({ username: "johndoe" }, server); + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); +} + +add_task(async function setup() { + scheduler = Service.scheduler; + clientsEngine = Service.clientsEngine; + + // Don't remove stale clients when syncing. This is a test-only workaround + // that lets us add clients directly to the store, without losing them on + // the next sync. + clientsEngine._removeRemoteClient = async id => {}; +}); + +add_task(async function test_successful_sync_adjustSyncInterval() { + enableValidationPrefs(); + + _("Test successful sync calling adjustSyncInterval"); + let syncSuccesses = 0; + function onSyncFinish() { + _("Sync success."); + syncSuccesses++; + } + Svc.Obs.add("weave:service:sync:finish", onSyncFinish); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Confirm defaults + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.ok(!scheduler.hasIncomingItems); + + _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); + // idle == true && numClients <= 1 && hasIncomingItems == false + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncSuccesses, 1); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncSuccesses, 2); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncSuccesses, 3); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == true && numClients <= 1 && hasIncomingItems == true + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncSuccesses, 4); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + _( + "Test as long as idle && numClients > 1 our sync interval is idleInterval." + ); + // idle == true && numClients > 1 && hasIncomingItems == true + await Service.clientsEngine._store.create({ + id: "foo", + cleartext: { name: "bar", type: "mobile" }, + }); + await Service.sync(); + Assert.equal(syncSuccesses, 5); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + // idle == true && numClients > 1 && hasIncomingItems == false + scheduler.hasIncomingItems = false; + await Service.sync(); + Assert.equal(syncSuccesses, 6); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + _("Test non-idle, numClients > 1, no incoming items => activeInterval."); + // idle == false && numClients > 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncSuccesses, 7); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + _("Test non-idle, numClients > 1, incoming items => immediateInterval."); + // idle == false && numClients > 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncSuccesses, 8); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); // gets reset to false + Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); + + Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_unsuccessful_sync_adjustSyncInterval() { + enableValidationPrefs(); + + _("Test unsuccessful sync calling adjustSyncInterval"); + + let syncFailures = 0; + function onSyncError() { + _("Sync error."); + syncFailures++; + } + Svc.Obs.add("weave:service:sync:error", onSyncError); + + _("Test unsuccessful sync calls adjustSyncInterval"); + // Force sync to fail. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Confirm defaults + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.ok(!scheduler.hasIncomingItems); + + _("Test as long as numClients <= 1 our sync interval is SINGLE_USER."); + // idle == true && numClients <= 1 && hasIncomingItems == false + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncFailures, 1); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncFailures, 2); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == false && numClients <= 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncFailures, 3); + Assert.ok(!scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // idle == true && numClients <= 1 && hasIncomingItems == true + scheduler.idle = true; + await Service.sync(); + Assert.equal(syncFailures, 4); + Assert.ok(scheduler.idle); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + _( + "Test as long as idle && numClients > 1 our sync interval is idleInterval." + ); + // idle == true && numClients > 1 && hasIncomingItems == true + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + + await Service.sync(); + Assert.equal(syncFailures, 5); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + // idle == true && numClients > 1 && hasIncomingItems == false + scheduler.hasIncomingItems = false; + await Service.sync(); + Assert.equal(syncFailures, 6); + Assert.ok(scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + _("Test non-idle, numClients > 1, no incoming items => activeInterval."); + // idle == false && numClients > 1 && hasIncomingItems == false + scheduler.idle = false; + await Service.sync(); + Assert.equal(syncFailures, 7); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + _("Test non-idle, numClients > 1, incoming items => immediateInterval."); + // idle == false && numClients > 1 && hasIncomingItems == true + scheduler.hasIncomingItems = true; + await Service.sync(); + Assert.equal(syncFailures, 8); + Assert.ok(!scheduler.idle); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.hasIncomingItems); // gets reset to false + Assert.equal(scheduler.syncInterval, scheduler.immediateInterval); + + await Service.startOver(); + Svc.Obs.remove("weave:service:sync:error", onSyncError); + await promiseStopServer(server); +}); + +add_task(async function test_back_triggers_sync() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Single device: no sync triggered. + scheduler.idle = true; + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.ok(!scheduler.idle); + + // Multiple devices: sync is triggered. + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + + let promiseDone = promiseOneObserver("weave:service:sync:finish"); + + scheduler.idle = true; + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.ok(!scheduler.idle); + await promiseDone; + + Service.recordManager.clearCache(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + scheduler.setDefaults(); + await clientsEngine.resetClient(); + + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_adjust_interval_on_sync_error() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + let syncFailures = 0; + function onSyncError() { + _("Sync error."); + syncFailures++; + } + Svc.Obs.add("weave:service:sync:error", onSyncError); + + _("Test unsuccessful sync updates client mode & sync intervals"); + // Force a sync fail. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + Assert.equal(syncFailures, 0); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + await Service.sync(); + + Assert.equal(syncFailures, 1); + Assert.ok(scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + Svc.Obs.remove("weave:service:sync:error", onSyncError); + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_bug671378_scenario() { + enableValidationPrefs(); + + // Test scenario similar to bug 671378. This bug appeared when a score + // update occurred that wasn't large enough to trigger a sync so + // scheduleNextSync() was called without a time interval parameter, + // setting nextSync to a non-zero value and preventing the timer from + // being adjusted in the next call to scheduleNextSync(). + let server = await sync_httpd_setup(); + await setUp(server); + + let syncSuccesses = 0; + function onSyncFinish() { + _("Sync success."); + syncSuccesses++; + } + Svc.Obs.add("weave:service:sync:finish", onSyncFinish); + + // After first sync call, syncInterval & syncTimer are singleDeviceInterval. + await Service.sync(); + Assert.equal(syncSuccesses, 1); + Assert.equal(false, scheduler.numClients > 1); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + + let promiseDone = new Promise(resolve => { + // Wrap scheduleNextSync so we are notified when it is finished. + scheduler._scheduleNextSync = scheduler.scheduleNextSync; + scheduler.scheduleNextSync = function () { + scheduler._scheduleNextSync(); + + // Check on sync:finish scheduleNextSync sets the appropriate + // syncInterval and syncTimer values. + if (syncSuccesses == 2) { + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + scheduler.scheduleNextSync = scheduler._scheduleNextSync; + Svc.Obs.remove("weave:service:sync:finish", onSyncFinish); + Service.startOver().then(() => { + server.stop(resolve); + }); + } + }; + }); + + // Set nextSync != 0 + // syncInterval still hasn't been set by call to updateClientMode. + // Explicitly trying to invoke scheduleNextSync during a sync + // (to immitate a score update that isn't big enough to trigger a sync). + Svc.Obs.add("weave:service:sync:start", function onSyncStart() { + // Wait for other sync:start observers to be called so that + // nextSync is set to 0. + CommonUtils.nextTick(function () { + Svc.Obs.remove("weave:service:sync:start", onSyncStart); + + scheduler.scheduleNextSync(); + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + }); + }); + + await Service.clientsEngine._store.create({ + id: "foo", + cleartext: { name: "bar", type: "mobile" }, + }); + await Service.sync(); + await promiseDone; +}); + +add_task(async function test_adjust_timer_larger_syncInterval() { + _( + "Test syncInterval > current timout period && nextSync != 0, syncInterval is NOT used." + ); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + scheduler.scheduleNextSync(); + + // Ensure we have a small interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncTimer.delay, scheduler.activeInterval); + + // Make interval large again + await clientsEngine._wipeClient(); + Svc.PrefBranch.clearUserPref("clients.devices.mobile"); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + scheduler.scheduleNextSync(); + + // Ensure timer delay remains as the small interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + // SyncSchedule. + await Service.startOver(); +}); + +add_task(async function test_adjust_timer_smaller_syncInterval() { + _( + "Test current timout > syncInterval period && nextSync != 0, syncInterval is used." + ); + scheduler.scheduleNextSync(); + + // Ensure we have a large interval. + Assert.notEqual(scheduler.nextSync, 0); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + + // Make interval smaller + Svc.PrefBranch.setIntPref("clients.devices.mobile", 2); + scheduler.updateClientMode(); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + scheduler.scheduleNextSync(); + + // Ensure smaller timer delay is used. + Assert.notEqual(scheduler.nextSync, 0); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + // SyncSchedule. + await Service.startOver(); +}); diff --git a/services/sync/tests/unit/test_keys.js b/services/sync/tests/unit/test_keys.js new file mode 100644 index 0000000000..8cc5d4055c --- /dev/null +++ b/services/sync/tests/unit/test_keys.js @@ -0,0 +1,242 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { CollectionKeyManager, CryptoWrapper } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); + +var collectionKeys = new CollectionKeyManager(); + +function do_check_keypair_eq(a, b) { + Assert.equal(2, a.length); + Assert.equal(2, b.length); + Assert.equal(a[0], b[0]); + Assert.equal(a[1], b[1]); +} + +add_test(function test_set_invalid_values() { + _("Ensure that setting invalid encryption and HMAC key values is caught."); + + let bundle = new BulkKeyBundle("foo"); + + let thrown = false; + try { + bundle.encryptionKey = null; + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("Encryption key can only be set to"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + try { + bundle.encryptionKey = ["trollololol"]; + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("Encryption key can only be set to"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + try { + bundle.hmacKey = Utils.generateRandomBytesLegacy(15); + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + try { + bundle.hmacKey = null; + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("HMAC key can only be set to string"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + try { + bundle.hmacKey = ["trollolol"]; + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("HMAC key can only be set to"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + try { + bundle.hmacKey = Utils.generateRandomBytesLegacy(15); + } catch (ex) { + thrown = true; + Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0); + } finally { + Assert.ok(thrown); + thrown = false; + } + + run_next_test(); +}); + +add_task(async function test_ensureLoggedIn() { + let log = Log.repository.getLogger("Test"); + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + await configureIdentity(); + + let keyBundle = Weave.Service.identity.syncKeyBundle; + + /* + * Build a test version of storage/crypto/keys. + * Encrypt it with the sync key. + * Pass it into the CollectionKeyManager. + */ + + log.info("Building storage keys..."); + let storage_keys = new CryptoWrapper("crypto", "keys"); + let default_key64 = await Weave.Crypto.generateRandomKey(); + let default_hmac64 = await Weave.Crypto.generateRandomKey(); + let bookmarks_key64 = await Weave.Crypto.generateRandomKey(); + let bookmarks_hmac64 = await Weave.Crypto.generateRandomKey(); + + storage_keys.cleartext = { + default: [default_key64, default_hmac64], + collections: { bookmarks: [bookmarks_key64, bookmarks_hmac64] }, + }; + storage_keys.modified = Date.now() / 1000; + storage_keys.id = "keys"; + + log.info("Encrypting storage keys..."); + + // Use passphrase (sync key) itself to encrypt the key bundle. + await storage_keys.encrypt(keyBundle); + + // Sanity checking. + Assert.ok(null == storage_keys.cleartext); + Assert.ok(null != storage_keys.ciphertext); + + log.info("Updating collection keys."); + + // updateContents decrypts the object, releasing the payload for us to use. + // Returns true, because the default key has changed. + Assert.ok(await collectionKeys.updateContents(keyBundle, storage_keys)); + let payload = storage_keys.cleartext; + + _("CK: " + JSON.stringify(collectionKeys._collections)); + + // Test that the CollectionKeyManager returns a similar WBO. + let wbo = collectionKeys.asWBO("crypto", "keys"); + + _("WBO: " + JSON.stringify(wbo)); + _("WBO cleartext: " + JSON.stringify(wbo.cleartext)); + + // Check the individual contents. + Assert.equal(wbo.collection, "crypto"); + Assert.equal(wbo.id, "keys"); + Assert.equal(undefined, wbo.modified); + Assert.equal(collectionKeys.lastModified, storage_keys.modified); + Assert.ok(!!wbo.cleartext.default); + do_check_keypair_eq(payload.default, wbo.cleartext.default); + do_check_keypair_eq( + payload.collections.bookmarks, + wbo.cleartext.collections.bookmarks + ); + + Assert.ok("bookmarks" in collectionKeys._collections); + Assert.equal(false, "tabs" in collectionKeys._collections); + + _("Updating contents twice with the same data doesn't proceed."); + await storage_keys.encrypt(keyBundle); + Assert.equal( + false, + await collectionKeys.updateContents(keyBundle, storage_keys) + ); + + /* + * Test that we get the right keys out when we ask for + * a collection's tokens. + */ + let b1 = new BulkKeyBundle("bookmarks"); + b1.keyPairB64 = [bookmarks_key64, bookmarks_hmac64]; + let b2 = collectionKeys.keyForCollection("bookmarks"); + do_check_keypair_eq(b1.keyPair, b2.keyPair); + + // Check key equality. + Assert.ok(b1.equals(b2)); + Assert.ok(b2.equals(b1)); + + b1 = new BulkKeyBundle("[default]"); + b1.keyPairB64 = [default_key64, default_hmac64]; + + Assert.ok(!b1.equals(b2)); + Assert.ok(!b2.equals(b1)); + + b2 = collectionKeys.keyForCollection(null); + do_check_keypair_eq(b1.keyPair, b2.keyPair); + + /* + * Checking for update times. + */ + let info_collections = {}; + Assert.ok(collectionKeys.updateNeeded(info_collections)); + info_collections.crypto = 5000; + Assert.ok(!collectionKeys.updateNeeded(info_collections)); + info_collections.crypto = 1 + Date.now() / 1000; // Add one in case computers are fast! + Assert.ok(collectionKeys.updateNeeded(info_collections)); + + collectionKeys.lastModified = null; + Assert.ok(collectionKeys.updateNeeded({})); + + /* + * Check _compareKeyBundleCollections. + */ + async function newBundle(name) { + let r = new BulkKeyBundle(name); + await r.generateRandom(); + return r; + } + let k1 = await newBundle("k1"); + let k2 = await newBundle("k2"); + let k3 = await newBundle("k3"); + let k4 = await newBundle("k4"); + let k5 = await newBundle("k5"); + let coll1 = { foo: k1, bar: k2 }; + let coll2 = { foo: k1, bar: k2 }; + let coll3 = { foo: k1, bar: k3 }; + let coll4 = { foo: k4 }; + let coll5 = { baz: k5, bar: k2 }; + let coll6 = {}; + + let d1 = collectionKeys._compareKeyBundleCollections(coll1, coll2); // [] + let d2 = collectionKeys._compareKeyBundleCollections(coll1, coll3); // ["bar"] + let d3 = collectionKeys._compareKeyBundleCollections(coll3, coll2); // ["bar"] + let d4 = collectionKeys._compareKeyBundleCollections(coll1, coll4); // ["bar", "foo"] + let d5 = collectionKeys._compareKeyBundleCollections(coll5, coll2); // ["baz", "foo"] + let d6 = collectionKeys._compareKeyBundleCollections(coll6, coll1); // ["bar", "foo"] + let d7 = collectionKeys._compareKeyBundleCollections(coll5, coll5); // [] + let d8 = collectionKeys._compareKeyBundleCollections(coll6, coll6); // [] + + Assert.ok(d1.same); + Assert.ok(!d2.same); + Assert.ok(!d3.same); + Assert.ok(!d4.same); + Assert.ok(!d5.same); + Assert.ok(!d6.same); + Assert.ok(d7.same); + Assert.ok(d8.same); + + Assert.deepEqual(d1.changed, []); + Assert.deepEqual(d2.changed, ["bar"]); + Assert.deepEqual(d3.changed, ["bar"]); + Assert.deepEqual(d4.changed, ["bar", "foo"]); + Assert.deepEqual(d5.changed, ["baz", "foo"]); + Assert.deepEqual(d6.changed, ["bar", "foo"]); +}); diff --git a/services/sync/tests/unit/test_load_modules.js b/services/sync/tests/unit/test_load_modules.js new file mode 100644 index 0000000000..93ef883d4b --- /dev/null +++ b/services/sync/tests/unit/test_load_modules.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const modules = [ + "addonutils.sys.mjs", + "addonsreconciler.sys.mjs", + "constants.sys.mjs", + "engines/addons.sys.mjs", + "engines/clients.sys.mjs", + "engines/extension-storage.sys.mjs", + "engines/passwords.sys.mjs", + "engines/prefs.sys.mjs", + "engines.sys.mjs", + "keys.sys.mjs", + "main.sys.mjs", + "policies.sys.mjs", + "record.sys.mjs", + "resource.sys.mjs", + "service.sys.mjs", + "stages/declined.sys.mjs", + "stages/enginesync.sys.mjs", + "status.sys.mjs", + "sync_auth.sys.mjs", + "util.sys.mjs", +]; + +if (AppConstants.MOZ_APP_NAME != "thunderbird") { + modules.push( + "engines/bookmarks.sys.mjs", + "engines/forms.sys.mjs", + "engines/history.sys.mjs", + "engines/tabs.sys.mjs" + ); +} + +const testingModules = [ + "fakeservices.sys.mjs", + "rotaryengine.sys.mjs", + "utils.sys.mjs", + "fxa_utils.sys.mjs", +]; + +function run_test() { + for (let m of modules) { + let res = "resource://services-sync/" + m; + _("Attempting to load " + res); + ChromeUtils.importESModule(res); + } + + for (let m of testingModules) { + let res = "resource://testing-common/services/sync/" + m; + _("Attempting to load " + res); + ChromeUtils.importESModule(res); + } +} diff --git a/services/sync/tests/unit/test_node_reassignment.js b/services/sync/tests/unit/test_node_reassignment.js new file mode 100644 index 0000000000..e3352af318 --- /dev/null +++ b/services/sync/tests/unit/test_node_reassignment.js @@ -0,0 +1,523 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +_( + "Test that node reassignment responses are respected on all kinds of " + + "requests." +); + +const { RESTRequest } = ChromeUtils.importESModule( + "resource://services-common/rest.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function setup() { + validate_all_future_pings(); +}); + +/** + * Emulate the following Zeus config: + * $draining = data.get($prefix . $host . " draining"); + * if ($draining == "drain.") { + * log.warn($log_host_db_status . " migrating=1 (node-reassignment)" . + * $log_suffix); + * http.sendResponse("401 Node reassignment", $content_type, + * '"server request: node reassignment"', ""); + * } + */ +const reassignBody = '"server request: node reassignment"'; + +// API-compatible with SyncServer handler. Bind `handler` to something to use +// as a ServerCollection handler. +function handleReassign(handler, req, resp) { + resp.setStatusLine(req.httpVersion, 401, "Node reassignment"); + resp.setHeader("Content-Type", "application/json"); + resp.bodyOutputStream.write(reassignBody, reassignBody.length); +} + +async function prepareServer() { + let server = new SyncServer(); + server.registerUser("johndoe"); + server.start(); + syncTestLogging(); + await configureIdentity({ username: "johndoe" }, server); + return server; +} + +function getReassigned() { + try { + return Services.prefs.getBoolPref("services.sync.lastSyncReassigned"); + } catch (ex) { + if (ex.result != Cr.NS_ERROR_UNEXPECTED) { + do_throw( + "Got exception retrieving lastSyncReassigned: " + Log.exceptionStr(ex) + ); + } + } + return false; +} + +/** + * Make a test request to `url`, then watch the result of two syncs + * to ensure that a node request was made. + * Runs `between` between the two. This can be used to undo deliberate failure + * setup, detach observers, etc. + */ +async function syncAndExpectNodeReassignment( + server, + firstNotification, + between, + secondNotification, + url +) { + let deferred = Promise.withResolvers(); + + let getTokenCount = 0; + let mockTSC = { + // TokenServerClient + async getTokenUsingOAuth() { + getTokenCount++; + return { endpoint: server.baseURI + "1.1/johndoe/" }; + }, + }; + Service.identity._tokenServerClient = mockTSC; + + // Make sure that it works! + let request = new RESTRequest(url); + let response = await request.get(); + Assert.equal(response.status, 401); + + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + Assert.equal(Service.clusterURL, ""); + + // Allow for tests to clean up error conditions. + between(); + } + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Second sync nextTick."); + Assert.equal(getTokenCount, 1); + Service.startOver().then(() => { + server.stop(deferred.resolve); + }); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + await Service.sync(); + + await deferred.promise; +} + +add_task(async function test_momentary_401_engine() { + enableValidationPrefs(); + + _("Test a failure for engine URLs that's resolved by reassignment."); + let server = await prepareServer(); + let john = server.user("johndoe"); + + _("Enabling the Rotary engine."); + let { engine, syncID, tracker } = await registerRotaryEngine(); + + // We need the server to be correctly set up prior to experimenting. Do this + // through a sync. + let global = { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + rotary: { version: engine.version, syncID }, + }; + john.createCollection("meta").insert("global", global); + + _("First sync to prepare server contents."); + await Service.sync(); + + let numResets = 0; + let observeReset = (obs, topic) => { + if (topic == "rotary") { + numResets += 1; + } + }; + _("Adding observer that we saw an engine reset."); + Svc.Obs.add("weave:engine:reset-client:finish", observeReset); + + _("Setting up Rotary collection to 401."); + let rotary = john.createCollection("rotary"); + let oldHandler = rotary.collectionHandler; + rotary.collectionHandler = handleReassign.bind(this, undefined); + + // We want to verify that the clusterURL pref has been cleared after a 401 + // inside a sync. Flag the Rotary engine to need syncing. + john.collection("rotary").timestamp += 1000; + + function between() { + _("Undoing test changes."); + rotary.collectionHandler = oldHandler; + + function onLoginStart() { + // lastSyncReassigned shouldn't be cleared until a sync has succeeded. + _("Ensuring that lastSyncReassigned is still set at next sync start."); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + Assert.ok(getReassigned()); + } + + _("Adding observer that lastSyncReassigned is still set on login."); + Svc.Obs.add("weave:service:login:start", onLoginStart); + } + + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:finish", + between, + "weave:service:sync:finish", + Service.storageURL + "rotary" + ); + + Svc.Obs.remove("weave:engine:reset-client:finish", observeReset); + Assert.equal(numResets, 1); + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); + +// This test ends up being a failing fetch *after we're already logged in*. +add_task(async function test_momentary_401_info_collections() { + enableValidationPrefs(); + + _("Test a failure for info/collections that's resolved by reassignment."); + let server = await prepareServer(); + + _("First sync to prepare server contents."); + await Service.sync(); + + // Return a 401 for info requests, particularly info/collections. + let oldHandler = server.toplevelHandlers.info; + server.toplevelHandlers.info = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.info = oldHandler; + } + + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.infoURL + ); +}); + +add_task(async function test_momentary_401_storage_loggedin() { + enableValidationPrefs(); + + _( + "Test a failure for any storage URL, not just engine parts. " + + "Resolved by reassignment." + ); + let server = await prepareServer(); + + _("Performing initial sync to ensure we are logged in."); + await Service.sync(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + Assert.ok(Service.isLoggedIn, "already logged in"); + await syncAndExpectNodeReassignment( + server, + "weave:service:sync:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global" + ); +}); + +add_task(async function test_momentary_401_storage_loggedout() { + enableValidationPrefs(); + + _( + "Test a failure for any storage URL, not just engine parts. " + + "Resolved by reassignment." + ); + let server = await prepareServer(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + function undo() { + _("Undoing test changes."); + server.toplevelHandlers.storage = oldHandler; + } + + Assert.ok(!Service.isLoggedIn, "not already logged in"); + await syncAndExpectNodeReassignment( + server, + "weave:service:login:error", + undo, + "weave:service:sync:finish", + Service.storageURL + "meta/global" + ); +}); + +add_task(async function test_loop_avoidance_storage() { + enableValidationPrefs(); + + _( + "Test that a repeated failure doesn't result in a sync loop " + + "if node reassignment cannot resolve the failure." + ); + + let server = await prepareServer(); + + // Return a 401 for all storage requests. + let oldHandler = server.toplevelHandlers.storage; + server.toplevelHandlers.storage = handleReassign; + + let firstNotification = "weave:service:login:error"; + let secondNotification = "weave:service:login:error"; + let thirdNotification = "weave:service:sync:finish"; + + let deferred = Promise.withResolvers(); + + let getTokenCount = 0; + let mockTSC = { + // TokenServerClient + async getTokenUsingOAuth() { + getTokenCount++; + return { endpoint: server.baseURI + "1.1/johndoe/" }; + }, + }; + Service.identity._tokenServerClient = mockTSC; + + // Track the time. We want to make sure the duration between the first and + // second sync is small, and then that the duration between second and third + // is set to be large. + let now; + + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + Assert.equal(Service.clusterURL, ""); + + // We got a 401 mid-sync, and set the pref accordingly. + Assert.ok(Services.prefs.getBoolPref("services.sync.lastSyncReassigned")); + + // Update the timestamp. + now = Date.now(); + } + + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Svc.Obs.add(thirdNotification, onThirdSync); + + // This sync occurred within the backoff interval. + let elapsedTime = Date.now() - now; + Assert.ok(elapsedTime < MINIMUM_BACKOFF_INTERVAL); + + // This pref will be true until a sync completes successfully. + Assert.ok(getReassigned()); + + // The timer will be set for some distant time. + // We store nextSync in prefs, which offers us only limited resolution. + // Include that logic here. + let expectedNextSync = + 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000); + _("Next sync scheduled for " + Service.scheduler.nextSync); + _("Expected to be slightly greater than " + expectedNextSync); + + Assert.ok(Service.scheduler.nextSync >= expectedNextSync); + Assert.ok(!!Service.scheduler.syncTimer); + + // Undo our evil scheme. + server.toplevelHandlers.storage = oldHandler; + + // Bring the timer forward to kick off a successful sync, so we can watch + // the pref get cleared. + Service.scheduler.scheduleNextSync(0); + } + function onThirdSync() { + Svc.Obs.remove(thirdNotification, onThirdSync); + + // That'll do for now; no more syncs. + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Third sync nextTick."); + Assert.ok(!getReassigned()); + Assert.equal(getTokenCount, 2); + Service.startOver().then(() => { + server.stop(deferred.resolve); + }); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + + now = Date.now(); + await Service.sync(); + await deferred.promise; +}); + +add_task(async function test_loop_avoidance_engine() { + enableValidationPrefs(); + + _( + "Test that a repeated 401 in an engine doesn't result in a sync loop " + + "if node reassignment cannot resolve the failure." + ); + let server = await prepareServer(); + let john = server.user("johndoe"); + + _("Enabling the Rotary engine."); + let { engine, syncID, tracker } = await registerRotaryEngine(); + let deferred = Promise.withResolvers(); + + let getTokenCount = 0; + let mockTSC = { + // TokenServerClient + async getTokenUsingOAuth() { + getTokenCount++; + return { endpoint: server.baseURI + "1.1/johndoe/" }; + }, + }; + Service.identity._tokenServerClient = mockTSC; + + // We need the server to be correctly set up prior to experimenting. Do this + // through a sync. + let global = { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + rotary: { version: engine.version, syncID }, + }; + john.createCollection("meta").insert("global", global); + + _("First sync to prepare server contents."); + await Service.sync(); + + _("Setting up Rotary collection to 401."); + let rotary = john.createCollection("rotary"); + let oldHandler = rotary.collectionHandler; + rotary.collectionHandler = handleReassign.bind(this, undefined); + + // Flag the Rotary engine to need syncing. + john.collection("rotary").timestamp += 1000; + + function onLoginStart() { + // lastSyncReassigned shouldn't be cleared until a sync has succeeded. + _("Ensuring that lastSyncReassigned is still set at next sync start."); + Assert.ok(getReassigned()); + } + + function beforeSuccessfulSync() { + _("Undoing test changes."); + rotary.collectionHandler = oldHandler; + } + + let firstNotification = "weave:service:sync:finish"; + let secondNotification = "weave:service:sync:finish"; + let thirdNotification = "weave:service:sync:finish"; + + // Track the time. We want to make sure the duration between the first and + // second sync is small, and then that the duration between second and third + // is set to be large. + let now; + + function onFirstSync() { + _("First sync completed."); + Svc.Obs.remove(firstNotification, onFirstSync); + Svc.Obs.add(secondNotification, onSecondSync); + + Assert.equal(Service.clusterURL, ""); + + _("Adding observer that lastSyncReassigned is still set on login."); + Svc.Obs.add("weave:service:login:start", onLoginStart); + + // We got a 401 mid-sync, and set the pref accordingly. + Assert.ok(Services.prefs.getBoolPref("services.sync.lastSyncReassigned")); + + // Update the timestamp. + now = Date.now(); + } + + function onSecondSync() { + _("Second sync completed."); + Svc.Obs.remove(secondNotification, onSecondSync); + Svc.Obs.add(thirdNotification, onThirdSync); + + // This sync occurred within the backoff interval. + let elapsedTime = Date.now() - now; + Assert.ok(elapsedTime < MINIMUM_BACKOFF_INTERVAL); + + // This pref will be true until a sync completes successfully. + Assert.ok(getReassigned()); + + // The timer will be set for some distant time. + // We store nextSync in prefs, which offers us only limited resolution. + // Include that logic here. + let expectedNextSync = + 1000 * Math.floor((now + MINIMUM_BACKOFF_INTERVAL) / 1000); + _("Next sync scheduled for " + Service.scheduler.nextSync); + _("Expected to be slightly greater than " + expectedNextSync); + + Assert.ok(Service.scheduler.nextSync >= expectedNextSync); + Assert.ok(!!Service.scheduler.syncTimer); + + // Undo our evil scheme. + beforeSuccessfulSync(); + + // Bring the timer forward to kick off a successful sync, so we can watch + // the pref get cleared. + Service.scheduler.scheduleNextSync(0); + } + + function onThirdSync() { + Svc.Obs.remove(thirdNotification, onThirdSync); + + // That'll do for now; no more syncs. + Service.scheduler.clearSyncTriggers(); + + // Make absolutely sure that any event listeners are done with their work + // before we proceed. + waitForZeroTimer(function () { + _("Third sync nextTick."); + Assert.ok(!getReassigned()); + Assert.equal(getTokenCount, 2); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + Service.startOver().then(() => { + server.stop(deferred.resolve); + }); + }); + } + + Svc.Obs.add(firstNotification, onFirstSync); + + now = Date.now(); + await Service.sync(); + await deferred.promise; + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); diff --git a/services/sync/tests/unit/test_password_engine.js b/services/sync/tests/unit/test_password_engine.js new file mode 100644 index 0000000000..081403f63d --- /dev/null +++ b/services/sync/tests/unit/test_password_engine.js @@ -0,0 +1,1257 @@ +const { FXA_PWDMGR_HOST, FXA_PWDMGR_REALM } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +const { LoginRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/passwords.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const LoginInfo = Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" +); + +const { LoginCSVImport } = ChromeUtils.importESModule( + "resource://gre/modules/LoginCSVImport.sys.mjs" +); + +const { FileTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/FileTestUtils.sys.mjs" +); + +const PropertyBag = Components.Constructor( + "@mozilla.org/hash-property-bag;1", + Ci.nsIWritablePropertyBag +); + +async function cleanup(engine, server) { + await engine._tracker.stop(); + await engine.wipeClient(); + engine.lastModified = null; + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + if (server) { + await promiseStopServer(server); + } +} + +add_task(async function setup() { + // Disable addon sync because AddonManager won't be initialized here. + await Service.engineManager.unregister("addons"); + await Service.engineManager.unregister("extension-storage"); +}); + +add_task(async function test_ignored_fields() { + _("Only changes to syncable fields should be tracked"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + enableValidationPrefs(); + + let loginInfo = new LoginInfo( + "https://example.com", + "", + null, + "username", + "password", + "", + "" + ); + + // Setting syncCounter to -1 so that it will be incremented to 0 when added. + loginInfo.syncCounter = -1; + let login = await Services.logins.addLoginAsync(loginInfo); + login.QueryInterface(Ci.nsILoginMetaInfo); // For `guid`. + + engine._tracker.start(); + + try { + let nonSyncableProps = new PropertyBag(); + nonSyncableProps.setProperty("timeLastUsed", Date.now()); + nonSyncableProps.setProperty("timesUsed", 3); + Services.logins.modifyLogin(login, nonSyncableProps); + + let noChanges = await engine.pullNewChanges(); + deepEqual(noChanges, {}, "Should not track non-syncable fields"); + + let syncableProps = new PropertyBag(); + syncableProps.setProperty("username", "newuser"); + Services.logins.modifyLogin(login, syncableProps); + + let changes = await engine.pullNewChanges(); + deepEqual( + Object.keys(changes), + [login.guid], + "Should track syncable fields" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_ignored_sync_credentials() { + _("Sync credentials in login manager should be ignored"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + enableValidationPrefs(); + + engine._tracker.start(); + + try { + let login = await Services.logins.addLoginAsync( + new LoginInfo( + FXA_PWDMGR_HOST, + null, + FXA_PWDMGR_REALM, + "fxa-uid", + "creds", + "", + "" + ) + ); + + let noChanges = await engine.pullNewChanges(); + deepEqual(noChanges, {}, "Should not track new FxA credentials"); + + let props = new PropertyBag(); + props.setProperty("password", "newcreds"); + Services.logins.modifyLogin(login, props); + + noChanges = await engine.pullNewChanges(); + deepEqual(noChanges, {}, "Should not track changes to FxA credentials"); + + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: FXA_PWDMGR_HOST, + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 0); + equal(foundLogins[0].everSynced, false); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_password_engine() { + _("Basic password sync test"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + enableValidationPrefs(); + + _("Add new login to upload during first sync"); + let newLogin; + { + let login = new LoginInfo( + "https://example.com", + "", + null, + "username", + "password", + "", + "" + ); + await Services.logins.addLoginAsync(login); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://example.com", + }); + equal(logins.length, 1, "Should find new login in login manager"); + newLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo); + + // Insert a server record that's older, so that we prefer the local one. + let rec = new LoginRec("passwords", newLogin.guid); + rec.formSubmitURL = newLogin.formActionOrigin; + rec.httpRealm = newLogin.httpRealm; + rec.hostname = newLogin.origin; + rec.username = newLogin.username; + rec.password = "sekrit"; + let remotePasswordChangeTime = Date.now() - 1 * 60 * 60 * 24 * 1000; + rec.timeCreated = remotePasswordChangeTime; + rec.timePasswordChanged = remotePasswordChangeTime; + collection.insert( + newLogin.guid, + encryptPayload(rec.cleartext), + remotePasswordChangeTime / 1000 + ); + } + + _("Add login with older password change time to replace during first sync"); + let oldLogin; + { + let login = new LoginInfo( + "https://mozilla.com", + "", + null, + "us3r", + "0ldpa55", + "", + "" + ); + await Services.logins.addLoginAsync(login); + + let props = new PropertyBag(); + let localPasswordChangeTime = Date.now() - 1 * 60 * 60 * 24 * 1000; + props.setProperty("timePasswordChanged", localPasswordChangeTime); + Services.logins.modifyLogin(login, props); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://mozilla.com", + }); + equal(logins.length, 1, "Should find old login in login manager"); + oldLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo); + equal(oldLogin.timePasswordChanged, localPasswordChangeTime); + + let rec = new LoginRec("passwords", oldLogin.guid); + rec.hostname = oldLogin.origin; + rec.formSubmitURL = oldLogin.formActionOrigin; + rec.httpRealm = oldLogin.httpRealm; + rec.username = oldLogin.username; + // Change the password and bump the password change time to ensure we prefer + // the remote one during reconciliation. + rec.password = "n3wpa55"; + rec.usernameField = oldLogin.usernameField; + rec.passwordField = oldLogin.usernameField; + rec.timeCreated = oldLogin.timeCreated; + rec.timePasswordChanged = Date.now(); + collection.insert(oldLogin.guid, encryptPayload(rec.cleartext)); + } + + await engine._tracker.stop(); + + try { + await sync_engine_and_validate_telem(engine, false); + + let newRec = collection.cleartext(newLogin.guid); + equal( + newRec.password, + "password", + "Should update remote password for newer login" + ); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://mozilla.com", + }); + equal( + logins[0].password, + "n3wpa55", + "Should update local password for older login" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_outgoing() { + _("Test syncing outgoing records"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + let loginInfo = new LoginInfo( + "http://mozilla.com", + "http://mozilla.com", + null, + "theuser", + "thepassword", + "username", + "password" + ); + let login = await Services.logins.addLoginAsync(loginInfo); + + engine._tracker.start(); + + try { + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 1); + equal(foundLogins[0].everSynced, false); + equal(collection.count(), 0); + + let guid = foundLogins[0].QueryInterface(Ci.nsILoginMetaInfo).guid; + + let changes = await engine.getChangedIDs(); + let change = changes[guid]; + equal(Object.keys(changes).length, 1); + equal(change.counter, 1); + ok(!change.deleted); + + // This test modifies the password and then performs a sync and + // then ensures that the synced record is correct. This is done twice + // to ensure that syncing occurs correctly when the server record does not + // yet exist and when it does already exist. + for (let i = 1; i <= 2; i++) { + _("Modify the password iteration " + i); + foundLogins[0].password = "newpassword" + i; + Services.logins.modifyLogin(login, foundLogins[0]); + foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 1); + // On the first pass, the counter should be 2, one for the add and one for the modify. + // No sync has occurred yet so everSynced should be false. + // On the second pass, the counter will only be 1 for the modify. The everSynced + // property should be true as the sync happened on the last iteration. + equal(foundLogins[0].syncCounter, i == 2 ? 1 : 2); + equal(foundLogins[0].everSynced, i == 2); + + changes = await engine.getChangedIDs(); + change = changes[guid]; + equal(Object.keys(changes).length, 1); + equal(change.counter, i == 2 ? 1 : 2); + ok(!change.deleted); + + _("Perform sync after modifying the password"); + await sync_engine_and_validate_telem(engine, false); + + equal(Object.keys(await engine.getChangedIDs()), 0); + + // The remote login should have the updated password. + let newRec = collection.cleartext(guid); + equal( + newRec.password, + "newpassword" + i, + "Should update remote password for login" + ); + + foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 0); + equal(foundLogins[0].everSynced, true); + + login.password = "newpassword" + i; + } + + // Next, modify the username and sync. + _("Modify the username"); + foundLogins[0].username = "newuser"; + Services.logins.modifyLogin(login, foundLogins[0]); + foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 1); + equal(foundLogins[0].everSynced, true); + + _("Perform sync after modifying the username"); + await sync_engine_and_validate_telem(engine, false); + + // The remote login should have the updated password. + let newRec = collection.cleartext(guid); + equal( + newRec.username, + "newuser", + "Should update remote username for login" + ); + + foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 0); + equal(foundLogins[0].everSynced, true); + + // Finally, remove the login. The server record should be marked as deleted. + _("Remove the login"); + equal(collection.count(), 1); + equal(Services.logins.countLogins("", "", ""), 2); + equal((await Services.logins.getAllLogins()).length, 2); + ok(await engine._store.itemExists(guid)); + + ok((await engine._store.getAllIDs())[guid]); + + Services.logins.removeLogin(foundLogins[0]); + foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + equal(foundLogins.length, 0); + + changes = await engine.getChangedIDs(); + change = changes[guid]; + equal(Object.keys(changes).length, 1); + equal(change.counter, 1); + ok(change.deleted); + + _("Perform sync after removing the login"); + await sync_engine_and_validate_telem(engine, false); + + equal(collection.count(), 1); + let payload = collection.payloads()[0]; + ok(payload.deleted); + + equal(Object.keys(await engine.getChangedIDs()), 0); + + // All of these should not include the deleted login. Only the FxA password should exist. + equal(Services.logins.countLogins("", "", ""), 1); + equal((await Services.logins.getAllLogins()).length, 1); + ok(!(await engine._store.itemExists(guid))); + + // getAllIDs includes deleted items but skips the FxA login. + ok((await engine._store.getAllIDs())[guid]); + let deletedLogin = await engine._store._getLoginFromGUID(guid); + + equal(deletedLogin.hostname, null, "deleted login hostname"); + equal( + deletedLogin.formActionOrigin, + null, + "deleted login formActionOrigin" + ); + equal(deletedLogin.formSubmitURL, null, "deleted login formSubmitURL"); + equal(deletedLogin.httpRealm, null, "deleted login httpRealm"); + equal(deletedLogin.username, null, "deleted login username"); + equal(deletedLogin.password, null, "deleted login password"); + equal(deletedLogin.usernameField, "", "deleted login usernameField"); + equal(deletedLogin.passwordField, "", "deleted login passwordField"); + equal(deletedLogin.unknownFields, null, "deleted login unknownFields"); + equal(deletedLogin.timeCreated, 0, "deleted login timeCreated"); + equal(deletedLogin.timeLastUsed, 0, "deleted login timeLastUsed"); + equal(deletedLogin.timesUsed, 0, "deleted login timesUsed"); + + // These fields are not reset when the login is removed. + equal(deletedLogin.guid, guid, "deleted login guid"); + equal(deletedLogin.everSynced, true, "deleted login everSynced"); + equal(deletedLogin.syncCounter, 0, "deleted login syncCounter"); + ok( + deletedLogin.timePasswordChanged > 0, + "deleted login timePasswordChanged" + ); + } finally { + await engine._tracker.stop(); + + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_incoming() { + _("Test syncing incoming records"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + const checkFields = [ + "formSubmitURL", + "hostname", + "httpRealm", + "username", + "password", + "usernameField", + "passwordField", + "timeCreated", + ]; + + let guid1 = Utils.makeGUID(); + let details = { + formSubmitURL: "https://www.example.com", + hostname: "https://www.example.com", + httpRealm: null, + username: "camel", + password: "llama", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + try { + // This test creates a remote server record and then verifies that the login + // has been added locally after the sync occurs. + _("Create remote login"); + collection.insertRecord(Object.assign({}, details, { id: guid1 })); + + _("Perform sync when remote login has been added"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + equal(logins.length, 1); + + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + checkFields.forEach(field => { + equal(logins[0][field], details[field]); + }); + equal(logins[0].timePasswordChanged, details.timePasswordChanged); + equal(logins[0].syncCounter, 0); + equal(logins[0].everSynced, true); + + // Modify the password within the remote record and then sync again. + _("Perform sync when remote login's password has been modified"); + let newTime = Date.now(); + collection.updateRecord( + guid1, + cleartext => { + cleartext.password = "alpaca"; + }, + newTime / 1000 + 10 + ); + + await engine.setLastSync(newTime / 1000 - 30); + await sync_engine_and_validate_telem(engine, false); + + logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + equal(logins.length, 1); + + details.password = "alpaca"; + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + checkFields.forEach(field => { + equal(logins[0][field], details[field]); + }); + ok(logins[0].timePasswordChanged > details.timePasswordChanged); + equal(logins[0].syncCounter, 0); + equal(logins[0].everSynced, true); + + // Modify the username within the remote record and then sync again. + _("Perform sync when remote login's username has been modified"); + newTime = Date.now(); + collection.updateRecord( + guid1, + cleartext => { + cleartext.username = "guanaco"; + }, + newTime / 1000 + 10 + ); + + await engine.setLastSync(newTime / 1000 - 30); + await sync_engine_and_validate_telem(engine, false); + + logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + equal(logins.length, 1); + + details.username = "guanaco"; + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + checkFields.forEach(field => { + equal(logins[0][field], details[field]); + }); + ok(logins[0].timePasswordChanged > details.timePasswordChanged); + equal(logins[0].syncCounter, 0); + equal(logins[0].everSynced, true); + + // Mark the remote record as deleted and then sync again. + _("Perform sync when remote login has been marked for deletion"); + newTime = Date.now(); + collection.updateRecord( + guid1, + cleartext => { + cleartext.deleted = true; + }, + newTime / 1000 + 10 + ); + + await engine.setLastSync(newTime / 1000 - 30); + await sync_engine_and_validate_telem(engine, false); + + logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + equal(logins.length, 0); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_incoming_deleted() { + _("Test syncing incoming deleted records"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + let guid1 = Utils.makeGUID(); + let details2 = { + formSubmitURL: "https://www.example.org", + hostname: "https://www.example.org", + httpRealm: null, + username: "capybara", + password: "beaver", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + deleted: true, + }; + + try { + // This test creates a remote server record that has been deleted + // and then verifies that the login is not imported locally. + _("Create remote login"); + collection.insertRecord(Object.assign({}, details2, { id: guid1 })); + + _("Perform sync when remote login has been deleted"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + equal(logins.length, 0); + ok(!(await engine._store.getAllIDs())[guid1]); + ok(!(await engine._store.itemExists(guid1))); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_incoming_deleted_localchanged_remotenewer() { + _( + "Test syncing incoming deleted records where the local login has been changed but the remote record is newer" + ); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + let loginInfo = new LoginInfo( + "http://mozilla.com", + "http://mozilla.com", + null, + "kangaroo", + "kaola", + "username", + "password" + ); + let login = await Services.logins.addLoginAsync(loginInfo); + let guid = login.QueryInterface(Ci.nsILoginMetaInfo).guid; + + try { + _("Perform sync on new login"); + await sync_engine_and_validate_telem(engine, false); + + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://mozilla.com", + }); + foundLogins[0].password = "wallaby"; + Services.logins.modifyLogin(login, foundLogins[0]); + + // Use a time in the future to ensure that the remote record is newer. + collection.updateRecord( + guid, + cleartext => { + cleartext.deleted = true; + }, + Date.now() / 1000 + 1000 + ); + + _( + "Perform sync when remote login has been deleted and local login has been changed" + ); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://mozilla.com", + }); + equal(logins.length, 0); + ok(await engine._store.getAllIDs()); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_incoming_deleted_localchanged_localnewer() { + _( + "Test syncing incoming deleted records where the local login has been changed but the local record is newer" + ); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + let loginInfo = new LoginInfo( + "http://www.mozilla.com", + "http://www.mozilla.com", + null, + "lion", + "tiger", + "username", + "password" + ); + let login = await Services.logins.addLoginAsync(loginInfo); + let guid = login.QueryInterface(Ci.nsILoginMetaInfo).guid; + + try { + _("Perform sync on new login"); + await sync_engine_and_validate_telem(engine, false); + + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: "http://www.mozilla.com", + }); + foundLogins[0].password = "cheetah"; + Services.logins.modifyLogin(login, foundLogins[0]); + + // Use a time in the past to ensure that the local record is newer. + collection.updateRecord( + guid, + cleartext => { + cleartext.deleted = true; + }, + Date.now() / 1000 - 1000 + ); + + _( + "Perform sync when remote login has been deleted and local login has been changed" + ); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "http://www.mozilla.com", + }); + equal(logins.length, 1); + equal(logins[0].password, "cheetah"); + equal(logins[0].syncCounter, 0); + equal(logins[0].everSynced, true); + ok(await engine._store.getAllIDs()); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_incoming_no_formactionorigin() { + _("Test syncing incoming a record where there is no formActionOrigin"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + const checkFields = [ + "formSubmitURL", + "hostname", + "httpRealm", + "username", + "password", + "usernameField", + "passwordField", + "timeCreated", + ]; + + let guid1 = Utils.makeGUID(); + let details = { + formSubmitURL: "", + hostname: "https://www.example.com", + httpRealm: null, + username: "rabbit", + password: "squirrel", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + try { + // This test creates a remote server record and then verifies that the login + // has been added locally after the sync occurs. + _("Create remote login"); + collection.insertRecord(Object.assign({}, details, { id: guid1 })); + + _("Perform sync when remote login has been added"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + formActionOrigin: "", + }); + equal(logins.length, 1); + + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + checkFields.forEach(field => { + equal(logins[0][field], details[field]); + }); + equal(logins[0].timePasswordChanged, details.timePasswordChanged); + equal(logins[0].syncCounter, 0); + equal(logins[0].everSynced, true); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_password_dupe() { + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + let guid1 = Utils.makeGUID(); + let rec1 = new LoginRec("passwords", guid1); + let guid2 = Utils.makeGUID(); + let cleartext = { + formSubmitURL: "https://www.example.com", + hostname: "https://www.example.com", + httpRealm: null, + username: "foo", + password: "bar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Math.round(Date.now()), + timePasswordChanged: Math.round(Date.now()), + }; + rec1.cleartext = cleartext; + + _("Create remote record with same details and guid1"); + collection.insert(guid1, encryptPayload(rec1.cleartext)); + + _("Create remote record with guid2"); + collection.insert(guid2, encryptPayload(cleartext)); + + _("Create local record with same details and guid1"); + await engine._store.create(rec1); + + try { + _("Perform sync"); + await sync_engine_and_validate_telem(engine, true); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + + equal(logins.length, 1); + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid2); + equal(null, collection.payload(guid1)); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_updated_null_password_sync() { + _("Ensure updated null login username is converted to a string"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + let guid1 = Utils.makeGUID(); + let guid2 = Utils.makeGUID(); + let remoteDetails = { + formSubmitURL: "https://www.nullupdateexample.com", + hostname: "https://www.nullupdateexample.com", + httpRealm: null, + username: null, + password: "bar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + let localDetails = { + formSubmitURL: "https://www.nullupdateexample.com", + hostname: "https://www.nullupdateexample.com", + httpRealm: null, + username: "foo", + password: "foobar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + _("Create remote record with same details and guid1"); + collection.insertRecord(Object.assign({}, remoteDetails, { id: guid1 })); + + try { + _("Create local updated login with null password"); + await engine._store.update(Object.assign({}, localDetails, { id: guid2 })); + + _("Perform sync"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.nullupdateexample.com", + }); + + equal(logins.length, 1); + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_updated_undefined_password_sync() { + _("Ensure updated undefined login username is converted to a string"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + let guid1 = Utils.makeGUID(); + let guid2 = Utils.makeGUID(); + let remoteDetails = { + formSubmitURL: "https://www.undefinedupdateexample.com", + hostname: "https://www.undefinedupdateexample.com", + httpRealm: null, + username: undefined, + password: "bar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + let localDetails = { + formSubmitURL: "https://www.undefinedupdateexample.com", + hostname: "https://www.undefinedupdateexample.com", + httpRealm: null, + username: "foo", + password: "foobar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + _("Create remote record with same details and guid1"); + collection.insertRecord(Object.assign({}, remoteDetails, { id: guid1 })); + + try { + _("Create local updated login with undefined password"); + await engine._store.update(Object.assign({}, localDetails, { id: guid2 })); + + _("Perform sync"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.undefinedupdateexample.com", + }); + + equal(logins.length, 1); + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).guid, guid1); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_new_null_password_sync() { + _("Ensure new null login username is converted to a string"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let guid1 = Utils.makeGUID(); + let rec1 = new LoginRec("passwords", guid1); + rec1.cleartext = { + formSubmitURL: "https://www.example.com", + hostname: "https://www.example.com", + httpRealm: null, + username: null, + password: "bar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + try { + _("Create local login with null password"); + await engine._store.create(rec1); + + _("Perform sync"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + + equal(logins.length, 1); + notEqual(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, null); + notEqual(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, undefined); + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, ""); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_new_undefined_password_sync() { + _("Ensure new undefined login username is converted to a string"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let guid1 = Utils.makeGUID(); + let rec1 = new LoginRec("passwords", guid1); + rec1.cleartext = { + formSubmitURL: "https://www.example.com", + hostname: "https://www.example.com", + httpRealm: null, + username: undefined, + password: "bar", + usernameField: "username-field", + passwordField: "password-field", + timeCreated: Date.now(), + timePasswordChanged: Date.now(), + }; + + try { + _("Create local login with undefined password"); + await engine._store.create(rec1); + + _("Perform sync"); + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://www.example.com", + }); + + equal(logins.length, 1); + notEqual(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, null); + notEqual(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, undefined); + equal(logins[0].QueryInterface(Ci.nsILoginMetaInfo).username, ""); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_sync_password_validation() { + // This test isn't in test_password_validator to avoid duplicating cleanup. + _("Ensure that if a password validation happens, it ends up in the ping"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + Svc.PrefBranch.setIntPref("engine.passwords.validation.interval", 0); + Svc.PrefBranch.setIntPref( + "engine.passwords.validation.percentageChance", + 100 + ); + Svc.PrefBranch.setIntPref("engine.passwords.validation.maxRecords", -1); + Svc.PrefBranch.setBoolPref("engine.passwords.validation.enabled", true); + + try { + let ping = await wait_for_ping(() => Service.sync()); + + let engineInfo = ping.engines.find(e => e.name == "passwords"); + ok(engineInfo, "Engine should be in ping"); + + let validation = engineInfo.validation; + ok(validation, "Engine should have validation info"); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_roundtrip_unknown_fields() { + _( + "Testing that unknown fields from other clients get roundtripped back to server" + ); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("passwords"); + + enableValidationPrefs(); + + _("Add login with older password change time to replace during first sync"); + let oldLogin; + { + let login = new LoginInfo( + "https://mozilla.com", + "", + null, + "us3r", + "0ldpa55", + "", + "" + ); + await Services.logins.addLoginAsync(login); + + let props = new PropertyBag(); + let localPasswordChangeTime = Math.round( + Date.now() - 1 * 60 * 60 * 24 * 1000 + ); + props.setProperty("timePasswordChanged", localPasswordChangeTime); + Services.logins.modifyLogin(login, props); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://mozilla.com", + }); + equal(logins.length, 1, "Should find old login in login manager"); + oldLogin = logins[0].QueryInterface(Ci.nsILoginMetaInfo); + equal(oldLogin.timePasswordChanged, localPasswordChangeTime); + + let rec = new LoginRec("passwords", oldLogin.guid); + rec.hostname = oldLogin.origin; + rec.formSubmitURL = oldLogin.formActionOrigin; + rec.httpRealm = oldLogin.httpRealm; + rec.username = oldLogin.username; + // Change the password and bump the password change time to ensure we prefer + // the remote one during reconciliation. + rec.password = "n3wpa55"; + rec.usernameField = oldLogin.usernameField; + rec.passwordField = oldLogin.usernameField; + rec.timeCreated = oldLogin.timeCreated; + rec.timePasswordChanged = Math.round(Date.now()); + + // pretend other clients have some snazzy new fields + // we don't quite understand yet + rec.cleartext.someStrField = "I am a str"; + rec.cleartext.someObjField = { newField: "I am a new field" }; + collection.insert(oldLogin.guid, encryptPayload(rec.cleartext)); + } + + await engine._tracker.stop(); + + try { + await sync_engine_and_validate_telem(engine, false); + + let logins = await Services.logins.searchLoginsAsync({ + origin: "https://mozilla.com", + }); + equal( + logins[0].password, + "n3wpa55", + "Should update local password for older login" + ); + let expectedUnknowns = JSON.stringify({ + someStrField: "I am a str", + someObjField: { newField: "I am a new field" }, + }); + // Check that the local record has all unknown fields properly + // stringified + equal(logins[0].unknownFields, expectedUnknowns); + + // Check that the server has the unknown fields unfurled and on the + // top-level record + let serverRec = collection.cleartext(oldLogin.guid); + equal(serverRec.someStrField, "I am a str"); + equal(serverRec.someObjField.newField, "I am a new field"); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_new_passwords_from_csv() { + _("Test syncing records imported from a csv file"); + + let engine = Service.engineManager.get("passwords"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let collection = server.user("foo").collection("passwords"); + + engine._tracker.start(); + + let data = [ + { + hostname: "https://example.com", + url: "https://example.com/path", + username: "exampleuser", + password: "examplepassword", + }, + { + hostname: "https://mozilla.org", + url: "https://mozilla.org", + username: "mozillauser", + password: "mozillapassword", + }, + { + hostname: "https://www.example.org", + url: "https://www.example.org/example1/example2", + username: "person", + password: "mypassword", + }, + ]; + + let csvData = ["url,username,login_password"]; + for (let row of data) { + csvData.push(row.url + "," + row.username + "," + row.password); + } + + let csvFile = FileTestUtils.getTempFile(`firefox_logins.csv`); + await IOUtils.writeUTF8(csvFile.path, csvData.join("\r\n")); + + await LoginCSVImport.importFromCSV(csvFile.path); + + equal( + engine._tracker.score, + SCORE_INCREMENT_XLARGE, + "Should only get one update notification for import" + ); + + _("Ensure that the csv import is correct"); + for (let item of data) { + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: item.hostname, + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 1); + equal(foundLogins[0].everSynced, false); + equal(foundLogins[0].username, item.username); + equal(foundLogins[0].password, item.password); + } + + _("Perform sync after modifying the password"); + await sync_engine_and_validate_telem(engine, false); + + _("Verify that the sync counter and status are updated"); + for (let item of data) { + let foundLogins = await Services.logins.searchLoginsAsync({ + origin: item.hostname, + }); + equal(foundLogins.length, 1); + equal(foundLogins[0].syncCounter, 0); + equal(foundLogins[0].everSynced, true); + equal(foundLogins[0].username, item.username); + equal(foundLogins[0].password, item.password); + item.guid = foundLogins[0].guid; + } + + equal(Object.keys(await engine.getChangedIDs()), 0); + equal(collection.count(), 3); + + for (let item of data) { + // The remote login should have the imported username and password. + let newRec = collection.cleartext(item.guid); + equal(newRec.username, item.username); + equal(newRec.password, item.password); + } +}); diff --git a/services/sync/tests/unit/test_password_store.js b/services/sync/tests/unit/test_password_store.js new file mode 100644 index 0000000000..ed393d6241 --- /dev/null +++ b/services/sync/tests/unit/test_password_store.js @@ -0,0 +1,398 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { LoginRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/passwords.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { SyncedRecordsTelemetry } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" +); + +async function checkRecord( + name, + record, + expectedCount, + timeCreated, + expectedTimeCreated, + timePasswordChanged, + expectedTimePasswordChanged, + recordIsUpdated +) { + let engine = Service.engineManager.get("passwords"); + let store = engine._store; + + let logins = await Services.logins.searchLoginsAsync({ + origin: record.hostname, + formActionOrigin: record.formSubmitURL, + }); + + _("Record" + name + ":" + JSON.stringify(logins)); + _("Count" + name + ":" + logins.length); + + Assert.equal(logins.length, expectedCount); + + if (expectedCount > 0) { + Assert.ok(!!(await store.getAllIDs())[record.id]); + let stored_record = logins[0].QueryInterface(Ci.nsILoginMetaInfo); + + if (timeCreated !== undefined) { + Assert.equal(stored_record.timeCreated, expectedTimeCreated); + } + + if (timePasswordChanged !== undefined) { + if (recordIsUpdated) { + Assert.ok( + stored_record.timePasswordChanged >= expectedTimePasswordChanged + ); + } else { + Assert.equal( + stored_record.timePasswordChanged, + expectedTimePasswordChanged + ); + } + return stored_record.timePasswordChanged; + } + } else { + Assert.ok(!(await store.getAllIDs())[record.id]); + } + return undefined; +} + +async function changePassword( + name, + hostname, + password, + expectedCount, + timeCreated, + expectedTimeCreated, + timePasswordChanged, + expectedTimePasswordChanged, + insert, + recordIsUpdated +) { + const BOGUS_GUID = "zzzzzz" + hostname; + let record = new LoginRec("passwords", BOGUS_GUID); + record.cleartext = { + id: BOGUS_GUID, + hostname, + formSubmitURL: hostname, + username: "john", + password, + usernameField: "username", + passwordField: "password", + }; + + if (timeCreated !== undefined) { + record.timeCreated = timeCreated; + } + + if (timePasswordChanged !== undefined) { + record.timePasswordChanged = timePasswordChanged; + } + + let engine = Service.engineManager.get("passwords"); + let store = engine._store; + + if (insert) { + let countTelemetry = new SyncedRecordsTelemetry(); + Assert.equal( + (await store.applyIncomingBatch([record], countTelemetry)).length, + 0 + ); + } + + return checkRecord( + name, + record, + expectedCount, + timeCreated, + expectedTimeCreated, + timePasswordChanged, + expectedTimePasswordChanged, + recordIsUpdated + ); +} + +async function test_apply_records_with_times( + hostname, + timeCreated, + timePasswordChanged +) { + // The following record is going to be inserted in the store and it needs + // to be found there. Then its timestamps are going to be compared to + // the expected values. + await changePassword( + " ", + hostname, + "password", + 1, + timeCreated, + timeCreated, + timePasswordChanged, + timePasswordChanged, + true + ); +} + +async function test_apply_multiple_records_with_times() { + // The following records are going to be inserted in the store and they need + // to be found there. Then their timestamps are going to be compared to + // the expected values. + await changePassword( + "A", + "http://foo.a.com", + "password", + 1, + undefined, + undefined, + undefined, + undefined, + true + ); + await changePassword( + "B", + "http://foo.b.com", + "password", + 1, + 1000, + 1000, + undefined, + undefined, + true + ); + await changePassword( + "C", + "http://foo.c.com", + "password", + 1, + undefined, + undefined, + 1000, + 1000, + true + ); + await changePassword( + "D", + "http://foo.d.com", + "password", + 1, + 1000, + 1000, + 1000, + 1000, + true + ); + + // The following records are not going to be inserted in the store and they + // are not going to be found there. + await changePassword( + "NotInStoreA", + "http://foo.aaaa.com", + "password", + 0, + undefined, + undefined, + undefined, + undefined, + false + ); + await changePassword( + "NotInStoreB", + "http://foo.bbbb.com", + "password", + 0, + 1000, + 1000, + undefined, + undefined, + false + ); + await changePassword( + "NotInStoreC", + "http://foo.cccc.com", + "password", + 0, + undefined, + undefined, + 1000, + 1000, + false + ); + await changePassword( + "NotInStoreD", + "http://foo.dddd.com", + "password", + 0, + 1000, + 1000, + 1000, + 1000, + false + ); +} + +async function test_apply_same_record_with_different_times() { + // The following record is going to be inserted multiple times in the store + // and it needs to be found there. Then its timestamps are going to be + // compared to the expected values. + + /* eslint-disable no-unused-vars */ + /* The eslint linter thinks that timePasswordChanged is unused, even though + it is passed as an argument to changePassword. */ + var timePasswordChanged = 100; + timePasswordChanged = await changePassword( + "A", + "http://a.tn", + "password", + 1, + 100, + 100, + 100, + timePasswordChanged, + true + ); + timePasswordChanged = await changePassword( + "A", + "http://a.tn", + "password", + 1, + 100, + 100, + 800, + timePasswordChanged, + true, + true + ); + timePasswordChanged = await changePassword( + "A", + "http://a.tn", + "password", + 1, + 500, + 100, + 800, + timePasswordChanged, + true, + true + ); + timePasswordChanged = await changePassword( + "A", + "http://a.tn", + "password2", + 1, + 500, + 100, + 1536213005222, + timePasswordChanged, + true, + true + ); + timePasswordChanged = await changePassword( + "A", + "http://a.tn", + "password2", + 1, + 500, + 100, + 800, + timePasswordChanged, + true, + true + ); + /* eslint-enable no-unused-vars */ +} + +async function test_LoginRec_toString(store, recordData) { + let rec = await store.createRecord(recordData.id); + ok(rec); + ok(!rec.toString().includes(rec.password)); +} + +add_task(async function run_test() { + const BOGUS_GUID_A = "zzzzzzzzzzzz"; + const BOGUS_GUID_B = "yyyyyyyyyyyy"; + let recordA = new LoginRec("passwords", BOGUS_GUID_A); + let recordB = new LoginRec("passwords", BOGUS_GUID_B); + recordA.cleartext = { + id: BOGUS_GUID_A, + hostname: "http://foo.bar.com", + formSubmitURL: "http://foo.bar.com", + httpRealm: "secure", + username: "john", + password: "smith", + usernameField: "username", + passwordField: "password", + }; + recordB.cleartext = { + id: BOGUS_GUID_B, + hostname: "http://foo.baz.com", + formSubmitURL: "http://foo.baz.com", + username: "john", + password: "smith", + usernameField: "username", + passwordField: "password", + unknownStr: "an unknown string from another field", + }; + + let engine = Service.engineManager.get("passwords"); + let store = engine._store; + + try { + let countTelemetry = new SyncedRecordsTelemetry(); + Assert.equal( + (await store.applyIncomingBatch([recordA, recordB], countTelemetry)) + .length, + 0 + ); + + // Only the good record makes it to Services.logins. + let badLogins = await Services.logins.searchLoginsAsync({ + origin: recordA.hostname, + formActionOrigin: recordA.formSubmitURL, + httpRealm: recordA.httpRealm, + }); + let goodLogins = await Services.logins.searchLoginsAsync({ + origin: recordB.hostname, + formActionOrigin: recordB.formSubmitURL, + }); + + _("Bad: " + JSON.stringify(badLogins)); + _("Good: " + JSON.stringify(goodLogins)); + _("Count: " + badLogins.length + ", " + goodLogins.length); + + Assert.equal(goodLogins.length, 1); + Assert.equal(badLogins.length, 0); + + // applyIncoming should've put any unknown fields from the server + // into a catch-all unknownFields field + Assert.equal( + goodLogins[0].unknownFields, + JSON.stringify({ + unknownStr: "an unknown string from another field", + }) + ); + + Assert.ok(!!(await store.getAllIDs())[BOGUS_GUID_B]); + Assert.ok(!(await store.getAllIDs())[BOGUS_GUID_A]); + + await test_LoginRec_toString(store, recordB); + + await test_apply_records_with_times( + "http://afoo.baz.com", + undefined, + undefined + ); + await test_apply_records_with_times("http://bfoo.baz.com", 1000, undefined); + await test_apply_records_with_times("http://cfoo.baz.com", undefined, 2000); + await test_apply_records_with_times("http://dfoo.baz.com", 1000, 2000); + + await test_apply_multiple_records_with_times(); + + await test_apply_same_record_with_different_times(); + } finally { + await store.wipe(); + } +}); diff --git a/services/sync/tests/unit/test_password_tracker.js b/services/sync/tests/unit/test_password_tracker.js new file mode 100644 index 0000000000..77b46d1d2c --- /dev/null +++ b/services/sync/tests/unit/test_password_tracker.js @@ -0,0 +1,248 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { PasswordEngine, LoginRec } = ChromeUtils.importESModule( + "resource://services-sync/engines/passwords.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let engine; +let store; +let tracker; + +add_task(async function setup() { + await Service.engineManager.register(PasswordEngine); + engine = Service.engineManager.get("passwords"); + store = engine._store; + tracker = engine._tracker; +}); + +add_task(async function test_tracking() { + let recordNum = 0; + + _("Verify we've got an empty tracker to work with."); + let changes = await engine.getChangedIDs(); + do_check_empty(changes); + + let exceptionHappened = false; + try { + await tracker.getChangedIDs(); + } catch (ex) { + exceptionHappened = true; + } + ok(exceptionHappened, "tracker does not keep track of changes"); + + async function createPassword() { + _("RECORD NUM: " + recordNum); + let record = new LoginRec("passwords", "GUID" + recordNum); + record.cleartext = { + id: "GUID" + recordNum, + hostname: "http://foo.bar.com", + formSubmitURL: "http://foo.bar.com", + username: "john" + recordNum, + password: "smith", + usernameField: "username", + passwordField: "password", + }; + recordNum++; + let login = store._nsLoginInfoFromRecord(record); + await Services.logins.addLoginAsync(login); + await tracker.asyncObserver.promiseObserversComplete(); + } + + try { + tracker.start(); + await createPassword(); + changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, 1); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(changes.GUID0.counter, 1); + Assert.ok(typeof changes.GUID0.modified, "number"); + + _("Starting twice won't do any harm."); + tracker.start(); + await createPassword(); + changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, 2); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + Assert.equal(changes.GUID0.counter, 1); + Assert.equal(changes.GUID1.counter, 1); + + // The tracker doesn't keep track of changes, so 3 changes + // should still be returned, but the score is not updated. + _("Let's stop tracking again."); + tracker.resetScore(); + await tracker.stop(); + await createPassword(); + changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, 3); + Assert.equal(tracker.score, 0); + Assert.equal(changes.GUID0.counter, 1); + Assert.equal(changes.GUID1.counter, 1); + Assert.equal(changes.GUID2.counter, 1); + + _("Stopping twice won't do any harm."); + await tracker.stop(); + await createPassword(); + changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, 4); + Assert.equal(tracker.score, 0); + } finally { + _("Clean up."); + await store.wipe(); + tracker.resetScore(); + await tracker.stop(); + } +}); + +add_task(async function test_onWipe() { + _("Verify we've got an empty tracker to work with."); + const changes = await engine.getChangedIDs(); + do_check_empty(changes); + Assert.equal(tracker.score, 0); + + try { + _("A store wipe should increment the score"); + tracker.start(); + await store.wipe(); + await tracker.asyncObserver.promiseObserversComplete(); + + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + } finally { + tracker.resetScore(); + await tracker.stop(); + } +}); + +add_task(async function test_removeAllLogins() { + let recordNum = 0; + _("Verify that all tracked logins are removed."); + + // Perform this test twice, the first time where a sync is not performed + // between adding and removing items and the second time where a sync is + // performed. In the former case, the logins will just be deleted because + // they have never been synced, so they won't be detected as changes. In + // the latter case, the logins have been synced so they will be marked for + // deletion. + for (let syncBeforeRemove of [false, true]) { + async function createPassword() { + _("RECORD NUM: " + recordNum); + let record = new LoginRec("passwords", "GUID" + recordNum); + record.cleartext = { + id: "GUID" + recordNum, + hostname: "http://foo.bar.com", + formSubmitURL: "http://foo.bar.com", + username: "john" + recordNum, + password: "smith", + usernameField: "username", + passwordField: "password", + }; + recordNum++; + let login = store._nsLoginInfoFromRecord(record); + await Services.logins.addLoginAsync(login); + + await tracker.asyncObserver.promiseObserversComplete(); + } + try { + _("Tell tracker to start tracking changes"); + tracker.start(); + await createPassword(); + await createPassword(); + let changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, 2); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + + if (syncBeforeRemove) { + let logins = await Services.logins.getAllLogins(); + for (let login of logins) { + engine.markSynced(login.guid); + } + } + + _("Tell sync to remove all logins"); + Services.logins.removeAllUserFacingLogins(); + await tracker.asyncObserver.promiseObserversComplete(); + changes = await engine.getChangedIDs(); + do_check_attribute_count(changes, syncBeforeRemove ? 2 : 0); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 5); + + let deletedGuids = await engine._store.getAllIDs(); + if (syncBeforeRemove) { + for (let guid in deletedGuids) { + let deletedLogin = await engine._store._getLoginFromGUID(guid); + + Assert.equal(deletedLogin.hostname, null, "deleted login hostname"); + Assert.equal( + deletedLogin.formActionOrigin, + null, + "deleted login formActionOrigin" + ); + Assert.equal( + deletedLogin.formSubmitURL, + null, + "deleted login formSubmitURL" + ); + Assert.equal(deletedLogin.httpRealm, null, "deleted login httpRealm"); + Assert.equal(deletedLogin.username, null, "deleted login username"); + Assert.equal(deletedLogin.password, null, "deleted login password"); + Assert.equal( + deletedLogin.usernameField, + "", + "deleted login usernameField" + ); + Assert.equal( + deletedLogin.passwordField, + "", + "deleted login passwordField" + ); + Assert.equal( + deletedLogin.unknownFields, + null, + "deleted login unknownFields" + ); + Assert.equal( + deletedLogin.timeCreated, + 0, + "deleted login timeCreated" + ); + Assert.equal( + deletedLogin.timeLastUsed, + 0, + "deleted login timeLastUsed" + ); + Assert.equal(deletedLogin.timesUsed, 0, "deleted login timesUsed"); + + // These fields are not reset when the login is removed. + Assert.ok(deletedLogin.guid.startsWith("GUID"), "deleted login guid"); + Assert.equal( + deletedLogin.everSynced, + true, + "deleted login everSynced" + ); + Assert.equal( + deletedLogin.syncCounter, + 2, + "deleted login syncCounter" + ); + Assert.ok( + deletedLogin.timePasswordChanged > 0, + "deleted login timePasswordChanged" + ); + } + } else { + Assert.equal( + Object.keys(deletedGuids).length, + 0, + "no logins remain after removeAllUserFacingLogins" + ); + } + } finally { + _("Clean up."); + await store.wipe(); + tracker.resetScore(); + await tracker.stop(); + } + } +}); diff --git a/services/sync/tests/unit/test_password_validator.js b/services/sync/tests/unit/test_password_validator.js new file mode 100644 index 0000000000..445b119e1d --- /dev/null +++ b/services/sync/tests/unit/test_password_validator.js @@ -0,0 +1,176 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { PasswordValidator } = ChromeUtils.importESModule( + "resource://services-sync/engines/passwords.sys.mjs" +); + +function getDummyServerAndClient() { + return { + server: [ + { + id: "11111", + guid: "11111", + hostname: "https://www.11111.com", + formSubmitURL: "https://www.11111.com", + password: "qwerty123", + passwordField: "pass", + username: "foobar", + usernameField: "user", + httpRealm: null, + }, + { + id: "22222", + guid: "22222", + hostname: "https://www.22222.org", + formSubmitURL: "https://www.22222.org", + password: "hunter2", + passwordField: "passwd", + username: "baz12345", + usernameField: "user", + httpRealm: null, + }, + { + id: "33333", + guid: "33333", + hostname: "https://www.33333.com", + formSubmitURL: "https://www.33333.com", + password: "p4ssw0rd", + passwordField: "passwad", + username: "quux", + usernameField: "user", + httpRealm: null, + }, + ], + client: [ + { + id: "11111", + guid: "11111", + hostname: "https://www.11111.com", + formSubmitURL: "https://www.11111.com", + password: "qwerty123", + passwordField: "pass", + username: "foobar", + usernameField: "user", + httpRealm: null, + }, + { + id: "22222", + guid: "22222", + hostname: "https://www.22222.org", + formSubmitURL: "https://www.22222.org", + password: "hunter2", + passwordField: "passwd", + username: "baz12345", + usernameField: "user", + httpRealm: null, + }, + { + id: "33333", + guid: "33333", + hostname: "https://www.33333.com", + formSubmitURL: "https://www.33333.com", + password: "p4ssw0rd", + passwordField: "passwad", + username: "quux", + usernameField: "user", + httpRealm: null, + }, + ], + }; +} + +add_task(async function test_valid() { + let { server, client } = getDummyServerAndClient(); + let validator = new PasswordValidator(); + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + equal(clientRecords.length, 3); + equal(records.length, 3); + equal(deletedRecords.length, 0); + deepEqual(problemData, validator.emptyProblemData()); +}); + +add_task(async function test_missing() { + let validator = new PasswordValidator(); + { + let { server, client } = getDummyServerAndClient(); + + client.pop(); + + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + + equal(clientRecords.length, 2); + equal(records.length, 3); + equal(deletedRecords.length, 0); + + let expected = validator.emptyProblemData(); + expected.clientMissing.push("33333"); + deepEqual(problemData, expected); + } + { + let { server, client } = getDummyServerAndClient(); + + server.pop(); + + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + + equal(clientRecords.length, 3); + equal(records.length, 2); + equal(deletedRecords.length, 0); + + let expected = validator.emptyProblemData(); + expected.serverMissing.push("33333"); + deepEqual(problemData, expected); + } +}); + +add_task(async function test_deleted() { + let { server, client } = getDummyServerAndClient(); + let deletionRecord = { id: "444444", guid: "444444", deleted: true }; + + server.push(deletionRecord); + let validator = new PasswordValidator(); + + let { problemData, clientRecords, records, deletedRecords } = + await validator.compareClientWithServer(client, server); + + equal(clientRecords.length, 3); + equal(records.length, 4); + deepEqual(deletedRecords, [deletionRecord]); + + let expected = validator.emptyProblemData(); + deepEqual(problemData, expected); +}); + +add_task(async function test_duplicates() { + let validator = new PasswordValidator(); + { + let { server, client } = getDummyServerAndClient(); + client.push(Cu.cloneInto(client[0], {})); + + let { problemData } = await validator.compareClientWithServer( + client, + server + ); + + let expected = validator.emptyProblemData(); + expected.clientDuplicates.push("11111"); + deepEqual(problemData, expected); + } + { + let { server, client } = getDummyServerAndClient(); + server.push(Cu.cloneInto(server[server.length - 1], {})); + + let { problemData } = await validator.compareClientWithServer( + client, + server + ); + + let expected = validator.emptyProblemData(); + expected.duplicates.push("33333"); + deepEqual(problemData, expected); + } +}); diff --git a/services/sync/tests/unit/test_postqueue.js b/services/sync/tests/unit/test_postqueue.js new file mode 100644 index 0000000000..2e687bce11 --- /dev/null +++ b/services/sync/tests/unit/test_postqueue.js @@ -0,0 +1,985 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +let { PostQueue } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); + +function makeRecord(nbytes) { + return { + toJSON: () => ({ payload: "x".repeat(nbytes) }), + }; +} + +// Note: This is 14 bytes. Tests make assumptions about this (even if it's just +// in setting config.max_request_bytes to a specific value). +makeRecord.nonPayloadOverhead = JSON.stringify(makeRecord(0).toJSON()).length; + +// Gives how many encoded bytes a request with the given payload +// sizes will be (assuming the records were created by makeRecord) +// requestBytesFor([20]) => 22, requestBytesFor([20, 20]) => 43 +function requestBytesFor(recordPayloadByteCounts) { + let requestBytes = 1; + for (let size of recordPayloadByteCounts) { + requestBytes += size + 1 + makeRecord.nonPayloadOverhead; + } + return requestBytes; +} + +function makePostQueue(config, lastModTime, responseGenerator) { + let stats = { + posts: [], + batches: [], + }; + let poster = (data, headers, batch, commit) => { + let payloadBytes = 0; + let numRecords = 0; + for (let record of JSON.parse(data)) { + if (config.max_record_payload_bytes) { + less( + record.payload.length, + config.max_record_payload_bytes, + "PostQueue should respect max_record_payload_bytes" + ); + } + payloadBytes += record.payload.length; + ++numRecords; + } + + let thisPost = { + nbytes: data.length, + batch, + commit, + payloadBytes, + numRecords, + }; + + if (headers.length) { + thisPost.headers = headers; + } + + // check that we respected the provided limits for the post + if (config.max_post_records) { + lessOrEqual( + numRecords, + config.max_post_records, + "PostQueue should respect max_post_records" + ); + } + + if (config.max_post_bytes) { + less( + payloadBytes, + config.max_post_bytes, + "PostQueue should respect max_post_bytes" + ); + } + + if (config.max_request_bytes) { + less( + thisPost.nbytes, + config.max_request_bytes, + "PostQueue should respect max_request_bytes" + ); + } + + stats.posts.push(thisPost); + + // Call this now so we can check if there's a batch id in it. + // Kind of cludgey, but allows us to have the correct batch id even + // before the next post is made. + let nextResponse = responseGenerator.next().value; + + // Record info for the batch. + + let curBatch = stats.batches[stats.batches.length - 1]; + // If there's no batch, it committed, or we requested a new one, + // then we need to start a new one. + if (!curBatch || batch == "true" || curBatch.didCommit) { + curBatch = { + posts: 0, + payloadBytes: 0, + numRecords: 0, + didCommit: false, + batch, + serverBatch: false, + }; + if (nextResponse.obj && nextResponse.obj.batch) { + curBatch.batch = nextResponse.obj.batch; + curBatch.serverBatch = true; + } + stats.batches.push(curBatch); + } + + // If we provided a batch id, it must be the same as the current batch + if (batch && batch != "true") { + equal(curBatch.batch, batch); + } + + curBatch.posts += 1; + curBatch.payloadBytes += payloadBytes; + curBatch.numRecords += numRecords; + curBatch.didCommit = commit; + + // if this is an actual server batch (or it's a one-shot batch), check that + // we respected the provided total limits + if (commit && (batch == "true" || curBatch.serverBatch)) { + if (config.max_total_records) { + lessOrEqual( + curBatch.numRecords, + config.max_total_records, + "PostQueue should respect max_total_records" + ); + } + + if (config.max_total_bytes) { + less( + curBatch.payloadBytes, + config.max_total_bytes, + "PostQueue should respect max_total_bytes" + ); + } + } + + return Promise.resolve(nextResponse); + }; + + let done = () => {}; + let pq = new PostQueue(poster, lastModTime, config, getTestLogger(), done); + return { pq, stats }; +} + +add_task(async function test_simple() { + let config = { + max_request_bytes: 1000, + max_record_payload_bytes: 1000, + }; + + const time = 11111111; + + function* responseGenerator() { + yield { + success: true, + status: 200, + headers: { + "x-weave-timestamp": time + 100, + "x-last-modified": time + 100, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + await pq.enqueue(makeRecord(10)); + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([10]), + payloadBytes: 10, + numRecords: 1, + commit: true, // we don't know if we have batch semantics, so committed. + headers: [["x-if-unmodified-since", time]], + batch: "true", + }, + ]); + deepEqual(stats.batches, [ + { + posts: 1, + payloadBytes: 10, + numRecords: 1, + didCommit: true, + batch: "true", + serverBatch: false, + }, + ]); +}); + +// Test we do the right thing when we need to make multiple posts when there +// are no batch semantics +add_task(async function test_max_request_bytes_no_batch() { + let config = { + max_request_bytes: 50, + max_record_payload_bytes: 50, + }; + + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 200, + headers: { + "x-weave-timestamp": time + 100, + "x-last-modified": time + 100, + }, + }; + yield { + success: true, + status: 200, + headers: { + "x-weave-timestamp": time + 200, + "x-last-modified": time + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + let payloadSize = 20 - makeRecord.nonPayloadOverhead; + await pq.enqueue(makeRecord(payloadSize)); // total size now 22 bytes - "[" + record + "]" + await pq.enqueue(makeRecord(payloadSize)); // total size now 43 bytes - "[" + record + "," + record + "]" + await pq.enqueue(makeRecord(payloadSize)); // this will exceed our byte limit, so will be in the 2nd POST. + await pq.flush(true); + deepEqual(stats.posts, [ + { + nbytes: 43, // 43 for the first part + payloadBytes: payloadSize * 2, + numRecords: 2, + commit: false, + headers: [["x-if-unmodified-since", time]], + batch: "true", + }, + { + nbytes: 22, + payloadBytes: payloadSize, + numRecords: 1, + commit: false, // we know we aren't in a batch, so never commit. + headers: [["x-if-unmodified-since", time + 100]], + batch: null, + }, + ]); + equal(stats.batches.filter(x => x.didCommit).length, 0); + equal(pq.lastModified, time + 200); +}); + +add_task(async function test_max_record_payload_bytes_no_batch() { + let config = { + max_request_bytes: 100, + max_record_payload_bytes: 50, + }; + + const time = 11111111; + + function* responseGenerator() { + yield { + success: true, + status: 200, + headers: { + "x-weave-timestamp": time + 100, + "x-last-modified": time + 100, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + // Should trigger when the record really is too large to fit + let { enqueued } = await pq.enqueue(makeRecord(51)); + ok(!enqueued); + // Shouldn't trigger when the encoded record is too big + ok( + (await pq.enqueue(makeRecord(50 - makeRecord.nonPayloadOverhead))).enqueued + ); // total size now 52 bytes - "[" + record + "]" + ok( + (await pq.enqueue(makeRecord(46 - makeRecord.nonPayloadOverhead))).enqueued + ); // total size now 99 bytes - "[" + record0 + "," + record1 + "]" + + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: 99, + payloadBytes: 50 + 46 - makeRecord.nonPayloadOverhead * 2, + numRecords: 2, + commit: true, // we know we aren't in a batch, so never commit. + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 1, + payloadBytes: 50 + 46 - makeRecord.nonPayloadOverhead * 2, + numRecords: 2, + didCommit: true, + batch: "true", + serverBatch: false, + }, + ]); + + equal(pq.lastModified, time + 100); +}); + +// Batch tests. + +// Test making a single post when batch semantics are in place. + +add_task(async function test_single_batch() { + let config = { + max_post_bytes: 1000, + max_post_records: 100, + max_total_records: 200, + max_record_payload_bytes: 1000, + }; + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time, "x-weave-timestamp": time + 100 }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + ok((await pq.enqueue(makeRecord(10))).enqueued); + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([10]), + numRecords: 1, + payloadBytes: 10, + commit: true, // we don't know if we have batch semantics, so committed. + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 1, + payloadBytes: 10, + numRecords: 1, + didCommit: true, + batch: 1234, + serverBatch: true, + }, + ]); +}); + +// Test we do the right thing when we need to make multiple posts due to +// max_post_bytes when there are batch semantics in place. +add_task(async function test_max_post_bytes_batch() { + let config = { + max_post_bytes: 50, + max_post_records: 4, + max_total_bytes: 5000, + max_total_records: 100, + max_record_payload_bytes: 50, + max_request_bytes: 4000, + }; + + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time, "x-weave-timestamp": time + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { + "x-last-modified": time + 200, + "x-weave-timestamp": time + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + ok((await pq.enqueue(makeRecord(20))).enqueued); // 20 + ok((await pq.enqueue(makeRecord(20))).enqueued); // 40 + // 60 would overflow, so post + ok((await pq.enqueue(makeRecord(20))).enqueued); // 20 + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([20, 20]), + payloadBytes: 40, + numRecords: 2, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + { + nbytes: requestBytesFor([20]), + payloadBytes: 20, + numRecords: 1, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 60, + numRecords: 3, + didCommit: true, + batch: 1234, + serverBatch: true, + }, + ]); + + equal(pq.lastModified, time + 200); +}); + +// Test we do the right thing when we need to make multiple posts due to +// max_request_bytes when there are batch semantics in place. +add_task(async function test_max_request_bytes_batch() { + let config = { + max_post_bytes: 60, + max_post_records: 40, + max_total_bytes: 5000, + max_total_records: 100, + max_record_payload_bytes: 500, + max_request_bytes: 100, + }; + + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time, "x-weave-timestamp": time + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { + "x-last-modified": time + 200, + "x-weave-timestamp": time + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + ok((await pq.enqueue(makeRecord(10))).enqueued); // post: 10, request: 26 (10 + 14 + 2) + ok((await pq.enqueue(makeRecord(10))).enqueued); // post: 20, request: 51 (10 + 14 + 1) * 2 + 1 + ok((await pq.enqueue(makeRecord(10))).enqueued); // post: 30, request: 76 (10 + 14 + 1) * 3 + 1 + // 1 more would be post: 40 (fine), request: 101, So we should post. + ok((await pq.enqueue(makeRecord(10))).enqueued); + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([10, 10, 10]), + payloadBytes: 30, + numRecords: 3, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + { + nbytes: requestBytesFor([10]), + payloadBytes: 10, + numRecords: 1, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 40, + numRecords: 4, + didCommit: true, + batch: 1234, + serverBatch: true, + }, + ]); + + equal(pq.lastModified, time + 200); +}); + +// Test we do the right thing when the batch bytes limit is exceeded. +add_task(async function test_max_total_bytes_batch() { + let config = { + max_post_bytes: 50, + max_post_records: 20, + max_total_bytes: 70, + max_total_records: 100, + max_record_payload_bytes: 50, + max_request_bytes: 500, + }; + + const time0 = 11111111; + const time1 = 22222222; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time0, "x-weave-timestamp": time0 + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time1, "x-weave-timestamp": time1 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 5678 }, + headers: { "x-last-modified": time1, "x-weave-timestamp": time1 + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 5678 }, + headers: { + "x-last-modified": time1 + 200, + "x-weave-timestamp": time1 + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time0, responseGenerator()); + + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 20, batch: 20 + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 40, batch: 40 + + // this will exceed our POST byte limit, so will be in the 2nd POST - but still in the first batch. + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 20, batch: 60 + + // this will exceed our batch byte limit, so will be in a new batch. + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 20, batch: 20 + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 40, batch: 40 + // This will exceed POST byte limit, so will be in the 4th post, part of the 2nd batch. + ok((await pq.enqueue(makeRecord(20))).enqueued); // payloads = post: 20, batch: 60 + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([20, 20]), + payloadBytes: 40, + numRecords: 2, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time0]], + }, + { + nbytes: requestBytesFor([20]), + payloadBytes: 20, + numRecords: 1, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time0]], + }, + { + nbytes: requestBytesFor([20, 20]), + payloadBytes: 40, + numRecords: 2, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time1]], + }, + { + nbytes: requestBytesFor([20]), + payloadBytes: 20, + numRecords: 1, + commit: true, + batch: 5678, + headers: [["x-if-unmodified-since", time1]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 60, + numRecords: 3, + didCommit: true, + batch: 1234, + serverBatch: true, + }, + { + posts: 2, + payloadBytes: 60, + numRecords: 3, + didCommit: true, + batch: 5678, + serverBatch: true, + }, + ]); + + equal(pq.lastModified, time1 + 200); +}); + +// Test we split up the posts when we exceed the record limit when batch semantics +// are in place. +add_task(async function test_max_post_records_batch() { + let config = { + max_post_bytes: 1000, + max_post_records: 2, + max_total_bytes: 5000, + max_total_records: 100, + max_record_payload_bytes: 1000, + max_request_bytes: 1000, + }; + + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time, "x-weave-timestamp": time + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { + "x-last-modified": time + 200, + "x-weave-timestamp": time + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + + // will exceed record limit of 2, so will be in 2nd post. + ok((await pq.enqueue(makeRecord(20))).enqueued); + + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([20, 20]), + numRecords: 2, + payloadBytes: 40, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + { + nbytes: requestBytesFor([20]), + numRecords: 1, + payloadBytes: 20, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 60, + numRecords: 3, + batch: 1234, + serverBatch: true, + didCommit: true, + }, + ]); + + equal(pq.lastModified, time + 200); +}); + +// Test we do the right thing when the batch record limit is exceeded. +add_task(async function test_max_records_batch() { + let config = { + max_post_bytes: 1000, + max_post_records: 3, + max_total_bytes: 10000, + max_total_records: 5, + max_record_payload_bytes: 1000, + max_request_bytes: 10000, + }; + + const time0 = 11111111; + const time1 = 22222222; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time0, "x-weave-timestamp": time0 + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time1, "x-weave-timestamp": time1 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 5678 }, + headers: { "x-last-modified": time1, "x-weave-timestamp": time1 + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 5678 }, + headers: { + "x-last-modified": time1 + 200, + "x-weave-timestamp": time1 + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time0, responseGenerator()); + + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + ok((await pq.enqueue(makeRecord(20))).enqueued); + + ok((await pq.enqueue(makeRecord(20))).enqueued); + + await pq.flush(true); + + deepEqual(stats.posts, [ + { + // 3 records + nbytes: requestBytesFor([20, 20, 20]), + payloadBytes: 60, + numRecords: 3, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time0]], + }, + { + // 2 records -- end batch1 + nbytes: requestBytesFor([20, 20]), + payloadBytes: 40, + numRecords: 2, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time0]], + }, + { + // 3 records + nbytes: requestBytesFor([20, 20, 20]), + payloadBytes: 60, + numRecords: 3, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time1]], + }, + { + // 1 record -- end batch2 + nbytes: requestBytesFor([20]), + payloadBytes: 20, + numRecords: 1, + commit: true, + batch: 5678, + headers: [["x-if-unmodified-since", time1]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 100, + numRecords: 5, + batch: 1234, + serverBatch: true, + didCommit: true, + }, + { + posts: 2, + payloadBytes: 80, + numRecords: 4, + batch: 5678, + serverBatch: true, + didCommit: true, + }, + ]); + + equal(pq.lastModified, time1 + 200); +}); + +// Test we do the right thing when the limits are met but not exceeded. +add_task(async function test_packed_batch() { + let config = { + max_post_bytes: 41, + max_post_records: 4, + + max_total_bytes: 81, + max_total_records: 8, + + max_record_payload_bytes: 20 + makeRecord.nonPayloadOverhead + 1, + max_request_bytes: requestBytesFor([10, 10, 10, 10]) + 1, + }; + + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { "x-last-modified": time, "x-weave-timestamp": time + 100 }, + }; + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { + "x-last-modified": time + 200, + "x-weave-timestamp": time + 200, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + ok((await pq.enqueue(makeRecord(10))).enqueued); + + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([10, 10, 10, 10]), + numRecords: 4, + payloadBytes: 40, + commit: false, + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + { + nbytes: requestBytesFor([10, 10, 10, 10]), + numRecords: 4, + payloadBytes: 40, + commit: true, + batch: 1234, + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 2, + payloadBytes: 80, + numRecords: 8, + batch: 1234, + serverBatch: true, + didCommit: true, + }, + ]); + + equal(pq.lastModified, time + 200); +}); + +// Tests that check that a single record fails to enqueue for the provided config +async function test_enqueue_failure_case(failureLimit, config) { + const time = 11111111; + function* responseGenerator() { + yield { + success: true, + status: 202, + obj: { batch: 1234 }, + headers: { + "x-last-modified": time + 100, + "x-weave-timestamp": time + 100, + }, + }; + } + + let { pq, stats } = makePostQueue(config, time, responseGenerator()); + // Check on empty postqueue + let result = await pq.enqueue(makeRecord(failureLimit + 1)); + ok(!result.enqueued); + notEqual(result.error, undefined); + + ok((await pq.enqueue(makeRecord(5))).enqueued); + + // check on nonempty postqueue + result = await pq.enqueue(makeRecord(failureLimit + 1)); + ok(!result.enqueued); + notEqual(result.error, undefined); + + // make sure that we keep working, skipping the bad record entirely + // (handling the error the queue reported is left up to caller) + ok((await pq.enqueue(makeRecord(5))).enqueued); + + await pq.flush(true); + + deepEqual(stats.posts, [ + { + nbytes: requestBytesFor([5, 5]), + numRecords: 2, + payloadBytes: 10, + commit: true, + batch: "true", + headers: [["x-if-unmodified-since", time]], + }, + ]); + + deepEqual(stats.batches, [ + { + posts: 1, + payloadBytes: 10, + numRecords: 2, + batch: 1234, + serverBatch: true, + didCommit: true, + }, + ]); + + equal(pq.lastModified, time + 100); +} + +add_task(async function test_max_post_bytes_enqueue_failure() { + await test_enqueue_failure_case(50, { + max_post_bytes: 50, + max_post_records: 100, + + max_total_bytes: 5000, + max_total_records: 100, + + max_record_payload_bytes: 500, + max_request_bytes: 500, + }); +}); + +add_task(async function test_max_request_bytes_enqueue_failure() { + await test_enqueue_failure_case(50, { + max_post_bytes: 500, + max_post_records: 100, + + max_total_bytes: 5000, + max_total_records: 100, + + max_record_payload_bytes: 500, + max_request_bytes: 50, + }); +}); + +add_task(async function test_max_record_payload_bytes_enqueue_failure() { + await test_enqueue_failure_case(50, { + max_post_bytes: 500, + max_post_records: 100, + + max_total_bytes: 5000, + max_total_records: 100, + + max_record_payload_bytes: 50, + max_request_bytes: 500, + }); +}); diff --git a/services/sync/tests/unit/test_prefs_engine.js b/services/sync/tests/unit/test_prefs_engine.js new file mode 100644 index 0000000000..77a4474cb4 --- /dev/null +++ b/services/sync/tests/unit/test_prefs_engine.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { getPrefsGUIDForTest } = ChromeUtils.importESModule( + "resource://services-sync/engines/prefs.sys.mjs" +); +const PREFS_GUID = getPrefsGUIDForTest(); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +async function cleanup(engine, server) { + await engine._tracker.stop(); + await engine.wipeClient(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Service.recordManager.clearCache(); + await promiseStopServer(server); +} + +add_task(async function test_modified_after_fail() { + let engine = Service.engineManager.get("prefs"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + // The homepage pref is synced by default. + _("Set homepage before first sync"); + Services.prefs.setStringPref("browser.startup.homepage", "about:welcome"); + + _("First sync; create collection and pref record on server"); + await sync_engine_and_validate_telem(engine, false); + + let collection = server.user("foo").collection("prefs"); + equal( + collection.cleartext(PREFS_GUID).value["browser.startup.homepage"], + "about:welcome", + "Should upload homepage in pref record" + ); + ok( + !engine._tracker.modified, + "Tracker shouldn't be modified after first sync" + ); + + // Our tracker only has a `modified` flag that's reset after a + // successful upload. Force it to remain set by failing the + // upload. + _("Second sync; flag tracker as modified and throw on upload"); + Services.prefs.setStringPref("browser.startup.homepage", "about:robots"); + engine._tracker.modified = true; + let oldPost = collection.post; + collection.post = () => { + throw new Error("Sync this!"); + }; + await Assert.rejects( + sync_engine_and_validate_telem(engine, true), + ex => ex.success === false + ); + ok( + engine._tracker.modified, + "Tracker should remain modified after failed sync" + ); + + _("Third sync"); + collection.post = oldPost; + await sync_engine_and_validate_telem(engine, false); + equal( + collection.cleartext(PREFS_GUID).value["browser.startup.homepage"], + "about:robots", + "Should upload new homepage on third sync" + ); + ok( + !engine._tracker.modified, + "Tracker shouldn't be modified again after third sync" + ); + } finally { + await cleanup(engine, server); + } +}); + +add_task(async function test_allow_arbitrary() { + let engine = Service.engineManager.get("prefs"); + + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + try { + _("Create collection and pref record on server"); + await sync_engine_and_validate_telem(engine, false); + + let collection = server.user("foo").collection("prefs"); + + _("Insert arbitrary pref into remote record"); + let cleartext1 = collection.cleartext(PREFS_GUID); + cleartext1.value.let_viruses_take_over = true; + collection.insert( + PREFS_GUID, + encryptPayload(cleartext1), + new_timestamp() + 5 + ); + + _("Sync again; client shouldn't allow pref"); + await sync_engine_and_validate_telem(engine, false); + ok( + !Services.prefs.getBoolPref("let_viruses_take_over", false), + "Shouldn't allow arbitrary remote prefs without control pref" + ); + + _("Sync with control pref set; client should set new pref"); + Services.prefs.setBoolPref( + "services.sync.prefs.sync.let_viruses_take_over_take_two", + true + ); + + let cleartext2 = collection.cleartext(PREFS_GUID); + cleartext2.value.let_viruses_take_over_take_two = true; + collection.insert( + PREFS_GUID, + encryptPayload(cleartext2), + new_timestamp() + 5 + ); + // Reset the last sync time so that the engine fetches the record again. + await engine.setLastSync(0); + await sync_engine_and_validate_telem(engine, false); + ok( + Services.prefs.getBoolPref("let_viruses_take_over_take_two"), + "Should set arbitrary remote pref with control pref" + ); + } finally { + await cleanup(engine, server); + } +}); diff --git a/services/sync/tests/unit/test_prefs_store.js b/services/sync/tests/unit/test_prefs_store.js new file mode 100644 index 0000000000..53ee68fc95 --- /dev/null +++ b/services/sync/tests/unit/test_prefs_store.js @@ -0,0 +1,391 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); +PromiseTestUtils.allowMatchingRejectionsGlobally( + /Unable to arm timer, the object has been finalized\./ +); +PromiseTestUtils.allowMatchingRejectionsGlobally( + /IOUtils\.profileBeforeChange getter: IOUtils: profileBeforeChange phase has already finished/ +); + +const { PrefRec, getPrefsGUIDForTest } = ChromeUtils.importESModule( + "resource://services-sync/engines/prefs.sys.mjs" +); +const PREFS_GUID = getPrefsGUIDForTest(); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const DEFAULT_THEME_ID = "default-theme@mozilla.org"; +const COMPACT_THEME_ID = "firefox-compact-light@mozilla.org"; + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "1.9.2" +); +AddonTestUtils.overrideCertDB(); + +add_task(async function run_test() { + _("Test fixtures."); + // Part of this test ensures the default theme, via the preference + // extensions.activeThemeID, is synced correctly - so we do a little + // addons initialization to allow this to work. + + // Enable application scopes to ensure the builtin theme is going to + // be installed as part of the the addon manager startup. + Services.prefs.setIntPref( + "extensions.enabledScopes", + AddonManager.SCOPE_APPLICATION + ); + await AddonTestUtils.promiseStartupManager(); + + // Install another built-in theme. + await AddonManager.installBuiltinAddon("resource://builtin-themes/light/"); + + const defaultThemeAddon = await AddonManager.getAddonByID(DEFAULT_THEME_ID); + ok(defaultThemeAddon, "Got an addon wrapper for the default theme"); + + const otherThemeAddon = await AddonManager.getAddonByID(COMPACT_THEME_ID); + ok(otherThemeAddon, "Got an addon wrapper for the compact theme"); + + await otherThemeAddon.enable(); + + // read our custom prefs file before doing anything. + Services.prefs.readDefaultPrefsFromFile( + do_get_file("prefs_test_prefs_store.js") + ); + + let engine = Service.engineManager.get("prefs"); + let store = engine._store; + try { + _("Expect the compact light theme to be active"); + Assert.strictEqual( + Services.prefs.getStringPref("extensions.activeThemeID"), + COMPACT_THEME_ID + ); + + _("The GUID corresponds to XUL App ID."); + let allIDs = await store.getAllIDs(); + let ids = Object.keys(allIDs); + Assert.equal(ids.length, 1); + Assert.equal(ids[0], PREFS_GUID); + Assert.ok(allIDs[PREFS_GUID]); + + Assert.ok(await store.itemExists(PREFS_GUID)); + Assert.equal(false, await store.itemExists("random-gibberish")); + + _("Unknown prefs record is created as deleted."); + let record = await store.createRecord("random-gibberish", "prefs"); + Assert.ok(record.deleted); + + _("Prefs record contains only prefs that should be synced."); + record = await store.createRecord(PREFS_GUID, "prefs"); + Assert.strictEqual(record.value["testing.int"], 123); + Assert.strictEqual(record.value["testing.string"], "ohai"); + Assert.strictEqual(record.value["testing.bool"], true); + // non-existing prefs get null as the value + Assert.strictEqual(record.value["testing.nonexistent"], null); + // as do prefs that have a default value. + Assert.strictEqual(record.value["testing.default"], null); + Assert.strictEqual(record.value["testing.turned.off"], undefined); + Assert.strictEqual(record.value["testing.not.turned.on"], undefined); + + _("Prefs record contains the correct control prefs."); + // All control prefs which have the default value and where the pref + // itself is synced should appear, but with null as the value. + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.int"], + null + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.string"], + null + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.bool"], + null + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.dont.change"], + null + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.nonexistent"], + null + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.default"], + null + ); + + // but this control pref has a non-default value so that value is synced. + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.turned.off"], + false + ); + + _("Unsyncable prefs are treated correctly."); + // Prefs we consider unsyncable (since they are URLs that won't be stable on + // another firefox) shouldn't be included - neither the value nor the + // control pref should appear. + Assert.strictEqual(record.value["testing.unsynced.url"], undefined); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.unsynced.url"], + undefined + ); + // Other URLs with user prefs should be synced, though. + Assert.strictEqual( + record.value["testing.synced.url"], + "https://www.example.com" + ); + Assert.strictEqual( + record.value["services.sync.prefs.sync.testing.synced.url"], + null + ); + + _("Update some prefs, including one that's to be reset/deleted."); + // This pref is not going to be reset or deleted as there's no "control pref" + // in either the incoming record or locally. + Services.prefs.setStringPref( + "testing.deleted-without-control-pref", + "I'm deleted-without-control-pref" + ); + // Another pref with only a local control pref. + Services.prefs.setStringPref( + "testing.deleted-with-local-control-pref", + "I'm deleted-with-local-control-pref" + ); + Services.prefs.setBoolPref( + "services.sync.prefs.sync.testing.deleted-with-local-control-pref", + true + ); + // And a pref without a local control pref but one that's incoming. + Services.prefs.setStringPref( + "testing.deleted-with-incoming-control-pref", + "I'm deleted-with-incoming-control-pref" + ); + record = new PrefRec("prefs", PREFS_GUID); + record.value = { + "extensions.activeThemeID": DEFAULT_THEME_ID, + "testing.int": 42, + "testing.string": "im in ur prefs", + "testing.bool": false, + "testing.deleted-without-control-pref": null, + "testing.deleted-with-local-control-pref": null, + "testing.deleted-with-incoming-control-pref": null, + "services.sync.prefs.sync.testing.deleted-with-incoming-control-pref": true, + "testing.somepref": "im a new pref from other device", + "services.sync.prefs.sync.testing.somepref": true, + // Pretend some a stale remote client is overwriting it with a value + // we consider unsyncable. + "testing.synced.url": "blob:ebeb707a-502e-40c6-97a5-dd4bda901463", + // Make sure we can replace the unsynced URL with a valid URL. + "testing.unsynced.url": "https://www.example.com/2", + // Make sure our "master control pref" is ignored. + "services.sync.prefs.dangerously_allow_arbitrary": true, + "services.sync.prefs.sync.services.sync.prefs.dangerously_allow_arbitrary": true, + }; + + const onceAddonEnabled = AddonTestUtils.promiseAddonEvent("onEnabled"); + + await store.update(record); + Assert.strictEqual(Services.prefs.getIntPref("testing.int"), 42); + Assert.strictEqual( + Services.prefs.getStringPref("testing.string"), + "im in ur prefs" + ); + Assert.strictEqual(Services.prefs.getBoolPref("testing.bool"), false); + Assert.strictEqual( + Services.prefs.getStringPref("testing.deleted-without-control-pref"), + "I'm deleted-without-control-pref" + ); + Assert.strictEqual( + Services.prefs.getPrefType("testing.deleted-with-local-control-pref"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.strictEqual( + Services.prefs.getStringPref( + "testing.deleted-with-incoming-control-pref" + ), + "I'm deleted-with-incoming-control-pref" + ); + Assert.strictEqual( + Services.prefs.getStringPref("testing.dont.change"), + "Please don't change me." + ); + Assert.strictEqual( + Services.prefs.getPrefType("testing.somepref"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.strictEqual( + Services.prefs.getStringPref("testing.synced.url"), + "https://www.example.com" + ); + Assert.strictEqual( + Services.prefs.getStringPref("testing.unsynced.url"), + "https://www.example.com/2" + ); + Assert.strictEqual( + Svc.PrefBranch.getPrefType("prefs.sync.testing.somepref"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.strictEqual( + Services.prefs.getBoolPref( + "services.sync.prefs.dangerously_allow_arbitrary" + ), + false + ); + Assert.strictEqual( + Services.prefs.getPrefType( + "services.sync.prefs.sync.services.sync.prefs.dangerously_allow_arbitrary" + ), + Ci.nsIPrefBranch.PREF_INVALID + ); + + await onceAddonEnabled; + ok( + !defaultThemeAddon.userDisabled, + "the default theme should have been enabled" + ); + ok( + otherThemeAddon.userDisabled, + "the compact theme should have been disabled" + ); + + _("Only the current app's preferences are applied."); + record = new PrefRec("prefs", "some-fake-app"); + record.value = { + "testing.int": 98, + }; + await store.update(record); + Assert.equal(Services.prefs.getIntPref("testing.int"), 42); + } finally { + for (const pref of Services.prefs.getChildList("")) { + Services.prefs.clearUserPref(pref); + } + } +}); + +add_task(async function test_dangerously_allow() { + _("services.sync.prefs.dangerously_allow_arbitrary"); + // Bug 1538015 added a capability to "dangerously allow" arbitrary prefs. + // Bug 1854698 removed that capability but did keep the fact we never + // sync the pref which enabled the "dangerous" behaviour, just incase someone + // tries to sync it back to a profile which *does* support that pref. + Services.prefs.readDefaultPrefsFromFile( + do_get_file("prefs_test_prefs_store.js") + ); + + let engine = Service.engineManager.get("prefs"); + let store = engine._store; + try { + // an incoming record with our old "dangerous" pref. + let record = new PrefRec("prefs", PREFS_GUID); + record.value = { + "services.sync.prefs.dangerously_allow_arbitrary": true, + "services.sync.prefs.sync.services.sync.prefs.dangerously_allow_arbitrary": true, + }; + await store.update(record); + Assert.strictEqual( + Services.prefs.getBoolPref( + "services.sync.prefs.dangerously_allow_arbitrary" + ), + false + ); + Assert.strictEqual( + Services.prefs.getPrefType( + "services.sync.prefs.sync.services.sync.prefs.dangerously_allow_arbitrary" + ), + Ci.nsIPrefBranch.PREF_INVALID + ); + } finally { + for (const pref of Services.prefs.getChildList("")) { + Services.prefs.clearUserPref(pref); + } + } +}); + +add_task(async function test_incoming_sets_seen() { + _("Test the sync-seen allow-list"); + + let engine = Service.engineManager.get("prefs"); + let store = engine._store; + + Services.prefs.readDefaultPrefsFromFile( + do_get_file("prefs_test_prefs_store.js") + ); + const defaultValue = "the value"; + Assert.equal(Services.prefs.getStringPref("testing.seen"), defaultValue); + + let record = await store.createRecord(PREFS_GUID, "prefs"); + // Haven't seen a non-default value before, so remains null. + Assert.strictEqual(record.value["testing.seen"], null); + + // pretend an incoming record with the default value - it might not be + // the default everywhere, so we treat it specially. + record = new PrefRec("prefs", PREFS_GUID); + record.value = { + "testing.seen": defaultValue, + }; + await store.update(record); + // Our special control value should now be set. + Assert.strictEqual( + Services.prefs.getBoolPref("services.sync.prefs.sync-seen.testing.seen"), + true + ); + // It's still the default value, so the value is not considered changed + Assert.equal(Services.prefs.prefHasUserValue("testing.seen"), false); + + // But now that special control value is set, the record always contains the value. + record = await store.createRecord(PREFS_GUID, "prefs"); + Assert.strictEqual(record.value["testing.seen"], defaultValue); +}); + +add_task(async function test_outgoing_when_changed() { + _("Test the 'seen' pref is set first sync of non-default value"); + + let engine = Service.engineManager.get("prefs"); + let store = engine._store; + for (const pref of Services.prefs.getChildList("")) { + Services.prefs.clearUserPref(pref); + } + + Services.prefs.readDefaultPrefsFromFile( + do_get_file("prefs_test_prefs_store.js") + ); + const defaultValue = "the value"; + Assert.equal(Services.prefs.getStringPref("testing.seen"), defaultValue); + + let record = await store.createRecord(PREFS_GUID, "prefs"); + // Haven't seen a non-default value before, so remains null. + Assert.strictEqual(record.value["testing.seen"], null); + + // Change the value. + Services.prefs.setStringPref("testing.seen", "new value"); + record = await store.createRecord(PREFS_GUID, "prefs"); + // creating the record toggled that "seen" pref. + Assert.strictEqual( + Services.prefs.getBoolPref("services.sync.prefs.sync-seen.testing.seen"), + true + ); + Assert.strictEqual(Services.prefs.getStringPref("testing.seen"), "new value"); + + // Resetting the pref does not change that seen value. + Services.prefs.clearUserPref("testing.seen"); + Assert.strictEqual( + Services.prefs.getStringPref("testing.seen"), + defaultValue + ); + + record = await store.createRecord(PREFS_GUID, "prefs"); + Assert.strictEqual( + Services.prefs.getBoolPref("services.sync.prefs.sync-seen.testing.seen"), + true + ); +}); diff --git a/services/sync/tests/unit/test_prefs_tracker.js b/services/sync/tests/unit/test_prefs_tracker.js new file mode 100644 index 0000000000..ecf3f18420 --- /dev/null +++ b/services/sync/tests/unit/test_prefs_tracker.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function run_test() { + let engine = Service.engineManager.get("prefs"); + let tracker = engine._tracker; + + try { + _("tracker.modified corresponds to preference."); + // Assert preference is not defined. + Assert.equal( + Svc.PrefBranch.getPrefType("engine.prefs.modified"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.ok(!tracker.modified); + + tracker.modified = true; + Assert.equal(Svc.PrefBranch.getBoolPref("engine.prefs.modified"), true); + Assert.ok(tracker.modified); + + _("Engine's getChangedID() just returns the one GUID we have."); + let changedIDs = await engine.getChangedIDs(); + let ids = Object.keys(changedIDs); + Assert.equal(ids.length, 1); + Assert.equal(ids[0], CommonUtils.encodeBase64URL(Services.appinfo.ID)); + + Svc.PrefBranch.setBoolPref("engine.prefs.modified", false); + Assert.ok(!tracker.modified); + + _("No modified state, so no changed IDs."); + do_check_empty(await engine.getChangedIDs()); + + _("Initial score is 0"); + Assert.equal(tracker.score, 0); + + _("Test fixtures."); + Svc.PrefBranch.setBoolPref("prefs.sync.testing.int", true); + + _( + "Test fixtures haven't upped the tracker score yet because it hasn't started tracking yet." + ); + Assert.equal(tracker.score, 0); + + _("Tell the tracker to start tracking changes."); + tracker.start(); + Services.prefs.setIntPref("testing.int", 23); + await tracker.asyncObserver.promiseObserversComplete(); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE); + Assert.equal(tracker.modified, true); + + _("Clearing changed IDs reset modified status."); + await tracker.clearChangedIDs(); + Assert.equal(tracker.modified, false); + + _("Resetting a pref ups the score, too."); + Services.prefs.clearUserPref("testing.int"); + await tracker.asyncObserver.promiseObserversComplete(); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2); + Assert.equal(tracker.modified, true); + await tracker.clearChangedIDs(); + + _("So does changing a pref sync pref."); + Svc.PrefBranch.setBoolPref("prefs.sync.testing.int", false); + await tracker.asyncObserver.promiseObserversComplete(); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(tracker.modified, true); + await tracker.clearChangedIDs(); + + _( + "Now that the pref sync pref has been flipped, changes to it won't be picked up." + ); + Services.prefs.setIntPref("testing.int", 42); + await tracker.asyncObserver.promiseObserversComplete(); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(tracker.modified, false); + await tracker.clearChangedIDs(); + + _("Changing some other random pref won't do anything."); + Services.prefs.setStringPref("testing.other", "blergh"); + await tracker.asyncObserver.promiseObserversComplete(); + Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3); + Assert.equal(tracker.modified, false); + } finally { + await tracker.stop(); + for (const pref of Services.prefs.getChildList("")) { + Services.prefs.clearUserPref(pref); + } + } +}); diff --git a/services/sync/tests/unit/test_records_crypto.js b/services/sync/tests/unit/test_records_crypto.js new file mode 100644 index 0000000000..8b841653cd --- /dev/null +++ b/services/sync/tests/unit/test_records_crypto.js @@ -0,0 +1,189 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { CollectionKeyManager, CryptoWrapper } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +var cryptoWrap; + +function crypted_resource_handler(metadata, response) { + let obj = { + id: "resource", + modified: cryptoWrap.modified, + payload: JSON.stringify(cryptoWrap.payload), + }; + return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response); +} + +function prepareCryptoWrap(collection, id) { + let w = new CryptoWrapper(); + w.cleartext.stuff = "my payload here"; + w.collection = collection; + w.id = id; + return w; +} + +add_task(async function test_records_crypto() { + let server; + + await configureIdentity({ username: "john@example.com" }); + let keyBundle = Service.identity.syncKeyBundle; + + try { + let log = Log.repository.getLogger("Test"); + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + log.info("Setting up server and authenticator"); + + server = httpd_setup({ "/steam/resource": crypted_resource_handler }); + + log.info("Creating a record"); + + cryptoWrap = prepareCryptoWrap("steam", "resource"); + + log.info("cryptoWrap: " + cryptoWrap.toString()); + + log.info("Encrypting a record"); + + await cryptoWrap.encrypt(keyBundle); + log.info("Ciphertext is " + cryptoWrap.ciphertext); + Assert.ok(cryptoWrap.ciphertext != null); + + let firstIV = cryptoWrap.IV; + + log.info("Decrypting the record"); + + let payload = await cryptoWrap.decrypt(keyBundle); + Assert.equal(payload.stuff, "my payload here"); + Assert.notEqual(payload, cryptoWrap.payload); // wrap.data.payload is the encrypted one + + log.info("Make sure multiple decrypts cause failures"); + let error = ""; + try { + payload = await cryptoWrap.decrypt(keyBundle); + } catch (ex) { + error = ex; + } + Assert.equal(error.message, "No ciphertext: nothing to decrypt?"); + + log.info("Re-encrypting the record with alternate payload"); + + cryptoWrap.cleartext.stuff = "another payload"; + await cryptoWrap.encrypt(keyBundle); + let secondIV = cryptoWrap.IV; + payload = await cryptoWrap.decrypt(keyBundle); + Assert.equal(payload.stuff, "another payload"); + + log.info("Make sure multiple encrypts use different IVs"); + Assert.notEqual(firstIV, secondIV); + + log.info(await "Make sure differing ids cause failures"); + await cryptoWrap.encrypt(keyBundle); + cryptoWrap.data.id = "other"; + error = ""; + try { + await cryptoWrap.decrypt(keyBundle); + } catch (ex) { + error = ex; + } + Assert.equal(error.message, "Record id mismatch: resource != other"); + + log.info("Make sure wrong hmacs cause failures"); + await cryptoWrap.encrypt(keyBundle); + cryptoWrap.hmac = "foo"; + error = ""; + try { + await cryptoWrap.decrypt(keyBundle); + } catch (ex) { + error = ex; + } + Assert.equal( + error.message.substr(0, 42), + "Record SHA256 HMAC mismatch: should be foo" + ); + + // Checking per-collection keys and default key handling. + + await generateNewKeys(Service.collectionKeys); + let bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); + await bookmarkItem.encrypt( + Service.collectionKeys.keyForCollection("bookmarks") + ); + log.info("Ciphertext is " + bookmarkItem.ciphertext); + Assert.ok(bookmarkItem.ciphertext != null); + log.info("Decrypting the record explicitly with the default key."); + Assert.equal( + (await bookmarkItem.decrypt(Service.collectionKeys._default)).stuff, + "my payload here" + ); + + // Per-collection keys. + // Generate a key for "bookmarks". + await generateNewKeys(Service.collectionKeys, ["bookmarks"]); + bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); + Assert.equal(bookmarkItem.collection, "bookmarks"); + + // Encrypt. This'll use the "bookmarks" encryption key, because we have a + // special key for it. The same key will need to be used for decryption. + await bookmarkItem.encrypt( + Service.collectionKeys.keyForCollection("bookmarks") + ); + Assert.ok(bookmarkItem.ciphertext != null); + + // Attempt to use the default key, because this is a collision that could + // conceivably occur in the real world. Decryption will error, because + // it's not the bookmarks key. + let err; + try { + await bookmarkItem.decrypt(Service.collectionKeys._default); + } catch (ex) { + err = ex; + } + Assert.equal("Record SHA256 HMAC mismatch", err.message.substr(0, 27)); + + // Explicitly check that it's using the bookmarks key. + // This should succeed. + Assert.equal( + ( + await bookmarkItem.decrypt( + Service.collectionKeys.keyForCollection("bookmarks") + ) + ).stuff, + "my payload here" + ); + + Assert.ok(Service.collectionKeys.hasKeysFor(["bookmarks"])); + + // Add a key for some new collection and verify that it isn't the + // default key. + Assert.ok(!Service.collectionKeys.hasKeysFor(["forms"])); + Assert.ok(!Service.collectionKeys.hasKeysFor(["bookmarks", "forms"])); + let oldFormsKey = Service.collectionKeys.keyForCollection("forms"); + Assert.equal(oldFormsKey, Service.collectionKeys._default); + let newKeys = await Service.collectionKeys.ensureKeysFor(["forms"]); + Assert.ok(newKeys.hasKeysFor(["forms"])); + Assert.ok(newKeys.hasKeysFor(["bookmarks", "forms"])); + let newFormsKey = newKeys.keyForCollection("forms"); + Assert.notEqual(newFormsKey, oldFormsKey); + + // Verify that this doesn't overwrite keys + let regetKeys = await newKeys.ensureKeysFor(["forms"]); + Assert.equal(regetKeys.keyForCollection("forms"), newFormsKey); + + const emptyKeys = new CollectionKeyManager(); + payload = { + default: Service.collectionKeys._default.keyPairB64, + collections: {}, + }; + // Verify that not passing `modified` doesn't throw + emptyKeys.setContents(payload, null); + + log.info("Done!"); + } finally { + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_records_wbo.js b/services/sync/tests/unit/test_records_wbo.js new file mode 100644 index 0000000000..61ba33d749 --- /dev/null +++ b/services/sync/tests/unit/test_records_wbo.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_test(function test_toJSON() { + _("Create a record, for now without a TTL."); + let wbo = new WBORecord("coll", "a_record"); + wbo.modified = 12345; + wbo.sortindex = 42; + wbo.payload = {}; + + _( + "Verify that the JSON representation contains the WBO properties, but not TTL." + ); + let json = JSON.parse(JSON.stringify(wbo)); + Assert.equal(json.modified, 12345); + Assert.equal(json.sortindex, 42); + Assert.equal(json.payload, "{}"); + Assert.equal(false, "ttl" in json); + + _("Set a TTL, make sure it's present in the JSON representation."); + wbo.ttl = 30 * 60; + json = JSON.parse(JSON.stringify(wbo)); + Assert.equal(json.ttl, 30 * 60); + run_next_test(); +}); + +add_task(async function test_fetch() { + let record = { + id: "asdf-1234-asdf-1234", + modified: 2454725.98283, + payload: JSON.stringify({ cheese: "roquefort" }), + }; + let record2 = { + id: "record2", + modified: 2454725.98284, + payload: JSON.stringify({ cheese: "gruyere" }), + }; + let coll = [ + { + id: "record2", + modified: 2454725.98284, + payload: JSON.stringify({ cheese: "gruyere" }), + }, + ]; + + _("Setting up server."); + let server = httpd_setup({ + "/record": httpd_handler(200, "OK", JSON.stringify(record)), + "/record2": httpd_handler(200, "OK", JSON.stringify(record2)), + "/coll": httpd_handler(200, "OK", JSON.stringify(coll)), + }); + + try { + _("Fetching a WBO record"); + let rec = new WBORecord("coll", "record"); + await rec.fetch(Service.resource(server.baseURI + "/record")); + Assert.equal(rec.id, "asdf-1234-asdf-1234"); // NOT "record"! + + Assert.equal(rec.modified, 2454725.98283); + Assert.equal(typeof rec.payload, "object"); + Assert.equal(rec.payload.cheese, "roquefort"); + + _("Fetching a WBO record using the record manager"); + let rec2 = await Service.recordManager.get(server.baseURI + "/record2"); + Assert.equal(rec2.id, "record2"); + Assert.equal(rec2.modified, 2454725.98284); + Assert.equal(typeof rec2.payload, "object"); + Assert.equal(rec2.payload.cheese, "gruyere"); + Assert.equal(Service.recordManager.response.status, 200); + + // Testing collection extraction. + _("Extracting collection."); + let rec3 = new WBORecord("tabs", "foo"); // Create through constructor. + Assert.equal(rec3.collection, "tabs"); + } finally { + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_resource.js b/services/sync/tests/unit/test_resource.js new file mode 100644 index 0000000000..5182784639 --- /dev/null +++ b/services/sync/tests/unit/test_resource.js @@ -0,0 +1,554 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Observers } = ChromeUtils.importESModule( + "resource://services-common/observers.sys.mjs" +); +const { Resource } = ChromeUtils.importESModule( + "resource://services-sync/resource.sys.mjs" +); +const { SyncAuthManager } = ChromeUtils.importESModule( + "resource://services-sync/sync_auth.sys.mjs" +); + +var fetched = false; +function server_open(metadata, response) { + let body; + if (metadata.method == "GET") { + fetched = true; + body = "This path exists"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + } else { + body = "Wrong request method"; + response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); + } + response.bodyOutputStream.write(body, body.length); +} + +function server_protected(metadata, response) { + let body; + + if (has_hawk_header(metadata)) { + body = "This path exists and is protected"; + response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } else { + body = "This path exists and is protected - failed"; + response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); + response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); + } + + response.bodyOutputStream.write(body, body.length); +} + +function server_404(metadata, response) { + let body = "File not found"; + response.setStatusLine(metadata.httpVersion, 404, "Not Found"); + response.bodyOutputStream.write(body, body.length); +} + +var pacFetched = false; +function server_pac(metadata, response) { + _("Invoked PAC handler."); + pacFetched = true; + let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader( + "Content-Type", + "application/x-ns-proxy-autoconfig", + false + ); + response.bodyOutputStream.write(body, body.length); +} + +var sample_data = { + some: "sample_data", + injson: "format", + number: 42, +}; + +function server_upload(metadata, response) { + let body; + + let input = readBytesFromInputStream(metadata.bodyInputStream); + if (input == JSON.stringify(sample_data)) { + body = "Valid data upload via " + metadata.method; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + } else { + body = "Invalid data upload via " + metadata.method + ": " + input; + response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error"); + } + + response.bodyOutputStream.write(body, body.length); +} + +function server_delete(metadata, response) { + let body; + if (metadata.method == "DELETE") { + body = "This resource has been deleted"; + response.setStatusLine(metadata.httpVersion, 200, "OK"); + } else { + body = "Wrong request method"; + response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); + } + response.bodyOutputStream.write(body, body.length); +} + +function server_json(metadata, response) { + let body = JSON.stringify(sample_data); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +const TIMESTAMP = 1274380461; + +function server_timestamp(metadata, response) { + let body = "Thank you for your request"; + response.setHeader("X-Weave-Timestamp", "" + TIMESTAMP, false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +function server_backoff(metadata, response) { + let body = "Hey, back off!"; + response.setHeader("X-Weave-Backoff", "600", false); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +function server_quota_notice(request, response) { + let body = "You're approaching quota."; + response.setHeader("X-Weave-Quota-Remaining", "1048576", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +function server_quota_error(request, response) { + let body = "14"; + response.setHeader("X-Weave-Quota-Remaining", "-1024", false); + response.setStatusLine(request.httpVersion, 400, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +function server_headers(metadata, response) { + let ignore_headers = [ + "host", + "user-agent", + "accept-language", + "accept-encoding", + "accept-charset", + "keep-alive", + "connection", + "pragma", + "origin", + "cache-control", + "content-length", + ]; + let headers = metadata.headers; + let header_names = []; + while (headers.hasMoreElements()) { + let header = headers.getNext().toString(); + if (!ignore_headers.includes(header)) { + header_names.push(header); + } + } + header_names = header_names.sort(); + + headers = {}; + for (let header of header_names) { + headers[header] = metadata.getHeader(header); + } + let body = JSON.stringify(headers); + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); +} + +var quotaValue; +Observers.add("weave:service:quota:remaining", function (subject) { + quotaValue = subject; +}); + +function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + Svc.PrefBranch.setIntPref("network.numRetries", 1); // speed up test + run_next_test(); +} + +// This apparently has to come first in order for our PAC URL to be hit. +// Don't put any other HTTP requests earlier in the file! +add_task(async function test_proxy_auth_redirect() { + _( + "Ensure that a proxy auth redirect (which switches out our channel) " + + "doesn't break Resource." + ); + let server = httpd_setup({ + "/open": server_open, + "/pac2": server_pac, + }); + + PACSystemSettings.PACURI = server.baseURI + "/pac2"; + installFakePAC(); + let res = new Resource(server.baseURI + "/open"); + let result = await res.get(); + Assert.ok(pacFetched); + Assert.ok(fetched); + Assert.equal("This path exists", result.data); + pacFetched = fetched = false; + uninstallFakePAC(); + await promiseStopServer(server); +}); + +add_task(async function test_new_channel() { + _("Ensure a redirect to a new channel is handled properly."); + + let resourceRequested = false; + function resourceHandler(metadata, response) { + resourceRequested = true; + + let body = "Test"; + response.setHeader("Content-Type", "text/plain"); + response.bodyOutputStream.write(body, body.length); + } + + let locationURL; + function redirectHandler(metadata, response) { + let body = "Redirecting"; + response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT"); + response.setHeader("Location", locationURL); + response.bodyOutputStream.write(body, body.length); + } + + let server = httpd_setup({ + "/resource": resourceHandler, + "/redirect": redirectHandler, + }); + locationURL = server.baseURI + "/resource"; + + let request = new Resource(server.baseURI + "/redirect"); + let content = await request.get(); + Assert.ok(resourceRequested); + Assert.equal(200, content.status); + Assert.ok("content-type" in content.headers); + Assert.equal("text/plain", content.headers["content-type"]); + + await promiseStopServer(server); +}); + +var server; + +add_test(function setup() { + server = httpd_setup({ + "/open": server_open, + "/protected": server_protected, + "/404": server_404, + "/upload": server_upload, + "/delete": server_delete, + "/json": server_json, + "/timestamp": server_timestamp, + "/headers": server_headers, + "/backoff": server_backoff, + "/pac2": server_pac, + "/quota-notice": server_quota_notice, + "/quota-error": server_quota_error, + }); + + run_next_test(); +}); + +add_test(function test_members() { + _("Resource object members"); + let uri = server.baseURI + "/open"; + let res = new Resource(uri); + Assert.ok(res.uri instanceof Ci.nsIURI); + Assert.equal(res.uri.spec, uri); + Assert.equal(res.spec, uri); + Assert.equal(typeof res.headers, "object"); + Assert.equal(typeof res.authenticator, "object"); + + run_next_test(); +}); + +add_task(async function test_get() { + _("GET a non-password-protected resource"); + let res = new Resource(server.baseURI + "/open"); + let content = await res.get(); + Assert.equal(content.data, "This path exists"); + Assert.equal(content.status, 200); + Assert.ok(content.success); + + // Observe logging messages. + let resLogger = res._log; + let dbg = resLogger.debug; + let debugMessages = []; + resLogger.debug = function (msg, extra) { + debugMessages.push(`${msg}: ${JSON.stringify(extra)}`); + dbg.call(this, msg); + }; + + // Since we didn't receive proper JSON data, accessing content.obj + // will result in a SyntaxError from JSON.parse + let didThrow = false; + try { + content.obj; + } catch (ex) { + didThrow = true; + } + Assert.ok(didThrow); + Assert.equal(debugMessages.length, 1); + Assert.equal( + debugMessages[0], + 'Parse fail: Response body starts: "This path exists"' + ); + resLogger.debug = dbg; +}); + +add_test(function test_basicauth() { + _("Test that the BasicAuthenticator doesn't screw up header case."); + let res1 = new Resource(server.baseURI + "/foo"); + res1.setHeader("Authorization", "Basic foobar"); + Assert.equal(res1._headers.authorization, "Basic foobar"); + Assert.equal(res1.headers.authorization, "Basic foobar"); + + run_next_test(); +}); + +add_task(async function test_get_protected_fail() { + _( + "GET a password protected resource (test that it'll fail w/o pass, no throw)" + ); + let res2 = new Resource(server.baseURI + "/protected"); + let content = await res2.get(); + Assert.equal(content.data, "This path exists and is protected - failed"); + Assert.equal(content.status, 401); + Assert.ok(!content.success); +}); + +add_task(async function test_get_protected_success() { + _("GET a password protected resource"); + let identityConfig = makeIdentityConfig(); + let syncAuthManager = new SyncAuthManager(); + configureFxAccountIdentity(syncAuthManager, identityConfig); + let auth = syncAuthManager.getResourceAuthenticator(); + let res3 = new Resource(server.baseURI + "/protected"); + res3.authenticator = auth; + Assert.equal(res3.authenticator, auth); + let content = await res3.get(); + Assert.equal(content.data, "This path exists and is protected"); + Assert.equal(content.status, 200); + Assert.ok(content.success); +}); + +add_task(async function test_get_404() { + _("GET a non-existent resource (test that it'll fail, but not throw)"); + let res4 = new Resource(server.baseURI + "/404"); + let content = await res4.get(); + Assert.equal(content.data, "File not found"); + Assert.equal(content.status, 404); + Assert.ok(!content.success); + + // Check some headers of the 404 response + Assert.equal(content.headers.connection, "close"); + Assert.equal(content.headers.server, "httpd.js"); + Assert.equal(content.headers["content-length"], 14); +}); + +add_task(async function test_put_string() { + _("PUT to a resource (string)"); + let res_upload = new Resource(server.baseURI + "/upload"); + let content = await res_upload.put(JSON.stringify(sample_data)); + Assert.equal(content.data, "Valid data upload via PUT"); + Assert.equal(content.status, 200); +}); + +add_task(async function test_put_object() { + _("PUT to a resource (object)"); + let res_upload = new Resource(server.baseURI + "/upload"); + let content = await res_upload.put(sample_data); + Assert.equal(content.data, "Valid data upload via PUT"); + Assert.equal(content.status, 200); +}); + +add_task(async function test_post_string() { + _("POST to a resource (string)"); + let res_upload = new Resource(server.baseURI + "/upload"); + let content = await res_upload.post(JSON.stringify(sample_data)); + Assert.equal(content.data, "Valid data upload via POST"); + Assert.equal(content.status, 200); +}); + +add_task(async function test_post_object() { + _("POST to a resource (object)"); + let res_upload = new Resource(server.baseURI + "/upload"); + let content = await res_upload.post(sample_data); + Assert.equal(content.data, "Valid data upload via POST"); + Assert.equal(content.status, 200); +}); + +add_task(async function test_delete() { + _("DELETE a resource"); + let res6 = new Resource(server.baseURI + "/delete"); + let content = await res6.delete(); + Assert.equal(content.data, "This resource has been deleted"); + Assert.equal(content.status, 200); +}); + +add_task(async function test_json_body() { + _("JSON conversion of response body"); + let res7 = new Resource(server.baseURI + "/json"); + let content = await res7.get(); + Assert.equal(content.data, JSON.stringify(sample_data)); + Assert.equal(content.status, 200); + Assert.equal(JSON.stringify(content.obj), JSON.stringify(sample_data)); +}); + +add_task(async function test_weave_timestamp() { + _("X-Weave-Timestamp header updates Resource.serverTime"); + // Before having received any response containing the + // X-Weave-Timestamp header, Resource.serverTime is null. + Assert.equal(Resource.serverTime, null); + let res8 = new Resource(server.baseURI + "/timestamp"); + await res8.get(); + Assert.equal(Resource.serverTime, TIMESTAMP); +}); + +add_task(async function test_get_default_headers() { + _("GET: Accept defaults to application/json"); + let res_headers = new Resource(server.baseURI + "/headers"); + let content = JSON.parse((await res_headers.get()).data); + Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); +}); + +add_task(async function test_put_default_headers() { + _( + "PUT: Accept defaults to application/json, Content-Type defaults to text/plain" + ); + let res_headers = new Resource(server.baseURI + "/headers"); + let content = JSON.parse((await res_headers.put("data")).data); + Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); + Assert.equal(content["content-type"], "text/plain"); +}); + +add_task(async function test_post_default_headers() { + _( + "POST: Accept defaults to application/json, Content-Type defaults to text/plain" + ); + let res_headers = new Resource(server.baseURI + "/headers"); + let content = JSON.parse((await res_headers.post("data")).data); + Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); + Assert.equal(content["content-type"], "text/plain"); +}); + +add_task(async function test_setHeader() { + _("setHeader(): setting simple header"); + let res_headers = new Resource(server.baseURI + "/headers"); + res_headers.setHeader("X-What-Is-Weave", "awesome"); + Assert.equal(res_headers.headers["x-what-is-weave"], "awesome"); + let content = JSON.parse((await res_headers.get()).data); + Assert.equal(content["x-what-is-weave"], "awesome"); +}); + +add_task(async function test_setHeader_overwrite() { + _("setHeader(): setting multiple headers, overwriting existing header"); + let res_headers = new Resource(server.baseURI + "/headers"); + res_headers.setHeader("X-WHAT-is-Weave", "more awesomer"); + res_headers.setHeader("X-Another-Header", "hello world"); + Assert.equal(res_headers.headers["x-what-is-weave"], "more awesomer"); + Assert.equal(res_headers.headers["x-another-header"], "hello world"); + let content = JSON.parse((await res_headers.get()).data); + Assert.equal(content["x-what-is-weave"], "more awesomer"); + Assert.equal(content["x-another-header"], "hello world"); +}); + +add_task(async function test_put_override_content_type() { + _("PUT: override default Content-Type"); + let res_headers = new Resource(server.baseURI + "/headers"); + res_headers.setHeader("Content-Type", "application/foobar"); + Assert.equal(res_headers.headers["content-type"], "application/foobar"); + let content = JSON.parse((await res_headers.put("data")).data); + Assert.equal(content["content-type"], "application/foobar"); +}); + +add_task(async function test_post_override_content_type() { + _("POST: override default Content-Type"); + let res_headers = new Resource(server.baseURI + "/headers"); + res_headers.setHeader("Content-Type", "application/foobar"); + let content = JSON.parse((await res_headers.post("data")).data); + Assert.equal(content["content-type"], "application/foobar"); +}); + +add_task(async function test_weave_backoff() { + _("X-Weave-Backoff header notifies observer"); + let backoffInterval; + function onBackoff(subject, data) { + backoffInterval = subject; + } + Observers.add("weave:service:backoff:interval", onBackoff); + + let res10 = new Resource(server.baseURI + "/backoff"); + await res10.get(); + Assert.equal(backoffInterval, 600); +}); + +add_task(async function test_quota_error() { + _("X-Weave-Quota-Remaining header notifies observer on successful requests."); + let res10 = new Resource(server.baseURI + "/quota-error"); + let content = await res10.get(); + Assert.equal(content.status, 400); + Assert.equal(quotaValue, undefined); // HTTP 400, so no observer notification. +}); + +add_task(async function test_quota_notice() { + let res10 = new Resource(server.baseURI + "/quota-notice"); + let content = await res10.get(); + Assert.equal(content.status, 200); + Assert.equal(quotaValue, 1048576); +}); + +add_task(async function test_preserve_exceptions() { + _("Error handling preserves exception information"); + let res11 = new Resource("http://localhost:12345/does/not/exist"); + await Assert.rejects(res11.get(), error => { + Assert.notEqual(error, null); + Assert.equal(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); + Assert.equal(error.name, "NS_ERROR_CONNECTION_REFUSED"); + return true; + }); +}); + +add_task(async function test_timeout() { + _("Ensure channel timeouts are thrown appropriately."); + let res19 = new Resource(server.baseURI + "/json"); + res19.ABORT_TIMEOUT = 0; + await Assert.rejects(res19.get(), error => { + Assert.equal(error.result, Cr.NS_ERROR_NET_TIMEOUT); + return true; + }); +}); + +add_test(function test_uri_construction() { + _("Testing URI construction."); + let args = []; + args.push("newer=" + 1234); + args.push("limit=" + 1234); + args.push("sort=" + 1234); + + let query = "?" + args.join("&"); + + let uri1 = CommonUtils.makeURI("http://foo/" + query).QueryInterface( + Ci.nsIURL + ); + let uri2 = CommonUtils.makeURI("http://foo/").QueryInterface(Ci.nsIURL); + uri2 = uri2.mutate().setQuery(query).finalize().QueryInterface(Ci.nsIURL); + Assert.equal(uri1.query, uri2.query); + + run_next_test(); +}); + +/** + * End of tests that rely on a single HTTP server. + * All tests after this point must begin and end their own. + */ +add_test(function eliminate_server() { + server.stop(run_next_test); +}); diff --git a/services/sync/tests/unit/test_resource_header.js b/services/sync/tests/unit/test_resource_header.js new file mode 100644 index 0000000000..e45b4a9864 --- /dev/null +++ b/services/sync/tests/unit/test_resource_header.js @@ -0,0 +1,63 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { Resource } = ChromeUtils.importESModule( + "resource://services-sync/resource.sys.mjs" +); + +var httpServer = new HttpServer(); +httpServer.registerPathHandler("/content", contentHandler); +httpServer.start(-1); + +const HTTP_PORT = httpServer.identity.primaryPort; +const TEST_URL = "http://localhost:" + HTTP_PORT + "/content"; +const BODY = "response body"; + +// Keep headers for later inspection. +var auth = null; +var foo = null; +function contentHandler(metadata, response) { + _("Handling request."); + auth = metadata.getHeader("Authorization"); + foo = metadata.getHeader("X-Foo"); + + _("Extracted headers. " + auth + ", " + foo); + + response.setHeader("Content-Type", "text/plain"); + response.bodyOutputStream.write(BODY, BODY.length); +} + +// Set a proxy function to cause an internal redirect. +function triggerRedirect() { + const PROXY_FUNCTION = + "function FindProxyForURL(url, host) {" + + " return 'PROXY a_non_existent_domain_x7x6c572v:80; " + + "PROXY localhost:" + + HTTP_PORT + + "';" + + "}"; + + let prefs = Services.prefs.getBranch("network.proxy."); + prefs.setIntPref("type", 2); + prefs.setStringPref("autoconfig_url", "data:text/plain," + PROXY_FUNCTION); +} + +add_task(async function test_headers_copied() { + triggerRedirect(); + + _("Issuing request."); + let resource = new Resource(TEST_URL); + resource.setHeader("Authorization", "Basic foobar"); + resource.setHeader("X-Foo", "foofoo"); + + let result = await resource.get(TEST_URL); + _("Result: " + result.data); + + Assert.equal(result.data, BODY); + Assert.equal(auth, "Basic foobar"); + Assert.equal(foo, "foofoo"); + + await promiseStopServer(httpServer); +}); diff --git a/services/sync/tests/unit/test_resource_ua.js b/services/sync/tests/unit/test_resource_ua.js new file mode 100644 index 0000000000..115fa85b84 --- /dev/null +++ b/services/sync/tests/unit/test_resource_ua.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Resource } = ChromeUtils.importESModule( + "resource://services-sync/resource.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +var httpProtocolHandler = Cc[ + "@mozilla.org/network/protocol;1?name=http" +].getService(Ci.nsIHttpProtocolHandler); + +// Tracking info/collections. +var collectionsHelper = track_collections_helper(); + +var meta_global; +var server; + +var expectedUA; +var ua; +function uaHandler(f) { + return function (request, response) { + ua = request.getHeader("User-Agent"); + return f(request, response); + }; +} + +add_task(async function setup() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + meta_global = new ServerWBO("global"); + server = httpd_setup({ + "/1.1/johndoe/info/collections": uaHandler(collectionsHelper.handler), + "/1.1/johndoe/storage/meta/global": uaHandler(meta_global.handler()), + }); + + await configureIdentity({ username: "johndoe" }, server); + _("Server URL: " + server.baseURI); + + // Note this string is missing the trailing ".destkop" as the test + // adjusts the "client.type" pref where that portion comes from. + expectedUA = + Services.appinfo.name + + "/" + + Services.appinfo.version + + " (" + + httpProtocolHandler.oscpu + + ")" + + " FxSync/" + + WEAVE_VERSION + + "." + + Services.appinfo.appBuildID; +}); + +add_task(async function test_fetchInfo() { + _("Testing _fetchInfo."); + await Service.login(); + await Service._fetchInfo(); + _("User-Agent: " + ua); + Assert.equal(ua, expectedUA + ".desktop"); + ua = ""; +}); + +add_task(async function test_desktop_post() { + _("Testing direct Resource POST."); + let r = new Resource(server.baseURI + "/1.1/johndoe/storage/meta/global"); + await r.post("foo=bar"); + _("User-Agent: " + ua); + Assert.equal(ua, expectedUA + ".desktop"); + ua = ""; +}); + +add_task(async function test_desktop_get() { + _("Testing async."); + Svc.PrefBranch.setStringPref("client.type", "desktop"); + let r = new Resource(server.baseURI + "/1.1/johndoe/storage/meta/global"); + await r.get(); + _("User-Agent: " + ua); + Assert.equal(ua, expectedUA + ".desktop"); + ua = ""; +}); + +add_task(async function test_mobile_get() { + _("Testing mobile."); + Svc.PrefBranch.setStringPref("client.type", "mobile"); + let r = new Resource(server.baseURI + "/1.1/johndoe/storage/meta/global"); + await r.get(); + _("User-Agent: " + ua); + Assert.equal(ua, expectedUA + ".mobile"); + ua = ""; +}); + +add_test(function tear_down() { + server.stop(run_next_test); +}); diff --git a/services/sync/tests/unit/test_score_triggers.js b/services/sync/tests/unit/test_score_triggers.js new file mode 100644 index 0000000000..c6afa06407 --- /dev/null +++ b/services/sync/tests/unit/test_score_triggers.js @@ -0,0 +1,151 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); + +// Tracking info/collections. +var collectionsHelper = track_collections_helper(); +var upd = collectionsHelper.with_updated_collection; + +function sync_httpd_setup() { + let handlers = {}; + + handlers["/1.1/johndoe/storage/meta/global"] = new ServerWBO( + "global", + {} + ).handler(); + handlers["/1.1/johndoe/storage/steam"] = new ServerWBO("steam", {}).handler(); + + handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler; + delete collectionsHelper.collections.crypto; + delete collectionsHelper.collections.meta; + + let cr = new ServerWBO("keys"); + handlers["/1.1/johndoe/storage/crypto/keys"] = upd("crypto", cr.handler()); + + let cl = new ServerCollection(); + handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); + + return httpd_setup(handlers); +} + +async function setUp(server) { + let engineInfo = await registerRotaryEngine(); + await SyncTestingInfrastructure(server, "johndoe", "ilovejane"); + return engineInfo; +} + +add_task(async function test_tracker_score_updated() { + enableValidationPrefs(); + let { engine, tracker } = await registerRotaryEngine(); + + let scoreUpdated = 0; + + function onScoreUpdated() { + scoreUpdated++; + } + + Svc.Obs.add("weave:engine:score:updated", onScoreUpdated); + + try { + Assert.equal(engine.score, 0); + + tracker.score += SCORE_INCREMENT_SMALL; + Assert.equal(engine.score, SCORE_INCREMENT_SMALL); + + Assert.equal(scoreUpdated, 1); + } finally { + Svc.Obs.remove("weave:engine:score:updated", onScoreUpdated); + tracker.resetScore(); + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_sync_triggered() { + let server = sync_httpd_setup(); + let { engine, tracker } = await setUp(server); + + await Service.login(); + + Service.scheduler.syncThreshold = MULTI_DEVICE_THRESHOLD; + + Assert.equal(Status.login, LOGIN_SUCCEEDED); + tracker.score += SCORE_INCREMENT_XLARGE; + + await promiseOneObserver("weave:service:sync:finish"); + + await Service.startOver(); + await promiseStopServer(server); + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); + +add_task(async function test_clients_engine_sync_triggered() { + enableValidationPrefs(); + + _("Ensure that client engine score changes trigger a sync."); + + // The clients engine is not registered like other engines. Therefore, + // it needs special treatment throughout the code. Here, we verify the + // global score tracker gives it that treatment. See bug 676042 for more. + + let server = sync_httpd_setup(); + let { engine, tracker } = await setUp(server); + await Service.login(); + + Service.scheduler.syncThreshold = MULTI_DEVICE_THRESHOLD; + Assert.equal(Status.login, LOGIN_SUCCEEDED); + Service.clientsEngine._tracker.score += SCORE_INCREMENT_XLARGE; + + await promiseOneObserver("weave:service:sync:finish"); + _("Sync due to clients engine change completed."); + + await Service.startOver(); + await promiseStopServer(server); + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); + +add_task(async function test_incorrect_credentials_sync_not_triggered() { + enableValidationPrefs(); + + _( + "Ensure that score changes don't trigger a sync if Status.login != LOGIN_SUCCEEDED." + ); + let server = sync_httpd_setup(); + let { engine, tracker } = await setUp(server); + + // Ensure we don't actually try to sync. + function onSyncStart() { + do_throw("Should not get here!"); + } + Svc.Obs.add("weave:service:sync:start", onSyncStart); + + // Faking incorrect credentials to prevent score update. + Status.login = LOGIN_FAILED_LOGIN_REJECTED; + tracker.score += SCORE_INCREMENT_XLARGE; + + // First wait >100ms (nsITimers can take up to that much time to fire, so + // we can account for the timer in delayedAutoconnect) and then one event + // loop tick (to account for a possible call to weave:service:sync:start). + await promiseNamedTimer(150, {}, "timer"); + await Async.promiseYield(); + + Svc.Obs.remove("weave:service:sync:start", onSyncStart); + + Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED); + + await Service.startOver(); + await promiseStopServer(server); + + await tracker.clearChangedIDs(); + await Service.engineManager.unregister(engine); +}); diff --git a/services/sync/tests/unit/test_service_attributes.js b/services/sync/tests/unit/test_service_attributes.js new file mode 100644 index 0000000000..86ca3c30c0 --- /dev/null +++ b/services/sync/tests/unit/test_service_attributes.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { FakeGUIDService } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/fakeservices.sys.mjs" +); + +add_task(async function test_urls() { + _("URL related Service properties correspond to preference settings."); + try { + Assert.equal(Service.clusterURL, ""); + Assert.ok(!Service.userBaseURL); + Assert.equal(Service.infoURL, undefined); + Assert.equal(Service.storageURL, undefined); + Assert.equal(Service.metaURL, undefined); + + _("The 'clusterURL' attribute updates preferences and cached URLs."); + + // Since we don't have a cluster URL yet, these will still not be defined. + Assert.equal(Service.infoURL, undefined); + Assert.ok(!Service.userBaseURL); + Assert.equal(Service.storageURL, undefined); + Assert.equal(Service.metaURL, undefined); + + Service.clusterURL = "http://weave.cluster/1.1/johndoe/"; + + Assert.equal(Service.userBaseURL, "http://weave.cluster/1.1/johndoe/"); + Assert.equal( + Service.infoURL, + "http://weave.cluster/1.1/johndoe/info/collections" + ); + Assert.equal( + Service.storageURL, + "http://weave.cluster/1.1/johndoe/storage/" + ); + Assert.equal( + Service.metaURL, + "http://weave.cluster/1.1/johndoe/storage/meta/global" + ); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_test(function test_syncID() { + _("Service.syncID is auto-generated, corresponds to preference."); + new FakeGUIDService(); + + try { + // Ensure pristine environment + Assert.equal( + Svc.PrefBranch.getPrefType("client.syncID"), + Ci.nsIPrefBranch.PREF_INVALID + ); + + // Performing the first get on the attribute will generate a new GUID. + Assert.equal(Service.syncID, "fake-guid-00"); + Assert.equal(Svc.PrefBranch.getStringPref("client.syncID"), "fake-guid-00"); + + Svc.PrefBranch.setStringPref("client.syncID", Utils.makeGUID()); + Assert.equal(Svc.PrefBranch.getStringPref("client.syncID"), "fake-guid-01"); + Assert.equal(Service.syncID, "fake-guid-01"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + new FakeGUIDService(); + run_next_test(); + } +}); + +add_test(function test_locked() { + _("The 'locked' attribute can be toggled with lock() and unlock()"); + + // Defaults to false + Assert.equal(Service.locked, false); + + Assert.equal(Service.lock(), true); + Assert.equal(Service.locked, true); + + // Locking again will return false + Assert.equal(Service.lock(), false); + + Service.unlock(); + Assert.equal(Service.locked, false); + run_next_test(); +}); diff --git a/services/sync/tests/unit/test_service_cluster.js b/services/sync/tests/unit/test_service_cluster.js new file mode 100644 index 0000000000..b4c14f910d --- /dev/null +++ b/services/sync/tests/unit/test_service_cluster.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_findCluster() { + syncTestLogging(); + _("Test Service._findCluster()"); + try { + let whenReadyToAuthenticate = Promise.withResolvers(); + Service.identity.whenReadyToAuthenticate = whenReadyToAuthenticate; + whenReadyToAuthenticate.resolve(true); + + Service.identity._ensureValidToken = () => + Promise.reject(new Error("Connection refused")); + + _("_findCluster() throws on network errors (e.g. connection refused)."); + await Assert.rejects(Service.identity._findCluster(), /Connection refused/); + + Service.identity._ensureValidToken = () => + Promise.resolve({ endpoint: "http://weave.user.node" }); + + _("_findCluster() returns the user's cluster node"); + let cluster = await Service.identity._findCluster(); + Assert.equal(cluster, "http://weave.user.node/"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_setCluster() { + syncTestLogging(); + _("Test Service._setCluster()"); + try { + _("Check initial state."); + Assert.equal(Service.clusterURL, ""); + + Service.identity._findCluster = () => "http://weave.user.node/"; + + _("Set the cluster URL."); + Assert.ok(await Service.identity.setCluster()); + Assert.equal(Service.clusterURL, "http://weave.user.node/"); + + _("Setting it again won't make a difference if it's the same one."); + Assert.ok(!(await Service.identity.setCluster())); + Assert.equal(Service.clusterURL, "http://weave.user.node/"); + + _("A 'null' response won't make a difference either."); + Service.identity._findCluster = () => null; + Assert.ok(!(await Service.identity.setCluster())); + Assert.equal(Service.clusterURL, "http://weave.user.node/"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); diff --git a/services/sync/tests/unit/test_service_detect_upgrade.js b/services/sync/tests/unit/test_service_detect_upgrade.js new file mode 100644 index 0000000000..d0db19af93 --- /dev/null +++ b/services/sync/tests/unit/test_service_detect_upgrade.js @@ -0,0 +1,274 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { CryptoWrapper, WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function v4_upgrade() { + enableValidationPrefs(); + + let clients = new ServerCollection(); + let meta_global = new ServerWBO("global"); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + let collections = collectionsHelper.collections; + + let keysWBO = new ServerWBO("keys"); + let server = httpd_setup({ + // Special. + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), + + // Track modified times. + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), + + // Just so we don't get 404s in the logs. + "/1.1/johndoe/storage/bookmarks": new ServerCollection().handler(), + "/1.1/johndoe/storage/forms": new ServerCollection().handler(), + "/1.1/johndoe/storage/history": new ServerCollection().handler(), + "/1.1/johndoe/storage/passwords": new ServerCollection().handler(), + "/1.1/johndoe/storage/prefs": new ServerCollection().handler(), + }); + + try { + Service.status.resetSync(); + + _("Logging in."); + + await configureIdentity({ username: "johndoe" }, server); + + await Service.login(); + Assert.ok(Service.isLoggedIn); + await Service.verifyAndFetchSymmetricKeys(); + Assert.ok(await Service._remoteSetup()); + + async function test_out_of_date() { + _("Old meta/global: " + JSON.stringify(meta_global)); + meta_global.payload = JSON.stringify({ + syncID: "foooooooooooooooooooooooooo", + storageVersion: STORAGE_VERSION + 1, + }); + collections.meta = Date.now() / 1000; + _("New meta/global: " + JSON.stringify(meta_global)); + Service.recordManager.set(Service.metaURL, meta_global); + try { + await Service.sync(); + } catch (ex) {} + Assert.equal(Service.status.sync, VERSION_OUT_OF_DATE); + } + + // See what happens when we bump the storage version. + _("Syncing after server has been upgraded."); + await test_out_of_date(); + + // Same should happen after a wipe. + _("Syncing after server has been upgraded and wiped."); + await Service.wipeServer(); + await test_out_of_date(); + + // Now's a great time to test what happens when keys get replaced. + _("Syncing afresh..."); + Service.logout(); + Service.collectionKeys.clear(); + meta_global.payload = JSON.stringify({ + syncID: "foooooooooooooobbbbbbbbbbbb", + storageVersion: STORAGE_VERSION, + }); + collections.meta = Date.now() / 1000; + Service.recordManager.set(Service.metaURL, meta_global); + await Service.login(); + Assert.ok(Service.isLoggedIn); + await Service.sync(); + Assert.ok(Service.isLoggedIn); + + let serverDecrypted; + let serverKeys; + let serverResp; + + async function retrieve_server_default() { + serverKeys = serverResp = serverDecrypted = null; + + serverKeys = new CryptoWrapper("crypto", "keys"); + serverResp = ( + await serverKeys.fetch(Service.resource(Service.cryptoKeysURL)) + ).response; + Assert.ok(serverResp.success); + + serverDecrypted = await serverKeys.decrypt( + Service.identity.syncKeyBundle + ); + _("Retrieved WBO: " + JSON.stringify(serverDecrypted)); + _("serverKeys: " + JSON.stringify(serverKeys)); + + return serverDecrypted.default; + } + + async function retrieve_and_compare_default(should_succeed) { + let serverDefault = await retrieve_server_default(); + let localDefault = Service.collectionKeys.keyForCollection().keyPairB64; + + _("Retrieved keyBundle: " + JSON.stringify(serverDefault)); + _("Local keyBundle: " + JSON.stringify(localDefault)); + + if (should_succeed) { + Assert.equal( + JSON.stringify(serverDefault), + JSON.stringify(localDefault) + ); + } else { + Assert.notEqual( + JSON.stringify(serverDefault), + JSON.stringify(localDefault) + ); + } + } + + // Uses the objects set above. + async function set_server_keys(pair) { + serverDecrypted.default = pair; + serverKeys.cleartext = serverDecrypted; + await serverKeys.encrypt(Service.identity.syncKeyBundle); + await serverKeys.upload(Service.resource(Service.cryptoKeysURL)); + } + + _("Checking we have the latest keys."); + await retrieve_and_compare_default(true); + + _("Update keys on server."); + await set_server_keys([ + "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", + "aaaaaaaaaaaapxMO6TEWtLIOv9dj6kBAJdzhWDkkkis=", + ]); + + _("Checking that we no longer have the latest keys."); + await retrieve_and_compare_default(false); + + _("Indeed, they're what we set them to..."); + Assert.equal( + "KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", + (await retrieve_server_default())[0] + ); + + _("Sync. Should download changed keys automatically."); + let oldClientsModified = collections.clients; + let oldTabsModified = collections.tabs; + + await Service.login(); + await Service.sync(); + _("New key should have forced upload of data."); + _("Tabs: " + oldTabsModified + " < " + collections.tabs); + _("Clients: " + oldClientsModified + " < " + collections.clients); + Assert.ok(collections.clients > oldClientsModified); + Assert.ok(collections.tabs > oldTabsModified); + + _("... and keys will now match."); + await retrieve_and_compare_default(true); + + // Clean up. + await Service.startOver(); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); + +add_task(async function v5_upgrade() { + enableValidationPrefs(); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + let keysWBO = new ServerWBO("keys"); + let bulkWBO = new ServerWBO("bulk"); + let clients = new ServerCollection(); + let meta_global = new ServerWBO("global"); + + let server = httpd_setup({ + // Special. + "/1.1/johndoe/storage/meta/global": upd("meta", meta_global.handler()), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/crypto/bulk": upd("crypto", bulkWBO.handler()), + + // Track modified times. + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/tabs": upd("tabs", new ServerCollection().handler()), + }); + + try { + Service.status.resetSync(); + + Service.clusterURL = server.baseURI + "/"; + + await configureIdentity({ username: "johndoe" }, server); + + // Test an upgrade where the contents of the server would cause us to error + // -- keys decrypted with a different sync key, for example. + _("Testing v4 -> v5 (or similar) upgrade."); + async function update_server_keys(syncKeyBundle, wboName, collWBO) { + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", wboName); + await serverKeys.encrypt(syncKeyBundle); + let res = Service.resource(Service.storageURL + collWBO); + Assert.ok((await serverKeys.upload(res)).success); + } + + _("Bumping version."); + // Bump version on the server. + let m = new WBORecord("meta", "global"); + m.payload = { + syncID: "foooooooooooooooooooooooooo", + storageVersion: STORAGE_VERSION + 1, + }; + await m.upload(Service.resource(Service.metaURL)); + + _("New meta/global: " + JSON.stringify(meta_global)); + + // Fill the keys with bad data. + let badKeys = new BulkKeyBundle("crypto"); + await badKeys.generateRandom(); + await update_server_keys(badKeys, "keys", "crypto/keys"); // v4 + await update_server_keys(badKeys, "bulk", "crypto/bulk"); // v5 + + _("Generating new keys."); + await generateNewKeys(Service.collectionKeys); + + // Now sync and see what happens. It should be a version fail, not a crypto + // fail. + + _("Logging in."); + try { + await Service.login(); + } catch (e) { + _("Exception: " + e); + } + _("Status: " + Service.status); + Assert.ok(!Service.isLoggedIn); + Assert.equal(VERSION_OUT_OF_DATE, Service.status.sync); + + // Clean up. + await Service.startOver(); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); + +function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + run_next_test(); +} diff --git a/services/sync/tests/unit/test_service_login.js b/services/sync/tests/unit/test_service_login.js new file mode 100644 index 0000000000..c75799d38e --- /dev/null +++ b/services/sync/tests/unit/test_service_login.js @@ -0,0 +1,224 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + +function login_handling(handler) { + return function (request, response) { + if (has_hawk_header(request)) { + handler(request, response); + } else { + let body = "Unauthorized"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.setHeader("Content-Type", "text/plain"); + response.bodyOutputStream.write(body, body.length); + } + }; +} + +add_task(async function test_offline() { + try { + _("The right bits are set when we're offline."); + Services.io.offline = true; + Assert.ok(!(await Service.login())); + Assert.equal(Service.status.login, LOGIN_FAILED_NETWORK_ERROR); + Services.io.offline = false; + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +function setup() { + let janeHelper = track_collections_helper(); + let janeU = janeHelper.with_updated_collection; + let johnHelper = track_collections_helper(); + let johnU = johnHelper.with_updated_collection; + + let server = httpd_setup({ + "/1.1/johndoe/info/collections": login_handling(johnHelper.handler), + "/1.1/janedoe/info/collections": login_handling(janeHelper.handler), + + // We need these handlers because we test login, and login + // is where keys are generated or fetched. + // TODO: have Jane fetch her keys, not generate them... + "/1.1/johndoe/storage/crypto/keys": johnU( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": johnU( + "meta", + new ServerWBO("global").handler() + ), + "/1.1/janedoe/storage/crypto/keys": janeU( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/janedoe/storage/meta/global": janeU( + "meta", + new ServerWBO("global").handler() + ), + }); + + return server; +} + +add_task(async function test_not_logged_in() { + let server = setup(); + try { + await Service.login(); + Assert.ok(!Service.isLoggedIn, "no user configured, so can't be logged in"); + Assert.equal(Service._checkSync(), kSyncNotConfigured); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); + +add_task(async function test_login_logout() { + enableValidationPrefs(); + + let server = setup(); + + try { + _("Force the initial state."); + Service.status.service = STATUS_OK; + Assert.equal(Service.status.service, STATUS_OK); + + _("Try logging in. It won't work because we're not configured yet."); + await Service.login(); + Assert.equal(Service.status.service, CLIENT_NOT_CONFIGURED); + Assert.equal(Service.status.login, LOGIN_FAILED_NO_USERNAME); + Assert.ok(!Service.isLoggedIn); + + _("Try again with a configured account"); + await configureIdentity({ username: "johndoe" }, server); + await Service.login(); + Assert.equal(Service.status.service, STATUS_OK); + Assert.equal(Service.status.login, LOGIN_SUCCEEDED); + Assert.ok(Service.isLoggedIn); + + _("Logout."); + Service.logout(); + Assert.ok(!Service.isLoggedIn); + + _("Logging out again won't do any harm."); + Service.logout(); + Assert.ok(!Service.isLoggedIn); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); + +add_task(async function test_login_on_sync() { + enableValidationPrefs(); + + let server = setup(); + await configureIdentity({ username: "johndoe" }, server); + + try { + _("Sync calls login."); + let oldLogin = Service.login; + let loginCalled = false; + Service.login = async function () { + loginCalled = true; + Service.status.login = LOGIN_SUCCEEDED; + this._loggedIn = false; // So that sync aborts. + return true; + }; + + await Service.sync(); + + Assert.ok(loginCalled); + Service.login = oldLogin; + + // Stub mpLocked. + let mpLocked = true; + Utils.mpLocked = () => mpLocked; + + // Stub scheduleNextSync. This gets called within checkSyncStatus if we're + // ready to sync, so use it as an indicator. + let scheduleNextSyncF = Service.scheduler.scheduleNextSync; + let scheduleCalled = false; + Service.scheduler.scheduleNextSync = function (wait) { + scheduleCalled = true; + scheduleNextSyncF.call(this, wait); + }; + + // Autoconnect still tries to connect in the background (useful behavior: + // for non-MP users and unlocked MPs, this will detect version expiry + // earlier). + // + // Consequently, non-MP users will be logged in as in the pre-Bug 543784 world, + // and checkSyncStatus reflects that by waiting for login. + // + // This process doesn't apply if your MP is still locked, so we make + // checkSyncStatus accept a locked MP in place of being logged in. + // + // This test exercises these two branches. + + _("We're ready to sync if locked."); + Service.enabled = true; + Services.io.offline = false; + Service.scheduler.checkSyncStatus(); + Assert.ok(scheduleCalled); + + _("... and also if we're not locked."); + scheduleCalled = false; + mpLocked = false; + Service.scheduler.checkSyncStatus(); + Assert.ok(scheduleCalled); + Service.scheduler.scheduleNextSync = scheduleNextSyncF; + + // TODO: need better tests around master password prompting. See Bug 620583. + + mpLocked = true; + + // Testing exception handling if master password dialog is canceled. + // Do this by monkeypatching. + Service.identity.unlockAndVerifyAuthState = () => + Promise.resolve(MASTER_PASSWORD_LOCKED); + + let cSTCalled = false; + let lockedSyncCalled = false; + + Service.scheduler.clearSyncTriggers = function () { + cSTCalled = true; + }; + Service._lockedSync = async function () { + lockedSyncCalled = true; + }; + + _("If master password is canceled, login fails and we report lockage."); + Assert.ok(!(await Service.login())); + Assert.equal(Service.status.login, MASTER_PASSWORD_LOCKED); + Assert.equal(Service.status.service, LOGIN_FAILED); + _("Locked? " + Utils.mpLocked()); + _("checkSync reports the correct term."); + Assert.equal(Service._checkSync(), kSyncMasterPasswordLocked); + + _("Sync doesn't proceed and clears triggers if MP is still locked."); + await Service.sync(); + + Assert.ok(cSTCalled); + Assert.ok(!lockedSyncCalled); + + // N.B., a bunch of methods are stubbed at this point. Be careful putting + // new tests after this point! + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_service_startOver.js b/services/sync/tests/unit/test_service_startOver.js new file mode 100644 index 0000000000..22d92c76ef --- /dev/null +++ b/services/sync/tests/unit/test_service_startOver.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function BlaEngine() { + SyncEngine.call(this, "Bla", Service); +} +BlaEngine.prototype = { + removed: false, + async removeClientData() { + this.removed = true; + }, +}; +Object.setPrototypeOf(BlaEngine.prototype, SyncEngine.prototype); + +add_task(async function setup() { + await Service.engineManager.register(BlaEngine); +}); + +add_task(async function test_resetLocalData() { + await configureIdentity(); + Service.status.enforceBackoff = true; + Service.status.backoffInterval = 42; + Service.status.minimumNextSync = 23; + + // Verify set up. + Assert.equal(Service.status.checkSetup(), STATUS_OK); + + // Verify state that the observer sees. + let observerCalled = false; + Svc.Obs.add("weave:service:start-over", function onStartOver() { + Svc.Obs.remove("weave:service:start-over", onStartOver); + observerCalled = true; + + Assert.equal(Service.status.service, CLIENT_NOT_CONFIGURED); + }); + + await Service.startOver(); + Assert.ok(observerCalled); + + // Verify the site was nuked from orbit. + Assert.equal( + Svc.PrefBranch.getPrefType("username"), + Ci.nsIPrefBranch.PREF_INVALID + ); + + Assert.equal(Service.status.service, CLIENT_NOT_CONFIGURED); + Assert.ok(!Service.status.enforceBackoff); + Assert.equal(Service.status.backoffInterval, 0); + Assert.equal(Service.status.minimumNextSync, 0); +}); + +add_task(async function test_removeClientData() { + let engine = Service.engineManager.get("bla"); + + // No cluster URL = no removal. + Assert.ok(!engine.removed); + await Service.startOver(); + Assert.ok(!engine.removed); + + Service.clusterURL = "https://localhost/"; + + Assert.ok(!engine.removed); + await Service.startOver(); + Assert.ok(engine.removed); +}); + +add_task(async function test_reset_SyncScheduler() { + // Some non-default values for SyncScheduler's attributes. + Service.scheduler.idle = true; + Service.scheduler.hasIncomingItems = true; + Svc.PrefBranch.setIntPref("clients.devices.desktop", 42); + Service.scheduler.nextSync = Date.now(); + Service.scheduler.syncThreshold = MULTI_DEVICE_THRESHOLD; + Service.scheduler.syncInterval = Service.scheduler.activeInterval; + + await Service.startOver(); + + Assert.ok(!Service.scheduler.idle); + Assert.ok(!Service.scheduler.hasIncomingItems); + Assert.equal(Service.scheduler.numClients, 0); + Assert.equal(Service.scheduler.nextSync, 0); + Assert.equal(Service.scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + Assert.equal( + Service.scheduler.syncInterval, + Service.scheduler.singleDeviceInterval + ); +}); diff --git a/services/sync/tests/unit/test_service_startup.js b/services/sync/tests/unit/test_service_startup.js new file mode 100644 index 0000000000..66623a951a --- /dev/null +++ b/services/sync/tests/unit/test_service_startup.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Svc.PrefBranch.setStringPref("services.sync.log.appender.dump", "All"); +Svc.PrefBranch.setStringPref("registerEngines", "Tab,Bookmarks,Form,History"); + +add_task(async function run_test() { + validate_all_future_pings(); + _("When imported, Service.onStartup is called"); + + let xps = Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + Assert.ok(!xps.enabled); + + // Test fixtures + let { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" + ); + Services.prefs.setStringPref("services.sync.username", "johndoe"); + Assert.ok(xps.enabled); + + _("Service is enabled."); + Assert.equal(Service.enabled, true); + + _("Observers are notified of startup"); + Assert.ok(!Service.status.ready); + Assert.ok(!xps.ready); + + await promiseOneObserver("weave:service:ready"); + + Assert.ok(Service.status.ready); + Assert.ok(xps.ready); + + _("Engines are registered."); + let engines = Service.engineManager.getAll(); + if (AppConstants.MOZ_APP_NAME == "thunderbird") { + // Thunderbird's engines are registered later, so they're not here yet. + Assert.deepEqual( + engines.map(engine => engine.name), + [] + ); + } else { + Assert.deepEqual( + engines.map(engine => engine.name), + ["tabs", "bookmarks", "forms", "history"] + ); + } + + // Clean up. + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + + do_test_finished(); +}); diff --git a/services/sync/tests/unit/test_service_sync_401.js b/services/sync/tests/unit/test_service_sync_401.js new file mode 100644 index 0000000000..a0bde0b0ab --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_401.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function login_handling(handler) { + return function (request, response) { + if ( + request.hasHeader("Authorization") && + request.getHeader("Authorization").includes('Hawk id="id"') + ) { + handler(request, response); + } else { + let body = "Unauthorized"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.bodyOutputStream.write(body, body.length); + } + }; +} + +add_task(async function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + let server = httpd_setup({ + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": upd( + "meta", + new ServerWBO("global").handler() + ), + "/1.1/johndoe/info/collections": login_handling(collectionsHelper.handler), + }); + + const GLOBAL_SCORE = 42; + + try { + _("Set up test fixtures."); + await SyncTestingInfrastructure(server, "johndoe", "ilovejane"); + Service.scheduler.globalScore = GLOBAL_SCORE; + // Avoid daily ping + Svc.PrefBranch.setIntPref("lastPing", Math.floor(Date.now() / 1000)); + + let threw = false; + Svc.Obs.add("weave:service:sync:error", function (subject, data) { + threw = true; + }); + + _("Initial state: We're successfully logged in."); + await Service.login(); + Assert.ok(Service.isLoggedIn); + Assert.equal(Service.status.login, LOGIN_SUCCEEDED); + + _("Simulate having changed the password somewhere else."); + Service.identity._token.id = "somethingelse"; + Service.identity.unlockAndVerifyAuthState = () => + Promise.resolve(LOGIN_FAILED_LOGIN_REJECTED); + + _("Let's try to sync."); + await Service.sync(); + + _("Verify that sync() threw an exception."); + Assert.ok(threw); + + _("We're no longer logged in."); + Assert.ok(!Service.isLoggedIn); + + _("Sync status won't have changed yet, because we haven't tried again."); + + _("globalScore is reset upon starting a sync."); + Assert.equal(Service.scheduler.globalScore, 0); + + _("Our next sync will fail appropriately."); + try { + await Service.sync(); + } catch (ex) {} + Assert.equal(Service.status.login, LOGIN_FAILED_LOGIN_REJECTED); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_service_sync_locked.js b/services/sync/tests/unit/test_service_sync_locked.js new file mode 100644 index 0000000000..5a872e2708 --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_locked.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function run_test() { + validate_all_future_pings(); + let debug = []; + let info = []; + + function augmentLogger(old) { + let d = old.debug; + let i = old.info; + // For the purposes of this test we don't need to do full formatting + // of the 2nd param, as the ones we care about are always strings. + old.debug = function (m, p) { + debug.push(p ? m + ": " + (p.message || p) : m); + d.call(old, m, p); + }; + old.info = function (m, p) { + info.push(p ? m + ": " + (p.message || p) : m); + i.call(old, m, p); + }; + return old; + } + + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + augmentLogger(Service._log); + + // Avoid daily ping + Svc.PrefBranch.setIntPref("lastPing", Math.floor(Date.now() / 1000)); + + _("Check that sync will log appropriately if already in 'progress'."); + Service._locked = true; + await Service.sync(); + Service._locked = false; + + Assert.ok( + debug[debug.length - 2].startsWith( + 'Exception calling WrappedLock: Could not acquire lock. Label: "service.js: login".' + ) + ); + Assert.equal(info[info.length - 1], "Cannot start sync: already syncing?"); +}); diff --git a/services/sync/tests/unit/test_service_sync_remoteSetup.js b/services/sync/tests/unit/test_service_sync_remoteSetup.js new file mode 100644 index 0000000000..ec95e69c78 --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -0,0 +1,241 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +// This sucks, but this test fails if this engine is enabled, due to dumb +// things that aren't related to this engine. In short: +// * Because the addon manager isn't initialized, the addons engine fails to +// initialize. So we end up writing a meta/global with `extension-storage` +// but not addons. +// * After we sync, we discover 'addons' is locally enabled, but because it's +// not in m/g, we decide it's been remotely declined (and it decides this +// without even considering `declined`). So we disable 'addons'. +// * Disabling 'addons' means 'extension-storage' is disabled - but because +// that *is* in meta/global we re-update meta/global to remove it. +// * This test fails due to the extra, unexpected update of m/g. +// +// Another option would be to ensure the addons manager is initialized, but +// that's a larger patch and still isn't strictly relevant to what's being +// tested here, so... +Services.prefs.setBoolPref( + "services.sync.engine.extension-storage.force", + false +); + +add_task(async function run_test() { + enableValidationPrefs(); + + validate_all_future_pings(); + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + let clients = new ServerCollection(); + let meta_global = new ServerWBO("global"); + + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + let collections = collectionsHelper.collections; + + function wasCalledHandler(wbo) { + let handler = wbo.handler(); + return function () { + wbo.wasCalled = true; + handler.apply(this, arguments); + }; + } + + let keysWBO = new ServerWBO("keys"); + let cryptoColl = new ServerCollection({ keys: keysWBO }); + let metaColl = new ServerCollection({ global: meta_global }); + do_test_pending(); + + /** + * Handle the bulk DELETE request sent by wipeServer. + */ + function storageHandler(request, response) { + Assert.equal("DELETE", request.method); + Assert.ok(request.hasHeader("X-Confirm-Delete")); + + _("Wiping out all collections."); + cryptoColl.delete({}); + clients.delete({}); + metaColl.delete({}); + + let ts = new_timestamp(); + collectionsHelper.update_collection("crypto", ts); + collectionsHelper.update_collection("clients", ts); + collectionsHelper.update_collection("meta", ts); + return_timestamp(request, response, ts); + } + + const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global"; + + let handlers = { + "/1.1/johndoe/storage": storageHandler, + "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), + "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()), + "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), + "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), + "/1.1/johndoe/storage/meta/global": upd( + "meta", + wasCalledHandler(meta_global) + ), + "/1.1/johndoe/info/collections": collectionsHelper.handler, + }; + + function mockHandler(path, mock) { + server.registerPathHandler(path, mock(handlers[path])); + return { + restore() { + server.registerPathHandler(path, handlers[path]); + }, + }; + } + + let server = httpd_setup(handlers); + + try { + _("Checking Status.sync with no credentials."); + await Service.verifyAndFetchSymmetricKeys(); + Assert.equal(Service.status.sync, CREDENTIALS_CHANGED); + Assert.equal(Service.status.login, LOGIN_FAILED_NO_PASSPHRASE); + + await configureIdentity({ username: "johndoe" }, server); + + await Service.login(); + _("Checking that remoteSetup returns true when credentials have changed."); + (await Service.recordManager.get(Service.metaURL)).payload.syncID = + "foobar"; + Assert.ok(await Service._remoteSetup()); + + let returnStatusCode = (method, code) => oldMethod => (req, res) => { + if (req.method === method) { + res.setStatusLine(req.httpVersion, code, ""); + } else { + oldMethod(req, res); + } + }; + + let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401)); + Service.recordManager.del(Service.metaURL); + _( + "Checking that remoteSetup returns false on 401 on first get /meta/global." + ); + Assert.equal(false, await Service._remoteSetup()); + mock.restore(); + + await Service.login(); + mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); + Service.recordManager.del(Service.metaURL); + _( + "Checking that remoteSetup returns false on 503 on first get /meta/global." + ); + Assert.equal(false, await Service._remoteSetup()); + Assert.equal(Service.status.sync, METARECORD_DOWNLOAD_FAIL); + mock.restore(); + + await Service.login(); + mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); + Service.recordManager.del(Service.metaURL); + _("Checking that remoteSetup recovers on 404 on first get /meta/global."); + Assert.ok(await Service._remoteSetup()); + mock.restore(); + + let makeOutdatedMeta = async () => { + Service.metaModified = 0; + let infoResponse = await Service._fetchInfo(); + return { + status: infoResponse.status, + obj: { + crypto: infoResponse.obj.crypto, + clients: infoResponse.obj.clients, + meta: 1, + }, + }; + }; + + _( + "Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one." + ); + mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); + Service.recordManager.set(Service.metaURL, { isNew: false }); + Assert.ok(await Service._remoteSetup(await makeOutdatedMeta())); + mock.restore(); + + _( + "Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one." + ); + mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); + Service.status.sync = ""; + Service.recordManager.set(Service.metaURL, { isNew: false }); + Assert.equal(false, await Service._remoteSetup(await makeOutdatedMeta())); + Assert.equal(Service.status.sync, ""); + mock.restore(); + + metaColl.delete({}); + + _("Do an initial sync."); + await Service.sync(); + + _("Checking that remoteSetup returns true."); + Assert.ok(await Service._remoteSetup()); + + _("Verify that the meta record was uploaded."); + Assert.equal(meta_global.data.syncID, Service.syncID); + Assert.equal(meta_global.data.storageVersion, STORAGE_VERSION); + Assert.equal( + meta_global.data.engines.clients.version, + Service.clientsEngine.version + ); + Assert.equal( + meta_global.data.engines.clients.syncID, + await Service.clientsEngine.getSyncID() + ); + + _( + "Set the collection info hash so that sync() will remember the modified times for future runs." + ); + let lastSync = await Service.clientsEngine.getLastSync(); + collections.meta = lastSync; + collections.clients = lastSync; + await Service.sync(); + + _("Sync again and verify that meta/global wasn't downloaded again"); + meta_global.wasCalled = false; + await Service.sync(); + Assert.ok(!meta_global.wasCalled); + + _( + "Fake modified records. This will cause a redownload, but not reupload since it hasn't changed." + ); + collections.meta += 42; + meta_global.wasCalled = false; + + let metaModified = meta_global.modified; + + await Service.sync(); + Assert.ok(meta_global.wasCalled); + Assert.equal(metaModified, meta_global.modified); + + // Try to screw up HMAC calculation. + // Re-encrypt keys with a new random keybundle, and upload them to the + // server, just as might happen with a second client. + _("Attempting to screw up HMAC by re-encrypting keys."); + let keys = Service.collectionKeys.asWBO(); + let b = new BulkKeyBundle("hmacerror"); + await b.generateRandom(); + collections.crypto = keys.modified = 100 + Date.now() / 1000; // Future modification time. + await keys.encrypt(b); + await keys.upload(Service.resource(Service.cryptoKeysURL)); + + Assert.equal(false, await Service.verifyAndFetchSymmetricKeys()); + Assert.equal(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + server.stop(do_test_finished); + } +}); diff --git a/services/sync/tests/unit/test_service_sync_specified.js b/services/sync/tests/unit/test_service_sync_specified.js new file mode 100644 index 0000000000..845cdb3669 --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_specified.js @@ -0,0 +1,150 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let syncedEngines = []; + +function SteamEngine() { + SyncEngine.call(this, "Steam", Service); +} +SteamEngine.prototype = { + async _sync() { + syncedEngines.push(this.name); + }, +}; +Object.setPrototypeOf(SteamEngine.prototype, SyncEngine.prototype); + +function StirlingEngine() { + SyncEngine.call(this, "Stirling", Service); +} +StirlingEngine.prototype = { + async _sync() { + syncedEngines.push(this.name); + }, +}; +Object.setPrototypeOf(StirlingEngine.prototype, SteamEngine.prototype); + +// Tracking info/collections. +var collectionsHelper = track_collections_helper(); +var upd = collectionsHelper.with_updated_collection; + +function sync_httpd_setup(handlers) { + handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler; + delete collectionsHelper.collections.crypto; + delete collectionsHelper.collections.meta; + + let cr = new ServerWBO("keys"); + handlers["/1.1/johndoe/storage/crypto/keys"] = upd("crypto", cr.handler()); + + let cl = new ServerCollection(); + handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); + + return httpd_setup(handlers); +} + +async function setUp() { + syncedEngines = []; + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + engine.syncPriority = 1; + + engine = Service.engineManager.get("stirling"); + engine.enabled = true; + engine.syncPriority = 2; + + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(), + }); + await SyncTestingInfrastructure(server, "johndoe", "ilovejane"); + return server; +} + +add_task(async function setup() { + await Service.engineManager.clear(); + validate_all_future_pings(); + + await Service.engineManager.register(SteamEngine); + await Service.engineManager.register(StirlingEngine); +}); + +add_task(async function test_noEngines() { + enableValidationPrefs(); + + _("Test: An empty array of engines to sync does nothing."); + let server = await setUp(); + + try { + _("Sync with no engines specified."); + await Service.sync({ engines: [] }); + deepEqual(syncedEngines, [], "no engines were synced"); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_oneEngine() { + enableValidationPrefs(); + + _("Test: Only one engine is synced."); + let server = await setUp(); + + try { + _("Sync with 1 engine specified."); + await Service.sync({ engines: ["steam"] }); + deepEqual(syncedEngines, ["steam"]); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_bothEnginesSpecified() { + enableValidationPrefs(); + + _("Test: All engines are synced when specified in the correct order (1)."); + let server = await setUp(); + + try { + _("Sync with both engines specified."); + await Service.sync({ engines: ["steam", "stirling"] }); + deepEqual(syncedEngines, ["steam", "stirling"]); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_bothEnginesSpecified() { + enableValidationPrefs(); + + _("Test: All engines are synced when specified in the correct order (2)."); + let server = await setUp(); + + try { + _("Sync with both engines specified."); + await Service.sync({ engines: ["stirling", "steam"] }); + deepEqual(syncedEngines, ["stirling", "steam"]); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_bothEnginesDefault() { + enableValidationPrefs(); + + _("Test: All engines are synced when nothing is specified."); + let server = await setUp(); + + try { + await Service.sync(); + deepEqual(syncedEngines, ["steam", "stirling"]); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); diff --git a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js new file mode 100644 index 0000000000..fd8b3f71bc --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js @@ -0,0 +1,587 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const { EngineSynchronizer } = ChromeUtils.importESModule( + "resource://services-sync/stages/enginesync.sys.mjs" +); + +function QuietStore() { + Store.call("Quiet"); +} +QuietStore.prototype = { + async getAllIDs() { + return []; + }, +}; + +function SteamEngine() { + SyncEngine.call(this, "Steam", Service); +} +SteamEngine.prototype = { + // We're not interested in engine sync but what the service does. + _storeObj: QuietStore, + + _sync: async function _sync() { + await this._syncStartup(); + }, +}; +Object.setPrototypeOf(SteamEngine.prototype, SyncEngine.prototype); + +function StirlingEngine() { + SyncEngine.call(this, "Stirling", Service); +} +StirlingEngine.prototype = { + // This engine's enabled state is the same as the SteamEngine's. + get prefName() { + return "steam"; + }, +}; +Object.setPrototypeOf(StirlingEngine.prototype, SteamEngine.prototype); + +// Tracking info/collections. +var collectionsHelper = track_collections_helper(); +var upd = collectionsHelper.with_updated_collection; + +function sync_httpd_setup(handlers) { + handlers["/1.1/johndoe/info/collections"] = collectionsHelper.handler; + delete collectionsHelper.collections.crypto; + delete collectionsHelper.collections.meta; + + let cr = new ServerWBO("keys"); + handlers["/1.1/johndoe/storage/crypto/keys"] = upd("crypto", cr.handler()); + + let cl = new ServerCollection(); + handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); + + return httpd_setup(handlers); +} + +async function setUp(server) { + await SyncTestingInfrastructure(server, "johndoe", "ilovejane"); + // Ensure that the server has valid keys so that logging in will work and not + // result in a server wipe, rendering many of these tests useless. + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + let { success } = await serverKeys.upload( + Service.resource(Service.cryptoKeysURL) + ); + ok(success); +} + +const PAYLOAD = 42; + +add_task(async function setup() { + await Service.engineManager.clear(); + validate_all_future_pings(); + + await Service.engineManager.register(SteamEngine); + await Service.engineManager.register(StirlingEngine); +}); + +add_task(async function test_newAccount() { + enableValidationPrefs(); + + _("Test: New account does not disable locally enabled engines."); + let engine = Service.engineManager.get("steam"); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": new ServerWBO("global", {}).handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), + }); + await setUp(server); + + try { + _("Engine is enabled from the beginning."); + Service._ignorePrefObserver = true; + engine.enabled = true; + Service._ignorePrefObserver = false; + + _("Sync."); + await Service.sync(); + + _("Engine continues to be enabled."); + Assert.ok(engine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_enabledLocally() { + enableValidationPrefs(); + + _("Test: Engine is disabled on remote clients and enabled locally"); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: {}, + }); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), + }); + await setUp(server); + + try { + _("Enable engine locally."); + engine.enabled = true; + + _("Sync."); + await Service.sync(); + + _("Meta record now contains the new engine."); + Assert.ok(!!metaWBO.data.engines.steam); + + _("Engine continues to be enabled."); + Assert.ok(engine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_disabledLocally() { + enableValidationPrefs(); + + _("Test: Engine is enabled on remote clients and disabled locally"); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let syncID = await engine.resetLocalSyncID(); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { steam: { syncID, version: engine.version } }, + }); + let steamCollection = new ServerWBO("steam", PAYLOAD); + + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": steamCollection.handler(), + }); + await setUp(server); + + try { + _("Disable engine locally."); + Service._ignorePrefObserver = true; + engine.enabled = true; + Service._ignorePrefObserver = false; + engine.enabled = false; + + _("Sync."); + await Service.sync(); + + _("Meta record no longer contains engine."); + Assert.ok(!metaWBO.data.engines.steam); + + _("Server records are wiped."); + Assert.equal(steamCollection.payload, undefined); + + _("Engine continues to be disabled."); + Assert.ok(!engine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_disabledLocally_wipe503() { + enableValidationPrefs(); + + _("Test: Engine is enabled on remote clients and disabled locally"); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let syncID = await engine.resetLocalSyncID(); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { steam: { syncID, version: engine.version } }, + }); + + function service_unavailable(request, response) { + let body = "Service Unavailable"; + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + response.setHeader("Retry-After", "23"); + response.bodyOutputStream.write(body, body.length); + } + + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": service_unavailable, + }); + await setUp(server); + + _("Disable engine locally."); + Service._ignorePrefObserver = true; + engine.enabled = true; + Service._ignorePrefObserver = false; + engine.enabled = false; + + _("Sync."); + await Service.sync(); + Assert.equal(Service.status.sync, SERVER_MAINTENANCE); + + await Service.startOver(); + await promiseStopServer(server); +}); + +add_task(async function test_enabledRemotely() { + enableValidationPrefs(); + + _("Test: Engine is disabled locally and enabled on a remote client"); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let syncID = await engine.resetLocalSyncID(); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { steam: { syncID, version: engine.version } }, + }); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": upd("meta", metaWBO.handler()), + + "/1.1/johndoe/storage/steam": upd( + "steam", + new ServerWBO("steam", {}).handler() + ), + }); + await setUp(server); + + // We need to be very careful how we do this, so that we don't trigger a + // fresh start! + try { + _("Upload some keys to avoid a fresh start."); + let wbo = await Service.collectionKeys.generateNewKeysWBO(); + await wbo.encrypt(Service.identity.syncKeyBundle); + Assert.equal( + 200, + (await wbo.upload(Service.resource(Service.cryptoKeysURL))).status + ); + + _("Engine is disabled."); + Assert.ok(!engine.enabled); + + _("Sync."); + await Service.sync(); + + _("Engine is enabled."); + Assert.ok(engine.enabled); + + _("Meta record still present."); + Assert.equal(metaWBO.data.engines.steam.syncID, await engine.getSyncID()); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_disabledRemotelyTwoClients() { + enableValidationPrefs(); + + _( + "Test: Engine is enabled locally and disabled on a remote client... with two clients." + ); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: {}, + }); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": upd("meta", metaWBO.handler()), + + "/1.1/johndoe/storage/steam": upd( + "steam", + new ServerWBO("steam", {}).handler() + ), + }); + await setUp(server); + + try { + _("Enable engine locally."); + Service._ignorePrefObserver = true; + engine.enabled = true; + Service._ignorePrefObserver = false; + + _("Sync."); + await Service.sync(); + + _("Disable engine by deleting from meta/global."); + let d = metaWBO.data; + delete d.engines.steam; + metaWBO.payload = JSON.stringify(d); + metaWBO.modified = Date.now() / 1000; + + _("Add a second client and verify that the local pref is changed."); + Service.clientsEngine._store._remoteClients.foobar = { + name: "foobar", + type: "desktop", + }; + await Service.sync(); + + _("Engine is disabled."); + Assert.ok(!engine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_disabledRemotely() { + enableValidationPrefs(); + + _("Test: Engine is enabled locally and disabled on a remote client"); + Service.syncID = "abcdefghij"; + let engine = Service.engineManager.get("steam"); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: {}, + }); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), + }); + await setUp(server); + + try { + _("Enable engine locally."); + Service._ignorePrefObserver = true; + engine.enabled = true; + Service._ignorePrefObserver = false; + + _("Sync."); + await Service.sync(); + + _("Engine is not disabled: only one client."); + Assert.ok(engine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_dependentEnginesEnabledLocally() { + enableValidationPrefs(); + + _("Test: Engine is disabled on remote clients and enabled locally"); + Service.syncID = "abcdefghij"; + let steamEngine = Service.engineManager.get("steam"); + let stirlingEngine = Service.engineManager.get("stirling"); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: {}, + }); + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": new ServerWBO("steam", {}).handler(), + "/1.1/johndoe/storage/stirling": new ServerWBO("stirling", {}).handler(), + }); + await setUp(server); + + try { + _("Enable engine locally. Doing it on one is enough."); + steamEngine.enabled = true; + + _("Sync."); + await Service.sync(); + + _("Meta record now contains the new engines."); + Assert.ok(!!metaWBO.data.engines.steam); + Assert.ok(!!metaWBO.data.engines.stirling); + + _("Engines continue to be enabled."); + Assert.ok(steamEngine.enabled); + Assert.ok(stirlingEngine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_dependentEnginesDisabledLocally() { + enableValidationPrefs(); + + _( + "Test: Two dependent engines are enabled on remote clients and disabled locally" + ); + Service.syncID = "abcdefghij"; + let steamEngine = Service.engineManager.get("steam"); + let steamSyncID = await steamEngine.resetLocalSyncID(); + let stirlingEngine = Service.engineManager.get("stirling"); + let stirlingSyncID = await stirlingEngine.resetLocalSyncID(); + let metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + steam: { syncID: steamSyncID, version: steamEngine.version }, + stirling: { syncID: stirlingSyncID, version: stirlingEngine.version }, + }, + }); + + let steamCollection = new ServerWBO("steam", PAYLOAD); + let stirlingCollection = new ServerWBO("stirling", PAYLOAD); + + let server = sync_httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + "/1.1/johndoe/storage/steam": steamCollection.handler(), + "/1.1/johndoe/storage/stirling": stirlingCollection.handler(), + }); + await setUp(server); + + try { + _("Disable engines locally. Doing it on one is enough."); + Service._ignorePrefObserver = true; + steamEngine.enabled = true; + Assert.ok(stirlingEngine.enabled); + Service._ignorePrefObserver = false; + steamEngine.enabled = false; + Assert.ok(!stirlingEngine.enabled); + + _("Sync."); + await Service.sync(); + + _("Meta record no longer contains engines."); + Assert.ok(!metaWBO.data.engines.steam); + Assert.ok(!metaWBO.data.engines.stirling); + + _("Server records are wiped."); + Assert.equal(steamCollection.payload, undefined); + Assert.equal(stirlingCollection.payload, undefined); + + _("Engines continue to be disabled."); + Assert.ok(!steamEngine.enabled); + Assert.ok(!stirlingEngine.enabled); + } finally { + await Service.startOver(); + await promiseStopServer(server); + } +}); + +add_task(async function test_service_updateLocalEnginesState() { + Service.syncID = "abcdefghij"; + const engine = Service.engineManager.get("steam"); + const metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + declined: ["steam"], + engines: {}, + }); + const server = httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + }); + await SyncTestingInfrastructure(server, "johndoe"); + + // Disconnect sync. + await Service.startOver(); + Service._ignorePrefObserver = true; + // Steam engine is enabled on our machine. + engine.enabled = true; + Service._ignorePrefObserver = false; + Service.identity._findCluster = () => server.baseURI + "/1.1/johndoe/"; + + // Update engine state from the server. + await Service.updateLocalEnginesState(); + // Now disabled. + Assert.ok(!engine.enabled); +}); + +add_task(async function test_service_enableAfterUpdateState() { + Service.syncID = "abcdefghij"; + const engine = Service.engineManager.get("steam"); + const metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + declined: ["steam"], + engines: { someengine: {} }, + }); + const server = httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + }); + await SyncTestingInfrastructure(server, "johndoe"); + + // Disconnect sync. + await Service.startOver(); + Service.identity._findCluster = () => server.baseURI + "/1.1/johndoe/"; + + // Update engine state from the server. + await Service.updateLocalEnginesState(); + // Now disabled, reflecting what's on the server. + Assert.ok(!engine.enabled); + // Enable the engine, as though the user selected it via CWTS. + engine.enabled = true; + + // Do the "reconcile local and remote states" dance. + let engineSync = new EngineSynchronizer(Service); + await engineSync._updateEnabledEngines(); + await Service._maybeUpdateDeclined(); + // engine should remain enabled. + Assert.ok(engine.enabled); + // engine should no longer appear in declined on the server. + Assert.deepEqual(metaWBO.data.declined, []); +}); + +add_task(async function test_service_disableAfterUpdateState() { + Service.syncID = "abcdefghij"; + const engine = Service.engineManager.get("steam"); + const metaWBO = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + declined: [], + engines: { steam: {} }, + }); + const server = httpd_setup({ + "/1.1/johndoe/storage/meta/global": metaWBO.handler(), + }); + await SyncTestingInfrastructure(server, "johndoe"); + + // Disconnect sync. + await Service.startOver(); + Service.identity._findCluster = () => server.baseURI + "/1.1/johndoe/"; + + // Update engine state from the server. + await Service.updateLocalEnginesState(); + // Now enabled, reflecting what's on the server. + Assert.ok(engine.enabled); + // Disable the engine, as though via CWTS. + engine.enabled = false; + + // Do the "reconcile local and remote states" dance. + let engineSync = new EngineSynchronizer(Service); + await engineSync._updateEnabledEngines(); + await Service._maybeUpdateDeclined(); + // engine should remain disabled. + Assert.ok(!engine.enabled); + // engine should now appear in declined on the server. + Assert.deepEqual(metaWBO.data.declined, ["steam"]); + // and should have been removed from engines. + Assert.deepEqual(metaWBO.data.engines, {}); +}); + +add_task(async function test_service_updateLocalEnginesState_no_meta_global() { + Service.syncID = "abcdefghij"; + const engine = Service.engineManager.get("steam"); + // The server doesn't contain /meta/global (sync was never enabled). + const server = httpd_setup({}); + await SyncTestingInfrastructure(server, "johndoe"); + + // Disconnect sync. + await Service.startOver(); + Service._ignorePrefObserver = true; + // Steam engine is enabled on our machine. + engine.enabled = true; + Service._ignorePrefObserver = false; + Service.identity._findCluster = () => server.baseURI + "/1.1/johndoe/"; + + // Update engine state from the server. + await Service.updateLocalEnginesState(); + // Still enabled. + Assert.ok(engine.enabled); +}); diff --git a/services/sync/tests/unit/test_service_verifyLogin.js b/services/sync/tests/unit/test_service_verifyLogin.js new file mode 100644 index 0000000000..b99b5c692c --- /dev/null +++ b/services/sync/tests/unit/test_service_verifyLogin.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function login_handling(handler) { + return function (request, response) { + if (has_hawk_header(request)) { + handler(request, response); + } else { + let body = "Unauthorized"; + response.setStatusLine(request.httpVersion, 401, "Unauthorized"); + response.bodyOutputStream.write(body, body.length); + } + }; +} + +function service_unavailable(request, response) { + let body = "Service Unavailable"; + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + response.setHeader("Retry-After", "42"); + response.bodyOutputStream.write(body, body.length); +} + +function run_test() { + Log.repository.rootLogger.addAppender(new Log.DumpAppender()); + + run_next_test(); +} + +add_task(async function test_verifyLogin() { + // This test expects a clean slate -- no saved passphrase. + Services.logins.removeAllUserFacingLogins(); + let johnHelper = track_collections_helper(); + let johnU = johnHelper.with_updated_collection; + + do_test_pending(); + + let server = httpd_setup({ + "/1.1/johndoe/info/collections": login_handling(johnHelper.handler), + "/1.1/janedoe/info/collections": service_unavailable, + + "/1.1/johndoe/storage/crypto/keys": johnU( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": johnU( + "meta", + new ServerWBO("global").handler() + ), + }); + + try { + _("Force the initial state."); + Service.status.service = STATUS_OK; + Assert.equal(Service.status.service, STATUS_OK); + + _("Credentials won't check out because we're not configured yet."); + Service.status.resetSync(); + Assert.equal(false, await Service.verifyLogin()); + Assert.equal(Service.status.service, CLIENT_NOT_CONFIGURED); + Assert.equal(Service.status.login, LOGIN_FAILED_NO_USERNAME); + + _("Success if syncBundleKey is set."); + Service.status.resetSync(); + await configureIdentity({ username: "johndoe" }, server); + Assert.ok(await Service.verifyLogin()); + Assert.equal(Service.status.service, STATUS_OK); + Assert.equal(Service.status.login, LOGIN_SUCCEEDED); + + _( + "If verifyLogin() encounters a server error, it flips on the backoff flag and notifies observers on a 503 with Retry-After." + ); + Service.status.resetSync(); + await configureIdentity({ username: "janedoe" }, server); + Service._updateCachedURLs(); + Assert.ok(!Service.status.enforceBackoff); + let backoffInterval; + Svc.Obs.add( + "weave:service:backoff:interval", + function observe(subject, data) { + Svc.Obs.remove("weave:service:backoff:interval", observe); + backoffInterval = subject; + } + ); + Assert.equal(false, await Service.verifyLogin()); + Assert.ok(Service.status.enforceBackoff); + Assert.equal(backoffInterval, 42); + Assert.equal(Service.status.service, LOGIN_FAILED); + Assert.equal(Service.status.login, SERVER_MAINTENANCE); + + _( + "Ensure a network error when finding the cluster sets the right Status bits." + ); + Service.status.resetSync(); + Service.clusterURL = ""; + Service.identity._findCluster = () => "http://localhost:12345/"; + Assert.equal(false, await Service.verifyLogin()); + Assert.equal(Service.status.service, LOGIN_FAILED); + Assert.equal(Service.status.login, LOGIN_FAILED_NETWORK_ERROR); + + _( + "Ensure a network error when getting the collection info sets the right Status bits." + ); + Service.status.resetSync(); + Service.clusterURL = "http://localhost:12345/"; + Assert.equal(false, await Service.verifyLogin()); + Assert.equal(Service.status.service, LOGIN_FAILED); + Assert.equal(Service.status.login, LOGIN_FAILED_NETWORK_ERROR); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + server.stop(do_test_finished); + } +}); diff --git a/services/sync/tests/unit/test_service_wipeClient.js b/services/sync/tests/unit/test_service_wipeClient.js new file mode 100644 index 0000000000..aa48868ca0 --- /dev/null +++ b/services/sync/tests/unit/test_service_wipeClient.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +function CanDecryptEngine() { + SyncEngine.call(this, "CanDecrypt", Service); +} +CanDecryptEngine.prototype = { + // Override these methods with mocks for the test + async canDecrypt() { + return true; + }, + + wasWiped: false, + async wipeClient() { + this.wasWiped = true; + }, +}; +Object.setPrototypeOf(CanDecryptEngine.prototype, SyncEngine.prototype); + +function CannotDecryptEngine() { + SyncEngine.call(this, "CannotDecrypt", Service); +} +CannotDecryptEngine.prototype = { + // Override these methods with mocks for the test + async canDecrypt() { + return false; + }, + + wasWiped: false, + async wipeClient() { + this.wasWiped = true; + }, +}; +Object.setPrototypeOf(CannotDecryptEngine.prototype, SyncEngine.prototype); + +let canDecryptEngine; +let cannotDecryptEngine; + +add_task(async function setup() { + await Service.engineManager.clear(); + + await Service.engineManager.register(CanDecryptEngine); + await Service.engineManager.register(CannotDecryptEngine); + canDecryptEngine = Service.engineManager.get("candecrypt"); + cannotDecryptEngine = Service.engineManager.get("cannotdecrypt"); +}); + +add_task(async function test_withEngineList() { + try { + _("Ensure initial scenario."); + Assert.ok(!canDecryptEngine.wasWiped); + Assert.ok(!cannotDecryptEngine.wasWiped); + + _("Wipe local engine data."); + await Service.wipeClient(["candecrypt", "cannotdecrypt"]); + + _("Ensure only the engine that can decrypt was wiped."); + Assert.ok(canDecryptEngine.wasWiped); + Assert.ok(!cannotDecryptEngine.wasWiped); + } finally { + canDecryptEngine.wasWiped = false; + cannotDecryptEngine.wasWiped = false; + await Service.startOver(); + } +}); + +add_task(async function test_startOver_clears_keys() { + syncTestLogging(); + await generateNewKeys(Service.collectionKeys); + Assert.ok(!!Service.collectionKeys.keyForCollection()); + await Service.startOver(); + syncTestLogging(); + Assert.ok(!Service.collectionKeys.keyForCollection()); +}); diff --git a/services/sync/tests/unit/test_service_wipeServer.js b/services/sync/tests/unit/test_service_wipeServer.js new file mode 100644 index 0000000000..9fc2592aa8 --- /dev/null +++ b/services/sync/tests/unit/test_service_wipeServer.js @@ -0,0 +1,240 @@ +Svc.PrefBranch.setStringPref("registerEngines", ""); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +// configure the identity we use for this test. +const identityConfig = makeIdentityConfig({ username: "johndoe" }); + +function FakeCollection() { + this.deleted = false; +} +FakeCollection.prototype = { + handler() { + let self = this; + return function (request, response) { + let body = ""; + self.timestamp = new_timestamp(); + let timestamp = "" + self.timestamp; + if (request.method == "DELETE") { + body = timestamp; + self.deleted = true; + } + response.setHeader("X-Weave-Timestamp", timestamp); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(body, body.length); + }; + }, +}; + +async function setUpTestFixtures(server) { + Service.clusterURL = server.baseURI + "/"; + + await configureIdentity(identityConfig); +} + +add_task(async function test_wipeServer_list_success() { + _("Service.wipeServer() deletes collections given as argument."); + + let steam_coll = new FakeCollection(); + let diesel_coll = new FakeCollection(); + + let server = httpd_setup({ + "/1.1/johndoe/storage/steam": steam_coll.handler(), + "/1.1/johndoe/storage/diesel": diesel_coll.handler(), + "/1.1/johndoe/storage/petrol": httpd_handler(404, "Not Found"), + }); + + try { + await setUpTestFixtures(server); + await SyncTestingInfrastructure(server, "johndoe", "irrelevant"); + + _("Confirm initial environment."); + Assert.ok(!steam_coll.deleted); + Assert.ok(!diesel_coll.deleted); + + _( + "wipeServer() will happily ignore the non-existent collection and use the timestamp of the last DELETE that was successful." + ); + let timestamp = await Service.wipeServer(["steam", "diesel", "petrol"]); + Assert.equal(timestamp, diesel_coll.timestamp); + + _( + "wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted." + ); + Assert.ok(steam_coll.deleted); + Assert.ok(diesel_coll.deleted); + } finally { + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_wipeServer_list_503() { + _("Service.wipeServer() deletes collections given as argument."); + + let steam_coll = new FakeCollection(); + let diesel_coll = new FakeCollection(); + + let server = httpd_setup({ + "/1.1/johndoe/storage/steam": steam_coll.handler(), + "/1.1/johndoe/storage/petrol": httpd_handler(503, "Service Unavailable"), + "/1.1/johndoe/storage/diesel": diesel_coll.handler(), + }); + + try { + await setUpTestFixtures(server); + await SyncTestingInfrastructure(server, "johndoe", "irrelevant"); + + _("Confirm initial environment."); + Assert.ok(!steam_coll.deleted); + Assert.ok(!diesel_coll.deleted); + + _( + "wipeServer() will happily ignore the non-existent collection, delete the 'steam' collection and abort after an receiving an error on the 'petrol' collection." + ); + let error; + try { + await Service.wipeServer(["non-existent", "steam", "petrol", "diesel"]); + do_throw("Should have thrown!"); + } catch (ex) { + error = ex; + } + _("wipeServer() threw this exception: " + error); + Assert.equal(error.status, 503); + + _( + "wipeServer stopped deleting after encountering an error with the 'petrol' collection, thus only 'steam' has been deleted." + ); + Assert.ok(steam_coll.deleted); + Assert.ok(!diesel_coll.deleted); + } finally { + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_wipeServer_all_success() { + _("Service.wipeServer() deletes all the things."); + + /** + * Handle the bulk DELETE request sent by wipeServer. + */ + let deleted = false; + let serverTimestamp; + function storageHandler(request, response) { + Assert.equal("DELETE", request.method); + Assert.ok(request.hasHeader("X-Confirm-Delete")); + deleted = true; + serverTimestamp = return_timestamp(request, response); + } + + let server = httpd_setup({ + "/1.1/johndoe/storage": storageHandler, + }); + await setUpTestFixtures(server); + + _("Try deletion."); + await SyncTestingInfrastructure(server, "johndoe", "irrelevant"); + let returnedTimestamp = await Service.wipeServer(); + Assert.ok(deleted); + Assert.equal(returnedTimestamp, serverTimestamp); + + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } +}); + +add_task(async function test_wipeServer_all_404() { + _("Service.wipeServer() accepts a 404."); + + /** + * Handle the bulk DELETE request sent by wipeServer. Returns a 404. + */ + let deleted = false; + let serverTimestamp; + function storageHandler(request, response) { + Assert.equal("DELETE", request.method); + Assert.ok(request.hasHeader("X-Confirm-Delete")); + deleted = true; + serverTimestamp = new_timestamp(); + response.setHeader("X-Weave-Timestamp", "" + serverTimestamp); + response.setStatusLine(request.httpVersion, 404, "Not Found"); + } + + let server = httpd_setup({ + "/1.1/johndoe/storage": storageHandler, + }); + await setUpTestFixtures(server); + + _("Try deletion."); + await SyncTestingInfrastructure(server, "johndoe", "irrelevant"); + let returnedTimestamp = await Service.wipeServer(); + Assert.ok(deleted); + Assert.equal(returnedTimestamp, serverTimestamp); + + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } +}); + +add_task(async function test_wipeServer_all_503() { + _("Service.wipeServer() throws if it encounters a non-200/404 response."); + + /** + * Handle the bulk DELETE request sent by wipeServer. Returns a 503. + */ + function storageHandler(request, response) { + Assert.equal("DELETE", request.method); + Assert.ok(request.hasHeader("X-Confirm-Delete")); + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + } + + let server = httpd_setup({ + "/1.1/johndoe/storage": storageHandler, + }); + await setUpTestFixtures(server); + + _("Try deletion."); + let error; + try { + await SyncTestingInfrastructure(server, "johndoe", "irrelevant"); + await Service.wipeServer(); + do_throw("Should have thrown!"); + } catch (ex) { + error = ex; + } + Assert.equal(error.status, 503); + + await promiseStopServer(server); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } +}); + +add_task(async function test_wipeServer_all_connectionRefused() { + _("Service.wipeServer() throws if it encounters a network problem."); + let server = httpd_setup({}); + await setUpTestFixtures(server); + + Service.clusterURL = "http://localhost:4352/"; + + _("Try deletion."); + try { + await Service.wipeServer(); + do_throw("Should have thrown!"); + } catch (ex) { + Assert.equal(ex.result, Cr.NS_ERROR_CONNECTION_REFUSED); + } + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_status.js b/services/sync/tests/unit/test_status.js new file mode 100644 index 0000000000..5bcfa182c3 --- /dev/null +++ b/services/sync/tests/unit/test_status.js @@ -0,0 +1,83 @@ +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); + +function run_test() { + // Check initial states + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.backoffInterval, 0); + Assert.equal(Status.minimumNextSync, 0); + + Assert.equal(Status.service, STATUS_OK); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.equal(Status.login, LOGIN_SUCCEEDED); + if (Status.engines.length) { + do_throw("Status.engines should be empty."); + } + Assert.equal(Status.partial, false); + + // Check login status + for (let code of [LOGIN_FAILED_NO_USERNAME, LOGIN_FAILED_NO_PASSPHRASE]) { + Status.login = code; + Assert.equal(Status.login, code); + Assert.equal(Status.service, CLIENT_NOT_CONFIGURED); + Status.resetSync(); + } + + Status.login = LOGIN_FAILED; + Assert.equal(Status.login, LOGIN_FAILED); + Assert.equal(Status.service, LOGIN_FAILED); + Status.resetSync(); + + Status.login = LOGIN_SUCCEEDED; + Assert.equal(Status.login, LOGIN_SUCCEEDED); + Assert.equal(Status.service, STATUS_OK); + Status.resetSync(); + + // Check sync status + Status.sync = SYNC_FAILED; + Assert.equal(Status.sync, SYNC_FAILED); + Assert.equal(Status.service, SYNC_FAILED); + + Status.sync = SYNC_SUCCEEDED; + Assert.equal(Status.sync, SYNC_SUCCEEDED); + Assert.equal(Status.service, STATUS_OK); + + Status.resetSync(); + + // Check engine status + Status.engines = ["testEng1", ENGINE_SUCCEEDED]; + Assert.equal(Status.engines.testEng1, ENGINE_SUCCEEDED); + Assert.equal(Status.service, STATUS_OK); + + Status.engines = ["testEng2", ENGINE_DOWNLOAD_FAIL]; + Assert.equal(Status.engines.testEng1, ENGINE_SUCCEEDED); + Assert.equal(Status.engines.testEng2, ENGINE_DOWNLOAD_FAIL); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + Status.engines = ["testEng3", ENGINE_SUCCEEDED]; + Assert.equal(Status.engines.testEng1, ENGINE_SUCCEEDED); + Assert.equal(Status.engines.testEng2, ENGINE_DOWNLOAD_FAIL); + Assert.equal(Status.engines.testEng3, ENGINE_SUCCEEDED); + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + // Check resetSync + Status.sync = SYNC_FAILED; + Status.resetSync(); + + Assert.equal(Status.service, STATUS_OK); + Assert.equal(Status.sync, SYNC_SUCCEEDED); + if (Status.engines.length) { + do_throw("Status.engines should be empty."); + } + + // Check resetBackoff + Status.enforceBackoff = true; + Status.backOffInterval = 4815162342; + Status.backOffInterval = 42; + Status.resetBackoff(); + + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.backoffInterval, 0); + Assert.equal(Status.minimumNextSync, 0); +} diff --git a/services/sync/tests/unit/test_status_checkSetup.js b/services/sync/tests/unit/test_status_checkSetup.js new file mode 100644 index 0000000000..fe3e7cad8d --- /dev/null +++ b/services/sync/tests/unit/test_status_checkSetup.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); + +add_task(async function test_status_checkSetup() { + try { + _("Fresh setup, we're not configured."); + Assert.equal(Status.checkSetup(), CLIENT_NOT_CONFIGURED); + Assert.equal(Status.login, LOGIN_FAILED_NO_USERNAME); + Status.resetSync(); + + _("Let's provide the syncKeyBundle"); + await configureIdentity(); + + _("checkSetup()"); + Assert.equal(Status.checkSetup(), STATUS_OK); + Status.resetSync(); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); diff --git a/services/sync/tests/unit/test_sync_auth_manager.js b/services/sync/tests/unit/test_sync_auth_manager.js new file mode 100644 index 0000000000..9af40d26c6 --- /dev/null +++ b/services/sync/tests/unit/test_sync_auth_manager.js @@ -0,0 +1,1027 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AuthenticationError, SyncAuthManager } = ChromeUtils.importESModule( + "resource://services-sync/sync_auth.sys.mjs" +); +const { Resource } = ChromeUtils.importESModule( + "resource://services-sync/resource.sys.mjs" +); +const { initializeIdentityWithTokenServerResponse } = + ChromeUtils.importESModule( + "resource://testing-common/services/sync/fxa_utils.sys.mjs" + ); +const { HawkClient } = ChromeUtils.importESModule( + "resource://services-common/hawkclient.sys.mjs" +); +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { FxAccountsClient } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsClient.sys.mjs" +); +const { + ERRNO_INVALID_AUTH_TOKEN, + ONLOGIN_NOTIFICATION, + ONVERIFIED_NOTIFICATION, +} = ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsCommon.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); +const { TokenServerClient, TokenServerClientServerError } = + ChromeUtils.importESModule( + "resource://services-common/tokenserverclient.sys.mjs" + ); +const { AccountState } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); + +const SECOND_MS = 1000; +const MINUTE_MS = SECOND_MS * 60; +const HOUR_MS = MINUTE_MS * 60; + +const MOCK_ACCESS_TOKEN = + "e3c5caf17f27a0d9e351926a928938b3737df43e91d4992a5a5fca9a7bdef8ba"; + +var globalIdentityConfig = makeIdentityConfig(); +var globalSyncAuthManager = new SyncAuthManager(); +configureFxAccountIdentity(globalSyncAuthManager, globalIdentityConfig); + +/** + * Mock client clock and skew vs server in FxAccounts signed-in user module and + * API client. sync_auth.js queries these values to construct HAWK + * headers. We will use this to test clock skew compensation in these headers + * below. + */ +var MockFxAccountsClient = function () { + FxAccountsClient.apply(this); +}; +MockFxAccountsClient.prototype = { + accountStatus() { + return Promise.resolve(true); + }, + getScopedKeyData() { + return Promise.resolve({ + "https://identity.mozilla.com/apps/oldsync": { + identifier: "https://identity.mozilla.com/apps/oldsync", + keyRotationSecret: + "0000000000000000000000000000000000000000000000000000000000000000", + keyRotationTimestamp: 1234567890123, + }, + }); + }, +}; +Object.setPrototypeOf( + MockFxAccountsClient.prototype, + FxAccountsClient.prototype +); + +add_test(function test_initial_state() { + _("Verify initial state"); + Assert.ok(!globalSyncAuthManager._token); + Assert.ok(!globalSyncAuthManager._hasValidToken()); + run_next_test(); +}); + +add_task(async function test_initialialize() { + _("Verify start after fetching token"); + await globalSyncAuthManager._ensureValidToken(); + Assert.ok(!!globalSyncAuthManager._token); + Assert.ok(globalSyncAuthManager._hasValidToken()); +}); + +add_task(async function test_refreshOAuthTokenOn401() { + _("Refreshes the FXA OAuth token after a 401."); + let getTokenCount = 0; + let syncAuthManager = new SyncAuthManager(); + let identityConfig = makeIdentityConfig(); + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal); + syncAuthManager._fxaService._internal.initialize(); + syncAuthManager._fxaService.getOAuthToken = () => { + ++getTokenCount; + return Promise.resolve(MOCK_ACCESS_TOKEN); + }; + + let didReturn401 = false; + let didReturn200 = false; + let mockTSC = mockTokenServer(() => { + if (getTokenCount <= 1) { + didReturn401 = true; + return { + status: 401, + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }; + } + didReturn200 = true; + return { + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + id: "id", + key: "key", + api_endpoint: "http://example.com/", + uid: "uid", + duration: 300, + }), + }; + }); + + syncAuthManager._tokenServerClient = mockTSC; + + await syncAuthManager._ensureValidToken(); + + Assert.equal(getTokenCount, 2); + Assert.ok(didReturn401); + Assert.ok(didReturn200); + Assert.ok(syncAuthManager._token); + Assert.ok(syncAuthManager._hasValidToken()); +}); + +add_task(async function test_initialializeWithAuthErrorAndDeletedAccount() { + _("Verify sync state with auth error + account deleted"); + + var identityConfig = makeIdentityConfig(); + var syncAuthManager = new SyncAuthManager(); + + // Use the real `getOAuthToken` method that calls + // `mockFxAClient.accessTokenWithSessionToken`. + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + delete fxaInternal.getOAuthToken; + + configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal); + syncAuthManager._fxaService._internal.initialize(); + + let accessTokenWithSessionTokenCalled = false; + let accountStatusCalled = false; + let sessionStatusCalled = false; + + let AuthErrorMockFxAClient = function () { + FxAccountsClient.apply(this); + }; + AuthErrorMockFxAClient.prototype = { + accessTokenWithSessionToken() { + accessTokenWithSessionTokenCalled = true; + return Promise.reject({ + code: 401, + errno: ERRNO_INVALID_AUTH_TOKEN, + }); + }, + accountStatus() { + accountStatusCalled = true; + return Promise.resolve(false); + }, + sessionStatus() { + sessionStatusCalled = true; + return Promise.resolve(false); + }, + }; + Object.setPrototypeOf( + AuthErrorMockFxAClient.prototype, + FxAccountsClient.prototype + ); + + let mockFxAClient = new AuthErrorMockFxAClient(); + syncAuthManager._fxaService._internal._fxAccountsClient = mockFxAClient; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + AuthenticationError, + "should reject due to an auth error" + ); + + Assert.ok(accessTokenWithSessionTokenCalled); + Assert.ok(sessionStatusCalled); + Assert.ok(accountStatusCalled); + Assert.ok(!syncAuthManager._token); + Assert.ok(!syncAuthManager._hasValidToken()); +}); + +add_task(async function test_getResourceAuthenticator() { + _( + "SyncAuthManager supplies a Resource Authenticator callback which returns a Hawk header." + ); + configureFxAccountIdentity(globalSyncAuthManager); + let authenticator = globalSyncAuthManager.getResourceAuthenticator(); + Assert.ok(!!authenticator); + let req = { + uri: CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow"), + method: "GET", + }; + let output = await authenticator(req, "GET"); + Assert.ok("headers" in output); + Assert.ok("authorization" in output.headers); + Assert.ok(output.headers.authorization.startsWith("Hawk")); + _("Expected internal state after successful call."); + Assert.equal( + globalSyncAuthManager._token.uid, + globalIdentityConfig.fxaccount.token.uid + ); +}); + +add_task(async function test_resourceAuthenticatorSkew() { + _( + "SyncAuthManager Resource Authenticator compensates for clock skew in Hawk header." + ); + + // Clock is skewed 12 hours into the future + // We pick a date in the past so we don't risk concealing bugs in code that + // uses new Date() instead of our given date. + let now = + new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS; + let syncAuthManager = new SyncAuthManager(); + let hawkClient = new HawkClient("https://example.net/v1", "/foo"); + + // mock fxa hawk client skew + hawkClient.now = function () { + dump("mocked client now: " + now + "\n"); + return now; + }; + // Imagine there's already been one fxa request and the hawk client has + // already detected skew vs the fxa auth server. + let localtimeOffsetMsec = -1 * 12 * HOUR_MS; + hawkClient._localtimeOffsetMsec = localtimeOffsetMsec; + + let fxaClient = new MockFxAccountsClient(); + fxaClient.hawk = hawkClient; + + // Sanity check + Assert.equal(hawkClient.now(), now); + Assert.equal(hawkClient.localtimeOffsetMsec, localtimeOffsetMsec); + + // Properly picked up by the client + Assert.equal(fxaClient.now(), now); + Assert.equal(fxaClient.localtimeOffsetMsec, localtimeOffsetMsec); + + let identityConfig = makeIdentityConfig(); + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + fxaInternal._now_is = now; + fxaInternal.fxAccountsClient = fxaClient; + + // Mocks within mocks... + configureFxAccountIdentity( + syncAuthManager, + globalIdentityConfig, + fxaInternal + ); + + Assert.equal(syncAuthManager._fxaService._internal.now(), now); + Assert.equal( + syncAuthManager._fxaService._internal.localtimeOffsetMsec, + localtimeOffsetMsec + ); + + Assert.equal(syncAuthManager._fxaService._internal.now(), now); + Assert.equal( + syncAuthManager._fxaService._internal.localtimeOffsetMsec, + localtimeOffsetMsec + ); + + let request = new Resource("https://example.net/i/like/pie/"); + let authenticator = syncAuthManager.getResourceAuthenticator(); + let output = await authenticator(request, "GET"); + dump("output" + JSON.stringify(output)); + let authHeader = output.headers.authorization; + Assert.ok(authHeader.startsWith("Hawk")); + + // Skew correction is applied in the header and we're within the two-minute + // window. + Assert.equal(getTimestamp(authHeader), now - 12 * HOUR_MS); + Assert.ok(getTimestampDelta(authHeader, now) - 12 * HOUR_MS < 2 * MINUTE_MS); +}); + +add_task(async function test_RESTResourceAuthenticatorSkew() { + _( + "SyncAuthManager REST Resource Authenticator compensates for clock skew in Hawk header." + ); + + // Clock is skewed 12 hours into the future from our arbitary date + let now = + new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS; + let syncAuthManager = new SyncAuthManager(); + let hawkClient = new HawkClient("https://example.net/v1", "/foo"); + + // mock fxa hawk client skew + hawkClient.now = function () { + return now; + }; + // Imagine there's already been one fxa request and the hawk client has + // already detected skew vs the fxa auth server. + hawkClient._localtimeOffsetMsec = -1 * 12 * HOUR_MS; + + let fxaClient = new MockFxAccountsClient(); + fxaClient.hawk = hawkClient; + + let identityConfig = makeIdentityConfig(); + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + fxaInternal._now_is = now; + fxaInternal.fxAccountsClient = fxaClient; + + configureFxAccountIdentity( + syncAuthManager, + globalIdentityConfig, + fxaInternal + ); + + Assert.equal(syncAuthManager._fxaService._internal.now(), now); + + let request = new Resource("https://example.net/i/like/pie/"); + let authenticator = syncAuthManager.getResourceAuthenticator(); + let output = await authenticator(request, "GET"); + dump("output" + JSON.stringify(output)); + let authHeader = output.headers.authorization; + Assert.ok(authHeader.startsWith("Hawk")); + + // Skew correction is applied in the header and we're within the two-minute + // window. + Assert.equal(getTimestamp(authHeader), now - 12 * HOUR_MS); + Assert.ok(getTimestampDelta(authHeader, now) - 12 * HOUR_MS < 2 * MINUTE_MS); +}); + +add_task(async function test_ensureLoggedIn() { + configureFxAccountIdentity(globalSyncAuthManager); + await globalSyncAuthManager._ensureValidToken(); + Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked"); + Assert.ok(globalSyncAuthManager._token); + + // arrange for no logged in user. + let fxa = globalSyncAuthManager._fxaService; + let signedInUser = + fxa._internal.currentAccountState.storageManager.accountData; + fxa._internal.currentAccountState.storageManager.accountData = null; + await Assert.rejects( + globalSyncAuthManager._ensureValidToken(true), + /no user is logged in/, + "expecting rejection due to no user" + ); + // Restore the logged in user to what it was. + fxa._internal.currentAccountState.storageManager.accountData = signedInUser; + Status.login = LOGIN_FAILED_LOGIN_REJECTED; + await globalSyncAuthManager._ensureValidToken(true); + Assert.equal(Status.login, LOGIN_SUCCEEDED, "final ensureLoggedIn worked"); +}); + +add_task(async function test_syncState() { + // Avoid polling for an unverified user. + let identityConfig = makeIdentityConfig(); + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + fxaInternal.startVerifiedCheck = () => {}; + configureFxAccountIdentity( + globalSyncAuthManager, + globalIdentityConfig, + fxaInternal + ); + + // arrange for no logged in user. + let fxa = globalSyncAuthManager._fxaService; + let signedInUser = + fxa._internal.currentAccountState.storageManager.accountData; + fxa._internal.currentAccountState.storageManager.accountData = null; + await Assert.rejects( + globalSyncAuthManager._ensureValidToken(true), + /no user is logged in/, + "expecting rejection due to no user" + ); + // Restore to an unverified user. + Services.prefs.setStringPref("services.sync.username", signedInUser.email); + signedInUser.verified = false; + fxa._internal.currentAccountState.storageManager.accountData = signedInUser; + Status.login = LOGIN_FAILED_LOGIN_REJECTED; + // The sync_auth observers are async, so call them directly. + await globalSyncAuthManager.observe(null, ONLOGIN_NOTIFICATION, ""); + Assert.equal( + Status.login, + LOGIN_FAILED_LOGIN_REJECTED, + "should not have changed the login state for an unverified user" + ); + + // now pretend the user because verified. + signedInUser.verified = true; + await globalSyncAuthManager.observe(null, ONVERIFIED_NOTIFICATION, ""); + Assert.equal( + Status.login, + LOGIN_SUCCEEDED, + "should have changed the login state to success" + ); +}); + +add_task(async function test_tokenExpiration() { + _("SyncAuthManager notices token expiration:"); + let bimExp = new SyncAuthManager(); + configureFxAccountIdentity(bimExp, globalIdentityConfig); + + let authenticator = bimExp.getResourceAuthenticator(); + Assert.ok(!!authenticator); + let req = { + uri: CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow"), + method: "GET", + }; + await authenticator(req, "GET"); + + // Mock the clock. + _("Forcing the token to expire ..."); + Object.defineProperty(bimExp, "_now", { + value: function customNow() { + return Date.now() + 3000001; + }, + writable: true, + }); + Assert.ok(bimExp._token.expiration < bimExp._now()); + _("... means SyncAuthManager knows to re-fetch it on the next call."); + Assert.ok(!bimExp._hasValidToken()); +}); + +add_task(async function test_getTokenErrors() { + _("SyncAuthManager correctly handles various failures to get a token."); + + _("Arrange for a 401 - Sync should reflect an auth error."); + initializeIdentityWithTokenServerResponse({ + status: 401, + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }); + let syncAuthManager = Service.identity; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + AuthenticationError, + "should reject due to 401" + ); + Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected"); + + // XXX - other interesting responses to return? + + // And for good measure, some totally "unexpected" errors - we generally + // assume these problems are going to magically go away at some point. + _( + "Arrange for an empty body with a 200 response - should reflect a network error." + ); + initializeIdentityWithTokenServerResponse({ + status: 200, + headers: [], + body: "", + }); + syncAuthManager = Service.identity; + await Assert.rejects( + syncAuthManager._ensureValidToken(), + TokenServerClientServerError, + "should reject due to non-JSON response" + ); + Assert.equal( + Status.login, + LOGIN_FAILED_NETWORK_ERROR, + "login state is LOGIN_FAILED_NETWORK_ERROR" + ); +}); + +add_task(async function test_refreshAccessTokenOn401() { + _("SyncAuthManager refreshes the FXA OAuth access token after a 401."); + var identityConfig = makeIdentityConfig(); + var syncAuthManager = new SyncAuthManager(); + // Use the real `getOAuthToken` method that calls + // `mockFxAClient.accessTokenWithSessionToken`. + let fxaInternal = makeFxAccountsInternalMock(identityConfig); + delete fxaInternal.getOAuthToken; + configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal); + syncAuthManager._fxaService._internal.initialize(); + + let getTokenCount = 0; + + let CheckSignMockFxAClient = function () { + FxAccountsClient.apply(this); + }; + CheckSignMockFxAClient.prototype = { + accessTokenWithSessionToken() { + ++getTokenCount; + return Promise.resolve({ access_token: "token" }); + }, + }; + Object.setPrototypeOf( + CheckSignMockFxAClient.prototype, + FxAccountsClient.prototype + ); + + let mockFxAClient = new CheckSignMockFxAClient(); + syncAuthManager._fxaService._internal._fxAccountsClient = mockFxAClient; + + let didReturn401 = false; + let didReturn200 = false; + let mockTSC = mockTokenServer(() => { + if (getTokenCount <= 1) { + didReturn401 = true; + return { + status: 401, + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }; + } + didReturn200 = true; + return { + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + id: "id", + key: "key", + api_endpoint: "http://example.com/", + uid: "uid", + duration: 300, + }), + }; + }); + + syncAuthManager._tokenServerClient = mockTSC; + + await syncAuthManager._ensureValidToken(); + + Assert.equal(getTokenCount, 2); + Assert.ok(didReturn401); + Assert.ok(didReturn200); + Assert.ok(syncAuthManager._token); + Assert.ok(syncAuthManager._hasValidToken()); +}); + +add_task(async function test_getTokenErrorWithRetry() { + _("tokenserver sends an observer notification on various backoff headers."); + + // Set Sync's backoffInterval to zero - after we simulated the backoff header + // it should reflect the value we sent. + Status.backoffInterval = 0; + _("Arrange for a 503 with a Retry-After header."); + initializeIdentityWithTokenServerResponse({ + status: 503, + headers: { "content-type": "application/json", "retry-after": "100" }, + body: JSON.stringify({}), + }); + let syncAuthManager = Service.identity; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + TokenServerClientServerError, + "should reject due to 503" + ); + + // The observer should have fired - check it got the value in the response. + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected"); + // Sync will have the value in ms with some slop - so check it is at least that. + Assert.ok(Status.backoffInterval >= 100000); + + _("Arrange for a 200 with an X-Backoff header."); + Status.backoffInterval = 0; + initializeIdentityWithTokenServerResponse({ + status: 503, + headers: { "content-type": "application/json", "x-backoff": "200" }, + body: JSON.stringify({}), + }); + syncAuthManager = Service.identity; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + TokenServerClientServerError, + "should reject due to no token in response" + ); + + // The observer should have fired - check it got the value in the response. + Assert.ok(Status.backoffInterval >= 200000); +}); + +add_task(async function test_getKeysErrorWithBackoff() { + _( + "Auth server (via hawk) sends an observer notification on backoff headers." + ); + + // Set Sync's backoffInterval to zero - after we simulated the backoff header + // it should reflect the value we sent. + Status.backoffInterval = 0; + _("Arrange for a 503 with a X-Backoff header."); + + let config = makeIdentityConfig(); + // We want no scopedKeys so we attempt to fetch them. + delete config.fxaccount.user.scopedKeys; + config.fxaccount.user.keyFetchToken = "keyfetchtoken"; + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + Assert.equal(method, "get"); + Assert.equal(uri, "http://mockedserver:9999/account/keys"); + return { + status: 503, + headers: { "content-type": "application/json", "x-backoff": "100" }, + body: "{}", + }; + } + ); + + let syncAuthManager = Service.identity; + await Assert.rejects( + syncAuthManager._ensureValidToken(), + TokenServerClientServerError, + "should reject due to 503" + ); + + // The observer should have fired - check it got the value in the response. + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected"); + // Sync will have the value in ms with some slop - so check it is at least that. + Assert.ok(Status.backoffInterval >= 100000); +}); + +add_task(async function test_getKeysErrorWithRetry() { + _("Auth server (via hawk) sends an observer notification on retry headers."); + + // Set Sync's backoffInterval to zero - after we simulated the backoff header + // it should reflect the value we sent. + Status.backoffInterval = 0; + _("Arrange for a 503 with a Retry-After header."); + + let config = makeIdentityConfig(); + // We want no scopedKeys so we attempt to fetch them. + delete config.fxaccount.user.scopedKeys; + config.fxaccount.user.keyFetchToken = "keyfetchtoken"; + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + Assert.equal(method, "get"); + Assert.equal(uri, "http://mockedserver:9999/account/keys"); + return { + status: 503, + headers: { "content-type": "application/json", "retry-after": "100" }, + body: "{}", + }; + } + ); + + let syncAuthManager = Service.identity; + await Assert.rejects( + syncAuthManager._ensureValidToken(), + TokenServerClientServerError, + "should reject due to 503" + ); + + // The observer should have fired - check it got the value in the response. + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected"); + // Sync will have the value in ms with some slop - so check it is at least that. + Assert.ok(Status.backoffInterval >= 100000); +}); + +add_task(async function test_getHAWKErrors() { + _("SyncAuthManager correctly handles various HAWK failures."); + + _("Arrange for a 401 - Sync should reflect an auth error."); + let config = makeIdentityConfig(); + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + if (uri == "http://mockedserver:9999/oauth/token") { + Assert.equal(method, "post"); + return { + status: 401, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + code: 401, + errno: 110, + error: "invalid token", + }), + }; + } + // For any follow-up requests that check account status. + return { + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({}), + }; + } + ); + Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected"); + + // XXX - other interesting responses to return? + + // And for good measure, some totally "unexpected" errors - we generally + // assume these problems are going to magically go away at some point. + _( + "Arrange for an empty body with a 200 response - should reflect a network error." + ); + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + Assert.equal(method, "post"); + Assert.equal(uri, "http://mockedserver:9999/oauth/token"); + return { + status: 200, + headers: [], + body: "", + }; + } + ); + Assert.equal( + Status.login, + LOGIN_FAILED_NETWORK_ERROR, + "login state is LOGIN_FAILED_NETWORK_ERROR" + ); +}); + +add_task(async function test_getGetKeysFailing401() { + _("SyncAuthManager correctly handles 401 responses fetching keys."); + + _("Arrange for a 401 - Sync should reflect an auth error."); + let config = makeIdentityConfig(); + // We want no scopedKeys so we attempt to fetch them. + delete config.fxaccount.user.scopedKeys; + config.fxaccount.user.keyFetchToken = "keyfetchtoken"; + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + Assert.equal(method, "get"); + Assert.equal(uri, "http://mockedserver:9999/account/keys"); + return { + status: 401, + headers: { "content-type": "application/json" }, + body: "{}", + }; + } + ); + Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected"); +}); + +add_task(async function test_getGetKeysFailing503() { + _("SyncAuthManager correctly handles 5XX responses fetching keys."); + + _("Arrange for a 503 - Sync should reflect a network error."); + let config = makeIdentityConfig(); + // We want no scopedKeys so we attempt to fetch them. + delete config.fxaccount.user.scopedKeys; + config.fxaccount.user.keyFetchToken = "keyfetchtoken"; + await initializeIdentityWithHAWKResponseFactory( + config, + function (method, data, uri) { + Assert.equal(method, "get"); + Assert.equal(uri, "http://mockedserver:9999/account/keys"); + return { + status: 503, + headers: { "content-type": "application/json" }, + body: "{}", + }; + } + ); + Assert.equal( + Status.login, + LOGIN_FAILED_NETWORK_ERROR, + "state reflects network error" + ); +}); + +add_task(async function test_getKeysMissing() { + _( + "SyncAuthManager correctly handles getKeyForScope succeeding but not returning the key." + ); + + let syncAuthManager = new SyncAuthManager(); + let identityConfig = makeIdentityConfig(); + // our mock identity config already has scopedKeys remove them or we never + // try and fetch them. + delete identityConfig.fxaccount.user.scopedKeys; + identityConfig.fxaccount.user.keyFetchToken = "keyFetchToken"; + + configureFxAccountIdentity(syncAuthManager, identityConfig); + + // Mock a fxAccounts object + let fxa = new FxAccounts({ + fxAccountsClient: new MockFxAccountsClient(), + newAccountState(credentials) { + // We only expect this to be called with null indicating the (mock) + // storage should be read. + if (credentials) { + throw new Error("Not expecting to have credentials passed"); + } + let storageManager = new MockFxaStorageManager(); + storageManager.initialize(identityConfig.fxaccount.user); + return new AccountState(storageManager); + }, + // And the keys object with a mock that returns no keys. + keys: { + getKeyForScope() { + return Promise.resolve(null); + }, + }, + }); + + syncAuthManager._fxaService = fxa; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + /browser does not have the sync key, cannot sync/ + ); +}); + +add_task(async function test_getKeysUnexpecedError() { + _( + "SyncAuthManager correctly handles getKeyForScope throwing an unexpected error." + ); + + let syncAuthManager = new SyncAuthManager(); + let identityConfig = makeIdentityConfig(); + // our mock identity config already has scopedKeys - remove them or we never + // try and fetch them. + delete identityConfig.fxaccount.user.scopedKeys; + identityConfig.fxaccount.user.keyFetchToken = "keyFetchToken"; + + configureFxAccountIdentity(syncAuthManager, identityConfig); + + // Mock a fxAccounts object + let fxa = new FxAccounts({ + fxAccountsClient: new MockFxAccountsClient(), + newAccountState(credentials) { + // We only expect this to be called with null indicating the (mock) + // storage should be read. + if (credentials) { + throw new Error("Not expecting to have credentials passed"); + } + let storageManager = new MockFxaStorageManager(); + storageManager.initialize(identityConfig.fxaccount.user); + return new AccountState(storageManager); + }, + // And the keys object with a mock that returns no keys. + keys: { + async getKeyForScope() { + throw new Error("well that was unexpected"); + }, + }, + }); + + syncAuthManager._fxaService = fxa; + + await Assert.rejects( + syncAuthManager._ensureValidToken(), + /well that was unexpected/ + ); +}); + +add_task(async function test_signedInUserMissing() { + _( + "SyncAuthManager detects getSignedInUser returning incomplete account data" + ); + + let syncAuthManager = new SyncAuthManager(); + // Delete stored keys and the key fetch token. + delete globalIdentityConfig.fxaccount.user.scopedKeys; + delete globalIdentityConfig.fxaccount.user.keyFetchToken; + + configureFxAccountIdentity(syncAuthManager, globalIdentityConfig); + + let fxa = new FxAccounts({ + fetchAndUnwrapKeys() { + return Promise.resolve({}); + }, + fxAccountsClient: new MockFxAccountsClient(), + newAccountState(credentials) { + // We only expect this to be called with null indicating the (mock) + // storage should be read. + if (credentials) { + throw new Error("Not expecting to have credentials passed"); + } + let storageManager = new MockFxaStorageManager(); + storageManager.initialize(globalIdentityConfig.fxaccount.user); + return new AccountState(storageManager); + }, + }); + + syncAuthManager._fxaService = fxa; + + let status = await syncAuthManager.unlockAndVerifyAuthState(); + Assert.equal(status, LOGIN_FAILED_LOGIN_REJECTED); +}); + +// End of tests +// Utility functions follow + +// Create a new sync_auth object and initialize it with a +// hawk mock that simulates HTTP responses. +// The callback function will be called each time the mocked hawk server wants +// to make a request. The result of the callback should be the mock response +// object that will be returned to hawk. +// A token server mock will be used that doesn't hit a server, so we move +// directly to a hawk request. +async function initializeIdentityWithHAWKResponseFactory( + config, + cbGetResponse +) { + // A mock request object. + function MockRESTRequest(uri, credentials, extra) { + this._uri = uri; + this._credentials = credentials; + this._extra = extra; + } + MockRESTRequest.prototype = { + setHeader() {}, + async post(data) { + this.response = cbGetResponse( + "post", + data, + this._uri, + this._credentials, + this._extra + ); + return this.response; + }, + async get() { + // Skip /status requests (sync_auth checks if the account still + // exists after an auth error) + if (this._uri.startsWith("http://mockedserver:9999/account/status")) { + this.response = { + status: 200, + headers: { "content-type": "application/json" }, + body: JSON.stringify({ exists: true }), + }; + } else { + this.response = cbGetResponse( + "get", + null, + this._uri, + this._credentials, + this._extra + ); + } + return this.response; + }, + }; + + // The hawk client. + function MockedHawkClient() {} + MockedHawkClient.prototype = new HawkClient("http://mockedserver:9999"); + MockedHawkClient.prototype.constructor = MockedHawkClient; + MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function ( + uri, + credentials, + extra + ) { + return new MockRESTRequest(uri, credentials, extra); + }; + // Arrange for the same observerPrefix as FxAccountsClient uses + MockedHawkClient.prototype.observerPrefix = "FxA:hawk"; + + // tie it all together - configureFxAccountIdentity isn't useful here :( + let fxaClient = new MockFxAccountsClient(); + fxaClient.hawk = new MockedHawkClient(); + let internal = { + fxAccountsClient: fxaClient, + newAccountState(credentials) { + // We only expect this to be called with null indicating the (mock) + // storage should be read. + if (credentials) { + throw new Error("Not expecting to have credentials passed"); + } + let storageManager = new MockFxaStorageManager(); + storageManager.initialize(config.fxaccount.user); + return new AccountState(storageManager); + }, + }; + let fxa = new FxAccounts(internal); + + globalSyncAuthManager._fxaService = fxa; + await Assert.rejects( + globalSyncAuthManager._ensureValidToken(true), + // TODO: Ideally this should have a specific check for an error. + () => true, + "expecting rejection due to hawk error" + ); +} + +function getTimestamp(hawkAuthHeader) { + return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS; +} + +function getTimestampDelta(hawkAuthHeader, now = Date.now()) { + return Math.abs(getTimestamp(hawkAuthHeader) - now); +} + +function mockTokenServer(func) { + let requestLog = Log.repository.getLogger("testing.mock-rest"); + if (!requestLog.appenders.length) { + // might as well see what it says :) + requestLog.addAppender(new Log.DumpAppender()); + requestLog.level = Log.Level.Trace; + } + function MockRESTRequest(url) {} + MockRESTRequest.prototype = { + _log: requestLog, + setHeader() {}, + async get() { + this.response = func(); + return this.response; + }, + }; + // The mocked TokenServer client which will get the response. + function MockTSC() {} + MockTSC.prototype = new TokenServerClient(); + MockTSC.prototype.constructor = MockTSC; + MockTSC.prototype.newRESTRequest = function (url) { + return new MockRESTRequest(url); + }; + // Arrange for the same observerPrefix as sync_auth uses. + MockTSC.prototype.observerPrefix = "weave:service"; + return new MockTSC(); +} diff --git a/services/sync/tests/unit/test_syncedtabs.js b/services/sync/tests/unit/test_syncedtabs.js new file mode 100644 index 0000000000..79ab3e0686 --- /dev/null +++ b/services/sync/tests/unit/test_syncedtabs.js @@ -0,0 +1,342 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim:set ts=2 sw=2 sts=2 et: + */ +"use strict"; + +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { SyncedTabs } = ChromeUtils.importESModule( + "resource://services-sync/SyncedTabs.sys.mjs" +); + +Log.repository.getLogger("Sync.RemoteTabs").addAppender(new Log.DumpAppender()); + +// A mock "Tabs" engine which the SyncedTabs module will use instead of the real +// engine. We pass a constructor that Sync creates. +function MockTabsEngine() { + this.clients = {}; // We'll set this dynamically + // Mock fxAccounts + recentDeviceList as if we hit the FxA server + this.fxAccounts = { + device: { + recentDeviceList: [ + { + id: 1, + name: "updated desktop name", + availableCommands: { + "https://identity.mozilla.com/cmd/open-uri": "baz", + }, + }, + { + id: 2, + name: "updated mobile name", + availableCommands: { + "https://identity.mozilla.com/cmd/open-uri": "boo", + }, + }, + ], + }, + }; +} + +MockTabsEngine.prototype = { + name: "tabs", + enabled: true, + + getAllClients() { + return Object.values(this.clients); + }, + + getOpenURLs() { + return new Set(); + }, +}; + +let tabsEngine; + +// A clients engine that doesn't need to be a constructor. +let MockClientsEngine = { + clientSettings: null, // Set in `configureClients`. + + isMobile(guid) { + if (!guid.endsWith("desktop") && !guid.endsWith("mobile")) { + throw new Error( + "this module expected guids to end with 'desktop' or 'mobile'" + ); + } + return guid.endsWith("mobile"); + }, + remoteClientExists(id) { + return this.clientSettings[id] !== false; + }, + getClientName(id) { + if (this.clientSettings[id]) { + return this.clientSettings[id]; + } + let client = tabsEngine.clients[id]; + let fxaDevice = tabsEngine.fxAccounts.device.recentDeviceList.find( + device => device.id === client.fxaDeviceId + ); + return fxaDevice ? fxaDevice.name : client.clientName; + }, + + getClientFxaDeviceId(id) { + if (this.clientSettings[id]) { + return this.clientSettings[id]; + } + return tabsEngine.clients[id].fxaDeviceId; + }, + + getClientType(id) { + return "desktop"; + }, +}; + +function configureClients(clients, clientSettings = {}) { + // each client record is expected to have an id. + for (let [guid, client] of Object.entries(clients)) { + client.id = guid; + } + tabsEngine.clients = clients; + // Apply clients collection overrides. + MockClientsEngine.clientSettings = clientSettings; + // Send an observer that pretends the engine just finished a sync. + Services.obs.notifyObservers(null, "weave:engine:sync:finish", "tabs"); +} + +add_task(async function setup() { + await Weave.Service.promiseInitialized; + // Configure Sync with our mock tabs engine and force it to become initialized. + await Weave.Service.engineManager.unregister("tabs"); + await Weave.Service.engineManager.register(MockTabsEngine); + Weave.Service.clientsEngine = MockClientsEngine; + tabsEngine = Weave.Service.engineManager.get("tabs"); + + // Tell the Sync XPCOM service it is initialized. + let weaveXPCService = Cc["@mozilla.org/weave/service;1"].getService( + Ci.nsISupports + ).wrappedJSObject; + weaveXPCService.ready = true; +}); + +// The tests. +add_task(async function test_noClients() { + // no clients, can't be tabs. + await configureClients({}); + + let tabs = await SyncedTabs.getTabClients(); + equal(Object.keys(tabs).length, 0); +}); + +add_task(async function test_clientWithTabs() { + await configureClients({ + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + icon: "http://foo.com/favicon", + lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT + }, + ], + }, + guid_mobile: { + clientName: "My Phone", + tabs: [], + }, + }); + + let clients = await SyncedTabs.getTabClients(); + equal(clients.length, 2); + clients.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + equal(clients[0].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); + equal(clients[0].tabs[0].icon, "http://foo.com/favicon"); + equal(clients[0].tabs[0].lastUsed, 1655745700); + // second client has no tabs. + equal(clients[1].tabs.length, 0); +}); + +add_task(async function test_staleClientWithTabs() { + await configureClients( + { + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + icon: "http://foo.com/favicon", + lastUsed: 1655745750, + }, + ], + }, + guid_mobile: { + clientName: "My Phone", + tabs: [], + }, + guid_stale_mobile: { + clientName: "My Deleted Phone", + tabs: [], + }, + guid_stale_desktop: { + clientName: "My Deleted Laptop", + tabs: [ + { + urlHistory: ["https://bar.com/"], + icon: "https://bar.com/favicon", + lastUsed: 1655745700, + }, + ], + }, + guid_stale_name_desktop: { + clientName: "My Generic Device", + tabs: [ + { + urlHistory: ["https://example.edu/"], + icon: "https://example.edu/favicon", + lastUsed: 1655745800, + }, + ], + }, + }, + { + guid_stale_mobile: false, + guid_stale_desktop: false, + // We should always use the device name from the clients collection, instead + // of the possibly stale tabs collection. + guid_stale_name_desktop: "My Laptop", + } + ); + let clients = await SyncedTabs.getTabClients(); + clients.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + equal(clients.length, 3); + equal(clients[0].name, "My Desktop"); + equal(clients[0].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); + equal(clients[0].tabs[0].lastUsed, 1655745750); + equal(clients[1].name, "My Laptop"); + equal(clients[1].tabs.length, 1); + equal(clients[1].tabs[0].url, "https://example.edu/"); + equal(clients[1].tabs[0].lastUsed, 1655745800); + equal(clients[2].name, "My Phone"); + equal(clients[2].tabs.length, 0); +}); + +add_task(async function test_clientWithTabsIconsDisabled() { + Services.prefs.setBoolPref("services.sync.syncedTabs.showRemoteIcons", false); + await configureClients({ + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + icon: "http://foo.com/favicon", + }, + ], + }, + }); + + let clients = await SyncedTabs.getTabClients(); + equal(clients.length, 1); + clients.sort((a, b) => { + return a.name.localeCompare(b.name); + }); + equal(clients[0].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); + // Expect the default favicon due to the pref being false. + equal(clients[0].tabs[0].icon, "page-icon:http://foo.com/"); + Services.prefs.clearUserPref("services.sync.syncedTabs.showRemoteIcons"); +}); + +add_task(async function test_filter() { + // Nothing matches. + await configureClients({ + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + title: "A test page.", + }, + { + urlHistory: ["http://bar.com/"], + title: "Another page.", + }, + ], + }, + }); + + let clients = await SyncedTabs.getTabClients("foo"); + equal(clients.length, 1); + equal(clients[0].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); + // check it matches the title. + clients = await SyncedTabs.getTabClients("test"); + equal(clients.length, 1); + equal(clients[0].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); +}); + +add_task(async function test_duplicatesTabsAcrossClients() { + await configureClients({ + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + title: "A test page.", + }, + ], + }, + guid_mobile: { + clientName: "My Phone", + tabs: [ + { + urlHistory: ["http://foo.com/"], + title: "A test page.", + }, + ], + }, + }); + + let clients = await SyncedTabs.getTabClients(); + equal(clients.length, 2); + equal(clients[0].tabs.length, 1); + equal(clients[1].tabs.length, 1); + equal(clients[0].tabs[0].url, "http://foo.com/"); + equal(clients[1].tabs[0].url, "http://foo.com/"); +}); + +add_task(async function test_clientsTabUpdatedName() { + // See the "fxAccounts" object in the MockEngine above for the device list + await configureClients({ + guid_desktop: { + clientName: "My Desktop", + tabs: [ + { + urlHistory: ["http://foo.com/"], + icon: "http://foo.com/favicon", + }, + ], + fxaDeviceId: 1, + }, + guid_mobile: { + clientName: "My Phone", + tabs: [ + { + urlHistory: ["http://bar.com/"], + icon: "http://bar.com/favicon", + }, + ], + fxaDeviceId: 2, + }, + }); + let clients = await SyncedTabs.getTabClients(); + equal(clients.length, 2); + equal(clients[0].name, "updated desktop name"); + equal(clients[1].name, "updated mobile name"); +}); diff --git a/services/sync/tests/unit/test_syncengine.js b/services/sync/tests/unit/test_syncengine.js new file mode 100644 index 0000000000..50995a4e40 --- /dev/null +++ b/services/sync/tests/unit/test_syncengine.js @@ -0,0 +1,302 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +async function makeSteamEngine() { + let engine = new SyncEngine("Steam", Service); + await engine.initialize(); + return engine; +} + +function guidSetOfSize(length) { + return new SerializableSet(Array.from({ length }, () => Utils.makeGUID())); +} + +function assertSetsEqual(a, b) { + // Assert.deepEqual doesn't understand Set. + Assert.deepEqual(Array.from(a).sort(), Array.from(b).sort()); +} + +async function testSteamEngineStorage(test) { + try { + let setupEngine = await makeSteamEngine(); + + if (test.setup) { + await test.setup(setupEngine); + } + + // Finalize the engine to flush the backlog and previous failed to disk. + await setupEngine.finalize(); + + if (test.beforeCheck) { + await test.beforeCheck(); + } + + let checkEngine = await makeSteamEngine(); + await test.check(checkEngine); + + await checkEngine.resetClient(); + await checkEngine.finalize(); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +} + +let server; + +add_task(async function setup() { + server = httpd_setup({}); +}); + +add_task(async function test_url_attributes() { + _("SyncEngine url attributes"); + await SyncTestingInfrastructure(server); + Service.clusterURL = "https://cluster/1.1/foo/"; + let engine = await makeSteamEngine(); + try { + Assert.equal(engine.storageURL, "https://cluster/1.1/foo/storage/"); + Assert.equal(engine.engineURL, "https://cluster/1.1/foo/storage/steam"); + Assert.equal(engine.metaURL, "https://cluster/1.1/foo/storage/meta/global"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_syncID() { + _("SyncEngine.syncID corresponds to preference"); + await SyncTestingInfrastructure(server); + let engine = await makeSteamEngine(); + try { + // Ensure pristine environment + Assert.equal( + Svc.PrefBranch.getPrefType("steam.syncID"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.equal(await engine.getSyncID(), ""); + + // Performing the first get on the attribute will generate a new GUID. + Assert.equal(await engine.resetLocalSyncID(), "fake-guid-00"); + Assert.equal(Svc.PrefBranch.getStringPref("steam.syncID"), "fake-guid-00"); + + Svc.PrefBranch.setStringPref("steam.syncID", Utils.makeGUID()); + Assert.equal(Svc.PrefBranch.getStringPref("steam.syncID"), "fake-guid-01"); + Assert.equal(await engine.getSyncID(), "fake-guid-01"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_lastSync() { + _("SyncEngine.lastSync corresponds to preferences"); + await SyncTestingInfrastructure(server); + let engine = await makeSteamEngine(); + try { + // Ensure pristine environment + Assert.equal( + Svc.PrefBranch.getPrefType("steam.lastSync"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.equal(await engine.getLastSync(), 0); + + // Floats are properly stored as floats and synced with the preference + await engine.setLastSync(123.45); + Assert.equal(await engine.getLastSync(), 123.45); + Assert.equal(Svc.PrefBranch.getStringPref("steam.lastSync"), "123.45"); + + // Integer is properly stored + await engine.setLastSync(67890); + Assert.equal(await engine.getLastSync(), 67890); + Assert.equal(Svc.PrefBranch.getStringPref("steam.lastSync"), "67890"); + + // resetLastSync() resets the value (and preference) to 0 + await engine.resetLastSync(); + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(Svc.PrefBranch.getStringPref("steam.lastSync"), "0"); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_toFetch() { + _("SyncEngine.toFetch corresponds to file on disk"); + await SyncTestingInfrastructure(server); + + await testSteamEngineStorage({ + toFetch: guidSetOfSize(3), + setup(engine) { + // Ensure pristine environment + Assert.equal(engine.toFetch.size, 0); + + // Write file to disk + engine.toFetch = this.toFetch; + Assert.equal(engine.toFetch, this.toFetch); + }, + check(engine) { + // toFetch is written asynchronously + assertSetsEqual(engine.toFetch, this.toFetch); + }, + }); + + await testSteamEngineStorage({ + toFetch: guidSetOfSize(4), + toFetch2: guidSetOfSize(5), + setup(engine) { + // Make sure it work for consecutive writes before the callback is executed. + engine.toFetch = this.toFetch; + Assert.equal(engine.toFetch, this.toFetch); + + engine.toFetch = this.toFetch2; + Assert.equal(engine.toFetch, this.toFetch2); + }, + check(engine) { + assertSetsEqual(engine.toFetch, this.toFetch2); + }, + }); + + await testSteamEngineStorage({ + toFetch: guidSetOfSize(2), + async beforeCheck() { + let toFetchPath = PathUtils.join( + PathUtils.profileDir, + "weave", + "toFetch", + "steam.json" + ); + await IOUtils.writeJSON(toFetchPath, this.toFetch, { + tmpPath: toFetchPath + ".tmp", + }); + }, + check(engine) { + // Read file from disk + assertSetsEqual(engine.toFetch, this.toFetch); + }, + }); +}); + +add_task(async function test_previousFailed() { + _("SyncEngine.previousFailed corresponds to file on disk"); + await SyncTestingInfrastructure(server); + + await testSteamEngineStorage({ + previousFailed: guidSetOfSize(3), + setup(engine) { + // Ensure pristine environment + Assert.equal(engine.previousFailed.size, 0); + + // Write file to disk + engine.previousFailed = this.previousFailed; + Assert.equal(engine.previousFailed, this.previousFailed); + }, + check(engine) { + // previousFailed is written asynchronously + assertSetsEqual(engine.previousFailed, this.previousFailed); + }, + }); + + await testSteamEngineStorage({ + previousFailed: guidSetOfSize(4), + previousFailed2: guidSetOfSize(5), + setup(engine) { + // Make sure it work for consecutive writes before the callback is executed. + engine.previousFailed = this.previousFailed; + Assert.equal(engine.previousFailed, this.previousFailed); + + engine.previousFailed = this.previousFailed2; + Assert.equal(engine.previousFailed, this.previousFailed2); + }, + check(engine) { + assertSetsEqual(engine.previousFailed, this.previousFailed2); + }, + }); + + await testSteamEngineStorage({ + previousFailed: guidSetOfSize(2), + async beforeCheck() { + let previousFailedPath = PathUtils.join( + PathUtils.profileDir, + "weave", + "failed", + "steam.json" + ); + await IOUtils.writeJSON(previousFailedPath, this.previousFailed, { + tmpPath: previousFailedPath + ".tmp", + }); + }, + check(engine) { + // Read file from disk + assertSetsEqual(engine.previousFailed, this.previousFailed); + }, + }); +}); + +add_task(async function test_resetClient() { + _("SyncEngine.resetClient resets lastSync and toFetch"); + await SyncTestingInfrastructure(server); + let engine = await makeSteamEngine(); + try { + // Ensure pristine environment + Assert.equal( + Svc.PrefBranch.getPrefType("steam.lastSync"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.equal(engine.toFetch.size, 0); + + await engine.setLastSync(123.45); + engine.toFetch = guidSetOfSize(4); + engine.previousFailed = guidSetOfSize(3); + + await engine.resetClient(); + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.toFetch.size, 0); + Assert.equal(engine.previousFailed.size, 0); + } finally { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function test_wipeServer() { + _("SyncEngine.wipeServer deletes server data and resets the client."); + let engine = await makeSteamEngine(); + + const PAYLOAD = 42; + let steamCollection = new ServerWBO("steam", PAYLOAD); + let steamServer = httpd_setup({ + "/1.1/foo/storage/steam": steamCollection.handler(), + }); + await SyncTestingInfrastructure(steamServer); + do_test_pending(); + + try { + // Some data to reset. + await engine.setLastSync(123.45); + engine.toFetch = guidSetOfSize(3); + + _("Wipe server data and reset client."); + await engine.wipeServer(); + Assert.equal(steamCollection.payload, undefined); + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.toFetch.size, 0); + } finally { + steamServer.stop(do_test_finished); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + } +}); + +add_task(async function finish() { + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_syncengine_sync.js b/services/sync/tests/unit/test_syncengine_sync.js new file mode 100644 index 0000000000..efd061d5bc --- /dev/null +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -0,0 +1,1781 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Weave } = ChromeUtils.importESModule( + "resource://services-sync/main.sys.mjs" +); +const { WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { RotaryEngine } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/rotaryengine.sys.mjs" +); + +function makeRotaryEngine() { + return new RotaryEngine(Service); +} + +async function clean(engine) { + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + Svc.PrefBranch.setStringPref("log.logger.engine.rotary", "Trace"); + Service.recordManager.clearCache(); + await engine._tracker.clearChangedIDs(); + await engine.finalize(); +} + +async function cleanAndGo(engine, server) { + await clean(engine); + await promiseStopServer(server); +} + +async function promiseClean(engine, server) { + await clean(engine); + await promiseStopServer(server); +} + +async function createServerAndConfigureClient() { + let engine = new RotaryEngine(Service); + let syncID = await engine.resetLocalSyncID(); + + let contents = { + meta: { + global: { engines: { rotary: { version: engine.version, syncID } } }, + }, + crypto: {}, + rotary: {}, + }; + + const USER = "foo"; + let server = new SyncServer(); + server.registerUser(USER, "password"); + server.createContents(USER, contents); + server.start(); + + await SyncTestingInfrastructure(server, USER); + Service._updateCachedURLs(); + + return [engine, server, USER]; +} + +/* + * Tests + * + * SyncEngine._sync() is divided into four rather independent steps: + * + * - _syncStartup() + * - _processIncoming() + * - _uploadOutgoing() + * - _syncFinish() + * + * In the spirit of unit testing, these are tested individually for + * different scenarios below. + */ + +add_task(async function setup() { + await generateNewKeys(Service.collectionKeys); + Svc.PrefBranch.setStringPref("log.logger.engine.rotary", "Trace"); +}); + +add_task(async function test_syncStartup_emptyOrOutdatedGlobalsResetsSync() { + _( + "SyncEngine._syncStartup resets sync and wipes server data if there's no or an outdated global record" + ); + + // Some server side data that's going to be wiped + let collection = new ServerCollection(); + collection.insert( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + collection.insert( + "scotsman", + encryptPayload({ id: "scotsman", denomination: "Flying Scotsman" }) + ); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + engine._store.items = { rekolok: "Rekonstruktionslokomotive" }; + try { + // Confirm initial environment + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.rekolok, undefined); + let metaGlobal = await Service.recordManager.get(engine.metaURL); + Assert.equal(metaGlobal.payload.engines, undefined); + Assert.ok(!!collection.payload("flying")); + Assert.ok(!!collection.payload("scotsman")); + + await engine.setLastSync(Date.now() / 1000); + + // Trying to prompt a wipe -- we no longer track CryptoMeta per engine, + // so it has nothing to check. + await engine._syncStartup(); + + // The meta/global WBO has been filled with data about the engine + let engineData = metaGlobal.payload.engines.rotary; + Assert.equal(engineData.version, engine.version); + Assert.equal(engineData.syncID, await engine.getSyncID()); + + // Sync was reset and server data was wiped + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(collection.payload("flying"), undefined); + Assert.equal(collection.payload("scotsman"), undefined); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_syncStartup_serverHasNewerVersion() { + _("SyncEngine._syncStartup "); + + let global = new ServerWBO("global", { + engines: { rotary: { version: 23456 } }, + }); + let server = httpd_setup({ + "/1.1/foo/storage/meta/global": global.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + try { + // The server has a newer version of the data and our engine can + // handle. That should give us an exception. + let error; + try { + await engine._syncStartup(); + } catch (ex) { + error = ex; + } + Assert.equal(error.failureCode, VERSION_OUT_OF_DATE); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_syncStartup_syncIDMismatchResetsClient() { + _("SyncEngine._syncStartup resets sync if syncIDs don't match"); + + let server = sync_httpd_setup({}); + + await SyncTestingInfrastructure(server); + + // global record with a different syncID than our engine has + let engine = makeRotaryEngine(); + let global = new ServerWBO("global", { + engines: { rotary: { version: engine.version, syncID: "foobar" } }, + }); + server.registerPathHandler("/1.1/foo/storage/meta/global", global.handler()); + + try { + // Confirm initial environment + Assert.equal(await engine.getSyncID(), ""); + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.rekolok, undefined); + + await engine.setLastSync(Date.now() / 1000); + await engine._syncStartup(); + + // The engine has assumed the server's syncID + Assert.equal(await engine.getSyncID(), "foobar"); + + // Sync was reset + Assert.equal(await engine.getLastSync(), 0); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_emptyServer() { + _("SyncEngine._processIncoming working with an empty server backend"); + + let collection = new ServerCollection(); + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + try { + // Merely ensure that this code path is run without any errors + await engine._processIncoming(); + Assert.equal(await engine.getLastSync(), 0); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_createFromServer() { + _("SyncEngine._processIncoming creates new records from server data"); + + // Some server records that will be downloaded + let collection = new ServerCollection(); + collection.insert( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + collection.insert( + "scotsman", + encryptPayload({ id: "scotsman", denomination: "Flying Scotsman" }) + ); + + // Two pathological cases involving relative URIs gone wrong. + let pathologicalPayload = encryptPayload({ + id: "../pathological", + denomination: "Pathological Case", + }); + collection.insert("../pathological", pathologicalPayload); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + "/1.1/foo/storage/rotary/flying": collection.wbo("flying").handler(), + "/1.1/foo/storage/rotary/scotsman": collection.wbo("scotsman").handler(), + }); + + await SyncTestingInfrastructure(server); + + await generateNewKeys(Service.collectionKeys); + + let engine = makeRotaryEngine(); + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + // Confirm initial environment + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.lastModified, null); + Assert.equal(engine._store.items.flying, undefined); + Assert.equal(engine._store.items.scotsman, undefined); + Assert.equal(engine._store.items["../pathological"], undefined); + + await engine._syncStartup(); + await engine._processIncoming(); + + // Timestamps of last sync and last server modification are set. + Assert.ok((await engine.getLastSync()) > 0); + Assert.ok(engine.lastModified > 0); + + // Local records have been created from the server data. + Assert.equal(engine._store.items.flying, "LNER Class A3 4472"); + Assert.equal(engine._store.items.scotsman, "Flying Scotsman"); + Assert.equal(engine._store.items["../pathological"], "Pathological Case"); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_reconcile() { + _("SyncEngine._processIncoming updates local records"); + + let collection = new ServerCollection(); + + // This server record is newer than the corresponding client one, + // so it'll update its data. + collection.insert( + "newrecord", + encryptPayload({ id: "newrecord", denomination: "New stuff..." }) + ); + + // This server record is newer than the corresponding client one, + // so it'll update its data. + collection.insert( + "newerserver", + encryptPayload({ id: "newerserver", denomination: "New data!" }) + ); + + // This server record is 2 mins older than the client counterpart + // but identical to it, so we're expecting the client record's + // changedID to be reset. + collection.insert( + "olderidentical", + encryptPayload({ + id: "olderidentical", + denomination: "Older but identical", + }) + ); + collection._wbos.olderidentical.modified -= 120; + + // This item simply has different data than the corresponding client + // record (which is unmodified), so it will update the client as well + collection.insert( + "updateclient", + encryptPayload({ id: "updateclient", denomination: "Get this!" }) + ); + + // This is a dupe of 'original'. + collection.insert( + "duplication", + encryptPayload({ id: "duplication", denomination: "Original Entry" }) + ); + + // This record is marked as deleted, so we're expecting the client + // record to be removed. + collection.insert( + "nukeme", + encryptPayload({ id: "nukeme", denomination: "Nuke me!", deleted: true }) + ); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + engine._store.items = { + newerserver: "New data, but not as new as server!", + olderidentical: "Older but identical", + updateclient: "Got data?", + original: "Original Entry", + long_original: "Long Original Entry", + nukeme: "Nuke me!", + }; + // Make this record 1 min old, thus older than the one on the server + await engine._tracker.addChangedID("newerserver", Date.now() / 1000 - 60); + // This record has been changed 2 mins later than the one on the server + await engine._tracker.addChangedID("olderidentical", Date.now() / 1000); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + // Confirm initial environment + Assert.equal(engine._store.items.newrecord, undefined); + Assert.equal( + engine._store.items.newerserver, + "New data, but not as new as server!" + ); + Assert.equal(engine._store.items.olderidentical, "Older but identical"); + Assert.equal(engine._store.items.updateclient, "Got data?"); + Assert.equal(engine._store.items.nukeme, "Nuke me!"); + let changes = await engine._tracker.getChangedIDs(); + Assert.ok(changes.olderidentical > 0); + + await engine._syncStartup(); + await engine._processIncoming(); + + // Timestamps of last sync and last server modification are set. + Assert.ok((await engine.getLastSync()) > 0); + Assert.ok(engine.lastModified > 0); + + // The new record is created. + Assert.equal(engine._store.items.newrecord, "New stuff..."); + + // The 'newerserver' record is updated since the server data is newer. + Assert.equal(engine._store.items.newerserver, "New data!"); + + // The data for 'olderidentical' is identical on the server, so + // it's no longer marked as changed anymore. + Assert.equal(engine._store.items.olderidentical, "Older but identical"); + changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.olderidentical, undefined); + + // Updated with server data. + Assert.equal(engine._store.items.updateclient, "Get this!"); + + // The incoming ID is preferred. + Assert.equal(engine._store.items.original, undefined); + Assert.equal(engine._store.items.duplication, "Original Entry"); + Assert.notEqual(engine._delete.ids.indexOf("original"), -1); + + // The 'nukeme' record marked as deleted is removed. + Assert.equal(engine._store.items.nukeme, undefined); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_reconcile_local_deleted() { + _("Ensure local, duplicate ID is deleted on server."); + + // When a duplicate is resolved, the local ID (which is never taken) should + // be deleted on the server. + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + let record = encryptPayload({ + id: "DUPE_INCOMING", + denomination: "incoming", + }); + let wbo = new ServerWBO("DUPE_INCOMING", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + record = encryptPayload({ id: "DUPE_LOCAL", denomination: "local" }); + wbo = new ServerWBO("DUPE_LOCAL", record, now - 1); + server.insertWBO(user, "rotary", wbo); + + await engine._store.create({ id: "DUPE_LOCAL", denomination: "local" }); + Assert.ok(await engine._store.itemExists("DUPE_LOCAL")); + Assert.equal("DUPE_LOCAL", await engine._findDupe({ id: "DUPE_INCOMING" })); + + await engine._sync(); + + do_check_attribute_count(engine._store.items, 1); + Assert.ok("DUPE_INCOMING" in engine._store.items); + + let collection = server.getCollection(user, "rotary"); + Assert.equal(1, collection.count()); + Assert.notEqual(undefined, collection.wbo("DUPE_INCOMING")); + + await cleanAndGo(engine, server); +}); + +add_task(async function test_processIncoming_reconcile_equivalent() { + _("Ensure proper handling of incoming records that match local."); + + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + let record = encryptPayload({ id: "entry", denomination: "denomination" }); + let wbo = new ServerWBO("entry", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + engine._store.items = { entry: "denomination" }; + Assert.ok(await engine._store.itemExists("entry")); + + await engine._sync(); + + do_check_attribute_count(engine._store.items, 1); + + await cleanAndGo(engine, server); +}); + +add_task( + async function test_processIncoming_reconcile_locally_deleted_dupe_new() { + _( + "Ensure locally deleted duplicate record newer than incoming is handled." + ); + + // This is a somewhat complicated test. It ensures that if a client receives + // a modified record for an item that is deleted locally but with a different + // ID that the incoming record is ignored. This is a corner case for record + // handling, but it needs to be supported. + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + let record = encryptPayload({ + id: "DUPE_INCOMING", + denomination: "incoming", + }); + let wbo = new ServerWBO("DUPE_INCOMING", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + // Simulate a locally-deleted item. + engine._store.items = {}; + await engine._tracker.addChangedID("DUPE_LOCAL", now + 3); + Assert.equal(false, await engine._store.itemExists("DUPE_LOCAL")); + Assert.equal(false, await engine._store.itemExists("DUPE_INCOMING")); + Assert.equal("DUPE_LOCAL", await engine._findDupe({ id: "DUPE_INCOMING" })); + + engine.lastModified = server.getCollection(user, engine.name).timestamp; + await engine._sync(); + + // After the sync, the server's payload for the original ID should be marked + // as deleted. + do_check_empty(engine._store.items); + let collection = server.getCollection(user, "rotary"); + Assert.equal(1, collection.count()); + wbo = collection.wbo("DUPE_INCOMING"); + Assert.notEqual(null, wbo); + let payload = wbo.getCleartext(); + Assert.ok(payload.deleted); + + await cleanAndGo(engine, server); + } +); + +add_task( + async function test_processIncoming_reconcile_locally_deleted_dupe_old() { + _( + "Ensure locally deleted duplicate record older than incoming is restored." + ); + + // This is similar to the above test except it tests the condition where the + // incoming record is newer than the local deletion, therefore overriding it. + + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + let record = encryptPayload({ + id: "DUPE_INCOMING", + denomination: "incoming", + }); + let wbo = new ServerWBO("DUPE_INCOMING", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + // Simulate a locally-deleted item. + engine._store.items = {}; + await engine._tracker.addChangedID("DUPE_LOCAL", now + 1); + Assert.equal(false, await engine._store.itemExists("DUPE_LOCAL")); + Assert.equal(false, await engine._store.itemExists("DUPE_INCOMING")); + Assert.equal("DUPE_LOCAL", await engine._findDupe({ id: "DUPE_INCOMING" })); + + await engine._sync(); + + // Since the remote change is newer, the incoming item should exist locally. + do_check_attribute_count(engine._store.items, 1); + Assert.ok("DUPE_INCOMING" in engine._store.items); + Assert.equal("incoming", engine._store.items.DUPE_INCOMING); + + let collection = server.getCollection(user, "rotary"); + Assert.equal(1, collection.count()); + wbo = collection.wbo("DUPE_INCOMING"); + let payload = wbo.getCleartext(); + Assert.equal("incoming", payload.denomination); + + await cleanAndGo(engine, server); + } +); + +add_task(async function test_processIncoming_reconcile_changed_dupe() { + _("Ensure that locally changed duplicate record is handled properly."); + + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + // The local record is newer than the incoming one, so it should be retained. + let record = encryptPayload({ + id: "DUPE_INCOMING", + denomination: "incoming", + }); + let wbo = new ServerWBO("DUPE_INCOMING", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + await engine._store.create({ id: "DUPE_LOCAL", denomination: "local" }); + await engine._tracker.addChangedID("DUPE_LOCAL", now + 3); + Assert.ok(await engine._store.itemExists("DUPE_LOCAL")); + Assert.equal("DUPE_LOCAL", await engine._findDupe({ id: "DUPE_INCOMING" })); + + engine.lastModified = server.getCollection(user, engine.name).timestamp; + await engine._sync(); + + // The ID should have been changed to incoming. + do_check_attribute_count(engine._store.items, 1); + Assert.ok("DUPE_INCOMING" in engine._store.items); + + // On the server, the local ID should be deleted and the incoming ID should + // have its payload set to what was in the local record. + let collection = server.getCollection(user, "rotary"); + Assert.equal(1, collection.count()); + wbo = collection.wbo("DUPE_INCOMING"); + Assert.notEqual(undefined, wbo); + let payload = wbo.getCleartext(); + Assert.equal("local", payload.denomination); + + await cleanAndGo(engine, server); +}); + +add_task(async function test_processIncoming_reconcile_changed_dupe_new() { + _("Ensure locally changed duplicate record older than incoming is ignored."); + + // This test is similar to the above except the incoming record is younger + // than the local record. The incoming record should be authoritative. + let [engine, server, user] = await createServerAndConfigureClient(); + + let now = Date.now() / 1000 - 10; + await engine.setLastSync(now); + engine.lastModified = now + 1; + + let record = encryptPayload({ + id: "DUPE_INCOMING", + denomination: "incoming", + }); + let wbo = new ServerWBO("DUPE_INCOMING", record, now + 2); + server.insertWBO(user, "rotary", wbo); + + await engine._store.create({ id: "DUPE_LOCAL", denomination: "local" }); + await engine._tracker.addChangedID("DUPE_LOCAL", now + 1); + Assert.ok(await engine._store.itemExists("DUPE_LOCAL")); + Assert.equal("DUPE_LOCAL", await engine._findDupe({ id: "DUPE_INCOMING" })); + + engine.lastModified = server.getCollection(user, engine.name).timestamp; + await engine._sync(); + + // The ID should have been changed to incoming. + do_check_attribute_count(engine._store.items, 1); + Assert.ok("DUPE_INCOMING" in engine._store.items); + + // On the server, the local ID should be deleted and the incoming ID should + // have its payload retained. + let collection = server.getCollection(user, "rotary"); + Assert.equal(1, collection.count()); + wbo = collection.wbo("DUPE_INCOMING"); + Assert.notEqual(undefined, wbo); + let payload = wbo.getCleartext(); + Assert.equal("incoming", payload.denomination); + await cleanAndGo(engine, server); +}); + +add_task(async function test_processIncoming_resume_toFetch() { + _( + "toFetch and previousFailed items left over from previous syncs are fetched on the next sync, along with new items." + ); + + const LASTSYNC = Date.now() / 1000; + + // Server records that will be downloaded + let collection = new ServerCollection(); + collection.insert( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + collection.insert( + "scotsman", + encryptPayload({ id: "scotsman", denomination: "Flying Scotsman" }) + ); + collection.insert( + "rekolok", + encryptPayload({ id: "rekolok", denomination: "Rekonstruktionslokomotive" }) + ); + for (let i = 0; i < 3; i++) { + let id = "failed" + i; + let payload = encryptPayload({ id, denomination: "Record No. " + i }); + let wbo = new ServerWBO(id, payload); + wbo.modified = LASTSYNC - 10; + collection.insertWBO(wbo); + } + + collection.wbo("flying").modified = collection.wbo("scotsman").modified = + LASTSYNC - 10; + collection._wbos.rekolok.modified = LASTSYNC + 10; + + // Time travel 10 seconds into the future but still download the above WBOs. + let engine = makeRotaryEngine(); + await engine.setLastSync(LASTSYNC); + engine.toFetch = new SerializableSet(["flying", "scotsman"]); + engine.previousFailed = new SerializableSet([ + "failed0", + "failed1", + "failed2", + ]); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + try { + // Confirm initial environment + Assert.equal(engine._store.items.flying, undefined); + Assert.equal(engine._store.items.scotsman, undefined); + Assert.equal(engine._store.items.rekolok, undefined); + + await engine._syncStartup(); + await engine._processIncoming(); + + // Local records have been created from the server data. + Assert.equal(engine._store.items.flying, "LNER Class A3 4472"); + Assert.equal(engine._store.items.scotsman, "Flying Scotsman"); + Assert.equal(engine._store.items.rekolok, "Rekonstruktionslokomotive"); + Assert.equal(engine._store.items.failed0, "Record No. 0"); + Assert.equal(engine._store.items.failed1, "Record No. 1"); + Assert.equal(engine._store.items.failed2, "Record No. 2"); + Assert.equal(engine.previousFailed.size, 0); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_notify_count() { + _("Ensure that failed records are reported only once."); + + const NUMBER_OF_RECORDS = 15; + + // Engine that fails every 5 records. + let engine = makeRotaryEngine(); + engine._store._applyIncomingBatch = engine._store.applyIncomingBatch; + engine._store.applyIncomingBatch = async function (records, countTelemetry) { + let sortedRecords = records.sort((a, b) => (a.id > b.id ? 1 : -1)); + let recordsToApply = [], + recordsToFail = []; + for (let i = 0; i < sortedRecords.length; i++) { + (i % 5 === 0 ? recordsToFail : recordsToApply).push(sortedRecords[i]); + } + recordsToFail.forEach(() => { + countTelemetry.addIncomingFailedReason("failed message"); + }); + await engine._store._applyIncomingBatch(recordsToApply, countTelemetry); + + return recordsToFail.map(record => record.id); + }; + + // Create a batch of server side records. + let collection = new ServerCollection(); + for (var i = 0; i < NUMBER_OF_RECORDS; i++) { + let id = "record-no-" + i.toString(10).padStart(2, "0"); + let payload = encryptPayload({ id, denomination: "Record No. " + id }); + collection.insert(id, payload); + } + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + try { + // Confirm initial environment. + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.toFetch.size, 0); + Assert.equal(engine.previousFailed.size, 0); + do_check_empty(engine._store.items); + + let called = 0; + let counts; + function onApplied(count) { + _("Called with " + JSON.stringify(counts)); + counts = count; + called++; + } + Svc.Obs.add("weave:engine:sync:applied", onApplied); + + // Do sync. + await engine._syncStartup(); + await engine._processIncoming(); + + // Confirm failures. + do_check_attribute_count(engine._store.items, 12); + Assert.deepEqual( + Array.from(engine.previousFailed).sort(), + ["record-no-00", "record-no-05", "record-no-10"].sort() + ); + + // There are newly failed records and they are reported. + Assert.equal(called, 1); + Assert.equal(counts.failed, 3); + Assert.equal(counts.failedReasons[0].count, 3); + Assert.equal(counts.failedReasons[0].name, "failed message"); + Assert.equal(counts.applied, 15); + Assert.equal(counts.newFailed, 3); + Assert.equal(counts.succeeded, 12); + + // Sync again, 1 of the failed items are the same, the rest didn't fail. + await engine._processIncoming(); + + // Confirming removed failures. + do_check_attribute_count(engine._store.items, 14); + // After failing twice the record that failed again [record-no-00] + // should NOT be stored to try again + Assert.deepEqual(Array.from(engine.previousFailed), []); + + Assert.equal(called, 2); + Assert.equal(counts.failed, 1); + Assert.equal(counts.failedReasons[0].count, 1); + Assert.equal(counts.failedReasons[0].name, "failed message"); + Assert.equal(counts.applied, 3); + Assert.equal(counts.newFailed, 0); + Assert.equal(counts.succeeded, 2); + + Svc.Obs.remove("weave:engine:sync:applied", onApplied); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_previousFailed() { + _("Ensure that failed records are retried."); + + const NUMBER_OF_RECORDS = 14; + + // Engine that alternates between failing and applying every 2 records. + let engine = makeRotaryEngine(); + engine._store._applyIncomingBatch = engine._store.applyIncomingBatch; + engine._store.applyIncomingBatch = async function (records, countTelemetry) { + let sortedRecords = records.sort((a, b) => (a.id > b.id ? 1 : -1)); + let recordsToApply = [], + recordsToFail = []; + let chunks = Array.from(PlacesUtils.chunkArray(sortedRecords, 2)); + for (let i = 0; i < chunks.length; i++) { + (i % 2 === 0 ? recordsToFail : recordsToApply).push(...chunks[i]); + } + await engine._store._applyIncomingBatch(recordsToApply, countTelemetry); + return recordsToFail.map(record => record.id); + }; + + // Create a batch of server side records. + let collection = new ServerCollection(); + for (var i = 0; i < NUMBER_OF_RECORDS; i++) { + let id = "record-no-" + i.toString(10).padStart(2, "0"); + let payload = encryptPayload({ id, denomination: "Record No. " + i }); + collection.insert(id, payload); + } + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + try { + // Confirm initial environment. + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.toFetch.size, 0); + Assert.equal(engine.previousFailed.size, 0); + do_check_empty(engine._store.items); + + // Initial failed items in previousFailed to be reset. + let previousFailed = new SerializableSet([ + Utils.makeGUID(), + Utils.makeGUID(), + Utils.makeGUID(), + ]); + engine.previousFailed = previousFailed; + Assert.equal(engine.previousFailed, previousFailed); + + // Do sync. + await engine._syncStartup(); + await engine._processIncoming(); + + // Expected result: 4 sync batches with 2 failures each => 8 failures + do_check_attribute_count(engine._store.items, 6); + Assert.deepEqual( + Array.from(engine.previousFailed).sort(), + [ + "record-no-00", + "record-no-01", + "record-no-04", + "record-no-05", + "record-no-08", + "record-no-09", + "record-no-12", + "record-no-13", + ].sort() + ); + + // Sync again with the same failed items (records 0, 1, 8, 9). + await engine._processIncoming(); + + do_check_attribute_count(engine._store.items, 10); + // A second sync with the same failed items should NOT add the same items again. + // Items that did not fail a second time should no longer be in previousFailed. + Assert.deepEqual(Array.from(engine.previousFailed).sort(), []); + + // Refetched items that didn't fail the second time are in engine._store.items. + Assert.equal(engine._store.items["record-no-04"], "Record No. 4"); + Assert.equal(engine._store.items["record-no-05"], "Record No. 5"); + Assert.equal(engine._store.items["record-no-12"], "Record No. 12"); + Assert.equal(engine._store.items["record-no-13"], "Record No. 13"); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_failed_records() { + _( + "Ensure that failed records from _reconcile and applyIncomingBatch are refetched." + ); + + // Let's create three and a bit batches worth of server side records. + let APPLY_BATCH_SIZE = 50; + let collection = new ServerCollection(); + const NUMBER_OF_RECORDS = APPLY_BATCH_SIZE * 3 + 5; + for (let i = 0; i < NUMBER_OF_RECORDS; i++) { + let id = "record-no-" + i; + let payload = encryptPayload({ id, denomination: "Record No. " + id }); + let wbo = new ServerWBO(id, payload); + wbo.modified = Date.now() / 1000 + 60 * (i - APPLY_BATCH_SIZE * 3); + collection.insertWBO(wbo); + } + + // Engine that batches but likes to throw on a couple of records, + // two in each batch: the even ones fail in reconcile, the odd ones + // in applyIncoming. + const BOGUS_RECORDS = [ + "record-no-" + 42, + "record-no-" + 23, + "record-no-" + (42 + APPLY_BATCH_SIZE), + "record-no-" + (23 + APPLY_BATCH_SIZE), + "record-no-" + (42 + APPLY_BATCH_SIZE * 2), + "record-no-" + (23 + APPLY_BATCH_SIZE * 2), + "record-no-" + (2 + APPLY_BATCH_SIZE * 3), + "record-no-" + (1 + APPLY_BATCH_SIZE * 3), + ]; + let engine = makeRotaryEngine(); + + engine.__reconcile = engine._reconcile; + engine._reconcile = async function _reconcile(record) { + if (BOGUS_RECORDS.indexOf(record.id) % 2 == 0) { + throw new Error("I don't like this record! Baaaaaah!"); + } + return this.__reconcile.apply(this, arguments); + }; + engine._store._applyIncoming = engine._store.applyIncoming; + engine._store.applyIncoming = async function (record) { + if (BOGUS_RECORDS.indexOf(record.id) % 2 == 1) { + throw new Error("I don't like this record! Baaaaaah!"); + } + return this._applyIncoming.apply(this, arguments); + }; + + // Keep track of requests made of a collection. + let count = 0; + let uris = []; + function recording_handler(recordedCollection) { + let h = recordedCollection.handler(); + return function (req, res) { + ++count; + uris.push(req.path + "?" + req.queryString); + return h(req, res); + }; + } + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": recording_handler(collection), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + // Confirm initial environment + Assert.equal(await engine.getLastSync(), 0); + Assert.equal(engine.toFetch.size, 0); + Assert.equal(engine.previousFailed.size, 0); + do_check_empty(engine._store.items); + + let observerSubject; + let observerData; + Svc.Obs.add("weave:engine:sync:applied", function onApplied(subject, data) { + Svc.Obs.remove("weave:engine:sync:applied", onApplied); + observerSubject = subject; + observerData = data; + }); + + await engine._syncStartup(); + await engine._processIncoming(); + + // Ensure that all records but the bogus 4 have been applied. + do_check_attribute_count( + engine._store.items, + NUMBER_OF_RECORDS - BOGUS_RECORDS.length + ); + + // Ensure that the bogus records will be fetched again on the next sync. + Assert.equal(engine.previousFailed.size, BOGUS_RECORDS.length); + Assert.deepEqual( + Array.from(engine.previousFailed).sort(), + BOGUS_RECORDS.sort() + ); + + // Ensure the observer was notified + Assert.equal(observerData, engine.name); + Assert.equal(observerSubject.failed, BOGUS_RECORDS.length); + Assert.equal(observerSubject.newFailed, BOGUS_RECORDS.length); + + // Testing batching of failed item fetches. + // Try to sync again. Ensure that we split the request into chunks to avoid + // URI length limitations. + async function batchDownload(batchSize) { + count = 0; + uris = []; + engine.guidFetchBatchSize = batchSize; + await engine._processIncoming(); + _("Tried again. Requests: " + count + "; URIs: " + JSON.stringify(uris)); + return count; + } + + // There are 8 bad records, so this needs 3 fetches. + _("Test batching with ID batch size 3, normal mobile batch size."); + Assert.equal(await batchDownload(3), 3); + + // Since there the previous batch failed again, there should be + // no more records to fetch + _("Test that the second time a record failed to sync, gets ignored"); + Assert.equal(await batchDownload(BOGUS_RECORDS.length), 0); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_processIncoming_decrypt_failed() { + _("Ensure that records failing to decrypt are either replaced or refetched."); + + // Some good and some bogus records. One doesn't contain valid JSON, + // the other will throw during decrypt. + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + collection._wbos.nojson = new ServerWBO("nojson", "This is invalid JSON"); + collection._wbos.nojson2 = new ServerWBO("nojson2", "This is invalid JSON"); + collection._wbos.scotsman = new ServerWBO( + "scotsman", + encryptPayload({ id: "scotsman", denomination: "Flying Scotsman" }) + ); + collection._wbos.nodecrypt = new ServerWBO("nodecrypt", "Decrypt this!"); + collection._wbos.nodecrypt2 = new ServerWBO("nodecrypt2", "Decrypt this!"); + + // Patch the fake crypto service to throw on the record above. + Weave.Crypto._decrypt = Weave.Crypto.decrypt; + Weave.Crypto.decrypt = function (ciphertext) { + if (ciphertext == "Decrypt this!") { + throw new Error( + "Derp! Cipher finalized failed. Im ur crypto destroyin ur recordz." + ); + } + return this._decrypt.apply(this, arguments); + }; + + // Some broken records also exist locally. + let engine = makeRotaryEngine(); + engine.enabled = true; + engine._store.items = { nojson: "Valid JSON", nodecrypt: "Valid ciphertext" }; + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + try { + // Confirm initial state + Assert.equal(engine.toFetch.size, 0); + Assert.equal(engine.previousFailed.size, 0); + + let observerSubject; + let observerData; + Svc.Obs.add("weave:engine:sync:applied", function onApplied(subject, data) { + Svc.Obs.remove("weave:engine:sync:applied", onApplied); + observerSubject = subject; + observerData = data; + }); + + await engine.setLastSync(collection.wbo("nojson").modified - 1); + let ping = await sync_engine_and_validate_telem(engine, true); + Assert.equal(ping.engines[0].incoming.applied, 2); + Assert.equal(ping.engines[0].incoming.failed, 4); + console.log("incoming telem: ", ping.engines[0].incoming); + Assert.equal( + ping.engines[0].incoming.failedReasons[0].name, + "No ciphertext: nothing to decrypt?" + ); + // There should be 4 of the same error + Assert.equal(ping.engines[0].incoming.failedReasons[0].count, 4); + + Assert.equal(engine.previousFailed.size, 4); + Assert.ok(engine.previousFailed.has("nojson")); + Assert.ok(engine.previousFailed.has("nojson2")); + Assert.ok(engine.previousFailed.has("nodecrypt")); + Assert.ok(engine.previousFailed.has("nodecrypt2")); + + // Ensure the observer was notified + Assert.equal(observerData, engine.name); + Assert.equal(observerSubject.applied, 2); + Assert.equal(observerSubject.failed, 4); + Assert.equal(observerSubject.failedReasons[0].count, 4); + } finally { + await promiseClean(engine, server); + } +}); + +add_task(async function test_uploadOutgoing_toEmptyServer() { + _("SyncEngine._uploadOutgoing uploads new records to server"); + + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO("flying"); + collection._wbos.scotsman = new ServerWBO("scotsman"); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + "/1.1/foo/storage/rotary/flying": collection.wbo("flying").handler(), + "/1.1/foo/storage/rotary/scotsman": collection.wbo("scotsman").handler(), + }); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let engine = makeRotaryEngine(); + engine._store.items = { + flying: "LNER Class A3 4472", + scotsman: "Flying Scotsman", + }; + // Mark one of these records as changed + await engine._tracker.addChangedID("scotsman", 0); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + await engine.setLastSync(123); // needs to be non-zero so that tracker is queried + + // Confirm initial environment + Assert.equal(collection.payload("flying"), undefined); + Assert.equal(collection.payload("scotsman"), undefined); + + await engine._syncStartup(); + await engine._uploadOutgoing(); + + // Ensure the marked record ('scotsman') has been uploaded and is + // no longer marked. + Assert.equal(collection.payload("flying"), undefined); + Assert.ok(!!collection.payload("scotsman")); + Assert.equal(collection.cleartext("scotsman").id, "scotsman"); + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.scotsman, undefined); + + // The 'flying' record wasn't marked so it wasn't uploaded + Assert.equal(collection.payload("flying"), undefined); + } finally { + await cleanAndGo(engine, server); + } +}); + +async function test_uploadOutgoing_max_record_payload_bytes( + allowSkippedRecord +) { + _( + "SyncEngine._uploadOutgoing throws when payload is bigger than max_record_payload_bytes" + ); + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO("flying"); + collection._wbos.scotsman = new ServerWBO("scotsman"); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + "/1.1/foo/storage/rotary/flying": collection.wbo("flying").handler(), + "/1.1/foo/storage/rotary/scotsman": collection.wbo("scotsman").handler(), + }); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let engine = makeRotaryEngine(); + engine.allowSkippedRecord = allowSkippedRecord; + engine._store.items = { flying: "a".repeat(1024 * 1024), scotsman: "abcd" }; + + await engine._tracker.addChangedID("flying", 1000); + await engine._tracker.addChangedID("scotsman", 1000); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + await engine.setLastSync(1); // needs to be non-zero so that tracker is queried + + // Confirm initial environment + Assert.equal(collection.payload("flying"), undefined); + Assert.equal(collection.payload("scotsman"), undefined); + + await engine._syncStartup(); + await engine._uploadOutgoing(); + + if (!allowSkippedRecord) { + do_throw("should not get here"); + } + + await engine.trackRemainingChanges(); + + // Check we uploaded the other record to the server + Assert.ok(collection.payload("scotsman")); + // And that we won't try to upload the huge record next time. + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, undefined); + } catch (e) { + if (allowSkippedRecord) { + do_throw("should not get here"); + } + + await engine.trackRemainingChanges(); + + // Check that we will try to upload the huge record next time + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, 1000); + } finally { + // Check we didn't upload the oversized record to the server + Assert.equal(collection.payload("flying"), undefined); + await cleanAndGo(engine, server); + } +} + +add_task( + async function test_uploadOutgoing_max_record_payload_bytes_disallowSkippedRecords() { + return test_uploadOutgoing_max_record_payload_bytes(false); + } +); + +add_task( + async function test_uploadOutgoing_max_record_payload_bytes_allowSkippedRecords() { + return test_uploadOutgoing_max_record_payload_bytes(true); + } +); + +add_task(async function test_uploadOutgoing_failed() { + _( + "SyncEngine._uploadOutgoing doesn't clear the tracker of objects that failed to upload." + ); + + let collection = new ServerCollection(); + // We only define the "flying" WBO on the server, not the "scotsman" + // and "peppercorn" ones. + collection._wbos.flying = new ServerWBO("flying"); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + engine._store.items = { + flying: "LNER Class A3 4472", + scotsman: "Flying Scotsman", + peppercorn: "Peppercorn Class", + }; + // Mark these records as changed + const FLYING_CHANGED = 12345; + const SCOTSMAN_CHANGED = 23456; + const PEPPERCORN_CHANGED = 34567; + await engine._tracker.addChangedID("flying", FLYING_CHANGED); + await engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED); + await engine._tracker.addChangedID("peppercorn", PEPPERCORN_CHANGED); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + await engine.setLastSync(123); // needs to be non-zero so that tracker is queried + + // Confirm initial environment + Assert.equal(collection.payload("flying"), undefined); + let changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, FLYING_CHANGED); + Assert.equal(changes.scotsman, SCOTSMAN_CHANGED); + Assert.equal(changes.peppercorn, PEPPERCORN_CHANGED); + + engine.enabled = true; + await sync_engine_and_validate_telem(engine, true); + + // Ensure the 'flying' record has been uploaded and is no longer marked. + Assert.ok(!!collection.payload("flying")); + changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, undefined); + + // The 'scotsman' and 'peppercorn' records couldn't be uploaded so + // they weren't cleared from the tracker. + Assert.equal(changes.scotsman, SCOTSMAN_CHANGED); + Assert.equal(changes.peppercorn, PEPPERCORN_CHANGED); + } finally { + await promiseClean(engine, server); + } +}); + +async function createRecordFailTelemetry(allowSkippedRecord) { + Services.prefs.setStringPref("services.sync.username", "foo"); + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO("flying"); + collection._wbos.scotsman = new ServerWBO("scotsman"); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + engine.allowSkippedRecord = allowSkippedRecord; + let oldCreateRecord = engine._store.createRecord; + engine._store.createRecord = async (id, col) => { + if (id != "flying") { + throw new Error("oops"); + } + return oldCreateRecord.call(engine._store, id, col); + }; + engine._store.items = { + flying: "LNER Class A3 4472", + scotsman: "Flying Scotsman", + }; + // Mark these records as changed + const FLYING_CHANGED = 12345; + const SCOTSMAN_CHANGED = 23456; + await engine._tracker.addChangedID("flying", FLYING_CHANGED); + await engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + let ping; + try { + await engine.setLastSync(123); // needs to be non-zero so that tracker is queried + + // Confirm initial environment + Assert.equal(collection.payload("flying"), undefined); + let changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, FLYING_CHANGED); + Assert.equal(changes.scotsman, SCOTSMAN_CHANGED); + + engine.enabled = true; + ping = await sync_engine_and_validate_telem(engine, true, onErrorPing => { + ping = onErrorPing; + }); + + if (!allowSkippedRecord) { + do_throw("should not get here"); + } + + // Ensure the 'flying' record has been uploaded and is no longer marked. + Assert.ok(!!collection.payload("flying")); + changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.flying, undefined); + } catch (err) { + if (allowSkippedRecord) { + do_throw("should not get here"); + } + + // Ensure the 'flying' record has not been uploaded and is still marked + Assert.ok(!collection.payload("flying")); + const changes = await engine._tracker.getChangedIDs(); + Assert.ok(changes.flying); + } finally { + // We reported in telemetry that we failed a record + Assert.equal(ping.engines[0].outgoing[0].failed, 1); + Assert.equal(ping.engines[0].outgoing[0].failedReasons[0].name, "oops"); + + // In any case, the 'scotsman' record couldn't be created so it wasn't + // uploaded nor it was not cleared from the tracker. + Assert.ok(!collection.payload("scotsman")); + const changes = await engine._tracker.getChangedIDs(); + Assert.equal(changes.scotsman, SCOTSMAN_CHANGED); + + engine._store.createRecord = oldCreateRecord; + await promiseClean(engine, server); + } +} + +add_task( + async function test_uploadOutgoing_createRecord_throws_reported_telemetry() { + _( + "SyncEngine._uploadOutgoing reports a failed record to telemetry if createRecord throws" + ); + await createRecordFailTelemetry(true); + } +); + +add_task( + async function test_uploadOutgoing_createRecord_throws_dontAllowSkipRecord() { + _( + "SyncEngine._uploadOutgoing will throw if createRecord throws and allowSkipRecord is set to false" + ); + await createRecordFailTelemetry(false); + } +); + +add_task(async function test_uploadOutgoing_largeRecords() { + _( + "SyncEngine._uploadOutgoing throws on records larger than the max record payload size" + ); + + let collection = new ServerCollection(); + + let engine = makeRotaryEngine(); + engine.allowSkippedRecord = false; + engine._store.items["large-item"] = "Y".repeat( + Service.getMaxRecordPayloadSize() * 2 + ); + await engine._tracker.addChangedID("large-item", 0); + collection.insert("large-item"); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + try { + await engine._syncStartup(); + let error = null; + try { + await engine._uploadOutgoing(); + } catch (e) { + error = e; + } + ok(!!error); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_syncFinish_deleteByIds() { + _( + "SyncEngine._syncFinish deletes server records slated for deletion (list of record IDs)." + ); + + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + collection._wbos.scotsman = new ServerWBO( + "scotsman", + encryptPayload({ id: "scotsman", denomination: "Flying Scotsman" }) + ); + collection._wbos.rekolok = new ServerWBO( + "rekolok", + encryptPayload({ id: "rekolok", denomination: "Rekonstruktionslokomotive" }) + ); + + let server = httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + try { + engine._delete = { ids: ["flying", "rekolok"] }; + await engine._syncFinish(); + + // The 'flying' and 'rekolok' records were deleted while the + // 'scotsman' one wasn't. + Assert.equal(collection.payload("flying"), undefined); + Assert.ok(!!collection.payload("scotsman")); + Assert.equal(collection.payload("rekolok"), undefined); + + // The deletion todo list has been reset. + Assert.equal(engine._delete.ids, undefined); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_syncFinish_deleteLotsInBatches() { + _( + "SyncEngine._syncFinish deletes server records in batches of 100 (list of record IDs)." + ); + + let collection = new ServerCollection(); + + // Let's count how many times the client does a DELETE request to the server + var noOfUploads = 0; + collection.delete = (function (orig) { + return function () { + noOfUploads++; + return orig.apply(this, arguments); + }; + })(collection.delete); + + // Create a bunch of records on the server + let now = Date.now(); + for (var i = 0; i < 234; i++) { + let id = "record-no-" + i; + let payload = encryptPayload({ id, denomination: "Record No. " + i }); + let wbo = new ServerWBO(id, payload); + wbo.modified = now / 1000 - 60 * (i + 110); + collection.insertWBO(wbo); + } + + let server = httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let engine = makeRotaryEngine(); + try { + // Confirm initial environment + Assert.equal(noOfUploads, 0); + + // Declare what we want to have deleted: all records no. 100 and + // up and all records that are less than 200 mins old (which are + // records 0 thru 90). + engine._delete = { ids: [], newer: now / 1000 - 60 * 200.5 }; + for (i = 100; i < 234; i++) { + engine._delete.ids.push("record-no-" + i); + } + + await engine._syncFinish(); + + // Ensure that the appropriate server data has been wiped while + // preserving records 90 thru 200. + for (i = 0; i < 234; i++) { + let id = "record-no-" + i; + if (i <= 90 || i >= 100) { + Assert.equal(collection.payload(id), undefined); + } else { + Assert.ok(!!collection.payload(id)); + } + } + + // The deletion was done in batches + Assert.equal(noOfUploads, 2 + 1); + + // The deletion todo list has been reset. + Assert.equal(engine._delete.ids, undefined); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_sync_partialUpload() { + _("SyncEngine.sync() keeps changedIDs that couldn't be uploaded."); + + let collection = new ServerCollection(); + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + let oldServerConfiguration = Service.serverConfiguration; + Service.serverConfiguration = { + max_post_records: 100, + }; + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let engine = makeRotaryEngine(); + + // Let the third upload fail completely + var noOfUploads = 0; + collection.post = (function (orig) { + return function () { + if (noOfUploads == 2) { + throw new Error("FAIL!"); + } + noOfUploads++; + return orig.apply(this, arguments); + }; + })(collection.post); + + // Create a bunch of records (and server side handlers) + for (let i = 0; i < 234; i++) { + let id = "record-no-" + i; + engine._store.items[id] = "Record No. " + i; + await engine._tracker.addChangedID(id, i); + // Let two items in the first upload batch fail. + if (i != 23 && i != 42) { + collection.insert(id); + } + } + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + await engine.setLastSync(123); // needs to be non-zero so that tracker is queried + + engine.enabled = true; + let error; + try { + await sync_engine_and_validate_telem(engine, true); + } catch (ex) { + error = ex; + } + + ok(!!error); + + const changes = await engine._tracker.getChangedIDs(); + for (let i = 0; i < 234; i++) { + let id = "record-no-" + i; + // Ensure failed records are back in the tracker: + // * records no. 23 and 42 were rejected by the server, + // * records after the third batch and higher couldn't be uploaded because + // we failed hard on the 3rd upload. + if (i == 23 || i == 42 || i >= 200) { + Assert.equal(changes[id], i); + } else { + Assert.equal(false, id in changes); + } + } + } finally { + Service.serverConfiguration = oldServerConfiguration; + await promiseClean(engine, server); + } +}); + +add_task(async function test_canDecrypt_noCryptoKeys() { + _( + "SyncEngine.canDecrypt returns false if the engine fails to decrypt items on the server, e.g. due to a missing crypto key collection." + ); + + // Wipe collection keys so we can test the desired scenario. + Service.collectionKeys.clear(); + + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + let engine = makeRotaryEngine(); + try { + Assert.equal(false, await engine.canDecrypt()); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_canDecrypt_true() { + _( + "SyncEngine.canDecrypt returns true if the engine can decrypt the items on the server." + ); + + await generateNewKeys(Service.collectionKeys); + + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO( + "flying", + encryptPayload({ id: "flying", denomination: "LNER Class A3 4472" }) + ); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + let engine = makeRotaryEngine(); + try { + Assert.ok(await engine.canDecrypt()); + } finally { + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_syncapplied_observer() { + const NUMBER_OF_RECORDS = 10; + + let engine = makeRotaryEngine(); + + // Create a batch of server side records. + let collection = new ServerCollection(); + for (var i = 0; i < NUMBER_OF_RECORDS; i++) { + let id = "record-no-" + i; + let payload = encryptPayload({ id, denomination: "Record No. " + id }); + collection.insert(id, payload); + } + + let server = httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + let numApplyCalls = 0; + let engine_name; + let count; + function onApplied(subject, data) { + numApplyCalls++; + engine_name = data; + count = subject; + } + + Svc.Obs.add("weave:engine:sync:applied", onApplied); + + try { + Service.scheduler.hasIncomingItems = false; + + // Do sync. + await engine._syncStartup(); + await engine._processIncoming(); + + do_check_attribute_count(engine._store.items, 10); + + Assert.equal(numApplyCalls, 1); + Assert.equal(engine_name, "rotary"); + Assert.equal(count.applied, 10); + + Assert.ok(Service.scheduler.hasIncomingItems); + } finally { + await cleanAndGo(engine, server); + Service.scheduler.hasIncomingItems = false; + Svc.Obs.remove("weave:engine:sync:applied", onApplied); + } +}); diff --git a/services/sync/tests/unit/test_syncscheduler.js b/services/sync/tests/unit/test_syncscheduler.js new file mode 100644 index 0000000000..98b7937da3 --- /dev/null +++ b/services/sync/tests/unit/test_syncscheduler.js @@ -0,0 +1,1195 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const { SyncAuthManager } = ChromeUtils.importESModule( + "resource://services-sync/sync_auth.sys.mjs" +); +const { SyncScheduler } = ChromeUtils.importESModule( + "resource://services-sync/policies.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { Status } = ChromeUtils.importESModule( + "resource://services-sync/status.sys.mjs" +); + +function CatapultEngine() { + SyncEngine.call(this, "Catapult", Service); +} +CatapultEngine.prototype = { + exception: null, // tests fill this in + async _sync() { + throw this.exception; + }, +}; +Object.setPrototypeOf(CatapultEngine.prototype, SyncEngine.prototype); + +var scheduler = new SyncScheduler(Service); +let clientsEngine; + +async function sync_httpd_setup() { + let clientsSyncID = await clientsEngine.resetLocalSyncID(); + let global = new ServerWBO("global", { + syncID: Service.syncID, + storageVersion: STORAGE_VERSION, + engines: { + clients: { version: clientsEngine.version, syncID: clientsSyncID }, + }, + }); + let clientsColl = new ServerCollection({}, true); + + // Tracking info/collections. + let collectionsHelper = track_collections_helper(); + let upd = collectionsHelper.with_updated_collection; + + return httpd_setup({ + "/1.1/johndoe@mozilla.com/storage/meta/global": upd( + "meta", + global.handler() + ), + "/1.1/johndoe@mozilla.com/info/collections": collectionsHelper.handler, + "/1.1/johndoe@mozilla.com/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe@mozilla.com/storage/clients": upd( + "clients", + clientsColl.handler() + ), + }); +} + +async function setUp(server) { + await configureIdentity({ username: "johndoe@mozilla.com" }, server); + + await generateNewKeys(Service.collectionKeys); + let serverKeys = Service.collectionKeys.asWBO("crypto", "keys"); + await serverKeys.encrypt(Service.identity.syncKeyBundle); + let result = ( + await serverKeys.upload(Service.resource(Service.cryptoKeysURL)) + ).success; + return result; +} + +async function cleanUpAndGo(server) { + await Async.promiseYield(); + await clientsEngine._store.wipe(); + await Service.startOver(); + // Re-enable logging, which we just disabled. + syncTestLogging(); + if (server) { + await promiseStopServer(server); + } +} + +add_task(async function setup() { + await Service.promiseInitialized; + clientsEngine = Service.clientsEngine; + // Don't remove stale clients when syncing. This is a test-only workaround + // that lets us add clients directly to the store, without losing them on + // the next sync. + clientsEngine._removeRemoteClient = async id => {}; + await Service.engineManager.clear(); + + validate_all_future_pings(); + + scheduler.setDefaults(); + + await Service.engineManager.register(CatapultEngine); +}); + +add_test(function test_prefAttributes() { + _("Test various attributes corresponding to preferences."); + + const INTERVAL = 42 * 60 * 1000; // 42 minutes + const THRESHOLD = 3142; + const SCORE = 2718; + const TIMESTAMP1 = 1275493471649; + + _( + "The 'nextSync' attribute stores a millisecond timestamp rounded down to the nearest second." + ); + Assert.equal(scheduler.nextSync, 0); + scheduler.nextSync = TIMESTAMP1; + Assert.equal(scheduler.nextSync, Math.floor(TIMESTAMP1 / 1000) * 1000); + + _("'syncInterval' defaults to singleDeviceInterval."); + Assert.equal( + Svc.PrefBranch.getPrefType("syncInterval"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + _("'syncInterval' corresponds to a preference setting."); + scheduler.syncInterval = INTERVAL; + Assert.equal(scheduler.syncInterval, INTERVAL); + Assert.equal(Svc.PrefBranch.getIntPref("syncInterval"), INTERVAL); + + _( + "'syncThreshold' corresponds to preference, defaults to SINGLE_USER_THRESHOLD" + ); + Assert.equal( + Svc.PrefBranch.getPrefType("syncThreshold"), + Ci.nsIPrefBranch.PREF_INVALID + ); + Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + scheduler.syncThreshold = THRESHOLD; + Assert.equal(scheduler.syncThreshold, THRESHOLD); + + _("'globalScore' corresponds to preference, defaults to zero."); + Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), 0); + Assert.equal(scheduler.globalScore, 0); + scheduler.globalScore = SCORE; + Assert.equal(scheduler.globalScore, SCORE); + Assert.equal(Svc.PrefBranch.getIntPref("globalScore"), SCORE); + + _("Intervals correspond to default preferences."); + Assert.equal( + scheduler.singleDeviceInterval, + Svc.PrefBranch.getIntPref("scheduler.fxa.singleDeviceInterval") * 1000 + ); + Assert.equal( + scheduler.idleInterval, + Svc.PrefBranch.getIntPref("scheduler.idleInterval") * 1000 + ); + Assert.equal( + scheduler.activeInterval, + Svc.PrefBranch.getIntPref("scheduler.activeInterval") * 1000 + ); + Assert.equal( + scheduler.immediateInterval, + Svc.PrefBranch.getIntPref("scheduler.immediateInterval") * 1000 + ); + + _("Custom values for prefs will take effect after a restart."); + Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 420); + Svc.PrefBranch.setIntPref("scheduler.idleInterval", 230); + Svc.PrefBranch.setIntPref("scheduler.activeInterval", 180); + Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 31415); + scheduler.setDefaults(); + Assert.equal(scheduler.idleInterval, 230000); + Assert.equal(scheduler.singleDeviceInterval, 420000); + Assert.equal(scheduler.activeInterval, 180000); + Assert.equal(scheduler.immediateInterval, 31415000); + + _("Custom values for interval prefs can't be less than 60 seconds."); + Svc.PrefBranch.setIntPref("scheduler.fxa.singleDeviceInterval", 42); + Svc.PrefBranch.setIntPref("scheduler.idleInterval", 50); + Svc.PrefBranch.setIntPref("scheduler.activeInterval", 50); + Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 10); + scheduler.setDefaults(); + Assert.equal(scheduler.idleInterval, 60000); + Assert.equal(scheduler.singleDeviceInterval, 60000); + Assert.equal(scheduler.activeInterval, 60000); + Assert.equal(scheduler.immediateInterval, 60000); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + scheduler.setDefaults(); + run_next_test(); +}); + +add_task(async function test_sync_skipped_low_score_no_resync() { + enableValidationPrefs(); + let server = await sync_httpd_setup(); + + function SkipEngine() { + SyncEngine.call(this, "Skip", Service); + this.syncs = 0; + } + + SkipEngine.prototype = { + _sync() { + do_throw("Should have been skipped"); + }, + shouldSkipSync() { + return true; + }, + }; + Object.setPrototypeOf(SkipEngine.prototype, SyncEngine.prototype); + await Service.engineManager.register(SkipEngine); + + let engine = Service.engineManager.get("skip"); + engine.enabled = true; + engine._tracker._score = 30; + + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await setUp(server)); + + let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished"); + + let synced = false; + function onSyncStarted() { + Assert.ok(!synced, "Only should sync once"); + synced = true; + } + + await Service.sync(); + + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Svc.Obs.add("weave:service:sync:start", onSyncStarted); + await resyncDoneObserver; + + Svc.Obs.remove("weave:service:sync:start", onSyncStarted); + engine._tracker._store = 0; + await cleanUpAndGo(server); +}); + +add_task(async function test_updateClientMode() { + _( + "Test updateClientMode adjusts scheduling attributes based on # of clients appropriately" + ); + Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.idle); + + // Trigger a change in interval & threshold by noting there are multiple clients. + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.updateClientMode(); + + Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.idle); + + // Resets the number of clients to 0. + await clientsEngine.resetClient(); + Svc.PrefBranch.clearUserPref("clients.devices.mobile"); + scheduler.updateClientMode(); + + // Goes back to single user if # clients is 1. + Assert.equal(scheduler.numClients, 1); + Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.idle); + + await cleanUpAndGo(); +}); + +add_task(async function test_masterpassword_locked_retry_interval() { + enableValidationPrefs(); + + _( + "Test Status.login = MASTER_PASSWORD_LOCKED results in reschedule at MASTER_PASSWORD interval" + ); + let loginFailed = false; + Svc.Obs.add("weave:service:login:error", function onLoginError() { + Svc.Obs.remove("weave:service:login:error", onLoginError); + loginFailed = true; + }); + + let rescheduleInterval = false; + + let oldScheduleAtInterval = SyncScheduler.prototype.scheduleAtInterval; + SyncScheduler.prototype.scheduleAtInterval = function (interval) { + rescheduleInterval = true; + Assert.equal(interval, MASTER_PASSWORD_LOCKED_RETRY_INTERVAL); + }; + + let oldVerifyLogin = Service.verifyLogin; + Service.verifyLogin = async function () { + Status.login = MASTER_PASSWORD_LOCKED; + return false; + }; + + let server = await sync_httpd_setup(); + await setUp(server); + + await Service.sync(); + + Assert.ok(loginFailed); + Assert.equal(Status.login, MASTER_PASSWORD_LOCKED); + Assert.ok(rescheduleInterval); + + Service.verifyLogin = oldVerifyLogin; + SyncScheduler.prototype.scheduleAtInterval = oldScheduleAtInterval; + + await cleanUpAndGo(server); +}); + +add_task(async function test_calculateBackoff() { + Assert.equal(Status.backoffInterval, 0); + + // Test no interval larger than the maximum backoff is used if + // Status.backoffInterval is smaller. + Status.backoffInterval = 5; + let backoffInterval = Utils.calculateBackoff( + 50, + MAXIMUM_BACKOFF_INTERVAL, + Status.backoffInterval + ); + + Assert.equal(backoffInterval, MAXIMUM_BACKOFF_INTERVAL); + + // Test Status.backoffInterval is used if it is + // larger than MAXIMUM_BACKOFF_INTERVAL. + Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10; + backoffInterval = Utils.calculateBackoff( + 50, + MAXIMUM_BACKOFF_INTERVAL, + Status.backoffInterval + ); + + Assert.equal(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10); + + await cleanUpAndGo(); +}); + +add_task(async function test_scheduleNextSync_nowOrPast() { + enableValidationPrefs(); + + let promiseObserved = promiseOneObserver("weave:service:sync:finish"); + + let server = await sync_httpd_setup(); + await setUp(server); + + // We're late for a sync... + scheduler.scheduleNextSync(-1); + await promiseObserved; + await cleanUpAndGo(server); +}); + +add_task(async function test_scheduleNextSync_future_noBackoff() { + enableValidationPrefs(); + + _( + "scheduleNextSync() uses the current syncInterval if no interval is provided." + ); + // Test backoffInterval is 0 as expected. + Assert.equal(Status.backoffInterval, 0); + + _("Test setting sync interval when nextSync == 0"); + scheduler.nextSync = 0; + scheduler.scheduleNextSync(); + + // nextSync - Date.now() might be smaller than expectedInterval + // since some time has passed since we called scheduleNextSync(). + Assert.ok(scheduler.nextSync - Date.now() <= scheduler.syncInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.syncInterval); + + _("Test setting sync interval when nextSync != 0"); + scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval; + scheduler.scheduleNextSync(); + + // nextSync - Date.now() might be smaller than expectedInterval + // since some time has passed since we called scheduleNextSync(). + Assert.ok(scheduler.nextSync - Date.now() <= scheduler.syncInterval); + Assert.ok(scheduler.syncTimer.delay <= scheduler.syncInterval); + + _( + "Scheduling requests for intervals larger than the current one will be ignored." + ); + // Request a sync at a longer interval. The sync that's already scheduled + // for sooner takes precedence. + let nextSync = scheduler.nextSync; + let timerDelay = scheduler.syncTimer.delay; + let requestedInterval = scheduler.syncInterval * 10; + scheduler.scheduleNextSync(requestedInterval); + Assert.equal(scheduler.nextSync, nextSync); + Assert.equal(scheduler.syncTimer.delay, timerDelay); + + // We can schedule anything we want if there isn't a sync scheduled. + scheduler.nextSync = 0; + scheduler.scheduleNextSync(requestedInterval); + Assert.ok(scheduler.nextSync <= Date.now() + requestedInterval); + Assert.equal(scheduler.syncTimer.delay, requestedInterval); + + // Request a sync at the smallest possible interval (0 triggers now). + scheduler.scheduleNextSync(1); + Assert.ok(scheduler.nextSync <= Date.now() + 1); + Assert.equal(scheduler.syncTimer.delay, 1); + + await cleanUpAndGo(); +}); + +add_task(async function test_scheduleNextSync_future_backoff() { + enableValidationPrefs(); + + _("scheduleNextSync() will honour backoff in all scheduling requests."); + // Let's take a backoff interval that's bigger than the default sync interval. + const BACKOFF = 7337; + Status.backoffInterval = scheduler.syncInterval + BACKOFF; + + _("Test setting sync interval when nextSync == 0"); + scheduler.nextSync = 0; + scheduler.scheduleNextSync(); + + // nextSync - Date.now() might be smaller than expectedInterval + // since some time has passed since we called scheduleNextSync(). + Assert.ok(scheduler.nextSync - Date.now() <= Status.backoffInterval); + Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); + + _("Test setting sync interval when nextSync != 0"); + scheduler.nextSync = Date.now() + scheduler.singleDeviceInterval; + scheduler.scheduleNextSync(); + + // nextSync - Date.now() might be smaller than expectedInterval + // since some time has passed since we called scheduleNextSync(). + Assert.ok(scheduler.nextSync - Date.now() <= Status.backoffInterval); + Assert.ok(scheduler.syncTimer.delay <= Status.backoffInterval); + + // Request a sync at a longer interval. The sync that's already scheduled + // for sooner takes precedence. + let nextSync = scheduler.nextSync; + let timerDelay = scheduler.syncTimer.delay; + let requestedInterval = scheduler.syncInterval * 10; + Assert.ok(requestedInterval > Status.backoffInterval); + scheduler.scheduleNextSync(requestedInterval); + Assert.equal(scheduler.nextSync, nextSync); + Assert.equal(scheduler.syncTimer.delay, timerDelay); + + // We can schedule anything we want if there isn't a sync scheduled. + scheduler.nextSync = 0; + scheduler.scheduleNextSync(requestedInterval); + Assert.ok(scheduler.nextSync <= Date.now() + requestedInterval); + Assert.equal(scheduler.syncTimer.delay, requestedInterval); + + // Request a sync at the smallest possible interval (0 triggers now). + scheduler.scheduleNextSync(1); + Assert.ok(scheduler.nextSync <= Date.now() + Status.backoffInterval); + Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); + + await cleanUpAndGo(); +}); + +add_task(async function test_handleSyncError() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Force sync to fail. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + _("Ensure expected initial environment."); + Assert.equal(scheduler._syncErrors, 0); + Assert.ok(!Status.enforceBackoff); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(Status.backoffInterval, 0); + + // Trigger sync with an error several times & observe + // functionality of handleSyncError() + _("Test first error calls scheduleNextSync on default interval"); + await Service.sync(); + Assert.ok(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + Assert.equal(scheduler._syncErrors, 1); + Assert.ok(!Status.enforceBackoff); + scheduler.syncTimer.clear(); + + _("Test second error still calls scheduleNextSync on default interval"); + await Service.sync(); + Assert.ok(scheduler.nextSync <= Date.now() + scheduler.singleDeviceInterval); + Assert.equal(scheduler.syncTimer.delay, scheduler.singleDeviceInterval); + Assert.equal(scheduler._syncErrors, 2); + Assert.ok(!Status.enforceBackoff); + scheduler.syncTimer.clear(); + + _("Test third error sets Status.enforceBackoff and calls scheduleAtInterval"); + await Service.sync(); + let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); + Assert.equal(Status.backoffInterval, 0); + Assert.ok(scheduler.nextSync <= Date.now() + maxInterval); + Assert.ok(scheduler.syncTimer.delay <= maxInterval); + Assert.equal(scheduler._syncErrors, 3); + Assert.ok(Status.enforceBackoff); + + // Status.enforceBackoff is false but there are still errors. + Status.resetBackoff(); + Assert.ok(!Status.enforceBackoff); + Assert.equal(scheduler._syncErrors, 3); + scheduler.syncTimer.clear(); + + _( + "Test fourth error still calls scheduleAtInterval even if enforceBackoff was reset" + ); + await Service.sync(); + maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); + Assert.ok(scheduler.nextSync <= Date.now() + maxInterval); + Assert.ok(scheduler.syncTimer.delay <= maxInterval); + Assert.equal(scheduler._syncErrors, 4); + Assert.ok(Status.enforceBackoff); + scheduler.syncTimer.clear(); + + _("Arrange for a successful sync to reset the scheduler error count"); + let promiseObserved = promiseOneObserver("weave:service:sync:finish"); + Svc.PrefBranch.setStringPref("firstSync", "wipeRemote"); + scheduler.scheduleNextSync(-1); + await promiseObserved; + await cleanUpAndGo(server); +}); + +add_task(async function test_client_sync_finish_updateClientMode() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Confirm defaults. + Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.ok(!scheduler.idle); + + // Trigger a change in interval & threshold by adding a client. + await clientsEngine._store.create({ + id: "foo", + cleartext: { os: "mobile", version: "0.01", type: "desktop" }, + }); + Assert.equal(false, scheduler.numClients > 1); + scheduler.updateClientMode(); + await Service.sync(); + + Assert.equal(scheduler.syncThreshold, MULTI_DEVICE_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.numClients > 1); + Assert.ok(!scheduler.idle); + + // Resets the number of clients to 0. + await clientsEngine.resetClient(); + // Also re-init the server, or we suck our "foo" client back down. + await setUp(server); + + await Service.sync(); + + // Goes back to single user if # clients is 1. + Assert.equal(scheduler.numClients, 1); + Assert.equal(scheduler.syncThreshold, SINGLE_USER_THRESHOLD); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + Assert.equal(false, scheduler.numClients > 1); + Assert.ok(!scheduler.idle); + + await cleanUpAndGo(server); +}); + +add_task(async function test_autoconnect_nextSync_past() { + enableValidationPrefs(); + + let promiseObserved = promiseOneObserver("weave:service:sync:finish"); + // nextSync will be 0 by default, so it's way in the past. + + let server = await sync_httpd_setup(); + await setUp(server); + + scheduler.autoConnect(); + await promiseObserved; + await cleanUpAndGo(server); +}); + +add_task(async function test_autoconnect_nextSync_future() { + enableValidationPrefs(); + + let previousSync = Date.now() + scheduler.syncInterval / 2; + scheduler.nextSync = previousSync; + // nextSync rounds to the nearest second. + let expectedSync = scheduler.nextSync; + let expectedInterval = expectedSync - Date.now() - 1000; + + // Ensure we don't actually try to sync (or log in for that matter). + function onLoginStart() { + do_throw("Should not get here!"); + } + Svc.Obs.add("weave:service:login:start", onLoginStart); + + await configureIdentity({ username: "johndoe@mozilla.com" }); + scheduler.autoConnect(); + await promiseZeroTimer(); + + Assert.equal(scheduler.nextSync, expectedSync); + Assert.ok(scheduler.syncTimer.delay >= expectedInterval); + + Svc.Obs.remove("weave:service:login:start", onLoginStart); + await cleanUpAndGo(); +}); + +add_task(async function test_autoconnect_mp_locked() { + let server = await sync_httpd_setup(); + await setUp(server); + + // Pretend user did not unlock master password. + let origLocked = Utils.mpLocked; + Utils.mpLocked = () => true; + + let origEnsureMPUnlocked = Utils.ensureMPUnlocked; + Utils.ensureMPUnlocked = () => { + _("Faking Master Password entry cancelation."); + return false; + }; + let origFxA = Service.identity._fxaService; + Service.identity._fxaService = new FxAccounts({ + currentAccountState: { + getUserAccountData(...args) { + return origFxA._internal.currentAccountState.getUserAccountData( + ...args + ); + }, + }, + keys: { + canGetKeyForScope() { + return false; + }, + }, + }); + // A locked master password will still trigger a sync, but then we'll hit + // MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL. + let promiseObserved = promiseOneObserver("weave:service:login:error"); + + scheduler.autoConnect(); + await promiseObserved; + + await Async.promiseYield(); + + Assert.equal(Status.login, MASTER_PASSWORD_LOCKED); + + Utils.mpLocked = origLocked; + Utils.ensureMPUnlocked = origEnsureMPUnlocked; + Service.identity._fxaService = origFxA; + + await cleanUpAndGo(server); +}); + +add_task(async function test_no_autoconnect_during_wizard() { + let server = await sync_httpd_setup(); + await setUp(server); + + // Simulate the Sync setup wizard. + Svc.PrefBranch.setStringPref("firstSync", "notReady"); + + // Ensure we don't actually try to sync (or log in for that matter). + function onLoginStart() { + do_throw("Should not get here!"); + } + Svc.Obs.add("weave:service:login:start", onLoginStart); + + scheduler.autoConnect(0); + await promiseZeroTimer(); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + await cleanUpAndGo(server); +}); + +add_task(async function test_no_autoconnect_status_not_ok() { + let server = await sync_httpd_setup(); + Status.__authManager = Service.identity = new SyncAuthManager(); + + // Ensure we don't actually try to sync (or log in for that matter). + function onLoginStart() { + do_throw("Should not get here!"); + } + Svc.Obs.add("weave:service:login:start", onLoginStart); + + scheduler.autoConnect(); + await promiseZeroTimer(); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + + Assert.equal(Status.service, CLIENT_NOT_CONFIGURED); + Assert.equal(Status.login, LOGIN_FAILED_NO_USERNAME); + + await cleanUpAndGo(server); +}); + +add_task(async function test_idle_adjustSyncInterval() { + // Confirm defaults. + Assert.equal(scheduler.idle, false); + + // Single device: nothing changes. + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.equal(scheduler.idle, true); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); + + // Multiple devices: switch to idle interval. + scheduler.idle = false; + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.updateClientMode(); + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.equal(scheduler.idle, true); + Assert.equal(scheduler.syncInterval, scheduler.idleInterval); + + await cleanUpAndGo(); +}); + +add_task(async function test_back_triggersSync() { + // Confirm defaults. + Assert.ok(!scheduler.idle); + Assert.equal(Status.backoffInterval, 0); + + // Set up: Define 2 clients and put the system in idle. + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.ok(scheduler.idle); + + // We don't actually expect the sync (or the login, for that matter) to + // succeed. We just want to ensure that it was attempted. + let promiseObserved = promiseOneObserver("weave:service:login:error"); + + // Send an 'active' event to trigger sync soonish. + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + await promiseObserved; + await cleanUpAndGo(); +}); + +add_task(async function test_active_triggersSync_observesBackoff() { + // Confirm defaults. + Assert.ok(!scheduler.idle); + + // Set up: Set backoff, define 2 clients and put the system in idle. + const BACKOFF = 7337; + Status.backoffInterval = scheduler.idleInterval + BACKOFF; + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.equal(scheduler.idle, true); + + function onLoginStart() { + do_throw("Shouldn't have kicked off a sync!"); + } + Svc.Obs.add("weave:service:login:start", onLoginStart); + + let promiseTimer = promiseNamedTimer( + IDLE_OBSERVER_BACK_DELAY * 1.5, + {}, + "timer" + ); + + // Send an 'active' event to try to trigger sync soonish. + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + await promiseTimer; + Svc.Obs.remove("weave:service:login:start", onLoginStart); + + Assert.ok(scheduler.nextSync <= Date.now() + Status.backoffInterval); + Assert.equal(scheduler.syncTimer.delay, Status.backoffInterval); + + await cleanUpAndGo(); +}); + +add_task(async function test_back_debouncing() { + _( + "Ensure spurious back-then-idle events, as observed on OS X, don't trigger a sync." + ); + + // Confirm defaults. + Assert.equal(scheduler.idle, false); + + // Set up: Define 2 clients and put the system in idle. + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + Assert.equal(scheduler.idle, true); + + function onLoginStart() { + do_throw("Shouldn't have kicked off a sync!"); + } + Svc.Obs.add("weave:service:login:start", onLoginStart); + + // Create spurious back-then-idle events as observed on OS X: + scheduler.observe( + null, + "active", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + scheduler.observe( + null, + "idle", + Svc.PrefBranch.getIntPref("scheduler.idleTime") + ); + + await promiseNamedTimer(IDLE_OBSERVER_BACK_DELAY * 1.5, {}, "timer"); + Svc.Obs.remove("weave:service:login:start", onLoginStart); + await cleanUpAndGo(); +}); + +add_task(async function test_no_sync_node() { + enableValidationPrefs(); + + // Test when Status.sync == NO_SYNC_NODE_FOUND + // it is not overwritten on sync:finish + let server = await sync_httpd_setup(); + await setUp(server); + + let oldfc = Service.identity._findCluster; + Service.identity._findCluster = () => null; + Service.clusterURL = ""; + try { + await Service.sync(); + Assert.equal(Status.sync, NO_SYNC_NODE_FOUND); + Assert.equal(scheduler.syncTimer.delay, NO_SYNC_NODE_INTERVAL); + + await cleanUpAndGo(server); + } finally { + Service.identity._findCluster = oldfc; + } +}); + +add_task(async function test_sync_failed_partial_500s() { + enableValidationPrefs(); + + _("Test a 5xx status calls handleSyncError."); + scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF; + let server = await sync_httpd_setup(); + + let engine = Service.engineManager.get("catapult"); + engine.enabled = true; + engine.exception = { status: 500 }; + + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await setUp(server)); + + await Service.sync(); + + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + let maxInterval = scheduler._syncErrors * (2 * MINIMUM_BACKOFF_INTERVAL); + Assert.equal(Status.backoffInterval, 0); + Assert.ok(Status.enforceBackoff); + Assert.equal(scheduler._syncErrors, 4); + Assert.ok(scheduler.nextSync <= Date.now() + maxInterval); + Assert.ok(scheduler.syncTimer.delay <= maxInterval); + + await cleanUpAndGo(server); +}); + +add_task(async function test_sync_failed_partial_noresync() { + enableValidationPrefs(); + let server = await sync_httpd_setup(); + + let engine = Service.engineManager.get("catapult"); + engine.enabled = true; + engine.exception = "Bad news"; + engine._tracker._score = MULTI_DEVICE_THRESHOLD + 1; + + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await setUp(server)); + + let resyncDoneObserver = promiseOneObserver("weave:service:resyncs-finished"); + + await Service.sync(); + + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + + function onSyncStarted() { + do_throw("Should not start resync when previous sync failed"); + } + + Svc.Obs.add("weave:service:sync:start", onSyncStarted); + await resyncDoneObserver; + + Svc.Obs.remove("weave:service:sync:start", onSyncStarted); + engine._tracker._store = 0; + await cleanUpAndGo(server); +}); + +add_task(async function test_sync_failed_partial_400s() { + enableValidationPrefs(); + + _("Test a non-5xx status doesn't call handleSyncError."); + scheduler._syncErrors = MAX_ERROR_COUNT_BEFORE_BACKOFF; + let server = await sync_httpd_setup(); + + let engine = Service.engineManager.get("catapult"); + engine.enabled = true; + engine.exception = { status: 400 }; + + // Have multiple devices for an active interval. + await clientsEngine._store.create({ + id: "foo", + cleartext: { os: "mobile", version: "0.01", type: "desktop" }, + }); + + Assert.equal(Status.sync, SYNC_SUCCEEDED); + + Assert.ok(await setUp(server)); + + await Service.sync(); + + Assert.equal(Status.service, SYNC_FAILED_PARTIAL); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + + Assert.equal(Status.backoffInterval, 0); + Assert.ok(!Status.enforceBackoff); + Assert.equal(scheduler._syncErrors, 0); + Assert.ok(scheduler.nextSync <= Date.now() + scheduler.activeInterval); + Assert.ok(scheduler.syncTimer.delay <= scheduler.activeInterval); + + await cleanUpAndGo(server); +}); + +add_task(async function test_sync_X_Weave_Backoff() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Use an odd value on purpose so that it doesn't happen to coincide with one + // of the sync intervals. + const BACKOFF = 7337; + + // Extend info/collections so that we can put it into server maintenance mode. + const INFO_COLLECTIONS = "/1.1/johndoe@mozilla.com/info/collections"; + let infoColl = server._handler._overridePaths[INFO_COLLECTIONS]; + let serverBackoff = false; + function infoCollWithBackoff(request, response) { + if (serverBackoff) { + response.setHeader("X-Weave-Backoff", "" + BACKOFF); + } + infoColl(request, response); + } + server.registerPathHandler(INFO_COLLECTIONS, infoCollWithBackoff); + + // Pretend we have two clients so that the regular sync interval is + // sufficiently low. + await clientsEngine._store.create({ + id: "foo", + cleartext: { os: "mobile", version: "0.01", type: "desktop" }, + }); + let rec = await clientsEngine._store.createRecord("foo", "clients"); + await rec.encrypt(Service.collectionKeys.keyForCollection("clients")); + await rec.upload(Service.resource(clientsEngine.engineURL + rec.id)); + + // Sync once to log in and get everything set up. Let's verify our initial + // values. + await Service.sync(); + Assert.equal(Status.backoffInterval, 0); + Assert.equal(Status.minimumNextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.nextSync <= Date.now() + scheduler.syncInterval); + // Sanity check that we picked the right value for BACKOFF: + Assert.ok(scheduler.syncInterval < BACKOFF * 1000); + + // Turn on server maintenance and sync again. + serverBackoff = true; + await Service.sync(); + + Assert.ok(Status.backoffInterval >= BACKOFF * 1000); + // Allowing 20 seconds worth of of leeway between when Status.minimumNextSync + // was set and when this line gets executed. + let minimumExpectedDelay = (BACKOFF - 20) * 1000; + Assert.ok(Status.minimumNextSync >= Date.now() + minimumExpectedDelay); + + // Verify that the next sync is actually going to wait that long. + Assert.ok(scheduler.nextSync >= Date.now() + minimumExpectedDelay); + Assert.ok(scheduler.syncTimer.delay >= minimumExpectedDelay); + + await cleanUpAndGo(server); +}); + +add_task(async function test_sync_503_Retry_After() { + enableValidationPrefs(); + + let server = await sync_httpd_setup(); + await setUp(server); + + // Use an odd value on purpose so that it doesn't happen to coincide with one + // of the sync intervals. + const BACKOFF = 7337; + + // Extend info/collections so that we can put it into server maintenance mode. + const INFO_COLLECTIONS = "/1.1/johndoe@mozilla.com/info/collections"; + let infoColl = server._handler._overridePaths[INFO_COLLECTIONS]; + let serverMaintenance = false; + function infoCollWithMaintenance(request, response) { + if (!serverMaintenance) { + infoColl(request, response); + return; + } + response.setHeader("Retry-After", "" + BACKOFF); + response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); + } + server.registerPathHandler(INFO_COLLECTIONS, infoCollWithMaintenance); + + // Pretend we have two clients so that the regular sync interval is + // sufficiently low. + await clientsEngine._store.create({ + id: "foo", + cleartext: { os: "mobile", version: "0.01", type: "desktop" }, + }); + let rec = await clientsEngine._store.createRecord("foo", "clients"); + await rec.encrypt(Service.collectionKeys.keyForCollection("clients")); + await rec.upload(Service.resource(clientsEngine.engineURL + rec.id)); + + // Sync once to log in and get everything set up. Let's verify our initial + // values. + await Service.sync(); + Assert.ok(!Status.enforceBackoff); + Assert.equal(Status.backoffInterval, 0); + Assert.equal(Status.minimumNextSync, 0); + Assert.equal(scheduler.syncInterval, scheduler.activeInterval); + Assert.ok(scheduler.nextSync <= Date.now() + scheduler.syncInterval); + // Sanity check that we picked the right value for BACKOFF: + Assert.ok(scheduler.syncInterval < BACKOFF * 1000); + + // Turn on server maintenance and sync again. + serverMaintenance = true; + await Service.sync(); + + Assert.ok(Status.enforceBackoff); + Assert.ok(Status.backoffInterval >= BACKOFF * 1000); + // Allowing 3 seconds worth of of leeway between when Status.minimumNextSync + // was set and when this line gets executed. + let minimumExpectedDelay = (BACKOFF - 3) * 1000; + Assert.ok(Status.minimumNextSync >= Date.now() + minimumExpectedDelay); + + // Verify that the next sync is actually going to wait that long. + Assert.ok(scheduler.nextSync >= Date.now() + minimumExpectedDelay); + Assert.ok(scheduler.syncTimer.delay >= minimumExpectedDelay); + + await cleanUpAndGo(server); +}); + +add_task(async function test_loginError_recoverable_reschedules() { + _("Verify that a recoverable login error schedules a new sync."); + await configureIdentity({ username: "johndoe@mozilla.com" }); + Service.clusterURL = "http://localhost:1234/"; + Status.resetSync(); // reset Status.login + + let promiseObserved = promiseOneObserver("weave:service:login:error"); + + // Let's set it up so that a sync is overdue, both in terms of previously + // scheduled syncs and the global score. We still do not expect an immediate + // sync because we just tried (duh). + scheduler.nextSync = Date.now() - 100000; + scheduler.globalScore = SINGLE_USER_THRESHOLD + 1; + function onSyncStart() { + do_throw("Shouldn't have started a sync!"); + } + Svc.Obs.add("weave:service:sync:start", onSyncStart); + + // Sanity check. + Assert.equal(scheduler.syncTimer, null); + Assert.equal(Status.checkSetup(), STATUS_OK); + Assert.equal(Status.login, LOGIN_SUCCEEDED); + + scheduler.scheduleNextSync(0); + await promiseObserved; + await Async.promiseYield(); + + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); + + let expectedNextSync = Date.now() + scheduler.syncInterval; + Assert.ok(scheduler.nextSync > Date.now()); + Assert.ok(scheduler.nextSync <= expectedNextSync); + Assert.ok(scheduler.syncTimer.delay > 0); + Assert.ok(scheduler.syncTimer.delay <= scheduler.syncInterval); + + Svc.Obs.remove("weave:service:sync:start", onSyncStart); + await cleanUpAndGo(); +}); + +add_task(async function test_loginError_fatal_clearsTriggers() { + _("Verify that a fatal login error clears sync triggers."); + await configureIdentity({ username: "johndoe@mozilla.com" }); + + let server = httpd_setup({ + "/1.1/johndoe@mozilla.com/info/collections": httpd_handler( + 401, + "Unauthorized" + ), + }); + + Service.clusterURL = server.baseURI + "/"; + Status.resetSync(); // reset Status.login + + let promiseObserved = promiseOneObserver("weave:service:login:error"); + + // Sanity check. + Assert.equal(scheduler.nextSync, 0); + Assert.equal(scheduler.syncTimer, null); + Assert.equal(Status.checkSetup(), STATUS_OK); + Assert.equal(Status.login, LOGIN_SUCCEEDED); + + scheduler.scheduleNextSync(0); + await promiseObserved; + await Async.promiseYield(); + + // For the FxA identity, a 401 on info/collections means a transient + // error, probably due to an inability to fetch a token. + Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR); + // syncs should still be scheduled. + Assert.ok(scheduler.nextSync > Date.now()); + Assert.ok(scheduler.syncTimer.delay > 0); + + await cleanUpAndGo(server); +}); + +add_task(async function test_proper_interval_on_only_failing() { + _("Ensure proper behavior when only failed records are applied."); + + // If an engine reports that no records succeeded, we shouldn't decrease the + // sync interval. + Assert.ok(!scheduler.hasIncomingItems); + const INTERVAL = 10000000; + scheduler.syncInterval = INTERVAL; + + Svc.Obs.notify("weave:service:sync:applied", { + applied: 2, + succeeded: 0, + failed: 2, + newFailed: 2, + reconciled: 0, + }); + + await Async.promiseYield(); + scheduler.adjustSyncInterval(); + Assert.ok(!scheduler.hasIncomingItems); + Assert.equal(scheduler.syncInterval, scheduler.singleDeviceInterval); +}); + +add_task(async function test_link_status_change() { + _("Check that we only attempt to sync when link status is up"); + try { + sinon.spy(scheduler, "scheduleNextSync"); + + Svc.Obs.notify("network:link-status-changed", null, "down"); + equal(scheduler.scheduleNextSync.callCount, 0); + + Svc.Obs.notify("network:link-status-changed", null, "change"); + equal(scheduler.scheduleNextSync.callCount, 0); + + Svc.Obs.notify("network:link-status-changed", null, "up"); + equal(scheduler.scheduleNextSync.callCount, 1); + + Svc.Obs.notify("network:link-status-changed", null, "change"); + equal(scheduler.scheduleNextSync.callCount, 1); + } finally { + scheduler.scheduleNextSync.restore(); + } +}); diff --git a/services/sync/tests/unit/test_tab_engine.js b/services/sync/tests/unit/test_tab_engine.js new file mode 100644 index 0000000000..5b1e61871e --- /dev/null +++ b/services/sync/tests/unit/test_tab_engine.js @@ -0,0 +1,226 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { TabProvider } = ChromeUtils.importESModule( + "resource://services-sync/engines/tabs.sys.mjs" +); +const { WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +let engine; +// We'll need the clients engine for testing as tabs is closely related +let clientsEngine; + +async function syncClientsEngine(server) { + clientsEngine._lastFxADevicesFetch = 0; + clientsEngine.lastModified = server.getCollection("foo", "clients").timestamp; + await clientsEngine._sync(); +} + +async function makeRemoteClients() { + let server = await serverForFoo(clientsEngine); + await configureIdentity({ username: "foo" }, server); + await Service.login(); + + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let remoteId = Utils.makeGUID(); + let remoteId2 = Utils.makeGUID(); + let collection = server.getCollection("foo", "clients"); + + _("Create remote client records"); + collection.insertRecord({ + id: remoteId, + name: "Remote client", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteId, + fxaDeviceName: "Fxa - Remote client", + protocols: ["1.5"], + }); + + collection.insertRecord({ + id: remoteId2, + name: "Remote client 2", + type: "desktop", + commands: [], + version: "48", + fxaDeviceId: remoteId2, + fxaDeviceName: "Fxa - Remote client 2", + protocols: ["1.5"], + }); + + let fxAccounts = clientsEngine.fxAccounts; + clientsEngine.fxAccounts = { + notifyDevices() { + return Promise.resolve(true); + }, + device: { + getLocalId() { + return fxAccounts.device.getLocalId(); + }, + getLocalName() { + return fxAccounts.device.getLocalName(); + }, + getLocalType() { + return fxAccounts.device.getLocalType(); + }, + recentDeviceList: [{ id: remoteId, name: "remote device" }], + refreshDeviceList() { + return Promise.resolve(true); + }, + }, + _internal: { + now() { + return Date.now(); + }, + }, + }; + + await syncClientsEngine(server); +} + +add_task(async function setup() { + clientsEngine = Service.clientsEngine; + // Make some clients to test with + await makeRemoteClients(); + + // Make the tabs engine for all the tests to use + engine = Service.engineManager.get("tabs"); + await engine.initialize(); + + // Since these are xpcshell tests, we'll need to mock this + TabProvider.shouldSkipWindow = mockShouldSkipWindow; +}); + +add_task(async function test_tab_engine_skips_incoming_local_record() { + _("Ensure incoming records that match local client ID are never applied."); + + let localID = clientsEngine.localID; + let collection = new ServerCollection(); + + _("Creating remote tab record with local client ID"); + let localRecord = encryptPayload({ + id: localID, + clientName: "local", + tabs: [ + { + title: "title", + urlHistory: ["http://foo.com/"], + icon: "", + lastUsed: 2000, + }, + ], + }); + collection.insert(localID, localRecord); + + _("Creating remote tab record with a different client ID"); + let remoteID = "fake-guid-00"; // remote should match one of the test clients + let remoteRecord = encryptPayload({ + id: remoteID, + clientName: "not local", + tabs: [ + { + title: "title2", + urlHistory: ["http://bar.com/"], + icon: "", + lastUsed: 3000, + }, + ], + }); + collection.insert(remoteID, remoteRecord); + + _("Setting up Sync server"); + let server = sync_httpd_setup({ + "/1.1/foo/storage/tabs": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { + tabs: { version: engine.version, syncID }, + }; + + await generateNewKeys(Service.collectionKeys); + + let promiseFinished = new Promise(resolve => { + let syncFinish = engine._syncFinish; + engine._syncFinish = async function () { + let remoteTabs = await engine._rustStore.getAll(); + equal( + remoteTabs.length, + 1, + "Remote client record was applied and local wasn't" + ); + let record = remoteTabs[0]; + equal(record.clientId, remoteID, "Remote client ID matches"); + + _("Ensure getAllClients returns the correct shape"); + let clients = await engine.getAllClients(); + equal(clients.length, 1); + let client = clients[0]; + equal(client.id, "fake-guid-00"); + equal(client.name, "Remote client"); + equal(client.type, "desktop"); + Assert.ok(client.lastModified); // lastModified should be filled in once serverModified is populated from the server + deepEqual(client.tabs, [ + { + title: "title2", + urlHistory: ["http://bar.com/"], + icon: "", + inactive: false, + lastUsed: 3000, + }, + ]); + await syncFinish.call(engine); + resolve(); + }; + }); + + _("Start sync"); + Service.scheduler.hasIncomingItems = false; + await engine._sync(); + await promiseFinished; + // Bug 1800185 - we don't want the sync scheduler to see these records as incoming. + Assert.ok(!Service.scheduler.hasIncomingItems); +}); + +// Ensure we trim tabs in the case of going past the max payload size allowed +add_task(async function test_too_many_tabs() { + let a_lot_of_tabs = []; + + for (let i = 0; i < 4000; ++i) { + a_lot_of_tabs.push( + `http://example${i}.com/some-super-long-url-chain-to-help-with-bytes` + ); + } + + TabProvider.getWindowEnumerator = mockGetWindowEnumerator.bind( + this, + a_lot_of_tabs + ); + + let encoder = Utils.utf8Encoder; + // see tryfitItems(..) in util.js + const computeSerializedSize = records => + encoder.encode(JSON.stringify(records)).byteLength; + + const maxPayloadSize = Service.getMaxRecordPayloadSize(); + const maxSerializedSize = (maxPayloadSize / 4) * 3 - 1500; + // We are over max payload size + Assert.ok(computeSerializedSize(a_lot_of_tabs) > maxSerializedSize); + let tabs = await engine.getTabsWithinPayloadSize(); + // We are now under max payload size + Assert.ok(computeSerializedSize(tabs) < maxSerializedSize); +}); diff --git a/services/sync/tests/unit/test_tab_provider.js b/services/sync/tests/unit/test_tab_provider.js new file mode 100644 index 0000000000..bbf68dea33 --- /dev/null +++ b/services/sync/tests/unit/test_tab_provider.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { TabProvider } = ChromeUtils.importESModule( + "resource://services-sync/engines/tabs.sys.mjs" +); + +add_task(async function test_getAllTabs() { + let provider = TabProvider; + provider.shouldSkipWindow = mockShouldSkipWindow; + + let tabs; + + provider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [ + "http://bar.com", + ]); + + _("Get all tabs."); + tabs = await provider.getAllTabsWithEstimatedMax( + false, + Number.MAX_SAFE_INTEGER + ); + _("Tabs: " + JSON.stringify(tabs)); + equal(tabs.length, 1); + equal(tabs[0].title, "title"); + equal(tabs[0].urlHistory.length, 1); + equal(tabs[0].urlHistory[0], "http://bar.com/"); + equal(tabs[0].icon, ""); + equal(tabs[0].lastUsed, 2); // windowenumerator returns in ms but the getAllTabs..() returns in seconds + + _("Get all tabs, and check that filtering works."); + provider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [ + "http://foo.com", + "about:foo", + ]); + tabs = await provider.getAllTabsWithEstimatedMax( + true, + Number.MAX_SAFE_INTEGER + ); + _("Filtered: " + JSON.stringify(tabs)); + equal(tabs.length, 1); + + _("Get all tabs, and check that they are properly sorted"); + provider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [ + "http://foo.com", + "http://bar.com", + ]); + tabs = await provider.getAllTabsWithEstimatedMax( + true, + Number.MAX_SAFE_INTEGER + ); + _("Ordered: " + JSON.stringify(tabs)); + equal(tabs[0].lastUsed > tabs[1].lastUsed, true); + + // reader mode URLs are provided. + provider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [ + "about:reader?url=http%3A%2F%2Ffoo.com%2F", + ]); + tabs = await provider.getAllTabsWithEstimatedMax( + true, + Number.MAX_SAFE_INTEGER + ); + equal(tabs[0].urlHistory[0], "http://foo.com/"); +}); diff --git a/services/sync/tests/unit/test_tab_quickwrite.js b/services/sync/tests/unit/test_tab_quickwrite.js new file mode 100644 index 0000000000..2a1c75c8c6 --- /dev/null +++ b/services/sync/tests/unit/test_tab_quickwrite.js @@ -0,0 +1,204 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.importESModule("resource://services-sync/engines/tabs.sys.mjs"); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const { TabProvider } = ChromeUtils.importESModule( + "resource://services-sync/engines/tabs.sys.mjs" +); + +const FAR_FUTURE = 4102405200000; // 2100/01/01 + +add_task(async function setup() { + // Since these are xpcshell tests, we'll need to mock ui features + TabProvider.shouldSkipWindow = mockShouldSkipWindow; + TabProvider.getWindowEnumerator = mockGetWindowEnumerator.bind(this, [ + "http://foo.com", + ]); +}); + +async function prepareServer() { + _("Setting up Sync server"); + Service.serverConfiguration = { + max_post_records: 100, + }; + + let server = new SyncServer(); + server.start(); + await SyncTestingInfrastructure(server, "username"); + server.registerUser("username"); + + let collection = server.createCollection("username", "tabs"); + await generateNewKeys(Service.collectionKeys); + + let engine = Service.engineManager.get("tabs"); + await engine.initialize(); + + return { server, collection, engine }; +} + +async function withPatchedValue(object, name, patchedVal, fn) { + _(`patching ${name}=${patchedVal}`); + let old = object[name]; + object[name] = patchedVal; + try { + await fn(); + } finally { + object[name] = old; + } +} + +add_task(async function test_tab_quickwrite_works() { + _("Ensure a simple quickWrite works."); + let { server, collection, engine } = await prepareServer(); + Assert.equal(collection.count(), 0, "starting with 0 tab records"); + Assert.ok(await engine.quickWrite()); + // Validate we didn't bork lastSync + let lastSync = await engine.getLastSync(); + Assert.ok(lastSync < FAR_FUTURE); + Assert.equal(collection.count(), 1, "tab record was written"); + + await promiseStopServer(server); +}); + +add_task(async function test_tab_bad_status() { + _("Ensure quickWrite silently aborts when we aren't setup correctly."); + let { server, engine } = await prepareServer(); + // Store the original lock to reset it back after this test + let lock = engine.lock; + // Arrange for this test to fail if it tries to take the lock. + engine.lock = function () { + throw new Error("this test should abort syncing before locking"); + }; + let quickWrite = engine.quickWrite.bind(engine); // lol javascript. + + await withPatchedValue(engine, "enabled", false, quickWrite); + await withPatchedValue(Service, "serverConfiguration", null, quickWrite); + + Services.prefs.clearUserPref("services.sync.username"); + await quickWrite(); + // Validate we didn't bork lastSync + let lastSync = await engine.getLastSync(); + Assert.ok(lastSync < FAR_FUTURE); + Service.status.resetSync(); + engine.lock = lock; + await promiseStopServer(server); +}); + +add_task(async function test_tab_quickwrite_lock() { + _("Ensure we fail to quickWrite if the engine is locked."); + let { server, collection, engine } = await prepareServer(); + + Assert.equal(collection.count(), 0, "starting with 0 tab records"); + engine.lock(); + Assert.ok(!(await engine.quickWrite())); + Assert.equal(collection.count(), 0, "didn't sync due to being locked"); + engine.unlock(); + + await promiseStopServer(server); +}); + +add_task(async function test_tab_quickwrite_keeps_old_tabs() { + _("Ensure we don't delete other tabs on quickWrite (bug 1801295)."); + let { server, engine } = await prepareServer(); + + // need a first sync to ensure everything is setup correctly. + await Service.sync({ engines: ["tabs"] }); + + const id = "fake-guid-99"; + let remoteRecord = encryptPayload({ + id, + clientName: "not local", + tabs: [ + { + title: "title2", + urlHistory: ["http://bar.com/"], + icon: "", + lastUsed: 3000, + }, + ], + }); + + let collection = server.getCollection("username", "tabs"); + collection.insert(id, remoteRecord); + + await Service.sync({ engines: ["tabs"] }); + + // collection should now have 2 records - ours and the pretend remote one we inserted. + Assert.equal(collection.count(), 2, "starting with 2 tab records"); + + // So fxAccounts.device.recentDeviceList is not null. + engine.service.clientsEngine.fxAccounts.device._deviceListCache = { + devices: [], + }; + // trick the clients engine into thinking it has a remote client with the same guid. + engine.service.clientsEngine._store._remoteClients = {}; + engine.service.clientsEngine._store._remoteClients[id] = { + id, + fxaDeviceId: id, + }; + + let clients = await engine.getAllClients(); + Assert.equal(clients.length, 1); + + _("Doing a quick-write"); + Assert.ok(await engine.quickWrite()); + + // Should still have our client after a quickWrite. + _("Grabbing clients after the quick-write"); + clients = await engine.getAllClients(); + Assert.equal(clients.length, 1); + + engine.service.clientsEngine._store._remoteClients = {}; + + await promiseStopServer(server); +}); + +add_task(async function test_tab_lastSync() { + _("Ensure we restore the lastSync timestamp after a quick-write."); + let { server, collection, engine } = await prepareServer(); + + await engine.initialize(); + await engine.service.clientsEngine.initialize(); + + let origLastSync = engine.lastSync; + Assert.ok(await engine.quickWrite()); + Assert.equal(engine.lastSync, origLastSync); + Assert.equal(collection.count(), 1, "successful sync"); + engine.unlock(); + + await promiseStopServer(server); +}); + +add_task(async function test_tab_quickWrite_telemetry() { + _("Ensure we record the telemetry we expect."); + // hook into telemetry + let telem = get_sync_test_telemetry(); + telem.payloads = []; + let oldSubmit = telem.submit; + let submitPromise = new Promise((resolve, reject) => { + telem.submit = function (ping) { + telem.submit = oldSubmit; + resolve(ping); + }; + }); + + let { server, collection, engine } = await prepareServer(); + + Assert.equal(collection.count(), 0, "starting with 0 tab records"); + Assert.ok(await engine.quickWrite()); + Assert.equal(collection.count(), 1, "tab record was written"); + + let ping = await submitPromise; + let syncs = ping.syncs; + Assert.equal(syncs.length, 1); + let sync = syncs[0]; + Assert.equal(sync.why, "quick-write"); + Assert.equal(sync.engines.length, 1); + Assert.equal(sync.engines[0].name, "tabs"); + + await promiseStopServer(server); +}); diff --git a/services/sync/tests/unit/test_tab_tracker.js b/services/sync/tests/unit/test_tab_tracker.js new file mode 100644 index 0000000000..8bb71a898a --- /dev/null +++ b/services/sync/tests/unit/test_tab_tracker.js @@ -0,0 +1,371 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +ChromeUtils.importESModule("resource://services-sync/engines/tabs.sys.mjs"); +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +const { SyncScheduler } = ChromeUtils.importESModule( + "resource://services-sync/policies.sys.mjs" +); + +var scheduler = new SyncScheduler(Service); +let clientsEngine; + +add_task(async function setup() { + await Service.promiseInitialized; + clientsEngine = Service.clientsEngine; + + scheduler.setDefaults(); +}); + +function fakeSvcWinMediator() { + // actions on windows are captured in logs + let logs = []; + delete Services.wm; + + function getNext() { + let elt = { addTopics: [], remTopics: [], numAPL: 0, numRPL: 0 }; + logs.push(elt); + return { + addEventListener(topic) { + elt.addTopics.push(topic); + }, + removeEventListener(topic) { + elt.remTopics.push(topic); + }, + gBrowser: { + addProgressListener() { + elt.numAPL++; + }, + removeProgressListener() { + elt.numRPL++; + }, + }, + }; + } + + Services.wm = { + getEnumerator() { + return [getNext(), getNext()]; + }, + }; + return logs; +} + +function fakeGetTabState(tab) { + return tab; +} + +function clearQuickWriteTimer(tracker) { + if (tracker.tabsQuickWriteTimer) { + tracker.tabsQuickWriteTimer.clear(); + } +} + +add_task(async function run_test() { + let engine = Service.engineManager.get("tabs"); + await engine.initialize(); + _("We assume that tabs have changed at startup."); + let tracker = engine._tracker; + tracker.getTabState = fakeGetTabState; + + Assert.ok(tracker.modified); + Assert.ok( + Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ + clientsEngine.localID, + ]) + ); + + let logs; + + _("Test listeners are registered on windows"); + logs = fakeSvcWinMediator(); + tracker.start(); + Assert.equal(logs.length, 2); + for (let log of logs) { + Assert.equal(log.addTopics.length, 3); + Assert.ok(log.addTopics.includes("TabOpen")); + Assert.ok(log.addTopics.includes("TabClose")); + Assert.ok(log.addTopics.includes("unload")); + Assert.equal(log.remTopics.length, 0); + Assert.equal(log.numAPL, 1, "Added 1 progress listener"); + Assert.equal(log.numRPL, 0, "Didn't remove a progress listener"); + } + + _("Test listeners are unregistered on windows"); + logs = fakeSvcWinMediator(); + await tracker.stop(); + Assert.equal(logs.length, 2); + for (let log of logs) { + Assert.equal(log.addTopics.length, 0); + Assert.equal(log.remTopics.length, 3); + Assert.ok(log.remTopics.includes("TabOpen")); + Assert.ok(log.remTopics.includes("TabClose")); + Assert.ok(log.remTopics.includes("unload")); + Assert.equal(log.numAPL, 0, "Didn't add a progress listener"); + Assert.equal(log.numRPL, 1, "Removed 1 progress listener"); + } + + _("Test tab listener"); + for (let evttype of ["TabOpen", "TabClose"]) { + // Pretend we just synced. + await tracker.clearChangedIDs(); + Assert.ok(!tracker.modified); + + // Send a fake tab event + tracker.onTab({ + type: evttype, + originalTarget: evttype, + target: { entries: [], currentURI: "about:config" }, + }); + Assert.ok(tracker.modified); + Assert.ok( + Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ + clientsEngine.localID, + ]) + ); + } + + // Pretend we just synced. + await tracker.clearChangedIDs(); + Assert.ok(!tracker.modified); + + tracker.onTab({ + type: "TabOpen", + originalTarget: "TabOpen", + target: { entries: [], currentURI: "about:config" }, + }); + Assert.ok( + Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ + clientsEngine.localID, + ]) + ); + + // Pretend we just synced and saw some progress listeners. + await tracker.clearChangedIDs(); + Assert.ok(!tracker.modified); + tracker.onLocationChange({ isTopLevel: false }, undefined, undefined, 0); + Assert.ok(!tracker.modified, "non-toplevel request didn't flag as modified"); + + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("https://www.mozilla.org"), + Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT + ); + Assert.ok( + tracker.modified, + "location change within the same document request did flag as modified" + ); + + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("https://www.mozilla.org") + ); + Assert.ok( + tracker.modified, + "location change for a new top-level document flagged as modified" + ); + Assert.ok( + Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ + clientsEngine.localID, + ]) + ); +}); + +add_task(async function run_sync_on_tab_change_test() { + let testPrefDelay = 20000; + + // This is the pref that determines sync delay after tab change + Svc.PrefBranch.setIntPref( + "syncedTabs.syncDelayAfterTabChange", + testPrefDelay + ); + // We should only be syncing on tab change if + // the user has > 1 client + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.updateClientMode(); + Assert.equal(scheduler.numClients, 2); + + let engine = Service.engineManager.get("tabs"); + + _("We assume that tabs have changed at startup."); + let tracker = engine._tracker; + tracker.getTabState = fakeGetTabState; + + Assert.ok(tracker.modified); + Assert.ok( + Utils.deepEquals(Object.keys(await engine.getChangedIDs()), [ + clientsEngine.localID, + ]) + ); + + _("Test sync is scheduled after a tab change"); + for (let evttype of ["TabOpen", "TabClose"]) { + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + + // Send a fake tab event + tracker.onTab({ + type: evttype, + originalTarget: evttype, + target: { entries: [], currentURI: "about:config" }, + }); + // Ensure the tracker fired + Assert.ok(tracker.modified); + // We should be more delayed at or more than what the pref is set at + let nextSchedule = tracker.tabsQuickWriteTimer.delay; + Assert.ok(nextSchedule >= testPrefDelay); + } + + _("Test sync is NOT scheduled after an unsupported tab open"); + for (let evttype of ["TabOpen"]) { + // Send a fake tab event + tracker.onTab({ + type: evttype, + originalTarget: evttype, + target: { entries: ["about:newtab"], currentURI: null }, + }); + // Ensure the tracker fired + Assert.ok(tracker.modified); + // We should be scheduling <= pref value + Assert.ok(scheduler.nextSync - Date.now() <= testPrefDelay); + } + + _("Test navigating within the same tab does NOT trigger a sync"); + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("https://www.mozilla.org"), + Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD + ); + Assert.ok( + !tracker.modified, + "location change for reloading doesn't trigger a sync" + ); + Assert.ok(!tracker.tabsQuickWriteTimer, "reload does not trigger a sync"); + + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + + _("Test navigating to an about page does trigger sync"); + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("about:config") + ); + Assert.ok(tracker.modified, "about page does not trigger a tab modified"); + Assert.ok( + tracker.tabsQuickWriteTimer, + "about schema should trigger a sync happening soon" + ); + + _("Test adjusting the filterScheme pref works"); + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + + Svc.PrefBranch.setStringPref( + "engine.tabs.filteredSchemes", + // Removing the about scheme for this test + "resource|chrome|file|blob|moz-extension" + ); + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("about:config") + ); + Assert.ok( + tracker.modified, + "about page triggers a modified after we changed the pref" + ); + Assert.ok( + tracker.tabsQuickWriteTimer, + "about page should schedule a quickWrite sync soon after we changed the pref" + ); + + _("Test no sync after tab change for accounts with <= 1 clients"); + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + // Setting clients to only 1 so we don't sync after a tab change + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 0); + scheduler.updateClientMode(); + Assert.equal(scheduler.numClients, 1); + + tracker.onLocationChange( + { isTopLevel: true }, + undefined, + Services.io.newURI("https://www.mozilla.org") + ); + Assert.ok( + tracker.modified, + "location change for a new top-level document flagged as modified" + ); + Assert.ok( + !tracker.tabsQuickWriteTimer, + "We should NOT be syncing shortly because there is only one client" + ); + + _("Changing the pref adjusts the sync schedule"); + Svc.PrefBranch.setIntPref("syncedTabs.syncDelayAfterTabChange", 10000); // 10seconds + let delayPref = Svc.PrefBranch.getIntPref( + "syncedTabs.syncDelayAfterTabChange" + ); + let evttype = "TabOpen"; + Assert.equal(delayPref, 10000); // ensure our pref is at 10s + // Only have task continuity if we have more than 1 device + Svc.PrefBranch.setIntPref("clients.devices.desktop", 1); + Svc.PrefBranch.setIntPref("clients.devices.mobile", 1); + scheduler.updateClientMode(); + Assert.equal(scheduler.numClients, 2); + clearQuickWriteTimer(tracker); + + // Fire ontab event + tracker.onTab({ + type: evttype, + originalTarget: evttype, + target: { entries: [], currentURI: "about:config" }, + }); + + // Ensure the tracker fired + Assert.ok(tracker.modified); + // We should be scheduling <= preference value + Assert.equal(tracker.tabsQuickWriteTimer.delay, delayPref); + + _("We should not have a sync scheduled if pref is at 0"); + + Svc.PrefBranch.setIntPref("syncedTabs.syncDelayAfterTabChange", 0); + // Pretend we just synced + await tracker.clearChangedIDs(); + clearQuickWriteTimer(tracker); + + // Fire ontab event + evttype = "TabOpen"; + tracker.onTab({ + type: evttype, + originalTarget: evttype, + target: { entries: [], currentURI: "about:config" }, + }); + // Ensure the tracker fired + Assert.ok(tracker.modified); + + // We should NOT be scheduled for a sync soon + Assert.ok(!tracker.tabsQuickWriteTimer); + + scheduler.setDefaults(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } +}); diff --git a/services/sync/tests/unit/test_telemetry.js b/services/sync/tests/unit/test_telemetry.js new file mode 100644 index 0000000000..961e96a01b --- /dev/null +++ b/services/sync/tests/unit/test_telemetry.js @@ -0,0 +1,1462 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); +const { WBORecord } = ChromeUtils.importESModule( + "resource://services-sync/record.sys.mjs" +); +const { Resource } = ChromeUtils.importESModule( + "resource://services-sync/resource.sys.mjs" +); +const { RotaryEngine } = ChromeUtils.importESModule( + "resource://testing-common/services/sync/rotaryengine.sys.mjs" +); +const { getFxAccountsSingleton } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +const fxAccounts = getFxAccountsSingleton(); + +function SteamStore(engine) { + Store.call(this, "Steam", engine); +} +Object.setPrototypeOf(SteamStore.prototype, Store.prototype); + +function SteamTracker(name, engine) { + LegacyTracker.call(this, name || "Steam", engine); +} +Object.setPrototypeOf(SteamTracker.prototype, LegacyTracker.prototype); + +function SteamEngine(service) { + SyncEngine.call(this, "steam", service); +} + +SteamEngine.prototype = { + _storeObj: SteamStore, + _trackerObj: SteamTracker, + _errToThrow: null, + problemsToReport: null, + async _sync() { + if (this._errToThrow) { + throw this._errToThrow; + } + }, + getValidator() { + return new SteamValidator(); + }, +}; +Object.setPrototypeOf(SteamEngine.prototype, SyncEngine.prototype); + +function BogusEngine(service) { + SyncEngine.call(this, "bogus", service); +} + +BogusEngine.prototype = Object.create(SteamEngine.prototype); + +class SteamValidator { + async canValidate() { + return true; + } + + async validate(engine) { + return { + problems: new SteamValidationProblemData(engine.problemsToReport), + version: 1, + duration: 0, + recordCount: 0, + }; + } +} + +class SteamValidationProblemData { + constructor(problemsToReport = []) { + this.problemsToReport = problemsToReport; + } + + getSummary() { + return this.problemsToReport; + } +} + +async function cleanAndGo(engine, server) { + await engine._tracker.clearChangedIDs(); + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + syncTestLogging(); + Service.recordManager.clearCache(); + await promiseStopServer(server); +} + +add_task(async function setup() { + // Avoid addon manager complaining about not being initialized + await Service.engineManager.unregister("addons"); + await Service.engineManager.unregister("extension-storage"); +}); + +add_task(async function test_basic() { + enableValidationPrefs(); + + let helper = track_collections_helper(); + let upd = helper.with_updated_collection; + + let handlers = { + "/1.1/johndoe/info/collections": helper.handler, + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": upd( + "meta", + new ServerWBO("global").handler() + ), + }; + + let collections = [ + "clients", + "bookmarks", + "forms", + "history", + "passwords", + "prefs", + "tabs", + ]; + + for (let coll of collections) { + handlers["/1.1/johndoe/storage/" + coll] = upd( + coll, + new ServerCollection({}, true).handler() + ); + } + + let server = httpd_setup(handlers); + await configureIdentity({ username: "johndoe" }, server); + + let ping = await wait_for_ping(() => Service.sync(), true, true); + + // Check the "os" block - we can't really check specific values, but can + // check it smells sane. + ok(ping.os, "there is an OS block"); + ok("name" in ping.os, "there is an OS name"); + ok("version" in ping.os, "there is an OS version"); + ok("locale" in ping.os, "there is an OS locale"); + + for (const pref of Svc.PrefBranch.getChildList("")) { + Svc.PrefBranch.clearUserPref(pref); + } + await promiseStopServer(server); +}); + +add_task(async function test_processIncoming_error() { + let engine = Service.engineManager.get("bookmarks"); + await engine.initialize(); + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let collection = server.user("foo").collection("bookmarks"); + try { + // Create a bogus record that when synced down will provoke a + // network error which in turn provokes an exception in _processIncoming. + const BOGUS_GUID = "zzzzzzzzzzzz"; + let bogus_record = collection.insert(BOGUS_GUID, "I'm a bogus record!"); + bogus_record.get = function get() { + throw new Error("Sync this!"); + }; + // Make the 10 minutes old so it will only be synced in the toFetch phase. + bogus_record.modified = Date.now() / 1000 - 60 * 10; + await engine.setLastSync(Date.now() / 1000 - 60); + engine.toFetch = new SerializableSet([BOGUS_GUID]); + + let error, pingPayload, fullPing; + try { + await sync_engine_and_validate_telem( + engine, + true, + (errPing, fullErrPing) => { + pingPayload = errPing; + fullPing = fullErrPing; + } + ); + } catch (ex) { + error = ex; + } + ok(!!error); + ok(!!pingPayload); + + equal(fullPing.uid, "f".repeat(32)); // as setup by SyncTestingInfrastructure + deepEqual(pingPayload.failureReason, { + name: "httperror", + code: 500, + }); + + equal(pingPayload.engines.length, 1); + + equal(pingPayload.engines[0].name, "bookmarks-buffered"); + deepEqual(pingPayload.engines[0].failureReason, { + name: "httperror", + code: 500, + }); + } finally { + await store.wipe(); + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_uploading() { + let engine = Service.engineManager.get("bookmarks"); + await engine.initialize(); + let store = engine._store; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + let bmk = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + url: "http://getfirefox.com/", + title: "Get Firefox!", + }); + + try { + let ping = await sync_engine_and_validate_telem(engine, false); + ok(!!ping); + equal(ping.engines.length, 1); + equal(ping.engines[0].name, "bookmarks-buffered"); + ok(!!ping.engines[0].outgoing); + greater(ping.engines[0].outgoing[0].sent, 0); + ok(!ping.engines[0].incoming); + + await PlacesUtils.bookmarks.update({ + guid: bmk.guid, + title: "New Title", + }); + + await store.wipe(); + await engine.resetClient(); + // We don't sync via the service, so don't re-hit info/collections, so + // lastModified remaning at zero breaks things subtly... + engine.lastModified = null; + + ping = await sync_engine_and_validate_telem(engine, false); + equal(ping.engines.length, 1); + equal(ping.engines[0].name, "bookmarks-buffered"); + equal(ping.engines[0].outgoing.length, 1); + ok(!!ping.engines[0].incoming); + } finally { + // Clean up. + await store.wipe(); + await cleanAndGo(engine, server); + } +}); + +add_task(async function test_upload_failed() { + let collection = new ServerCollection(); + collection._wbos.flying = new ServerWBO("flying"); + + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + + await SyncTestingInfrastructure(server); + await configureIdentity({ username: "foo" }, server); + + let engine = new RotaryEngine(Service); + engine._store.items = { + flying: "LNER Class A3 4472", + scotsman: "Flying Scotsman", + peppercorn: "Peppercorn Class", + }; + const FLYING_CHANGED = 12345; + const SCOTSMAN_CHANGED = 23456; + const PEPPERCORN_CHANGED = 34567; + await engine._tracker.addChangedID("flying", FLYING_CHANGED); + await engine._tracker.addChangedID("scotsman", SCOTSMAN_CHANGED); + await engine._tracker.addChangedID("peppercorn", PEPPERCORN_CHANGED); + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + await engine.setLastSync(123); // needs to be non-zero so that tracker is queried + let changes = await engine._tracker.getChangedIDs(); + _( + `test_upload_failed: Rotary tracker contents at first sync: ${JSON.stringify( + changes + )}` + ); + engine.enabled = true; + let ping = await sync_engine_and_validate_telem(engine, true); + ok(!!ping); + equal(ping.engines.length, 1); + equal(ping.engines[0].incoming, null); + deepEqual(ping.engines[0].outgoing, [ + { + sent: 3, + failed: 2, + failedReasons: [ + { name: "scotsman", count: 1 }, + { name: "peppercorn", count: 1 }, + ], + }, + ]); + await engine.setLastSync(123); + + changes = await engine._tracker.getChangedIDs(); + _( + `test_upload_failed: Rotary tracker contents at second sync: ${JSON.stringify( + changes + )}` + ); + ping = await sync_engine_and_validate_telem(engine, true); + ok(!!ping); + equal(ping.engines.length, 1); + deepEqual(ping.engines[0].outgoing, [ + { + sent: 2, + failed: 2, + failedReasons: [ + { name: "scotsman", count: 1 }, + { name: "peppercorn", count: 1 }, + ], + }, + ]); + } finally { + await cleanAndGo(engine, server); + await engine.finalize(); + } +}); + +add_task(async function test_sync_partialUpload() { + let collection = new ServerCollection(); + let server = sync_httpd_setup({ + "/1.1/foo/storage/rotary": collection.handler(), + }); + await SyncTestingInfrastructure(server); + await generateNewKeys(Service.collectionKeys); + + let engine = new RotaryEngine(Service); + await engine.setLastSync(123); + + // Create a bunch of records (and server side handlers) + for (let i = 0; i < 234; i++) { + let id = "record-no-" + i; + engine._store.items[id] = "Record No. " + i; + await engine._tracker.addChangedID(id, i); + // Let two items in the first upload batch fail. + if (i != 23 && i != 42) { + collection.insert(id); + } + } + + let syncID = await engine.resetLocalSyncID(); + let meta_global = Service.recordManager.set( + engine.metaURL, + new WBORecord(engine.metaURL) + ); + meta_global.payload.engines = { rotary: { version: engine.version, syncID } }; + + try { + let changes = await engine._tracker.getChangedIDs(); + _( + `test_sync_partialUpload: Rotary tracker contents at first sync: ${JSON.stringify( + changes + )}` + ); + engine.enabled = true; + let ping = await sync_engine_and_validate_telem(engine, true); + + ok(!!ping); + ok(!ping.failureReason); + equal(ping.engines.length, 1); + equal(ping.engines[0].name, "rotary"); + ok(!ping.engines[0].incoming); + ok(!ping.engines[0].failureReason); + deepEqual(ping.engines[0].outgoing, [ + { + sent: 234, + failed: 2, + failedReasons: [ + { name: "record-no-23", count: 1 }, + { name: "record-no-42", count: 1 }, + ], + }, + ]); + collection.post = function () { + throw new Error("Failure"); + }; + + engine._store.items["record-no-1000"] = "Record No. 1000"; + await engine._tracker.addChangedID("record-no-1000", 1000); + collection.insert("record-no-1000", 1000); + + await engine.setLastSync(123); + ping = null; + + changes = await engine._tracker.getChangedIDs(); + _( + `test_sync_partialUpload: Rotary tracker contents at second sync: ${JSON.stringify( + changes + )}` + ); + try { + // should throw + await sync_engine_and_validate_telem( + engine, + true, + errPing => (ping = errPing) + ); + } catch (e) {} + // It would be nice if we had a more descriptive error for this... + let uploadFailureError = { + name: "httperror", + code: 500, + }; + + ok(!!ping); + deepEqual(ping.failureReason, uploadFailureError); + equal(ping.engines.length, 1); + equal(ping.engines[0].name, "rotary"); + deepEqual(ping.engines[0].incoming, { + failed: 1, + failedReasons: [{ name: "No ciphertext: nothing to decrypt?", count: 1 }], + }); + ok(!ping.engines[0].outgoing); + deepEqual(ping.engines[0].failureReason, uploadFailureError); + } finally { + await cleanAndGo(engine, server); + await engine.finalize(); + } +}); + +add_task(async function test_generic_engine_fail() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let e = new Error("generic failure message"); + engine._errToThrow = e; + + try { + const changes = await engine._tracker.getChangedIDs(); + _( + `test_generic_engine_fail: Steam tracker contents: ${JSON.stringify( + changes + )}` + ); + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { + name: "unexpectederror", + error: String(e), + }); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_engine_fail_weird_errors() { + enableValidationPrefs(); + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + try { + let msg = "Bad things happened!"; + engine._errToThrow = { message: msg }; + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { + name: "unexpectederror", + error: "Bad things happened!", + }); + }); + let e = { msg }; + engine._errToThrow = e; + await sync_and_validate_telem(ping => { + deepEqual(ping.engines.find(err => err.name === "steam").failureReason, { + name: "unexpectederror", + error: JSON.stringify(e), + }); + }); + } finally { + await cleanAndGo(engine, server); + Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_overrideTelemetryName() { + enableValidationPrefs(["steam"]); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.overrideTelemetryName = "steam-but-better"; + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + + const problemsToReport = [ + { name: "someProblem", count: 123 }, + { name: "anotherProblem", count: 456 }, + ]; + + try { + info("Sync with validation problems"); + engine.problemsToReport = problemsToReport; + await sync_and_validate_telem(ping => { + let enginePing = ping.engines.find(e => e.name === "steam-but-better"); + ok(enginePing); + ok(!ping.engines.find(e => e.name === "steam")); + deepEqual( + enginePing.validation, + { + version: 1, + checked: 0, + problems: problemsToReport, + }, + "Should include validation report with overridden name" + ); + }); + + info("Sync without validation problems"); + engine.problemsToReport = null; + await sync_and_validate_telem(ping => { + let enginePing = ping.engines.find(e => e.name === "steam-but-better"); + ok(enginePing); + ok(!ping.engines.find(e => e.name === "steam")); + ok( + !enginePing.validation, + "Should not include validation report when there are no problems" + ); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_engine_fail_ioerror() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + // create an IOError to re-throw as part of Sync. + try { + // (Note that fakeservices.js has replaced Utils.jsonMove etc, but for + // this test we need the real one so we get real exceptions from the + // filesystem.) + await Utils._real_jsonMove("file-does-not-exist", "anything", {}); + } catch (ex) { + engine._errToThrow = ex; + } + ok(engine._errToThrow, "expecting exception"); + + try { + const changes = await engine._tracker.getChangedIDs(); + _( + `test_engine_fail_ioerror: Steam tracker contents: ${JSON.stringify( + changes + )}` + ); + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + let failureReason = ping.engines.find( + e => e.name === "steam" + ).failureReason; + equal(failureReason.name, "unexpectederror"); + // ensure the profile dir in the exception message has been stripped. + ok( + !failureReason.error.includes(PathUtils.profileDir), + failureReason.error + ); + ok(failureReason.error.includes("[profileDir]"), failureReason.error); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_error_detections() { + let telem = get_sync_test_telemetry(); + + // Non-network NS_ERROR_ codes get their own category. + Assert.deepEqual( + telem.transformError(Components.Exception("", Cr.NS_ERROR_FAILURE)), + { name: "nserror", code: Cr.NS_ERROR_FAILURE } + ); + + // Some NS_ERROR_ code in the "network" module are treated as http errors. + Assert.deepEqual( + telem.transformError(Components.Exception("", Cr.NS_ERROR_UNKNOWN_HOST)), + { name: "httperror", code: Cr.NS_ERROR_UNKNOWN_HOST } + ); + // Some NS_ERROR_ABORT is treated as network by our telemetry. + Assert.deepEqual( + telem.transformError(Components.Exception("", Cr.NS_ERROR_ABORT)), + { name: "httperror", code: Cr.NS_ERROR_ABORT } + ); +}); + +add_task(async function test_clean_urls() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + engine._errToThrow = new TypeError( + "http://www.google .com is not a valid URL." + ); + + try { + const changes = await engine._tracker.getChangedIDs(); + _(`test_clean_urls: Steam tracker contents: ${JSON.stringify(changes)}`); + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + let failureReason = ping.engines.find( + e => e.name === "steam" + ).failureReason; + equal(failureReason.name, "unexpectederror"); + equal(failureReason.error, "<URL> is not a valid URL."); + }); + // Handle other errors that include urls. + engine._errToThrow = + "Other error message that includes some:url/foo/bar/ in it."; + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + let failureReason = ping.engines.find( + e => e.name === "steam" + ).failureReason; + equal(failureReason.name, "unexpectederror"); + equal( + failureReason.error, + "Other error message that includes <URL> in it." + ); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +// Test sanitizing guid-related errors with the pattern of <guid: {guid}> +add_task(async function test_sanitize_bookmarks_guid() { + let { ErrorSanitizer } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" + ); + + for (let [original, expected] of [ + [ + "Can't insert Bookmark <guid: sknD84IdnSY2> into Folder <guid: odfninDdi93_3>", + "Can't insert Bookmark <GUID> into Folder <GUID>", + ], + [ + "Merge Error: Item <guid: H6fmPA16gZs9> can't contain itself", + "Merge Error: Item <GUID> can't contain itself", + ], + ]) { + const sanitized = ErrorSanitizer.cleanErrorMessage(original); + Assert.equal(sanitized, expected); + } +}); + +// Test sanitization of some hard-coded error strings. +add_task(async function test_clean_errors() { + let { ErrorSanitizer } = ChromeUtils.importESModule( + "resource://services-sync/telemetry.sys.mjs" + ); + + for (let [message, name, expected] of [ + [ + `Could not open the file at ${PathUtils.join( + PathUtils.profileDir, + "weave", + "addonsreconciler.json" + )} for writing`, + "NotFoundError", + "OS error [File/Path not found] Could not open the file at [profileDir]/weave/addonsreconciler.json for writing", + ], + [ + `Could not get info for the file at ${PathUtils.join( + PathUtils.profileDir, + "weave", + "addonsreconciler.json" + )}`, + "NotAllowedError", + "OS error [Permission denied] Could not get info for the file at [profileDir]/weave/addonsreconciler.json", + ], + ]) { + const error = new DOMException(message, name); + const sanitized = ErrorSanitizer.cleanErrorMessage(message, error); + Assert.equal(sanitized, expected); + } +}); + +// Arrange for a sync to hit a "real" OS error during a sync and make sure it's sanitized. +add_task(async function test_clean_real_os_error() { + enableValidationPrefs(); + + // Simulate a real error. + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let path = PathUtils.join(PathUtils.profileDir, "no", "such", "path.json"); + try { + await IOUtils.readJSON(path); + throw new Error("should fail to read the file"); + } catch (ex) { + engine._errToThrow = ex; + } + + try { + const changes = await engine._tracker.getChangedIDs(); + _(`test_clean_urls: Steam tracker contents: ${JSON.stringify(changes)}`); + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + let failureReason = ping.engines.find( + e => e.name === "steam" + ).failureReason; + equal(failureReason.name, "unexpectederror"); + equal( + failureReason.error, + "OS error [File/Path not found] Could not open the file at [profileDir]/no/such/path.json" + ); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_initial_sync_engines() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + // These are the only ones who actually have things to sync at startup. + let telemetryEngineNames = ["clients", "prefs", "tabs", "bookmarks-buffered"]; + let server = await serverForEnginesWithKeys( + { foo: "password" }, + ["bookmarks", "prefs", "tabs"].map(name => Service.engineManager.get(name)) + ); + await SyncTestingInfrastructure(server); + try { + const changes = await engine._tracker.getChangedIDs(); + _( + `test_initial_sync_engines: Steam tracker contents: ${JSON.stringify( + changes + )}` + ); + let ping = await wait_for_ping(() => Service.sync(), true); + + equal(ping.engines.find(e => e.name === "clients").outgoing[0].sent, 1); + equal(ping.engines.find(e => e.name === "tabs").outgoing[0].sent, 1); + + // for the rest we don't care about specifics + for (let e of ping.engines) { + if (!telemetryEngineNames.includes(engine.name)) { + continue; + } + greaterOrEqual(e.took, 1); + ok(!!e.outgoing); + equal(e.outgoing.length, 1); + notEqual(e.outgoing[0].sent, undefined); + equal(e.outgoing[0].failed, undefined); + equal(e.outgoing[0].failedReasons, undefined); + } + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_nserror() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + engine._errToThrow = Components.Exception( + "NS_ERROR_UNKNOWN_HOST", + Cr.NS_ERROR_UNKNOWN_HOST + ); + try { + const changes = await engine._tracker.getChangedIDs(); + _(`test_nserror: Steam tracker contents: ${JSON.stringify(changes)}`); + await sync_and_validate_telem(ping => { + deepEqual(ping.status, { + service: SYNC_FAILED_PARTIAL, + sync: LOGIN_FAILED_NETWORK_ERROR, + }); + let enginePing = ping.engines.find(e => e.name === "steam"); + deepEqual(enginePing.failureReason, { + name: "httperror", + code: Cr.NS_ERROR_UNKNOWN_HOST, + }); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_sync_why() { + enableValidationPrefs(); + + await Service.engineManager.register(SteamEngine); + let engine = Service.engineManager.get("steam"); + engine.enabled = true; + let server = await serverForFoo(engine); + await SyncTestingInfrastructure(server); + let e = new Error("generic failure message"); + engine._errToThrow = e; + + try { + const changes = await engine._tracker.getChangedIDs(); + _( + `test_generic_engine_fail: Steam tracker contents: ${JSON.stringify( + changes + )}` + ); + let ping = await wait_for_ping( + () => Service.sync({ why: "user" }), + true, + false + ); + _(JSON.stringify(ping)); + equal(ping.why, "user"); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_discarding() { + enableValidationPrefs(); + + let helper = track_collections_helper(); + let upd = helper.with_updated_collection; + let telem = get_sync_test_telemetry(); + telem.maxPayloadCount = 2; + telem.submissionInterval = Infinity; + let oldSubmit = telem.submit; + + let server; + try { + let handlers = { + "/1.1/johndoe/info/collections": helper.handler, + "/1.1/johndoe/storage/crypto/keys": upd( + "crypto", + new ServerWBO("keys").handler() + ), + "/1.1/johndoe/storage/meta/global": upd( + "meta", + new ServerWBO("global").handler() + ), + }; + + let collections = [ + "clients", + "bookmarks", + "forms", + "history", + "passwords", + "prefs", + "tabs", + ]; + + for (let coll of collections) { + handlers["/1.1/johndoe/storage/" + coll] = upd( + coll, + new ServerCollection({}, true).handler() + ); + } + + server = httpd_setup(handlers); + await configureIdentity({ username: "johndoe" }, server); + telem.submit = p => + ok( + false, + "Submitted telemetry ping when we should not have" + JSON.stringify(p) + ); + + for (let i = 0; i < 5; ++i) { + await Service.sync(); + } + telem.submit = oldSubmit; + telem.submissionInterval = -1; + let ping = await wait_for_ping(() => Service.sync(), true, true); // with this we've synced 6 times + equal(ping.syncs.length, 2); + equal(ping.discarded, 4); + } finally { + telem.maxPayloadCount = 500; + telem.submissionInterval = -1; + telem.submit = oldSubmit; + if (server) { + await promiseStopServer(server); + } + } +}); + +add_task(async function test_submit_interval() { + let telem = get_sync_test_telemetry(); + let oldSubmit = telem.submit; + let numSubmissions = 0; + telem.submit = function () { + numSubmissions += 1; + }; + + function notify(what, data = null) { + Svc.Obs.notify(what, JSON.stringify(data)); + } + + try { + // submissionInterval is set such that each sync should submit + notify("weave:service:sync:start", { why: "testing" }); + notify("weave:service:sync:finish"); + Assert.equal(numSubmissions, 1, "should submit this ping due to interval"); + + // As should each event outside of a sync. + Service.recordTelemetryEvent("object", "method"); + Assert.equal(numSubmissions, 2); + + // But events while we are syncing should not. + notify("weave:service:sync:start", { why: "testing" }); + Service.recordTelemetryEvent("object", "method"); + Assert.equal(numSubmissions, 2, "no submission for this event"); + notify("weave:service:sync:finish"); + Assert.equal(numSubmissions, 3, "was submitted after sync finish"); + } finally { + telem.submit = oldSubmit; + } +}); + +add_task(async function test_no_foreign_engines_in_error_ping() { + enableValidationPrefs(); + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + engine._errToThrow = new Error("Oh no!"); + await SyncTestingInfrastructure(server); + try { + await sync_and_validate_telem(ping => { + equal(ping.status.service, SYNC_FAILED_PARTIAL); + ok(ping.engines.every(e => e.name !== "bogus")); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_no_foreign_engines_in_success_ping() { + enableValidationPrefs(); + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + try { + await sync_and_validate_telem(ping => { + ok(ping.engines.every(e => e.name !== "bogus")); + }); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_events() { + enableValidationPrefs(); + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + + let telem = get_sync_test_telemetry(); + telem.submissionInterval = Infinity; + + try { + let serverTime = Resource.serverTime; + Service.recordTelemetryEvent("object", "method", "value", { foo: "bar" }); + let ping = await wait_for_ping(() => Service.sync(), true, true); + equal(ping.events.length, 1); + let [timestamp, category, method, object, value, extra] = ping.events[0]; + ok(typeof timestamp == "number" && timestamp > 0); // timestamp. + equal(category, "sync"); + equal(method, "method"); + equal(object, "object"); + equal(value, "value"); + deepEqual(extra, { foo: "bar", serverTime: String(serverTime) }); + ping = await wait_for_ping( + () => { + // Test with optional values. + Service.recordTelemetryEvent("object", "method"); + }, + false, + true + ); + equal(ping.events.length, 1); + equal(ping.events[0].length, 4); + + ping = await wait_for_ping( + () => { + Service.recordTelemetryEvent("object", "method", "extra"); + }, + false, + true + ); + equal(ping.events.length, 1); + equal(ping.events[0].length, 5); + + ping = await wait_for_ping( + () => { + Service.recordTelemetryEvent("object", "method", undefined, { + foo: "bar", + }); + }, + false, + true + ); + equal(ping.events.length, 1); + equal(ping.events[0].length, 6); + [timestamp, category, method, object, value, extra] = ping.events[0]; + equal(value, null); + + // Fake a submission due to shutdown. + ping = await wait_for_ping( + () => { + telem.submissionInterval = Infinity; + Service.recordTelemetryEvent("object", "method", undefined, { + foo: "bar", + }); + telem.finish("shutdown"); + }, + false, + true + ); + equal(ping.syncs.length, 0); + equal(ping.events.length, 1); + equal(ping.events[0].length, 6); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_histograms() { + enableValidationPrefs(); + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + try { + let histId = "TELEMETRY_TEST_LINEAR"; + Services.obs.notifyObservers(null, "weave:telemetry:histogram", histId); + let ping = await wait_for_ping(() => Service.sync(), true, true); + equal(Object.keys(ping.histograms).length, 1); + equal(ping.histograms[histId].sum, 0); + equal(ping.histograms[histId].histogram_type, 1); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_invalid_events() { + enableValidationPrefs(); + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + + async function checkNotRecorded(...args) { + Service.recordTelemetryEvent.call(args); + let ping = await wait_for_ping(() => Service.sync(), false, true); + equal(ping.events, undefined); + } + + await SyncTestingInfrastructure(server); + try { + let long21 = "l".repeat(21); + let long81 = "l".repeat(81); + let long86 = "l".repeat(86); + await checkNotRecorded("object"); + await checkNotRecorded("object", 2); + await checkNotRecorded(2, "method"); + await checkNotRecorded("object", "method", 2); + await checkNotRecorded("object", "method", "value", 2); + await checkNotRecorded("object", "method", "value", { foo: 2 }); + await checkNotRecorded(long21, "method", "value"); + await checkNotRecorded("object", long21, "value"); + await checkNotRecorded("object", "method", long81); + let badextra = {}; + badextra[long21] = "x"; + await checkNotRecorded("object", "method", "value", badextra); + badextra = { x: long86 }; + await checkNotRecorded("object", "method", "value", badextra); + for (let i = 0; i < 10; i++) { + badextra["name" + i] = "x"; + } + await checkNotRecorded("object", "method", "value", badextra); + } finally { + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_no_ping_for_self_hosters() { + enableValidationPrefs(); + + let telem = get_sync_test_telemetry(); + let oldSubmit = telem.submit; + + await Service.engineManager.register(BogusEngine); + let engine = Service.engineManager.get("bogus"); + engine.enabled = true; + let server = await serverForFoo(engine); + + await SyncTestingInfrastructure(server); + try { + let submitPromise = new Promise(resolve => { + telem.submit = function () { + let result = oldSubmit.apply(this, arguments); + resolve(result); + }; + }); + await Service.sync(); + let pingSubmitted = await submitPromise; + // The Sync testing infrastructure already sets up a custom token server, + // so we don't need to do anything to simulate a self-hosted user. + ok(!pingSubmitted, "Should not submit ping with custom token server URL"); + } finally { + telem.submit = oldSubmit; + await cleanAndGo(engine, server); + await Service.engineManager.unregister(engine); + } +}); + +add_task(async function test_fxa_device_telem() { + let t = get_sync_test_telemetry(); + let syncEnabled = true; + let oldGetClientsEngineRecords = t.getClientsEngineRecords; + let oldGetFxaDevices = t.getFxaDevices; + let oldSyncIsEnabled = t.syncIsEnabled; + let oldSanitizeFxaDeviceId = t.sanitizeFxaDeviceId; + t.syncIsEnabled = () => syncEnabled; + t.sanitizeFxaDeviceId = id => `So clean: ${id}`; + try { + let keep0 = Utils.makeGUID(); + let keep1 = Utils.makeGUID(); + let keep2 = Utils.makeGUID(); + let curdev = Utils.makeGUID(); + + let keep1Sync = Utils.makeGUID(); + let keep2Sync = Utils.makeGUID(); + let curdevSync = Utils.makeGUID(); + let fxaDevices = [ + { + id: curdev, + isCurrentDevice: true, + lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, + pushEndpointExpired: false, + type: "desktop", + name: "current device", + }, + { + id: keep0, + isCurrentDevice: false, + lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 10, + pushEndpointExpired: false, + type: "mobile", + name: "dupe", + }, + // Valid 2 + { + id: keep1, + isCurrentDevice: false, + lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, + pushEndpointExpired: false, + type: "desktop", + name: "valid2", + }, + // Valid 3 + { + id: keep2, + isCurrentDevice: false, + lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 5, + pushEndpointExpired: false, + type: "desktop", + name: "valid3", + }, + ]; + let clientInfo = [ + { + id: keep1Sync, + fxaDeviceId: keep1, + os: "Windows 30", + version: "Firefox 1 million", + }, + { + id: keep2Sync, + fxaDeviceId: keep2, + os: "firefox, but an os", + verison: "twelve", + }, + { + id: Utils.makeGUID(), + fxaDeviceId: null, + os: "apparently ios used to keep write these IDs as null.", + version: "Doesn't seem to anymore", + }, + { + id: curdevSync, + fxaDeviceId: curdev, + os: "emacs", + version: "22", + }, + { + id: Utils.makeGUID(), + fxaDeviceId: Utils.makeGUID(), + os: "not part of the fxa device set at all", + version: "foo bar baz", + }, + // keep0 intententionally omitted. + ]; + t.getClientsEngineRecords = () => clientInfo; + let devInfo = t.updateFxaDevices(fxaDevices); + equal(devInfo.deviceID, t.sanitizeFxaDeviceId(curdev)); + for (let d of devInfo.devices) { + ok(d.id.startsWith("So clean:")); + if (d.syncID) { + ok(d.syncID.startsWith("So clean:")); + } + } + equal(devInfo.devices.length, 4); + let k0 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep0)); + let k1 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep1)); + let k2 = devInfo.devices.find(d => d.id == t.sanitizeFxaDeviceId(keep2)); + + deepEqual(k0, { + id: t.sanitizeFxaDeviceId(keep0), + type: "mobile", + os: undefined, + version: undefined, + syncID: undefined, + }); + deepEqual(k1, { + id: t.sanitizeFxaDeviceId(keep1), + type: "desktop", + os: clientInfo[0].os, + version: clientInfo[0].version, + syncID: t.sanitizeFxaDeviceId(keep1Sync), + }); + deepEqual(k2, { + id: t.sanitizeFxaDeviceId(keep2), + type: "desktop", + os: clientInfo[1].os, + version: clientInfo[1].version, + syncID: t.sanitizeFxaDeviceId(keep2Sync), + }); + let newCurId = Utils.makeGUID(); + // Update the ID + fxaDevices[0].id = newCurId; + + let keep3 = Utils.makeGUID(); + fxaDevices.push({ + id: keep3, + isCurrentDevice: false, + lastAccessTime: Date.now() - 1000 * 60 * 60 * 24 * 1, + pushEndpointExpired: false, + type: "desktop", + name: "valid 4", + }); + devInfo = t.updateFxaDevices(fxaDevices); + + let afterSubmit = [keep0, keep1, keep2, keep3, newCurId] + .map(id => t.sanitizeFxaDeviceId(id)) + .sort(); + deepEqual(devInfo.devices.map(d => d.id).sort(), afterSubmit); + + // Reset this, as our override doesn't check for sync being enabled. + t.sanitizeFxaDeviceId = oldSanitizeFxaDeviceId; + syncEnabled = false; + fxAccounts.telemetry._setHashedUID(false); + devInfo = t.updateFxaDevices(fxaDevices); + equal(devInfo.deviceID, undefined); + equal(devInfo.devices.length, 5); + for (let d of devInfo.devices) { + equal(d.os, undefined); + equal(d.version, undefined); + equal(d.syncID, undefined); + // Type should still be present. + notEqual(d.type, undefined); + } + } finally { + t.getClientsEngineRecords = oldGetClientsEngineRecords; + t.getFxaDevices = oldGetFxaDevices; + t.syncIsEnabled = oldSyncIsEnabled; + t.sanitizeFxaDeviceId = oldSanitizeFxaDeviceId; + } +}); + +add_task(async function test_sanitize_fxa_device_id() { + let t = get_sync_test_telemetry(); + fxAccounts.telemetry._setHashedUID(false); + sinon.stub(t, "syncIsEnabled").callsFake(() => true); + const rawDeviceId = "raw one two three"; + try { + equal(t.sanitizeFxaDeviceId(rawDeviceId), null); + fxAccounts.telemetry._setHashedUID("mock uid"); + const sanitizedDeviceId = t.sanitizeFxaDeviceId(rawDeviceId); + ok(sanitizedDeviceId); + notEqual(sanitizedDeviceId, rawDeviceId); + } finally { + t.syncIsEnabled.restore(); + fxAccounts.telemetry._setHashedUID(false); + } +}); + +add_task(async function test_no_node_type() { + let server = sync_httpd_setup({}); + await configureIdentity(null, server); + + await sync_and_validate_telem(ping => { + ok(ping.syncNodeType === undefined); + }, true); + await promiseStopServer(server); +}); + +add_task(async function test_node_type() { + Service.identity.logout(); + let server = sync_httpd_setup({}); + await configureIdentity({ node_type: "the-node-type" }, server); + + await sync_and_validate_telem(ping => { + equal(ping.syncNodeType, "the-node-type"); + }, true); + await promiseStopServer(server); +}); + +add_task(async function test_node_type_change() { + let pingPromise = wait_for_pings(2); + + Service.identity.logout(); + let server = sync_httpd_setup({}); + await configureIdentity({ node_type: "first-node-type" }, server); + // Default to submitting each hour - we should still submit on node change. + let telem = get_sync_test_telemetry(); + telem.submissionInterval = 60 * 60 * 1000; + // reset the node type from previous test or our first sync will submit. + telem.lastSyncNodeType = null; + // do 2 syncs with the same node type. + await Service.sync(); + await Service.sync(); + // then another with a different node type. + Service.identity.logout(); + await configureIdentity({ node_type: "second-node-type" }, server); + await Service.sync(); + telem.finish(); + + let pings = await pingPromise; + equal(pings.length, 2); + equal(pings[0].syncs.length, 2, "2 syncs in first ping"); + equal(pings[0].syncNodeType, "first-node-type"); + equal(pings[1].syncs.length, 1, "1 sync in second ping"); + equal(pings[1].syncNodeType, "second-node-type"); + await promiseStopServer(server); +}); + +add_task(async function test_ids() { + let telem = get_sync_test_telemetry(); + Assert.ok(!telem._shouldSubmitForDataChange()); + fxAccounts.telemetry._setHashedUID("new_uid"); + Assert.ok(telem._shouldSubmitForDataChange()); + telem.maybeSubmitForDataChange(); + // now it's been submitted the new uid is current. + Assert.ok(!telem._shouldSubmitForDataChange()); +}); + +add_task(async function test_deletion_request_ping() { + async function assertRecordedSyncDeviceID(expected) { + // The scalar gets updated asynchronously, so wait a tick before checking. + await Promise.resolve(); + const scalars = + Services.telemetry.getSnapshotForScalars("deletion-request").parent || {}; + equal(scalars["deletion.request.sync_device_id"], expected); + } + + const MOCK_HASHED_UID = "00112233445566778899aabbccddeeff"; + const MOCK_DEVICE_ID1 = "ffeeddccbbaa99887766554433221100"; + const MOCK_DEVICE_ID2 = "aabbccddeeff99887766554433221100"; + + // Calculated by hand using SHA256(DEVICE_ID + HASHED_UID)[:32] + const SANITIZED_DEVICE_ID1 = "dd7c845006df9baa1c6d756926519c8c"; + const SANITIZED_DEVICE_ID2 = "0d06919a736fc029007e1786a091882c"; + + let currentDeviceID = null; + sinon.stub(fxAccounts.device, "getLocalId").callsFake(() => { + return Promise.resolve(currentDeviceID); + }); + let telem = get_sync_test_telemetry(); + sinon.stub(telem, "isProductionSyncUser").callsFake(() => true); + fxAccounts.telemetry._setHashedUID(false); + try { + // The scalar should start out undefined, since no user is actually logged in. + await assertRecordedSyncDeviceID(undefined); + + // If we start up without knowing the hashed UID, it should stay undefined. + telem.observe(null, "weave:service:ready"); + await assertRecordedSyncDeviceID(undefined); + + // But now let's say we've discovered the hashed UID from the server. + fxAccounts.telemetry._setHashedUID(MOCK_HASHED_UID); + currentDeviceID = MOCK_DEVICE_ID1; + + // Now when we load up, we'll record the sync device id. + telem.observe(null, "weave:service:ready"); + await assertRecordedSyncDeviceID(SANITIZED_DEVICE_ID1); + + // When the device-id changes we'll update it. + currentDeviceID = MOCK_DEVICE_ID2; + telem.observe(null, "fxaccounts:new_device_id"); + await assertRecordedSyncDeviceID(SANITIZED_DEVICE_ID2); + + // When the user signs out we'll clear it. + telem.observe(null, "fxaccounts:onlogout"); + await assertRecordedSyncDeviceID(""); + } finally { + fxAccounts.telemetry._setHashedUID(false); + telem.isProductionSyncUser.restore(); + fxAccounts.device.getLocalId.restore(); + } +}); diff --git a/services/sync/tests/unit/test_tracker_addChanged.js b/services/sync/tests/unit/test_tracker_addChanged.js new file mode 100644 index 0000000000..7f510794fc --- /dev/null +++ b/services/sync/tests/unit/test_tracker_addChanged.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function test_tracker_basics() { + let tracker = new LegacyTracker("Tracker", Service); + + let id = "the_id!"; + + _("Make sure nothing exists yet.."); + let changes = await tracker.getChangedIDs(); + Assert.equal(changes[id], null); + + _("Make sure adding of time 0 works"); + await tracker.addChangedID(id, 0); + changes = await tracker.getChangedIDs(); + Assert.equal(changes[id], 0); + + _("A newer time will replace the old 0"); + await tracker.addChangedID(id, 10); + changes = await tracker.getChangedIDs(); + Assert.equal(changes[id], 10); + + _("An older time will not replace the newer 10"); + await tracker.addChangedID(id, 5); + changes = await tracker.getChangedIDs(); + Assert.equal(changes[id], 10); + + _("Adding without time defaults to current time"); + await tracker.addChangedID(id); + changes = await tracker.getChangedIDs(); + Assert.ok(changes[id] > 10); +}); + +add_task(async function test_tracker_persistence() { + let tracker = new LegacyTracker("Tracker", Service); + let id = "abcdef"; + + let promiseSave = new Promise((resolve, reject) => { + let save = tracker._storage._save; + tracker._storage._save = function () { + save.call(tracker._storage).then(resolve, reject); + }; + }); + + await tracker.addChangedID(id, 5); + + await promiseSave; + + _("IDs saved."); + const changes = await tracker.getChangedIDs(); + Assert.equal(5, changes[id]); + + let json = await Utils.jsonLoad(["changes", "tracker"], tracker); + Assert.equal(5, json[id]); +}); diff --git a/services/sync/tests/unit/test_uistate.js b/services/sync/tests/unit/test_uistate.js new file mode 100644 index 0000000000..cb1ff1979e --- /dev/null +++ b/services/sync/tests/unit/test_uistate.js @@ -0,0 +1,324 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { UIState } = ChromeUtils.importESModule( + "resource://services-sync/UIState.sys.mjs" +); + +const UIStateInternal = UIState._internal; + +add_task(async function test_isReady_unconfigured() { + UIState.reset(); + + let refreshState = sinon.spy(UIStateInternal, "refreshState"); + + // On the first call, returns false + // Does trigger a refresh of the state - even though services.sync.username + // is undefined we still need to check the account state. + ok(!UIState.isReady()); + // resfreshState is called when idle - so only check after idle. + await new Promise(resolve => { + Services.tm.idleDispatchToMainThread(resolve); + }); + ok(refreshState.called); + refreshState.resetHistory(); + + // On subsequent calls, only return true + ok(UIState.isReady()); + ok(!refreshState.called); + + refreshState.restore(); +}); + +add_task(async function test_isReady_signedin() { + UIState.reset(); + Services.prefs.setStringPref("services.sync.username", "foo"); + + let refreshState = sinon.spy(UIStateInternal, "refreshState"); + + // On the first call, returns false and triggers a refresh of the state + ok(!UIState.isReady()); + await new Promise(resolve => { + Services.tm.idleDispatchToMainThread(resolve); + }); + ok(refreshState.calledOnce); + refreshState.resetHistory(); + + // On subsequent calls, only return true + ok(UIState.isReady()); + ok(!refreshState.called); + + refreshState.restore(); +}); + +add_task(async function test_refreshState_signedin() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + const now = new Date().toString(); + Services.prefs.setStringPref("services.sync.lastSync", now); + UIStateInternal.syncing = false; + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ + verified: true, + uid: "123", + email: "foo@bar.com", + displayName: "Foo Bar", + avatar: "https://foo/bar", + }), + hasLocalSession: () => Promise.resolve(true), + }; + + let state = await UIState.refresh(); + + equal(state.status, UIState.STATUS_SIGNED_IN); + equal(state.uid, "123"); + equal(state.email, "foo@bar.com"); + equal(state.displayName, "Foo Bar"); + equal(state.avatarURL, "https://foo/bar"); + equal(state.lastSync, now); + equal(state.syncing, false); + + UIStateInternal.fxAccounts = fxAccountsOrig; +}); + +add_task(async function test_refreshState_syncButNoFxA() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + const now = new Date().toString(); + Services.prefs.setStringPref("services.sync.lastSync", now); + Services.prefs.setStringPref("services.sync.username", "test@test.com"); + UIStateInternal.syncing = false; + + UIStateInternal.fxAccounts = { + getSignedInUser: () => Promise.resolve(null), + }; + + let state = await UIState.refresh(); + + equal(state.status, UIState.STATUS_LOGIN_FAILED); + equal(state.uid, undefined); + equal(state.email, "test@test.com"); + equal(state.displayName, undefined); + equal(state.avatarURL, undefined); + equal(state.lastSync, undefined); // only set when STATUS_SIGNED_IN. + equal(state.syncing, false); + + UIStateInternal.fxAccounts = fxAccountsOrig; + Services.prefs.clearUserPref("services.sync.lastSync"); + Services.prefs.clearUserPref("services.sync.username"); +}); + +add_task(async function test_refreshState_signedin_profile_unavailable() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + const now = new Date().toString(); + Services.prefs.setStringPref("services.sync.lastSync", now); + Services.prefs.setStringPref("services.sync.username", "test@test.com"); + UIStateInternal.syncing = false; + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), + hasLocalSession: () => Promise.resolve(true), + _internal: { + profile: { + getProfile: () => { + return Promise.reject(new Error("Profile unavailable")); + }, + }, + }, + }; + + let state = await UIState.refresh(); + + equal(state.status, UIState.STATUS_SIGNED_IN); + equal(state.uid, "123"); + equal(state.email, "foo@bar.com"); + equal(state.displayName, undefined); + equal(state.avatarURL, undefined); + equal(state.lastSync, now); + equal(state.syncing, false); + + UIStateInternal.fxAccounts = fxAccountsOrig; + Services.prefs.clearUserPref("services.sync.lastSync"); + Services.prefs.clearUserPref("services.sync.username"); +}); + +add_task(async function test_refreshState_unverified() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ verified: false, uid: "123", email: "foo@bar.com" }), + hasLocalSession: () => Promise.resolve(true), + }; + + let state = await UIState.refresh(); + + equal(state.status, UIState.STATUS_NOT_VERIFIED); + equal(state.uid, "123"); + equal(state.email, "foo@bar.com"); + equal(state.displayName, undefined); + equal(state.avatarURL, undefined); + equal(state.lastSync, undefined); + + UIStateInternal.fxAccounts = fxAccountsOrig; +}); + +add_task(async function test_refreshState_unverified_nosession() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ verified: false, uid: "123", email: "foo@bar.com" }), + hasLocalSession: () => Promise.resolve(false), + }; + + let state = await UIState.refresh(); + + // No session should "win" over the unverified state. + equal(state.status, UIState.STATUS_LOGIN_FAILED); + equal(state.uid, "123"); + equal(state.email, "foo@bar.com"); + equal(state.displayName, undefined); + equal(state.avatarURL, undefined); + equal(state.lastSync, undefined); + + UIStateInternal.fxAccounts = fxAccountsOrig; +}); + +add_task(async function test_refreshState_loginFailed() { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + let loginFailed = sinon.stub(UIStateInternal, "_loginFailed"); + loginFailed.returns(true); + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), + }; + + let state = await UIState.refresh(); + + equal(state.status, UIState.STATUS_LOGIN_FAILED); + equal(state.uid, "123"); + equal(state.email, "foo@bar.com"); + equal(state.displayName, undefined); + equal(state.avatarURL, undefined); + equal(state.lastSync, undefined); + + loginFailed.restore(); + UIStateInternal.fxAccounts = fxAccountsOrig; +}); + +add_task(async function test_observer_refreshState() { + let refreshState = sinon.spy(UIStateInternal, "refreshState"); + + let shouldRefresh = [ + "weave:service:login:got-hashed-id", + "weave:service:login:error", + "weave:service:ready", + "fxaccounts:onverified", + "fxaccounts:onlogin", + "fxaccounts:onlogout", + "fxaccounts:profilechange", + ]; + + for (let topic of shouldRefresh) { + let uiUpdateObserved = observeUIUpdate(); + Services.obs.notifyObservers(null, topic); + await uiUpdateObserved; + ok(refreshState.calledOnce); + refreshState.resetHistory(); + } + + refreshState.restore(); +}); + +// Drive the UIState in a configured state. +async function configureUIState(syncing, lastSync = new Date()) { + UIState.reset(); + const fxAccountsOrig = UIStateInternal.fxAccounts; + + UIStateInternal._syncing = syncing; + Services.prefs.setStringPref("services.sync.lastSync", lastSync.toString()); + Services.prefs.setStringPref("services.sync.username", "test@test.com"); + + UIStateInternal.fxAccounts = { + getSignedInUser: () => + Promise.resolve({ verified: true, uid: "123", email: "foo@bar.com" }), + hasLocalSession: () => Promise.resolve(true), + }; + await UIState.refresh(); + UIStateInternal.fxAccounts = fxAccountsOrig; +} + +add_task(async function test_syncStarted() { + await configureUIState(false); + + const oldState = Object.assign({}, UIState.get()); + ok(!oldState.syncing); + + let uiUpdateObserved = observeUIUpdate(); + Services.obs.notifyObservers(null, "weave:service:sync:start"); + await uiUpdateObserved; + + const newState = Object.assign({}, UIState.get()); + ok(newState.syncing); +}); + +add_task(async function test_syncFinished() { + let yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + await configureUIState(true, yesterday); + + const oldState = Object.assign({}, UIState.get()); + ok(oldState.syncing); + + let uiUpdateObserved = observeUIUpdate(); + Services.prefs.setStringPref("services.sync.lastSync", new Date().toString()); + Services.obs.notifyObservers(null, "weave:service:sync:finish"); + await uiUpdateObserved; + + const newState = Object.assign({}, UIState.get()); + ok(!newState.syncing); + ok(new Date(newState.lastSync) > new Date(oldState.lastSync)); +}); + +add_task(async function test_syncError() { + let yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + await configureUIState(true, yesterday); + + const oldState = Object.assign({}, UIState.get()); + ok(oldState.syncing); + + let uiUpdateObserved = observeUIUpdate(); + Services.obs.notifyObservers(null, "weave:service:sync:error"); + await uiUpdateObserved; + + const newState = Object.assign({}, UIState.get()); + ok(!newState.syncing); + deepEqual(newState.lastSync, oldState.lastSync); +}); + +function observeUIUpdate() { + return new Promise(resolve => { + let obs = (aSubject, aTopic, aData) => { + Services.obs.removeObserver(obs, aTopic); + const state = UIState.get(); + resolve(state); + }; + Services.obs.addObserver(obs, UIState.ON_UPDATE); + }); +} diff --git a/services/sync/tests/unit/test_utils_catch.js b/services/sync/tests/unit/test_utils_catch.js new file mode 100644 index 0000000000..590d04527f --- /dev/null +++ b/services/sync/tests/unit/test_utils_catch.js @@ -0,0 +1,119 @@ +const { Service } = ChromeUtils.importESModule( + "resource://services-sync/service.sys.mjs" +); + +add_task(async function run_test() { + _("Make sure catch when copied to an object will correctly catch stuff"); + let ret, rightThis, didCall, didThrow, wasCovfefe, wasLocked; + let obj = { + _catch: Utils.catch, + _log: { + debug(str) { + didThrow = str.search(/^Exception/) == 0; + }, + info(str) { + wasLocked = str.indexOf("Cannot start sync: already syncing?") == 0; + }, + }, + + func() { + return this._catch(async function () { + rightThis = this == obj; + didCall = true; + return 5; + })(); + }, + + throwy() { + return this._catch(async function () { + rightThis = this == obj; + didCall = true; + throw new Error("covfefe"); + })(); + }, + + callbacky() { + return this._catch( + async function () { + rightThis = this == obj; + didCall = true; + throw new Error("covfefe"); + }, + async function (ex) { + wasCovfefe = ex && ex.message == "covfefe"; + } + )(); + }, + + lockedy() { + return this._catch(async function () { + rightThis = this == obj; + didCall = true; + Utils.throwLockException(null); + })(); + }, + + lockedy_chained() { + return this._catch(async function () { + rightThis = this == obj; + didCall = true; + Utils.throwLockException(null); + })(); + }, + }; + + _("Make sure a normal call will call and return"); + rightThis = didCall = didThrow = wasLocked = false; + ret = await obj.func(); + Assert.equal(ret, 5); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.ok(!didThrow); + Assert.equal(wasCovfefe, undefined); + Assert.ok(!wasLocked); + + _( + "Make sure catch/throw results in debug call and caller doesn't need to handle exception" + ); + rightThis = didCall = didThrow = wasLocked = false; + ret = await obj.throwy(); + Assert.equal(ret, undefined); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.ok(didThrow); + Assert.equal(wasCovfefe, undefined); + Assert.ok(!wasLocked); + + _("Test callback for exception testing."); + rightThis = didCall = didThrow = wasLocked = false; + ret = await obj.callbacky(); + Assert.equal(ret, undefined); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.ok(didThrow); + Assert.ok(wasCovfefe); + Assert.ok(!wasLocked); + + _("Test the lock-aware catch that Service uses."); + obj._catch = Service._catch; + rightThis = didCall = didThrow = wasLocked = false; + wasCovfefe = undefined; + ret = await obj.lockedy(); + Assert.equal(ret, undefined); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.ok(didThrow); + Assert.equal(wasCovfefe, undefined); + Assert.ok(wasLocked); + + _("Test the lock-aware catch that Service uses with a chained promise."); + rightThis = didCall = didThrow = wasLocked = false; + wasCovfefe = undefined; + ret = await obj.lockedy_chained(); + Assert.equal(ret, undefined); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.ok(didThrow); + Assert.equal(wasCovfefe, undefined); + Assert.ok(wasLocked); +}); diff --git a/services/sync/tests/unit/test_utils_deepEquals.js b/services/sync/tests/unit/test_utils_deepEquals.js new file mode 100644 index 0000000000..218cc21b72 --- /dev/null +++ b/services/sync/tests/unit/test_utils_deepEquals.js @@ -0,0 +1,51 @@ +_("Make sure Utils.deepEquals correctly finds items that are deeply equal"); + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +function run_test() { + let data = + '[NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"}, [{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3]]'; + _("Generating two copies of data:", data); + /* eslint-disable no-eval */ + let d1 = eval(data); + let d2 = eval(data); + /* eslint-enable no-eval */ + + d1.forEach(function (a) { + _("Testing", a, typeof a, JSON.stringify([a])); + let numMatch = 0; + + d2.forEach(function (b) { + if (Utils.deepEquals(a, b)) { + numMatch++; + _("Found a match", b, typeof b, JSON.stringify([b])); + } + }); + + let expect = 1; + if (isNaN(a) && typeof a == "number") { + expect = 0; + _("Checking NaN should result in no matches"); + } + + _("Making sure we found the correct # match:", expect); + _("Actual matches:", numMatch); + Assert.equal(numMatch, expect); + }); + + _("Make sure adding undefined properties doesn't affect equalness"); + let a = {}; + let b = { a: undefined }; + Assert.ok(Utils.deepEquals(a, b)); + a.b = 5; + Assert.ok(!Utils.deepEquals(a, b)); + b.b = 5; + Assert.ok(Utils.deepEquals(a, b)); + a.c = undefined; + Assert.ok(Utils.deepEquals(a, b)); + b.d = undefined; + Assert.ok(Utils.deepEquals(a, b)); +} diff --git a/services/sync/tests/unit/test_utils_deferGetSet.js b/services/sync/tests/unit/test_utils_deferGetSet.js new file mode 100644 index 0000000000..6db812b844 --- /dev/null +++ b/services/sync/tests/unit/test_utils_deferGetSet.js @@ -0,0 +1,50 @@ +_( + "Make sure various combinations of deferGetSet arguments correctly defer getting/setting properties to another object" +); + +function run_test() { + let base = function () {}; + base.prototype = { + dst: {}, + + get a() { + return "a"; + }, + set b(val) { + this.dst.b = val + "!!!"; + }, + }; + let src = new base(); + + _("get/set a single property"); + Utils.deferGetSet(base, "dst", "foo"); + src.foo = "bar"; + Assert.equal(src.dst.foo, "bar"); + Assert.equal(src.foo, "bar"); + + _("editing the target also updates the source"); + src.dst.foo = "baz"; + Assert.equal(src.dst.foo, "baz"); + Assert.equal(src.foo, "baz"); + + _("handle multiple properties"); + Utils.deferGetSet(base, "dst", ["p1", "p2"]); + src.p1 = "v1"; + src.p2 = "v2"; + Assert.equal(src.p1, "v1"); + Assert.equal(src.dst.p1, "v1"); + Assert.equal(src.p2, "v2"); + Assert.equal(src.dst.p2, "v2"); + + _("make sure existing getter keeps its functionality"); + Utils.deferGetSet(base, "dst", "a"); + src.a = "not a"; + Assert.equal(src.dst.a, "not a"); + Assert.equal(src.a, "a"); + + _("make sure existing setter keeps its functionality"); + Utils.deferGetSet(base, "dst", "b"); + src.b = "b"; + Assert.equal(src.dst.b, "b!!!"); + Assert.equal(src.b, "b!!!"); +} diff --git a/services/sync/tests/unit/test_utils_json.js b/services/sync/tests/unit/test_utils_json.js new file mode 100644 index 0000000000..5bf26b2361 --- /dev/null +++ b/services/sync/tests/unit/test_utils_json.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +add_task(async function test_roundtrip() { + _("Do a simple write of an array to json and read"); + await Utils.jsonSave("foo", {}, ["v1", "v2"]); + + let foo = await Utils.jsonLoad("foo", {}); + Assert.equal(typeof foo, "object"); + Assert.equal(foo.length, 2); + Assert.equal(foo[0], "v1"); + Assert.equal(foo[1], "v2"); +}); + +add_task(async function test_string() { + _("Try saving simple strings"); + await Utils.jsonSave("str", {}, "hi"); + + let str = await Utils.jsonLoad("str", {}); + Assert.equal(typeof str, "string"); + Assert.equal(str.length, 2); + Assert.equal(str[0], "h"); + Assert.equal(str[1], "i"); +}); + +add_task(async function test_number() { + _("Try saving a number"); + await Utils.jsonSave("num", {}, 42); + + let num = await Utils.jsonLoad("num", {}); + Assert.equal(typeof num, "number"); + Assert.equal(num, 42); +}); + +add_task(async function test_nonexistent_file() { + _("Try loading a non-existent file."); + let val = await Utils.jsonLoad("non-existent", {}); + Assert.equal(val, undefined); +}); + +add_task(async function test_save_logging() { + _("Verify that writes are logged."); + let trace; + await Utils.jsonSave( + "log", + { + _log: { + trace(msg) { + trace = msg; + }, + }, + }, + "hi" + ); + Assert.ok(!!trace); +}); + +add_task(async function test_load_logging() { + _("Verify that reads and read errors are logged."); + + // Write a file with some invalid JSON + let file = await IOUtils.getFile(PathUtils.profileDir, "weave", "log.json"); + let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let flags = + FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE; + fos.init(file, flags, FileUtils.PERMS_FILE, fos.DEFER_OPEN); + let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance( + Ci.nsIConverterOutputStream + ); + stream.init(fos, "UTF-8"); + stream.writeString("invalid json!"); + stream.close(); + + let trace, debug; + let obj = { + _log: { + trace(msg) { + trace = msg; + }, + debug(msg) { + debug = msg; + }, + }, + }; + let val = await Utils.jsonLoad("log", obj); + Assert.ok(!val); + Assert.ok(!!trace); + Assert.ok(!!debug); +}); diff --git a/services/sync/tests/unit/test_utils_keyEncoding.js b/services/sync/tests/unit/test_utils_keyEncoding.js new file mode 100644 index 0000000000..30a8a4f2aa --- /dev/null +++ b/services/sync/tests/unit/test_utils_keyEncoding.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + Assert.equal( + Utils.encodeKeyBase32("foobarbafoobarba"), + "mzxw6ytb9jrgcztpn5rgc4tcme" + ); + Assert.equal( + Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), + "foobarbafoobarba" + ); + Assert.equal( + Utils.encodeKeyBase32( + "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + ), + "aeaqcaibaeaqcaibaeaqcaibae" + ); + Assert.equal( + Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"), + "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + ); +} diff --git a/services/sync/tests/unit/test_utils_lock.js b/services/sync/tests/unit/test_utils_lock.js new file mode 100644 index 0000000000..71d6486ff9 --- /dev/null +++ b/services/sync/tests/unit/test_utils_lock.js @@ -0,0 +1,76 @@ +_("Make sure lock prevents calling with a shared lock"); + +// Utility that we only use here. + +function do_check_begins(thing, startsWith) { + if (!(thing && thing.indexOf && thing.indexOf(startsWith) == 0)) { + do_throw(thing + " doesn't begin with " + startsWith); + } +} + +add_task(async function run_test() { + let ret, rightThis, didCall; + let state, lockState, lockedState, unlockState; + let obj = { + _lock: Utils.lock, + lock() { + lockState = ++state; + if (this._locked) { + lockedState = ++state; + return false; + } + this._locked = true; + return true; + }, + unlock() { + unlockState = ++state; + this._locked = false; + }, + + func() { + return this._lock("Test utils lock", async function () { + rightThis = this == obj; + didCall = true; + return 5; + })(); + }, + + throwy() { + return this._lock("Test utils lock throwy", async function () { + rightThis = this == obj; + didCall = true; + return this.throwy(); + })(); + }, + }; + + _("Make sure a normal call will call and return"); + rightThis = didCall = false; + state = 0; + ret = await obj.func(); + Assert.equal(ret, 5); + Assert.ok(rightThis); + Assert.ok(didCall); + Assert.equal(lockState, 1); + Assert.equal(unlockState, 2); + Assert.equal(state, 2); + + _("Make sure code that calls locked code throws"); + ret = null; + rightThis = didCall = false; + try { + ret = await obj.throwy(); + do_throw("throwy internal call should have thrown!"); + } catch (ex) { + // Should throw an Error, not a string. + do_check_begins(ex.message, "Could not acquire lock"); + } + Assert.equal(ret, null); + Assert.ok(rightThis); + Assert.ok(didCall); + _("Lock should be called twice so state 3 is skipped"); + Assert.equal(lockState, 4); + Assert.equal(lockedState, 5); + Assert.equal(unlockState, 6); + Assert.equal(state, 6); +}); diff --git a/services/sync/tests/unit/test_utils_makeGUID.js b/services/sync/tests/unit/test_utils_makeGUID.js new file mode 100644 index 0000000000..b1104c1114 --- /dev/null +++ b/services/sync/tests/unit/test_utils_makeGUID.js @@ -0,0 +1,44 @@ +const base64url = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; + +function run_test() { + _("Make sure makeGUID makes guids of the right length/characters"); + _("Create a bunch of guids to make sure they don't conflict"); + let guids = []; + for (let i = 0; i < 1000; i++) { + let newGuid = Utils.makeGUID(); + _("Generated " + newGuid); + + // Verify that the GUID's length is correct, even when it's URL encoded. + Assert.equal(newGuid.length, 12); + Assert.equal(encodeURIComponent(newGuid).length, 12); + + // Verify that the GUID only contains base64url characters + Assert.ok( + Array.prototype.every.call(newGuid, function (chr) { + return base64url.includes(chr); + }) + ); + + // Verify that Utils.checkGUID() correctly identifies them as valid. + Assert.ok(Utils.checkGUID(newGuid)); + + // Verify uniqueness within our sample of 1000. This could cause random + // failures, but they should be extremely rare. Otherwise we'd have a + // problem with GUID collisions. + Assert.ok( + guids.every(function (g) { + return g != newGuid; + }) + ); + guids.push(newGuid); + } + + _("Make sure checkGUID fails for invalid GUIDs"); + Assert.ok(!Utils.checkGUID(undefined)); + Assert.ok(!Utils.checkGUID(null)); + Assert.ok(!Utils.checkGUID("")); + Assert.ok(!Utils.checkGUID("blergh")); + Assert.ok(!Utils.checkGUID("ThisGUIDisWayTooLong")); + Assert.ok(!Utils.checkGUID("Invalid!!!!!")); +} diff --git a/services/sync/tests/unit/test_utils_notify.js b/services/sync/tests/unit/test_utils_notify.js new file mode 100644 index 0000000000..5c0c3702a6 --- /dev/null +++ b/services/sync/tests/unit/test_utils_notify.js @@ -0,0 +1,97 @@ +_("Make sure notify sends out the right notifications"); +add_task(async function run_test() { + let ret, rightThis, didCall; + let obj = { + notify: Utils.notify("foo:"), + _log: { + trace() {}, + }, + + func() { + return this.notify("bar", "baz", async function () { + rightThis = this == obj; + didCall = true; + return 5; + })(); + }, + + throwy() { + return this.notify("bad", "one", async function () { + rightThis = this == obj; + didCall = true; + throw new Error("covfefe"); + })(); + }, + }; + + let state = 0; + let makeObs = function (topic) { + let obj2 = { + observe(subject, obsTopic, data) { + this.state = ++state; + this.subject = subject; + this.topic = obsTopic; + this.data = data; + }, + }; + + Svc.Obs.add(topic, obj2); + return obj2; + }; + + _("Make sure a normal call will call and return with notifications"); + rightThis = didCall = false; + let fs = makeObs("foo:bar:start"); + let ff = makeObs("foo:bar:finish"); + let fe = makeObs("foo:bar:error"); + ret = await obj.func(); + Assert.equal(ret, 5); + Assert.ok(rightThis); + Assert.ok(didCall); + + Assert.equal(fs.state, 1); + Assert.equal(fs.subject, undefined); + Assert.equal(fs.topic, "foo:bar:start"); + Assert.equal(fs.data, "baz"); + + Assert.equal(ff.state, 2); + Assert.equal(ff.subject, 5); + Assert.equal(ff.topic, "foo:bar:finish"); + Assert.equal(ff.data, "baz"); + + Assert.equal(fe.state, undefined); + Assert.equal(fe.subject, undefined); + Assert.equal(fe.topic, undefined); + Assert.equal(fe.data, undefined); + + _("Make sure a throwy call will call and throw with notifications"); + ret = null; + rightThis = didCall = false; + let ts = makeObs("foo:bad:start"); + let tf = makeObs("foo:bad:finish"); + let te = makeObs("foo:bad:error"); + try { + ret = await obj.throwy(); + do_throw("throwy should have thrown!"); + } catch (ex) { + Assert.equal(ex.message, "covfefe"); + } + Assert.equal(ret, null); + Assert.ok(rightThis); + Assert.ok(didCall); + + Assert.equal(ts.state, 3); + Assert.equal(ts.subject, undefined); + Assert.equal(ts.topic, "foo:bad:start"); + Assert.equal(ts.data, "one"); + + Assert.equal(tf.state, undefined); + Assert.equal(tf.subject, undefined); + Assert.equal(tf.topic, undefined); + Assert.equal(tf.data, undefined); + + Assert.equal(te.state, 4); + Assert.equal(te.subject.message, "covfefe"); + Assert.equal(te.topic, "foo:bad:error"); + Assert.equal(te.data, "one"); +}); diff --git a/services/sync/tests/unit/test_utils_passphrase.js b/services/sync/tests/unit/test_utils_passphrase.js new file mode 100644 index 0000000000..fa58086113 --- /dev/null +++ b/services/sync/tests/unit/test_utils_passphrase.js @@ -0,0 +1,45 @@ +/* eslint no-tabs:"off" */ + +function run_test() { + _("Normalize passphrase recognizes hyphens."); + const pp = "26ect2thczm599m2ffqarbicjq"; + const hyphenated = "2-6ect2-thczm-599m2-ffqar-bicjq"; + Assert.equal(Utils.normalizePassphrase(hyphenated), pp); + + _("Skip whitespace."); + Assert.equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaa", + Utils.normalizePassphrase("aaaaaaaaaaaaaaaaaaaaaaaaaa ") + ); + Assert.equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaa", + Utils.normalizePassphrase(" aaaaaaaaaaaaaaaaaaaaaaaaaa") + ); + Assert.equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaa", + Utils.normalizePassphrase(" aaaaaaaaaaaaaaaaaaaaaaaaaa ") + ); + Assert.equal( + "aaaaaaaaaaaaaaaaaaaaaaaaaa", + Utils.normalizePassphrase(" a-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa ") + ); + Assert.ok(Utils.isPassphrase("aaaaaaaaaaaaaaaaaaaaaaaaaa ")); + Assert.ok(Utils.isPassphrase(" aaaaaaaaaaaaaaaaaaaaaaaaaa")); + Assert.ok(Utils.isPassphrase(" aaaaaaaaaaaaaaaaaaaaaaaaaa ")); + Assert.ok(Utils.isPassphrase(" a-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa ")); + Assert.ok(!Utils.isPassphrase(" -aaaaa-aaaaa-aaaaa-aaaaa-aaaaa ")); + + _("Normalizing 20-char passphrases."); + Assert.equal( + Utils.normalizePassphrase("abcde-abcde-abcde-abcde"), + "abcdeabcdeabcdeabcde" + ); + Assert.equal( + Utils.normalizePassphrase("a-bcde-abcde-abcde-abcde"), + "a-bcde-abcde-abcde-abcde" + ); + Assert.equal( + Utils.normalizePassphrase(" abcde-abcde-abcde-abcde "), + "abcdeabcdeabcdeabcde" + ); +} diff --git a/services/sync/tests/unit/xpcshell.toml b/services/sync/tests/unit/xpcshell.toml new file mode 100644 index 0000000000..e958c8a738 --- /dev/null +++ b/services/sync/tests/unit/xpcshell.toml @@ -0,0 +1,304 @@ +[DEFAULT] +head = "head_appinfo.js ../../../common/tests/unit/head_helpers.js head_helpers.js head_http_server.js head_errorhandler_common.js" +firefox-appdir = "browser" +prefs = ["identity.fxaccounts.enabled=true"] +support-files = [ + "addon1-search.json", + "bootstrap1-search.json", + "missing-sourceuri.json", + "missing-xpi-search.json", + "rewrite-search.json", + "sync_ping_schema.json", + "systemaddon-search.json", + "!/services/common/tests/unit/head_helpers.js", + "!/toolkit/components/extensions/test/xpcshell/head_sync.js", +] + +# The manifest is roughly ordered from low-level to high-level. When making +# systemic sweeping changes, this makes it easier to identify errors closer to +# the source. + +# Ensure we can import everything. + +["test_412.js"] + +["test_addon_utils.js"] +run-sequentially = "Restarts server, can't change pref." +tags = "addons" + +["test_addons_engine.js"] +run-sequentially = "Frequent timeouts, bug 1395148" +tags = "addons" + +["test_addons_reconciler.js"] +skip-if = ["appname == 'thunderbird'"] +tags = "addons" + +["test_addons_store.js"] +run-sequentially = "Frequent timeouts, bug 1395148" +tags = "addons" + +["test_addons_tracker.js"] +tags = "addons" + +["test_addons_validator.js"] +tags = "addons" + +["test_bookmark_batch_fail.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_decline_undecline.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_engine.js"] +skip-if = [ + "appname == 'thunderbird'", + "tsan", # Runs unreasonably slow on TSan, bug 1612707 +] + +["test_bookmark_order.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_places_query_rewriting.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_record.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_store.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_bookmark_tracker.js"] +skip-if = [ + "appname == 'thunderbird'", + "tsan", # Runs unreasonably slow on TSan, bug 1612707 +] +requesttimeoutfactor = 4 + +["test_bridged_engine.js"] + +["test_clients_engine.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_clients_escape.js"] + +["test_collection_getBatched.js"] + +["test_collections_recovery.js"] + +["test_corrupt_keys.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_declined.js"] + +["test_disconnect_shutdown.js"] + +["test_engine.js"] + +["test_engine_abort.js"] + +["test_engine_changes_during_sync.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_enginemanager.js"] + +["test_errorhandler_1.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_errorhandler_2.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_errorhandler_filelog.js"] + +["test_errorhandler_sync_checkServerError.js"] + +["test_extension_storage_engine.js"] +skip-if = ["appname == 'thunderbird'"] +run-sequentially = "extension-storage migration happens only once, and must be tested first." + +["test_extension_storage_engine_kinto.js"] +skip-if = ["appname == 'thunderbird'"] +run-sequentially = "extension-storage migration happens only once, and must be tested first." + +["test_extension_storage_migration_telem.js"] +skip-if = ["appname == 'thunderbird'"] +run-sequentially = "extension-storage migration happens only once, and must be tested first." + +["test_extension_storage_tracker_kinto.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_form_validator.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_forms_store.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_forms_tracker.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_fxa_node_reassignment.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_fxa_service_cluster.js"] +# Finally, we test each engine. + +["test_history_engine.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_history_store.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_history_tracker.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_hmac_error.js"] + +["test_httpd_sync_server.js"] +# HTTP layers. + +["test_interval_triggers.js"] + +["test_keys.js"] + +["test_load_modules.js"] +# util contains a bunch of functionality used throughout. + +["test_node_reassignment.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_password_engine.js"] + +["test_password_store.js"] + +["test_password_tracker.js"] + +["test_password_validator.js"] + +["test_postqueue.js"] +# Synced tabs. + +["test_prefs_engine.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_prefs_store.js"] +skip-if = ["appname == 'thunderbird'"] +support-files = ["prefs_test_prefs_store.js"] + +["test_prefs_tracker.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_records_crypto.js"] + +["test_records_wbo.js"] + +["test_resource.js"] + +["test_resource_header.js"] + +["test_resource_ua.js"] +# Generic Sync types. + +["test_score_triggers.js"] + +["test_service_attributes.js"] +# Bug 752243: Profile cleanup frequently fails +skip-if = [ + "os == 'mac'", + "os == 'linux'", +] + +["test_service_cluster.js"] + +["test_service_detect_upgrade.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_service_login.js"] + +["test_service_startOver.js"] + +["test_service_startup.js"] + +["test_service_sync_401.js"] + +["test_service_sync_locked.js"] + +["test_service_sync_remoteSetup.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_service_sync_specified.js"] + +["test_service_sync_updateEnabledEngines.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_service_verifyLogin.js"] + +["test_service_wipeClient.js"] + +["test_service_wipeServer.js"] +# Bug 752243: Profile cleanup frequently fails +skip-if = [ + "os == 'mac'", + "os == 'linux'", +] + +["test_status.js"] + +["test_status_checkSetup.js"] + +["test_sync_auth_manager.js"] +# Engine APIs. + +["test_syncedtabs.js"] + +["test_syncengine.js"] + +["test_syncengine_sync.js"] +run-sequentially = "Frequent timeouts, bug 1395148" + +["test_syncscheduler.js"] +run-sequentially = "Frequent timeouts, bug 1395148" +# Firefox Accounts specific tests + +["test_tab_engine.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_tab_provider.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_tab_quickwrite.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_tab_tracker.js"] +skip-if = ["appname == 'thunderbird'"] + +["test_telemetry.js"] +skip-if = [ + "appname == 'thunderbird'", + "tsan", # Unreasonably slow, bug 1612707 +] +requesttimeoutfactor = 4 + +["test_tracker_addChanged.js"] +# Service semantics. + +["test_uistate.js"] + +["test_utils_catch.js"] + +["test_utils_deepEquals.js"] + +["test_utils_deferGetSet.js"] + +["test_utils_json.js"] + +["test_utils_keyEncoding.js"] + +["test_utils_lock.js"] + +["test_utils_makeGUID.js"] +run-sequentially = "Disproportionately slows down full test run, bug 1450316" + +["test_utils_notify.js"] + +["test_utils_passphrase.js"] +# We have a number of other libraries that are pretty much standalone. diff --git a/services/sync/tps/extensions/tps/api.js b/services/sync/tps/extensions/tps/api.js new file mode 100644 index 0000000000..0843376630 --- /dev/null +++ b/services/sync/tps/extensions/tps/api.js @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +/* globals ExtensionAPI, Services, XPCOMUtils */ + +XPCOMUtils.defineLazyServiceGetter( + this, + "resProto", + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler" +); + +async function tpsStartup() { + try { + var { TPS } = ChromeUtils.importESModule("resource://tps/tps.sys.mjs"); + let { goQuitApplication } = ChromeUtils.importESModule( + "resource://tps/quit.sys.mjs" + ); + TPS.goQuitApplication = goQuitApplication; + + let testFile = Services.prefs.getStringPref("testing.tps.testFile", ""); + let testPhase = Services.prefs.getStringPref("testing.tps.testPhase", ""); + if (!testFile || !testPhase) { + // Note: this quits. + TPS.DumpError( + "TPS no longer takes arguments from the command line. " + + "instead you need to pass preferences `testing.tps.{testFile,testPhase}` " + + "and optionally `testing.tps.{logFile,ignoreUnusedEngines}`.\n" + ); + } + + let logFile = Services.prefs.getStringPref("testing.tps.logFile", ""); + let ignoreUnusedEngines = Services.prefs.getBoolPref( + "testing.tps.ignoreUnusedEngines", + false + ); + let options = { ignoreUnusedEngines }; + let testFileUri = Services.io.newFileURI(new FileUtils.File(testFile)).spec; + + try { + await TPS.RunTestPhase(testFileUri, testPhase, logFile, options); + } catch (err) { + TPS.DumpError("TestPhase failed", err); + } + } catch (e) { + if (typeof TPS != "undefined") { + // Note: This calls quit() under the hood + TPS.DumpError("Test initialization failed", e); + } + dump(`TPS test initialization failed: ${e} - ${e.stack}\n`); + // Try and quit right away, no reason to wait around for python + // to kill us if initialization failed. + Services.startup.quit(Ci.nsIAppStartup.eForceQuit); + } +} + +this.tps = class extends ExtensionAPI { + onStartup() { + resProto.setSubstitution( + "tps", + Services.io.newURI("resource/", null, this.extension.rootURI) + ); + /* Ignore the platform's online/offline status while running tests. */ + Services.io.manageOfflineStatus = false; + Services.io.offline = false; + tpsStartup(); + } + + onShutdown() { + resProto.setSubstitution("tps", null); + } +}; diff --git a/services/sync/tps/extensions/tps/manifest.json b/services/sync/tps/extensions/tps/manifest.json new file mode 100644 index 0000000000..c961e76506 --- /dev/null +++ b/services/sync/tps/extensions/tps/manifest.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "TPS", + "version": "1.0", + + "browser_specific_settings": { + "gecko": { + "id": "tps@mozilla.org" + } + }, + + "experiment_apis": { + "tps": { + "schema": "schema.json", + "parent": { + "scopes": ["addon_parent"], + "script": "api.js", + "paths": [["tps"]], + "events": ["startup"] + } + } + } +} diff --git a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs new file mode 100644 index 0000000000..81c0fd578a --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.sys.mjs @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { Log } from "resource://gre/modules/Log.sys.mjs"; +import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; + +import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs"; + +const fxAccounts = getFxAccountsSingleton(); +import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs"; +import { FxAccountsConfig } from "resource://gre/modules/FxAccountsConfig.sys.mjs"; +import { Logger } from "resource://tps/logger.sys.mjs"; + +/** + * Helper object for Firefox Accounts authentication + */ +export var Authentication = { + /** + * Check if an user has been logged in + */ + async isLoggedIn() { + return !!(await this.getSignedInUser()); + }, + + async isReady() { + let user = await this.getSignedInUser(); + return user && user.verified; + }, + + _getRestmailUsername(user) { + const restmailSuffix = "@restmail.net"; + if (user.toLowerCase().endsWith(restmailSuffix)) { + return user.slice(0, -restmailSuffix.length); + } + return null; + }, + + async shortWaitForVerification(ms) { + let userData = await this.getSignedInUser(); + let timeoutID; + let timeoutPromise = new Promise(resolve => { + timeoutID = setTimeout(() => { + Logger.logInfo(`Warning: no verification after ${ms}ms.`); + resolve(); + }, ms); + }); + await Promise.race([ + fxAccounts.whenVerified(userData).finally(() => clearTimeout(timeoutID)), + timeoutPromise, + ]); + userData = await this.getSignedInUser(); + return userData && userData.verified; + }, + + async _openVerificationPage(uri) { + let mainWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let newtab = mainWindow.gBrowser.addWebTab(uri); + let win = mainWindow.gBrowser.getBrowserForTab(newtab); + await new Promise(resolve => { + win.addEventListener("loadend", resolve, { once: true }); + }); + let didVerify = await this.shortWaitForVerification(10000); + mainWindow.gBrowser.removeTab(newtab); + return didVerify; + }, + + async _completeVerification(user) { + let username = this._getRestmailUsername(user); + if (!username) { + Logger.logInfo( + `Username "${user}" isn't a restmail username so can't complete verification` + ); + return false; + } + Logger.logInfo("Fetching mail (from restmail) for user " + username); + let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent( + username + )}`; + let triedAlready = new Set(); + const tries = 10; + const normalWait = 2000; + for (let i = 0; i < tries; ++i) { + let resp = await fetch(restmailURI); + let messages = await resp.json(); + // Sort so that the most recent emails are first. + messages.sort((a, b) => new Date(b.receivedAt) - new Date(a.receivedAt)); + for (let m of messages) { + // We look for a link that has a x-link that we haven't yet tried. + if (!m.headers["x-link"] || triedAlready.has(m.headers["x-link"])) { + continue; + } + let confirmLink = m.headers["x-link"]; + triedAlready.add(confirmLink); + Logger.logInfo("Trying confirmation link " + confirmLink); + try { + if (await this._openVerificationPage(confirmLink)) { + return true; + } + } catch (e) { + Logger.logInfo( + "Warning: Failed to follow confirmation link: " + + Log.exceptionStr(e) + ); + } + } + if (i === 0) { + // first time through after failing we'll do this. + await fxAccounts.resendVerificationEmail(); + } + if (await this.shortWaitForVerification(normalWait)) { + return true; + } + } + // One last try. + return this.shortWaitForVerification(normalWait); + }, + + async deleteEmail(user) { + let username = this._getRestmailUsername(user); + if (!username) { + Logger.logInfo("Not a restmail username, can't delete"); + return false; + } + Logger.logInfo("Deleting mail (from restmail) for user " + username); + let restmailURI = `https://www.restmail.net/mail/${encodeURIComponent( + username + )}`; + try { + // Clean up after ourselves. + let deleteResult = await fetch(restmailURI, { method: "DELETE" }); + if (!deleteResult.ok) { + Logger.logInfo( + `Warning: Got non-success status ${deleteResult.status} when deleting emails` + ); + return false; + } + } catch (e) { + Logger.logInfo( + "Warning: Failed to delete old emails: " + Log.exceptionStr(e) + ); + return false; + } + return true; + }, + + /** + * Wrapper to retrieve the currently signed in user + * + * @returns Information about the currently signed in user + */ + async getSignedInUser() { + try { + return await fxAccounts.getSignedInUser(); + } catch (error) { + Logger.logError( + "getSignedInUser() failed with: " + JSON.stringify(error) + ); + throw error; + } + }, + + /** + * Wrapper to synchronize the login of a user + * + * @param account + * Account information of the user to login + * @param account.username + * The username for the account (utf8) + * @param account.password + * The user's password + */ + async signIn(account) { + Logger.AssertTrue(account.username, "Username has been found"); + Logger.AssertTrue(account.password, "Password has been found"); + + Logger.logInfo("Login user: " + account.username); + + try { + // Required here since we don't go through the real login page + await FxAccountsConfig.ensureConfigured(); + + let client = new FxAccountsClient(); + let credentials = await client.signIn( + account.username, + account.password, + true + ); + await fxAccounts._internal.setSignedInUser(credentials); + if (!credentials.verified) { + await this._completeVerification(account.username); + } + + return true; + } catch (error) { + throw new Error("signIn() failed with: " + error.message); + } + }, + + /** + * Sign out of Firefox Accounts. + */ + async signOut() { + if (await Authentication.isLoggedIn()) { + // Note: This will clean up the device ID. + await fxAccounts.signOut(); + } + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/logger.sys.mjs b/services/sync/tps/extensions/tps/resource/logger.sys.mjs new file mode 100644 index 0000000000..a1995f88b6 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/logger.sys.mjs @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + ChromeUtils.import() and acts as a singleton. + Only the following listed symbols will exposed on import, and only when + and where imported. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", +}); + +export var Logger = { + _foStream: null, + _converter: null, + _potentialError: null, + + init(path) { + if (this._converter != null) { + // we're already open! + return; + } + + if (path) { + Services.prefs.setStringPref("tps.logfile", path); + } else { + path = Services.prefs.getStringPref("tps.logfile"); + } + + this._file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + this._file.initWithPath(path); + var exists = this._file.exists(); + + // Make a file output stream and converter to handle it. + this._foStream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + // If the file already exists, append it, otherwise create it. + var fileflags = exists ? 0x02 | 0x08 | 0x10 : 0x02 | 0x08 | 0x20; + + this._foStream.init(this._file, fileflags, 0o666, 0); + this._converter = Cc[ + "@mozilla.org/intl/converter-output-stream;1" + ].createInstance(Ci.nsIConverterOutputStream); + this._converter.init(this._foStream, "UTF-8"); + }, + + write(data) { + if (this._converter == null) { + console.error("TPS Logger.write called with _converter == null!"); + return; + } + this._converter.writeString(data); + }, + + close() { + if (this._converter != null) { + this._converter.close(); + this._converter = null; + this._foStream = null; + } + }, + + AssertTrue(bool, msg, showPotentialError) { + if (bool) { + return; + } + + if (showPotentialError && this._potentialError) { + msg += "; " + this._potentialError; + this._potentialError = null; + } + throw new Error("ASSERTION FAILED! " + msg); + }, + + AssertFalse(bool, msg, showPotentialError) { + return this.AssertTrue(!bool, msg, showPotentialError); + }, + + AssertEqual(got, expected, msg) { + if (!lazy.ObjectUtils.deepEqual(got, expected)) { + throw new Error( + "ASSERTION FAILED! " + + msg + + "; expected " + + JSON.stringify(expected) + + ", got " + + JSON.stringify(got) + ); + } + }, + + log(msg, withoutPrefix) { + dump(msg + "\n"); + if (withoutPrefix) { + this.write(msg + "\n"); + } else { + function pad(n, len) { + let s = "0000" + n; + return s.slice(-len); + } + + let now = new Date(); + let year = pad(now.getFullYear(), 4); + let month = pad(now.getMonth() + 1, 2); + let day = pad(now.getDate(), 2); + let hour = pad(now.getHours(), 2); + let minutes = pad(now.getMinutes(), 2); + let seconds = pad(now.getSeconds(), 2); + let ms = pad(now.getMilliseconds(), 3); + + this.write( + year + + "-" + + month + + "-" + + day + + " " + + hour + + ":" + + minutes + + ":" + + seconds + + "." + + ms + + " " + + msg + + "\n" + ); + } + }, + + clearPotentialError() { + this._potentialError = null; + }, + + logPotentialError(msg) { + this._potentialError = msg; + }, + + logLastPotentialError(msg) { + var message = msg; + if (this._potentialError) { + message = this._poentialError; + this._potentialError = null; + } + this.log("CROSSWEAVE ERROR: " + message); + }, + + logError(msg) { + this.log("CROSSWEAVE ERROR: " + msg); + }, + + logInfo(msg, withoutPrefix) { + if (withoutPrefix) { + this.log(msg, true); + } else { + this.log("CROSSWEAVE INFO: " + msg); + } + }, + + logPass(msg) { + this.log("CROSSWEAVE TEST PASS: " + msg); + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/addons.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/addons.sys.mjs new file mode 100644 index 0000000000..596f942a06 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/addons.sys.mjs @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs"; +import { AddonUtils } from "resource://services-sync/addonutils.sys.mjs"; +import { Logger } from "resource://tps/logger.sys.mjs"; + +export const STATE_ENABLED = 1; +export const STATE_DISABLED = 2; + +export function Addon(TPS, id) { + this.TPS = TPS; + this.id = id; +} + +Addon.prototype = { + addon: null, + + async uninstall() { + // find our addon locally + let addon = await AddonManager.getAddonByID(this.id); + Logger.AssertTrue( + !!addon, + "could not find addon " + this.id + " to uninstall" + ); + await AddonUtils.uninstallAddon(addon); + }, + + async find(state) { + let addon = await AddonManager.getAddonByID(this.id); + + if (!addon) { + Logger.logInfo("Could not find add-on with ID: " + this.id); + return false; + } + + this.addon = addon; + + Logger.logInfo( + "add-on found: " + addon.id + ", enabled: " + !addon.userDisabled + ); + if (state == STATE_ENABLED) { + Logger.AssertFalse(addon.userDisabled, "add-on is disabled: " + addon.id); + return true; + } else if (state == STATE_DISABLED) { + Logger.AssertTrue(addon.userDisabled, "add-on is enabled: " + addon.id); + return true; + } else if (state) { + throw new Error("Don't know how to handle state: " + state); + } else { + // No state, so just checking that it exists. + return true; + } + }, + + async install() { + // For Install, the id parameter initially passed is really the filename + // for the addon's install .xml; we'll read the actual id from the .xml. + + const result = await AddonUtils.installAddons([ + { id: this.id, requireSecureURI: false }, + ]); + + Logger.AssertEqual( + 1, + result.installedIDs.length, + "Exactly 1 add-on was installed." + ); + Logger.AssertEqual( + this.id, + result.installedIDs[0], + "Add-on was installed successfully: " + this.id + ); + }, + + async setEnabled(flag) { + Logger.AssertTrue(await this.find(), "Add-on is available."); + + let userDisabled; + if (flag == STATE_ENABLED) { + userDisabled = false; + } else if (flag == STATE_DISABLED) { + userDisabled = true; + } else { + throw new Error("Unknown flag to setEnabled: " + flag); + } + + AddonUtils.updateUserDisabled(this.addon, userDisabled); + + return true; + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/bookmarkValidator.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/bookmarkValidator.sys.mjs new file mode 100644 index 0000000000..a7724c6aaa --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/bookmarkValidator.sys.mjs @@ -0,0 +1,1063 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file was moved to tps from the main production code as it was unused +// after removal of the non-mirror bookmarks engine. +// It used to have a test before it was moved: +// https://searchfox.org/mozilla-central/rev/b1a5802e0f73bfd6d2096e5fefc2b47831a50b2d/services/sync/tests/unit/test_bookmark_validator.js + +import { CommonUtils } from "resource://services-common/utils.sys.mjs"; +import { Utils } from "resource://services-sync/util.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Async: "resource://services-common/async.sys.mjs", + PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", +}); + +const QUERY_PROTOCOL = "place:"; + +function areURLsEqual(a, b) { + if (a === b) { + return true; + } + if (a.startsWith(QUERY_PROTOCOL) != b.startsWith(QUERY_PROTOCOL)) { + return false; + } + // Tag queries are special because we rewrite them to point to the + // local tag folder ID. It's expected that the folders won't match, + // but all other params should. + let aParams = new URLSearchParams(a.slice(QUERY_PROTOCOL.length)); + let aType = +aParams.get("type"); + if (aType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) { + return false; + } + let bParams = new URLSearchParams(b.slice(QUERY_PROTOCOL.length)); + let bType = +bParams.get("type"); + if (bType != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) { + return false; + } + let aKeys = new Set(aParams.keys()); + let bKeys = new Set(bParams.keys()); + if (aKeys.size != bKeys.size) { + return false; + } + // Tag queries shouldn't reference multiple folders, or named folders like + // "TOOLBAR" or "BOOKMARKS_MENU". Just in case, we make sure all folder IDs + // are numeric. If they are, we ignore them when comparing the query params. + if (aKeys.has("folder") && aParams.getAll("folder").every(isFinite)) { + aKeys.delete("folder"); + } + if (bKeys.has("folder") && bParams.getAll("folder").every(isFinite)) { + bKeys.delete("folder"); + } + for (let key of aKeys) { + if (!bKeys.has(key)) { + return false; + } + if ( + !CommonUtils.arrayEqual( + aParams.getAll(key).sort(), + bParams.getAll(key).sort() + ) + ) { + return false; + } + } + for (let key of bKeys) { + if (!aKeys.has(key)) { + return false; + } + } + return true; +} + +const BOOKMARK_VALIDATOR_VERSION = 1; + +/** + * Result of bookmark validation. Contains the following fields which describe + * server-side problems unless otherwise specified. + * + * - missingIDs (number): # of objects with missing ids + * - duplicates (array of ids): ids seen more than once + * - parentChildMismatches (array of {parent: parentid, child: childid}): + * instances where the child's parentid and the parent's children array + * do not match + * - cycles (array of array of ids). List of cycles found in the server-side tree. + * - clientCycles (array of array of ids). List of cycles found in the client-side tree. + * - orphans (array of {id: string, parent: string}): List of nodes with + * either no parentid, or where the parent could not be found. + * - missingChildren (array of {parent: id, child: id}): + * List of parent/children where the child id couldn't be found + * - deletedChildren (array of { parent: id, child: id }): + * List of parent/children where child id was a deleted item (but still showed up + * in the children array) + * - multipleParents (array of {child: id, parents: array of ids}): + * List of children that were part of multiple parent arrays + * - deletedParents (array of ids) : List of records that aren't deleted but + * had deleted parents + * - childrenOnNonFolder (array of ids): list of non-folders that still have + * children arrays + * - duplicateChildren (array of ids): list of records who have the same + * child listed multiple times in their children array + * - parentNotFolder (array of ids): list of records that have parents that + * aren't folders + * - rootOnServer (boolean): true if the root came from the server + * - badClientRoots (array of ids): Contains any client-side root ids where + * the root is missing or isn't a (direct) child of the places root. + * + * - clientMissing: Array of ids on the server missing from the client + * - serverMissing: Array of ids on the client missing from the server + * - serverDeleted: Array of ids on the client that the server had marked as deleted. + * - serverUnexpected: Array of ids that appear on the server but shouldn't + * because the client attempts to never upload them. + * - differences: Array of {id: string, differences: string array} recording + * the non-structural properties that are differente between the client and server + * - structuralDifferences: As above, but contains the items where the differences were + * structural, that is, they contained childGUIDs or parentid + */ +export class BookmarkProblemData { + constructor() { + this.rootOnServer = false; + this.missingIDs = 0; + + this.duplicates = []; + this.parentChildMismatches = []; + this.cycles = []; + this.clientCycles = []; + this.orphans = []; + this.missingChildren = []; + this.deletedChildren = []; + this.multipleParents = []; + this.deletedParents = []; + this.childrenOnNonFolder = []; + this.duplicateChildren = []; + this.parentNotFolder = []; + + this.badClientRoots = []; + this.clientMissing = []; + this.serverMissing = []; + this.serverDeleted = []; + this.serverUnexpected = []; + this.differences = []; + this.structuralDifferences = []; + } + + /** + * Convert ("difference", [{ differences: ["tags", "name"] }, { differences: ["name"] }]) into + * [{ name: "difference:tags", count: 1}, { name: "difference:name", count: 2 }], etc. + */ + _summarizeDifferences(prefix, diffs) { + let diffCounts = new Map(); + for (let { differences } of diffs) { + for (let type of differences) { + let name = prefix + ":" + type; + let count = diffCounts.get(name) || 0; + diffCounts.set(name, count + 1); + } + } + return [...diffCounts].map(([name, count]) => ({ name, count })); + } + + /** + * Produce a list summarizing problems found. Each entry contains {name, count}, + * where name is the field name for the problem, and count is the number of times + * the problem was encountered. + * + * Validation has failed if all counts are not 0. + * + * If the `full` argument is truthy, we also include information about which + * properties we saw structural differences in. Currently, this means either + * "sdiff:parentid" and "sdiff:childGUIDS" may be present. + */ + getSummary(full) { + let result = [ + { name: "clientMissing", count: this.clientMissing.length }, + { name: "serverMissing", count: this.serverMissing.length }, + { name: "serverDeleted", count: this.serverDeleted.length }, + { name: "serverUnexpected", count: this.serverUnexpected.length }, + + { + name: "structuralDifferences", + count: this.structuralDifferences.length, + }, + { name: "differences", count: this.differences.length }, + + { name: "missingIDs", count: this.missingIDs }, + { name: "rootOnServer", count: this.rootOnServer ? 1 : 0 }, + + { name: "duplicates", count: this.duplicates.length }, + { + name: "parentChildMismatches", + count: this.parentChildMismatches.length, + }, + { name: "cycles", count: this.cycles.length }, + { name: "clientCycles", count: this.clientCycles.length }, + { name: "badClientRoots", count: this.badClientRoots.length }, + { name: "orphans", count: this.orphans.length }, + { name: "missingChildren", count: this.missingChildren.length }, + { name: "deletedChildren", count: this.deletedChildren.length }, + { name: "multipleParents", count: this.multipleParents.length }, + { name: "deletedParents", count: this.deletedParents.length }, + { name: "childrenOnNonFolder", count: this.childrenOnNonFolder.length }, + { name: "duplicateChildren", count: this.duplicateChildren.length }, + { name: "parentNotFolder", count: this.parentNotFolder.length }, + ]; + if (full) { + let structural = this._summarizeDifferences( + "sdiff", + this.structuralDifferences + ); + result.push.apply(result, structural); + } + return result; + } +} + +// Defined lazily to avoid initializing PlacesUtils.bookmarks too soon. +ChromeUtils.defineLazyGetter(lazy, "SYNCED_ROOTS", () => [ + lazy.PlacesUtils.bookmarks.menuGuid, + lazy.PlacesUtils.bookmarks.toolbarGuid, + lazy.PlacesUtils.bookmarks.unfiledGuid, + lazy.PlacesUtils.bookmarks.mobileGuid, +]); + +// Maps root GUIDs to their query folder names from +// toolkit/components/places/nsNavHistoryQuery.cpp. We follow queries that +// reference existing folders in the client tree, and detect cycles where a +// query references its containing folder. +ChromeUtils.defineLazyGetter(lazy, "ROOT_GUID_TO_QUERY_FOLDER_NAME", () => ({ + [lazy.PlacesUtils.bookmarks.rootGuid]: "PLACES_ROOT", + [lazy.PlacesUtils.bookmarks.menuGuid]: "BOOKMARKS_MENU", + + // Tags should never show up in our client tree, and never form cycles, but we + // report them just in case. + [lazy.PlacesUtils.bookmarks.tagsGuid]: "TAGS", + + [lazy.PlacesUtils.bookmarks.unfiledGuid]: "UNFILED_BOOKMARKS", + [lazy.PlacesUtils.bookmarks.toolbarGuid]: "TOOLBAR", + [lazy.PlacesUtils.bookmarks.mobileGuid]: "MOBILE_BOOKMARKS", +})); + +async function detectCycles(records) { + // currentPath and pathLookup contain the same data. pathLookup is faster to + // query, but currentPath gives is the order of traversal that we need in + // order to report the members of the cycles. + let pathLookup = new Set(); + let currentPath = []; + let cycles = []; + let seenEver = new Set(); + const yieldState = lazy.Async.yieldState(); + + const traverse = async node => { + if (pathLookup.has(node)) { + let cycleStart = currentPath.lastIndexOf(node); + let cyclePath = currentPath.slice(cycleStart).map(n => n.id); + cycles.push(cyclePath); + return; + } else if (seenEver.has(node)) { + // If we're checking the server, this is a problem, but it should already be reported. + // On the client, this could happen due to including `node.concrete` in the child list. + return; + } + seenEver.add(node); + let children = node.children || []; + if (node.concreteItems) { + children.push(...node.concreteItems); + } + if (children.length) { + pathLookup.add(node); + currentPath.push(node); + await lazy.Async.yieldingForEach(children, traverse, yieldState); + currentPath.pop(); + pathLookup.delete(node); + } + }; + + await lazy.Async.yieldingForEach( + records, + async record => { + if (!seenEver.has(record)) { + await traverse(record); + } + }, + yieldState + ); + + return cycles; +} + +class ServerRecordInspection { + constructor() { + this.serverRecords = null; + this.liveRecords = []; + + this.folders = []; + + this.root = null; + + this.idToRecord = new Map(); + + this.deletedIds = new Set(); + this.deletedRecords = []; + + this.problemData = new BookmarkProblemData(); + + // These are handled outside of problemData + this._orphans = new Map(); + this._multipleParents = new Map(); + + this.yieldState = lazy.Async.yieldState(); + } + + static async create(records) { + return new ServerRecordInspection().performInspection(records); + } + + async performInspection(records) { + await this._setRecords(records); + await this._linkParentIDs(); + await this._linkChildren(); + await this._findOrphans(); + await this._finish(); + return this; + } + + // We don't set orphans in this.problemData. Instead, we walk the tree at the + // end to find unreachable items. + _noteOrphan(id, parentId = undefined) { + // This probably shouldn't be called with a parentId twice, but if it + // happens we take the most recent one. + if (parentId || !this._orphans.has(id)) { + this._orphans.set(id, parentId); + } + } + + noteParent(child, parent) { + let parents = this._multipleParents.get(child); + if (!parents) { + this._multipleParents.set(child, [parent]); + } else { + parents.push(parent); + } + } + + noteMismatch(child, parent) { + let exists = this.problemData.parentChildMismatches.some( + match => match.child == child && match.parent == parent + ); + if (!exists) { + this.problemData.parentChildMismatches.push({ child, parent }); + } + } + + // - Populates `this.deletedIds`, `this.folders`, and `this.idToRecord` + // - calls `_initRoot` (thus initializing `this.root`). + async _setRecords(records) { + if (this.serverRecords) { + // In general this class is expected to be created, have + // `performInspection` called, and then only read from from that point on. + throw new Error("Bug: ServerRecordInspection can't `setRecords` twice"); + } + this.serverRecords = records; + let rootChildren = []; + + await lazy.Async.yieldingForEach( + this.serverRecords, + async record => { + if (!record.id) { + ++this.problemData.missingIDs; + return; + } + + if (record.deleted) { + this.deletedIds.add(record.id); + } + if (this.idToRecord.has(record.id)) { + this.problemData.duplicates.push(record.id); + return; + } + + this.idToRecord.set(record.id, record); + + if (!record.deleted) { + this.liveRecords.push(record); + + if (record.parentid == "places") { + rootChildren.push(record); + } + } + + if (!record.children) { + return; + } + + if (record.type != "folder") { + // Due to implementation details in engines/bookmarks.js, (Livemark + // subclassing BookmarkFolder) Livemarks will have a children array, + // but it should still be empty. + if (!record.children.length) { + return; + } + // Otherwise we mark it as an error and still try to resolve the children + this.problemData.childrenOnNonFolder.push(record.id); + } + + this.folders.push(record); + + if (new Set(record.children).size !== record.children.length) { + this.problemData.duplicateChildren.push(record.id); + } + + // After we're through with them, folder records store 3 (ugh) arrays that + // represent their folder information. The final fields looks like: + // + // - childGUIDs: The original `children` array, which is an array of + // record IDs. + // + // - unfilteredChildren: Contains more or less `childGUIDs.map(id => + // idToRecord.get(id))`, without the nulls for missing children. It will + // still have deleted, duplicate, mismatching, etc. children. + // + // - children: This is the 'cleaned' version of the child records that are + // safe to iterate over, etc.. If there are no reported problems, it should + // be identical to unfilteredChildren. + // + // The last two are left alone until later `this._linkChildren`, however. + record.childGUIDs = record.children; + + await lazy.Async.yieldingForEach( + record.childGUIDs, + id => { + this.noteParent(id, record.id); + }, + this.yieldState + ); + + record.children = []; + }, + this.yieldState + ); + + // Finish up some parts we can easily do now that we have idToRecord. + this.deletedRecords = Array.from(this.deletedIds, id => + this.idToRecord.get(id) + ); + + this._initRoot(rootChildren); + } + + _initRoot(rootChildren) { + let serverRoot = this.idToRecord.get("places"); + if (serverRoot) { + this.root = serverRoot; + this.problemData.rootOnServer = true; + return; + } + + // Fabricate a root. We want to be able to remember that it's fake, but + // would like to avoid it needing too much special casing, so we come up + // with children for it too (we just get these while we're iterating over + // the records to avoid needing two passes over a potentially large number + // of records). + + this.root = { + id: "places", + fake: true, + children: rootChildren, + childGUIDs: rootChildren.map(record => record.id), + type: "folder", + title: "", + }; + this.liveRecords.push(this.root); + this.idToRecord.set("places", this.root); + } + + // Adds `parent` to all records it can that have `parentid` + async _linkParentIDs() { + await lazy.Async.yieldingForEach( + this.idToRecord, + ([id, record]) => { + if (record == this.root || record.deleted) { + return false; + } + + // Check and update our orphan map. + let parentID = record.parentid; + let parent = this.idToRecord.get(parentID); + if (!parentID || !parent) { + this._noteOrphan(id, parentID); + return false; + } + + record.parent = parent; + + if (parent.deleted) { + this.problemData.deletedParents.push(id); + return true; + } else if (parent.type != "folder") { + this.problemData.parentNotFolder.push(record.id); + return true; + } + + if (parent.id !== "place" || this.problemData.rootOnServer) { + if (!parent.childGUIDs.includes(record.id)) { + this.noteMismatch(record.id, parent.id); + } + } + + if (parent.deleted && !record.deleted) { + this.problemData.deletedParents.push(record.id); + } + + // Note: We used to check if the parentName on the server matches the + // actual local parent name, but given this is used only for de-duping a + // record the first time it is seen and expensive to keep up-to-date, we + // decided to just stop recording it. See bug 1276969 for more. + return false; + }, + this.yieldState + ); + } + + // Build the children and unfilteredChildren arrays, (which are of record + // objects, not ids) + async _linkChildren() { + // Check that we aren't missing any children. + await lazy.Async.yieldingForEach( + this.folders, + async folder => { + folder.children = []; + folder.unfilteredChildren = []; + + let idsThisFolder = new Set(); + + await lazy.Async.yieldingForEach( + folder.childGUIDs, + childID => { + let child = this.idToRecord.get(childID); + + if (!child) { + this.problemData.missingChildren.push({ + parent: folder.id, + child: childID, + }); + return; + } + + if (child.deleted) { + this.problemData.deletedChildren.push({ + parent: folder.id, + child: childID, + }); + return; + } + + if (child.parentid != folder.id) { + this.noteMismatch(childID, folder.id); + return; + } + + if (idsThisFolder.has(childID)) { + // Already recorded earlier, we just don't want to mess up `children` + return; + } + folder.children.push(child); + }, + this.yieldState + ); + }, + this.yieldState + ); + } + + // Finds the orphans in the tree using something similar to a `mark and sweep` + // strategy. That is, we iterate over the children from the root, remembering + // which items we've seen. Then, we iterate all items, and know the ones we + // haven't seen are orphans. + async _findOrphans() { + let seen = new Set([this.root.id]); + + const inCycle = await lazy.Async.yieldingForEach( + Utils.walkTree(this.root), + ([node]) => { + if (seen.has(node.id)) { + // We're in an infloop due to a cycle. + // Return early to avoid reporting false positives for orphans. + return true; + } + seen.add(node.id); + + return false; + }, + this.yieldState + ); + + if (inCycle) { + return; + } + + await lazy.Async.yieldingForEach( + this.liveRecords, + (record, i) => { + if (!seen.has(record.id)) { + // We intentionally don't record the parentid here, since we only record + // that if the record refers to a parent that doesn't exist, which we + // have already handled (when linking parentid's). + this._noteOrphan(record.id); + } + }, + this.yieldState + ); + + await lazy.Async.yieldingForEach( + this._orphans, + ([id, parent]) => { + this.problemData.orphans.push({ id, parent }); + }, + this.yieldState + ); + } + + async _finish() { + this.problemData.cycles = await detectCycles(this.liveRecords); + + for (const [child, recordedParents] of this._multipleParents) { + let parents = new Set(recordedParents); + if (parents.size > 1) { + this.problemData.multipleParents.push({ child, parents: [...parents] }); + } + } + // Dedupe simple arrays in the problem data, so that we don't have to worry + // about it in the code + const idArrayProps = [ + "duplicates", + "deletedParents", + "childrenOnNonFolder", + "duplicateChildren", + "parentNotFolder", + ]; + for (let prop of idArrayProps) { + this.problemData[prop] = [...new Set(this.problemData[prop])]; + } + } +} + +export class BookmarkValidator { + constructor() { + this.yieldState = lazy.Async.yieldState(); + } + + async canValidate() { + return !(await lazy.PlacesSyncUtils.bookmarks.havePendingChanges()); + } + + async _followQueries(recordsByQueryId) { + await lazy.Async.yieldingForEach( + recordsByQueryId.values(), + entry => { + if ( + entry.type !== "query" && + (!entry.bmkUri || !entry.bmkUri.startsWith(QUERY_PROTOCOL)) + ) { + return; + } + let params = new URLSearchParams( + entry.bmkUri.slice(QUERY_PROTOCOL.length) + ); + // Queries with `excludeQueries` won't form cycles because they'll + // exclude all queries, including themselves, from the result set. + let excludeQueries = params.get("excludeQueries"); + if (excludeQueries === "1" || excludeQueries === "true") { + // `nsNavHistoryQuery::ParseQueryBooleanString` allows `1` and `true`. + return; + } + entry.concreteItems = []; + let queryIds = params.getAll("folder"); + for (let queryId of queryIds) { + let concreteItem = recordsByQueryId.get(queryId); + if (concreteItem) { + entry.concreteItems.push(concreteItem); + } + } + }, + this.yieldState + ); + } + + async createClientRecordsFromTree(clientTree) { + // Iterate over the treeNode, converting it to something more similar to what + // the server stores. + let records = []; + // A map of local IDs and well-known query folder names to records. Unlike + // GUIDs, local IDs aren't synced, since they're not stable across devices. + // New Places APIs use GUIDs to refer to bookmarks, but the legacy APIs + // still use local IDs. We use this mapping to parse `place:` queries that + // refer to folders via their local IDs. + let recordsByQueryId = new Map(); + let syncedRoots = lazy.SYNCED_ROOTS; + + const traverse = async (treeNode, synced) => { + if (!synced) { + synced = syncedRoots.includes(treeNode.guid); + } + let localId = treeNode.id; + let guid = lazy.PlacesSyncUtils.bookmarks.guidToRecordId(treeNode.guid); + let itemType = "item"; + treeNode.ignored = !synced; + treeNode.id = guid; + switch (treeNode.type) { + case lazy.PlacesUtils.TYPE_X_MOZ_PLACE: + if (treeNode.uri.startsWith(QUERY_PROTOCOL)) { + itemType = "query"; + } else { + itemType = "bookmark"; + } + break; + case lazy.PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: + let isLivemark = false; + if (treeNode.annos) { + for (let anno of treeNode.annos) { + if (anno.name === lazy.PlacesUtils.LMANNO_FEEDURI) { + isLivemark = true; + treeNode.feedUri = anno.value; + } else if (anno.name === lazy.PlacesUtils.LMANNO_SITEURI) { + isLivemark = true; + treeNode.siteUri = anno.value; + } + } + } + itemType = isLivemark ? "livemark" : "folder"; + break; + case lazy.PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: + itemType = "separator"; + break; + } + + if (treeNode.tags) { + treeNode.tags = treeNode.tags.split(","); + } else { + treeNode.tags = []; + } + treeNode.type = itemType; + treeNode.pos = treeNode.index; + treeNode.bmkUri = treeNode.uri; + records.push(treeNode); + if (treeNode.guid in lazy.ROOT_GUID_TO_QUERY_FOLDER_NAME) { + let queryId = lazy.ROOT_GUID_TO_QUERY_FOLDER_NAME[treeNode.guid]; + recordsByQueryId.set(queryId, treeNode); + } + if (localId) { + // Always add the ID, since it's still possible for a query to + // reference a root without using the well-known name. For example, + // `place:folder=${PlacesUtils.mobileFolderId}` and + // `place:folder=MOBILE_BOOKMARKS` are equivalent. + recordsByQueryId.set(localId.toString(10), treeNode); + } + if (treeNode.type === "folder") { + treeNode.childGUIDs = []; + if (!treeNode.children) { + treeNode.children = []; + } + + await lazy.Async.yieldingForEach( + treeNode.children, + async child => { + await traverse(child, synced); + child.parent = treeNode; + child.parentid = guid; + treeNode.childGUIDs.push(child.guid); + }, + this.yieldState + ); + } + }; + + await traverse(clientTree, false); + + clientTree.id = "places"; + await this._followQueries(recordsByQueryId); + return records; + } + + /** + * Process the server-side list. Mainly this builds the records into a tree, + * but it also records information about problems, and produces arrays of the + * deleted and non-deleted nodes. + * + * Returns an object containing: + * - records:Array of non-deleted records. Each record contains the following + * properties + * - childGUIDs (array of strings, only present if type is 'folder'): the + * list of child GUIDs stored on the server. + * - children (array of records, only present if type is 'folder'): + * each record has these same properties. This may differ in content + * from what you may expect from the childGUIDs list, as it won't + * contain any records that could not be found. + * - parent (record): The parent to this record. + * - Unchanged properties send down from the server: id, title, type, + * parentName, parentid, bmkURI, keyword, tags, pos, queryId + * - root: Root of the server-side bookmark tree. Has the same properties as + * above. + * - deletedRecords: As above, but only contains items that the server sent + * where it also sent indication that the item should be deleted. + * - problemData: a BookmarkProblemData object, with the caveat that + * the fields describing client/server relationship will not have been filled + * out yet. + */ + async inspectServerRecords(serverRecords) { + const data = await ServerRecordInspection.create(serverRecords); + return { + deletedRecords: data.deletedRecords, + records: data.liveRecords, + problemData: data.problemData, + root: data.root, + }; + } + + // Perform client-side sanity checking that doesn't involve server data + async _validateClient(problemData, clientRecords) { + problemData.clientCycles = await detectCycles(clientRecords); + for (let rootGUID of lazy.SYNCED_ROOTS) { + let record = clientRecords.find(record => record.guid === rootGUID); + if (!record || record.parentid !== "places") { + problemData.badClientRoots.push(rootGUID); + } + } + } + + async _computeUnifiedRecordMap(serverRecords, clientRecords) { + let allRecords = new Map(); + await lazy.Async.yieldingForEach( + serverRecords, + sr => { + if (sr.fake) { + return; + } + allRecords.set(sr.id, { client: null, server: sr }); + }, + this.yieldState + ); + + await lazy.Async.yieldingForEach( + clientRecords, + cr => { + let unified = allRecords.get(cr.id); + if (!unified) { + allRecords.set(cr.id, { client: cr, server: null }); + } else { + unified.client = cr; + } + }, + this.yieldState + ); + + return allRecords; + } + + _recordMissing(problems, id, clientRecord, serverRecord, serverTombstones) { + if (!clientRecord && serverRecord) { + problems.clientMissing.push(id); + } + if (!serverRecord && clientRecord) { + if (serverTombstones.has(id)) { + problems.serverDeleted.push(id); + } else if (!clientRecord.ignored && clientRecord.id != "places") { + problems.serverMissing.push(id); + } + } + } + + _compareRecords(client, server) { + let structuralDifferences = []; + let differences = []; + + // Don't bother comparing titles of roots. It's okay if locally it's + // "Mobile Bookmarks", but the server thinks it's "mobile". + // TODO: We probably should be handing other localized bookmarks (e.g. + // default bookmarks) here as well, see bug 1316041. + if (!lazy.SYNCED_ROOTS.includes(client.guid)) { + // We want to treat undefined, null and an empty string as identical + if ((client.title || "") !== (server.title || "")) { + differences.push("title"); + } + } + + if (client.parentid || server.parentid) { + if (client.parentid !== server.parentid) { + structuralDifferences.push("parentid"); + } + } + + if (client.tags || server.tags) { + let cl = client.tags ? [...client.tags].sort() : []; + let sl = server.tags ? [...server.tags].sort() : []; + if (!CommonUtils.arrayEqual(cl, sl)) { + differences.push("tags"); + } + } + + let sameType = client.type === server.type; + if (!sameType) { + if ( + server.type === "query" && + client.type === "bookmark" && + client.bmkUri.startsWith(QUERY_PROTOCOL) + ) { + sameType = true; + } + } + + if (!sameType) { + differences.push("type"); + } else { + switch (server.type) { + case "bookmark": + case "query": + if (!areURLsEqual(server.bmkUri, client.bmkUri)) { + differences.push("bmkUri"); + } + break; + case "separator": + if (server.pos != client.pos) { + differences.push("pos"); + } + break; + case "livemark": + if (server.feedUri != client.feedUri) { + differences.push("feedUri"); + } + if (server.siteUri != client.siteUri) { + differences.push("siteUri"); + } + break; + case "folder": + if (server.id === "places" && server.fake) { + // It's the fabricated places root. It won't have the GUIDs, but + // it doesn't matter. + break; + } + if (client.childGUIDs || server.childGUIDs) { + let cl = client.childGUIDs || []; + let sl = server.childGUIDs || []; + if (!CommonUtils.arrayEqual(cl, sl)) { + structuralDifferences.push("childGUIDs"); + } + } + break; + } + } + return { differences, structuralDifferences }; + } + + /** + * Compare the list of server records with the client tree. + * + * Returns the same data as described in the inspectServerRecords comment, + * with the following additional fields. + * - clientRecords: an array of client records in a similar format to + * the .records (ie, server records) entry. + * - problemData is the same as for inspectServerRecords, except all properties + * will be filled out. + */ + async compareServerWithClient(serverRecords, clientTree) { + let clientRecords = await this.createClientRecordsFromTree(clientTree); + let inspectionInfo = await this.inspectServerRecords(serverRecords); + inspectionInfo.clientRecords = clientRecords; + + // Mainly do this to remove deleted items and normalize child guids. + serverRecords = inspectionInfo.records; + let problemData = inspectionInfo.problemData; + + await this._validateClient(problemData, clientRecords); + + let allRecords = await this._computeUnifiedRecordMap( + serverRecords, + clientRecords + ); + + let serverDeleted = new Set(inspectionInfo.deletedRecords.map(r => r.id)); + + await lazy.Async.yieldingForEach( + allRecords, + ([id, { client, server }]) => { + if (!client || !server) { + this._recordMissing(problemData, id, client, server, serverDeleted); + return; + } + if (server && client && client.ignored) { + problemData.serverUnexpected.push(id); + } + let { differences, structuralDifferences } = this._compareRecords( + client, + server + ); + + if (differences.length) { + problemData.differences.push({ id, differences }); + } + if (structuralDifferences.length) { + problemData.structuralDifferences.push({ + id, + differences: structuralDifferences, + }); + } + }, + this.yieldState + ); + + return inspectionInfo; + } + + async _getServerState(engine) { + let collection = engine.itemSource(); + let collectionKey = engine.service.collectionKeys.keyForCollection( + engine.name + ); + collection.full = true; + let result = await collection.getBatched(); + if (!result.response.success) { + throw result.response; + } + let cleartexts = []; + await lazy.Async.yieldingForEach( + result.records, + async record => { + await record.decrypt(collectionKey); + cleartexts.push(record.cleartext); + }, + this.yieldState + ); + return cleartexts; + } + + async validate(engine) { + let start = Date.now(); + let clientTree = await lazy.PlacesUtils.promiseBookmarksTree("", { + includeItemIds: true, + }); + let serverState = await this._getServerState(engine); + let serverRecordCount = serverState.length; + let result = await this.compareServerWithClient(serverState, clientTree); + let end = Date.now(); + let duration = end - start; + + engine._log.debug(`Validated bookmarks in ${duration}ms`); + engine._log.debug(`Problem summary`); + for (let { name, count } of result.problemData.getSummary()) { + engine._log.debug(` ${name}: ${count}`); + } + + return { + duration, + version: this.version, + problems: result.problemData, + recordCount: serverRecordCount, + }; + } +} + +BookmarkValidator.prototype.version = BOOKMARK_VALIDATOR_VERSION; diff --git a/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs new file mode 100644 index 0000000000..e4aac948b5 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/bookmarks.sys.mjs @@ -0,0 +1,833 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { PlacesBackups } from "resource://gre/modules/PlacesBackups.sys.mjs"; + +import { PlacesSyncUtils } from "resource://gre/modules/PlacesSyncUtils.sys.mjs"; +import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs"; + +import { Logger } from "resource://tps/logger.sys.mjs"; + +export async function DumpBookmarks() { + let [bookmarks] = await PlacesBackups.getBookmarksTree(); + Logger.logInfo( + "Dumping Bookmarks...\n" + JSON.stringify(bookmarks, undefined, 2) + "\n\n" + ); +} + +/** + * extend, causes a child object to inherit from a parent + */ +function extend(child, supertype) { + Object.setPrototypeOf(child.prototype, supertype.prototype); +} +/** + * PlacesItemProps object, holds properties for places items + */ +function PlacesItemProps(props) { + this.location = null; + this.uri = null; + this.keyword = null; + this.title = null; + this.after = null; + this.before = null; + this.folder = null; + this.position = null; + this.delete = false; + this.tags = null; + this.last_item_pos = null; + this.type = null; + + for (var prop in props) { + if (prop in this) { + this[prop] = props[prop]; + } + } +} + +/** + * PlacesItem object. Base class for places items. + */ +export function PlacesItem(props) { + this.props = new PlacesItemProps(props); + if (this.props.location == null) { + this.props.location = "menu"; + } + if ("changes" in props) { + this.updateProps = new PlacesItemProps(props.changes); + } else { + this.updateProps = null; + } +} + +/** + * Instance methods for generic places items. + */ +PlacesItem.prototype = { + // an array of possible root folders for places items + _bookmarkFolders: { + places: PlacesUtils.bookmarks.rootGuid, + menu: PlacesUtils.bookmarks.menuGuid, + tags: PlacesUtils.bookmarks.tagsGuid, + unfiled: PlacesUtils.bookmarks.unfiledGuid, + toolbar: PlacesUtils.bookmarks.toolbarGuid, + mobile: PlacesUtils.bookmarks.mobileGuid, + }, + + _typeMap: new Map([ + [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, PlacesUtils.bookmarks.TYPE_FOLDER], + [ + PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR, + PlacesUtils.bookmarks.TYPE_SEPARATOR, + ], + [PlacesUtils.TYPE_X_MOZ_PLACE, PlacesUtils.bookmarks.TYPE_BOOKMARK], + ]), + + toString() { + var that = this; + var props = ["uri", "title", "location", "folder"]; + var string = + (this.props.type ? this.props.type + " " : "") + + "(" + + (function () { + var ret = []; + for (var i in props) { + if (that.props[props[i]]) { + ret.push(props[i] + ": " + that.props[props[i]]); + } + } + return ret; + })().join(", ") + + ")"; + return string; + }, + + /** + * GetPlacesChildGuid + * + * Finds the guid of the an item with the specified properties in the places + * database under the specified parent. + * + * @param folder The guid of the folder to search + * @param type The type of the item to find, or null to match any item; + * this is one of the PlacesUtils.bookmarks.TYPE_* values + * @param title The title of the item to find, or null to match any title + * @param uri The uri of the item to find, or null to match any uri + * + * @return the node id if the item was found, otherwise null + */ + async GetPlacesChildGuid(folder, type, title, uri) { + let children = (await PlacesUtils.promiseBookmarksTree(folder)).children; + if (!children) { + return null; + } + let guid = null; + for (let node of children) { + if (node.title == title) { + let nodeType = this._typeMap.get(node.type); + if (type == null || type == undefined || nodeType == type) { + if (uri == undefined || uri == null || node.uri.spec == uri.spec) { + // Note that this is suspect as we return the *last* matching + // child, which some tests rely on (ie, an early-return here causes + // at least 1 test to fail). But that's a yak for another day. + guid = node.guid; + } + } + } + } + return guid; + }, + + /** + * IsAdjacentTo + * + * Determines if this object is immediately adjacent to another. + * + * @param itemName The name of the other object; this may be any kind of + * places item + * @param relativePos The relative position of the other object. If -1, + * it means the other object should precede this one, if +1, + * the other object should come after this one + * @return true if this object is immediately adjacent to the other object, + * otherwise false + */ + async IsAdjacentTo(itemName, relativePos) { + Logger.AssertTrue( + this.props.folder_id != -1 && this.props.guid != null, + "Either folder_id or guid was invalid" + ); + let otherGuid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + itemName + ); + Logger.AssertTrue(otherGuid, "item " + itemName + " not found"); + let other_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index; + let this_pos = (await PlacesUtils.bookmarks.fetch(this.props.guid)).index; + if (other_pos + relativePos != this_pos) { + Logger.logPotentialError( + "Invalid position - " + + (this.props.title ? this.props.title : this.props.folder) + + " not " + + (relativePos == 1 ? "after " : "before ") + + itemName + + " for " + + this.toString() + ); + return false; + } + return true; + }, + + /** + * GetItemIndex + * + * Gets the item index for this places item. + * + * @return the item index, or -1 if there's an error + */ + async GetItemIndex() { + if (this.props.guid == null) { + return -1; + } + return (await PlacesUtils.bookmarks.fetch(this.props.guid)).index; + }, + + /** + * GetFolder + * + * Gets the folder guid for the specified bookmark folder + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder guid if the folder is found, otherwise null + */ + async GetFolder(location) { + let folder_parts = location.split("/"); + if (!(folder_parts[0] in this._bookmarkFolders)) { + return null; + } + let folderGuid = this._bookmarkFolders[folder_parts[0]]; + for (let i = 1; i < folder_parts.length; i++) { + let guid = await this.GetPlacesChildGuid( + folderGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + folder_parts[i] + ); + if (guid == null) { + return null; + } + folderGuid = guid; + } + return folderGuid; + }, + + /** + * CreateFolder + * + * Creates a bookmark folder. + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder id if the folder was created, otherwise -1 + */ + async CreateFolder(location) { + let folder_parts = location.split("/"); + if (!(folder_parts[0] in this._bookmarkFolders)) { + return -1; + } + let folderGuid = this._bookmarkFolders[folder_parts[0]]; + for (let i = 1; i < folder_parts.length; i++) { + let subfolderGuid = await this.GetPlacesChildGuid( + folderGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + folder_parts[i] + ); + if (subfolderGuid == null) { + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + name: folder_parts[i], + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + folderGuid = guid; + } else { + folderGuid = subfolderGuid; + } + } + return folderGuid; + }, + + /** + * GetOrCreateFolder + * + * Locates the specified folder; if not found it is created. + * + * @param location The full path of the folder, which must begin + * with one of the bookmark root folders + * @return the folder id if the folder was found or created, otherwise -1 + */ + async GetOrCreateFolder(location) { + let parentGuid = await this.GetFolder(location); + if (parentGuid == null) { + parentGuid = await this.CreateFolder(location); + } + return parentGuid; + }, + + /** + * CheckPosition + * + * Verifies the position of this places item. + * + * @param before The name of the places item that this item should be + before, or null if this check should be skipped + * @param after The name of the places item that this item should be + after, or null if this check should be skipped + * @param last_item_pos The index of the places item above this one, + * or null if this check should be skipped + * @return true if this item is in the correct position, otherwise false + */ + async CheckPosition(before, after, last_item_pos) { + if (after) { + if (!(await this.IsAdjacentTo(after, 1))) { + return false; + } + } + if (before) { + if (!(await this.IsAdjacentTo(before, -1))) { + return false; + } + } + if (last_item_pos != null && last_item_pos > -1) { + let index = await this.GetItemIndex(); + if (index != last_item_pos + 1) { + Logger.logPotentialError( + "Item not found at the expected index, got " + + index + + ", expected " + + (last_item_pos + 1) + + " for " + + this.toString() + ); + return false; + } + } + return true; + }, + + /** + * SetLocation + * + * Moves this places item to a different folder. + * + * @param location The full path of the folder to which to move this + * places item, which must begin with one of the bookmark root + * folders; if null, no changes are made + * @return nothing if successful, otherwise an exception is thrown + */ + async SetLocation(location) { + if (location != null) { + let newfolderGuid = await this.GetOrCreateFolder(location); + Logger.AssertTrue( + newfolderGuid, + "Location " + location + " doesn't exist; can't change item's location" + ); + await PlacesUtils.bookmarks.update({ + guid: this.props.guid, + parentGuid: newfolderGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + }); + this.props.parentGuid = newfolderGuid; + } + }, + + /** + * SetPosition + * + * Updates the position of this places item within this item's current + * folder. Use SetLocation to change folders. + * + * @param position The new index this item should be moved to; if null, + * no changes are made; if -1, this item is moved to the bottom of + * the current folder. Otherwise, must be a string which is the + * title of an existing item in the folder, who's current position + * is used as the index. + * @return nothing if successful, otherwise an exception is thrown + */ + async SetPosition(position) { + if (position == null) { + return; + } + let index = -1; + if (position != -1) { + let existingGuid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + position + ); + if (existingGuid) { + index = (await PlacesUtils.bookmarks.fetch(existingGuid)).index; + } + Logger.AssertTrue( + index != -1, + "position " + position + " is invalid; unable to change position" + ); + } + await PlacesUtils.bookmarks.update({ guid: this.props.guid, index }); + }, + + /** + * Update the title of this places item + * + * @param title The new title to set for this item; if null, no changes + * are made + * @return nothing + */ + async SetTitle(title) { + if (title != null) { + await PlacesUtils.bookmarks.update({ guid: this.props.guid, title }); + } + }, +}; + +/** + * Bookmark class constructor. Initializes instance properties. + */ +export function Bookmark(props) { + PlacesItem.call(this, props); + if (this.props.title == null) { + this.props.title = this.props.uri; + } + this.props.type = "bookmark"; +} + +/** + * Bookmark instance methods. + */ +Bookmark.prototype = { + /** + * SetKeyword + * + * Update this bookmark's keyword. + * + * @param keyword The keyword to set for this bookmark; if null, no + * changes are made + * @return nothing + */ + async SetKeyword(keyword) { + if (keyword != null) { + // Mirror logic from PlacesSyncUtils's updateBookmarkMetadata + let entry = await PlacesUtils.keywords.fetch({ url: this.props.uri }); + if (entry) { + await PlacesUtils.keywords.remove(entry); + } + await PlacesUtils.keywords.insert({ keyword, url: this.props.uri }); + } + }, + + /** + * SetUri + * + * Updates this bookmark's URI. + * + * @param uri The new URI to set for this boomark; if null, no changes + * are made + * @return nothing + */ + async SetUri(uri) { + if (uri) { + let url = Services.io.newURI(uri); + await PlacesUtils.bookmarks.update({ guid: this.props.guid, url }); + } + }, + + /** + * SetTags + * + * Updates this bookmark's tags. + * + * @param tags An array of tags which should be associated with this + * bookmark; any previous tags are removed; if this param is null, + * no changes are made. If this param is an empty array, all + * tags are removed from this bookmark. + * @return nothing + */ + SetTags(tags) { + if (tags != null) { + let URI = Services.io.newURI(this.props.uri); + PlacesUtils.tagging.untagURI(URI, null); + if (tags.length) { + PlacesUtils.tagging.tagURI(URI, tags); + } + } + }, + + /** + * Create + * + * Creates the bookmark described by this object's properties. + * + * @return the id of the created bookmark + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "bookmark, error creating folder " + + this.props.location + ); + let bookmarkURI = Services.io.newURI(this.props.uri); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + url: bookmarkURI, + title: this.props.title, + }); + this.props.guid = guid; + await this.SetKeyword(this.props.keyword); + await this.SetTags(this.props.tags); + return this.props.guid; + }, + + /** + * Update + * + * Updates this bookmark's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetTitle(this.updateProps.title); + await this.SetUri(this.updateProps.uri); + await this.SetKeyword(this.updateProps.keyword); + await this.SetTags(this.updateProps.tags); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + }, + + /** + * Find + * + * Locates the bookmark which corresponds to this object's properties. + * + * @return the bookmark guid if the bookmark was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + let bookmarkTitle = this.props.title; + this.props.guid = await this.GetPlacesChildGuid( + this.props.parentGuid, + null, + bookmarkTitle, + this.props.uri + ); + + if (!this.props.guid) { + Logger.logPotentialError(this.toString() + " not found"); + return null; + } + if (this.props.keyword != null) { + let { keyword } = await PlacesSyncUtils.bookmarks.fetch(this.props.guid); + if (keyword != this.props.keyword) { + Logger.logPotentialError( + "Incorrect keyword - expected: " + + this.props.keyword + + ", actual: " + + keyword + + " for " + + this.toString() + ); + return null; + } + } + if (this.props.tags != null) { + try { + let URI = Services.io.newURI(this.props.uri); + let tags = PlacesUtils.tagging.getTagsForURI(URI); + tags.sort(); + this.props.tags.sort(); + if (JSON.stringify(tags) != JSON.stringify(this.props.tags)) { + Logger.logPotentialError( + "Wrong tags - expected: " + + JSON.stringify(this.props.tags) + + ", actual: " + + JSON.stringify(tags) + + " for " + + this.toString() + ); + return null; + } + } catch (e) { + Logger.logPotentialError("error processing tags " + e); + return null; + } + } + if ( + !(await this.CheckPosition( + this.props.before, + this.props.after, + this.props.last_item_pos + )) + ) { + return null; + } + return this.props.guid; + }, + + /** + * Remove + * + * Removes this bookmark. The bookmark should have been located previously + * by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Remove"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, +}; + +extend(Bookmark, PlacesItem); + +/** + * BookmarkFolder class constructor. Initializes instance properties. + */ +export function BookmarkFolder(props) { + PlacesItem.call(this, props); + this.props.type = "folder"; +} + +/** + * BookmarkFolder instance methods + */ +BookmarkFolder.prototype = { + /** + * Create + * + * Creates the bookmark folder described by this object's properties. + * + * @return the id of the created bookmark folder + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "folder, error creating parent folder " + + this.props.location + ); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + title: this.props.folder, + index: PlacesUtils.bookmarks.DEFAULT_INDEX, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + this.props.guid = guid; + return this.props.parentGuid; + }, + + /** + * Find + * + * Locates the bookmark folder which corresponds to this object's + * properties. + * + * @return the folder guid if the folder was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + this.props.guid = await this.GetPlacesChildGuid( + this.props.parentGuid, + PlacesUtils.bookmarks.TYPE_FOLDER, + this.props.folder + ); + if (this.props.guid == null) { + return null; + } + if ( + !(await this.CheckPosition( + this.props.before, + this.props.after, + this.props.last_item_pos + )) + ) { + return null; + } + return this.props.guid; + }, + + /** + * Remove + * + * Removes this folder. The folder should have been located previously + * by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Remove"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, + + /** + * Update + * + * Updates this bookmark's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + await this.SetTitle(this.updateProps.folder); + }, +}; + +extend(BookmarkFolder, PlacesItem); + +/** + * Separator class constructor. Initializes instance properties. + */ +export function Separator(props) { + PlacesItem.call(this, props); + this.props.type = "separator"; +} + +/** + * Separator instance methods. + */ +Separator.prototype = { + /** + * Create + * + * Creates the bookmark separator described by this object's properties. + * + * @return the id of the created separator + */ + async Create() { + this.props.parentGuid = await this.GetOrCreateFolder(this.props.location); + Logger.AssertTrue( + this.props.parentGuid, + "Unable to create " + + "folder, error creating parent folder " + + this.props.location + ); + let { guid } = await PlacesUtils.bookmarks.insert({ + parentGuid: this.props.parentGuid, + type: PlacesUtils.bookmarks.TYPE_SEPARATOR, + }); + this.props.guid = guid; + return guid; + }, + + /** + * Find + * + * Locates the bookmark separator which corresponds to this object's + * properties. + * + * @return the item guid if the separator was found, otherwise null + */ + async Find() { + this.props.parentGuid = await this.GetFolder(this.props.location); + if (this.props.parentGuid == null) { + Logger.logError("Unable to find folder " + this.props.location); + return null; + } + if (this.props.before == null && this.props.last_item_pos == null) { + Logger.logPotentialError( + "Separator requires 'before' attribute if it's the" + + "first item in the list" + ); + return null; + } + let expected_pos = -1; + if (this.props.before) { + let otherGuid = this.GetPlacesChildGuid( + this.props.parentGuid, + null, + this.props.before + ); + if (otherGuid == null) { + Logger.logPotentialError( + "Can't find places item " + + this.props.before + + " for locating separator" + ); + return null; + } + expected_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index - 1; + } else { + expected_pos = this.props.last_item_pos + 1; + } + // Note these are IDs instead of GUIDs. + let children = await PlacesSyncUtils.bookmarks.fetchChildRecordIds( + this.props.parentGuid + ); + this.props.guid = children[expected_pos]; + if (this.props.guid == null) { + Logger.logPotentialError( + "No separator found at position " + expected_pos + ); + return null; + } + let info = await PlacesUtils.bookmarks.fetch(this.props.guid); + if (info.type != PlacesUtils.bookmarks.TYPE_SEPARATOR) { + Logger.logPotentialError( + "Places item at position " + expected_pos + " is not a separator" + ); + return null; + } + return this.props.guid; + }, + + /** + * Update + * + * Updates this separator's properties according the properties on this + * object's 'updateProps' property. + * + * @return nothing + */ + async Update() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await this.SetLocation(this.updateProps.location); + await this.SetPosition(this.updateProps.position); + return true; + }, + + /** + * Remove + * + * Removes this separator. The separator should have been located + * previously by a call to Find. + * + * @return nothing + */ + async Remove() { + Logger.AssertTrue(this.props.guid, "Invalid guid during Update"); + await PlacesUtils.bookmarks.remove(this.props.guid); + }, +}; + +extend(Separator, PlacesItem); diff --git a/services/sync/tps/extensions/tps/resource/modules/formautofill.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/formautofill.sys.mjs new file mode 100644 index 0000000000..587d7668f4 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/formautofill.sys.mjs @@ -0,0 +1,128 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { Logger } from "resource://tps/logger.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs", + formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs", +}); + +class FormAutofillBase { + constructor(props, subStorageName, fields) { + this._subStorageName = subStorageName; + this._fields = fields; + + this.props = {}; + this.updateProps = null; + if ("changes" in props) { + this.updateProps = props.changes; + } + for (const field of this._fields) { + this.props[field] = field in props ? props[field] : null; + } + } + + async getStorage() { + await lazy.formAutofillStorage.initialize(); + return lazy.formAutofillStorage[this._subStorageName]; + } + + async Create() { + const storage = await this.getStorage(); + await storage.add(this.props); + } + + async Find() { + const storage = await this.getStorage(); + return storage._data.find(entry => + this._fields.every(field => entry[field] === this.props[field]) + ); + } + + async Update() { + const storage = await this.getStorage(); + const { guid } = await this.Find(); + await storage.update(guid, this.updateProps, true); + } + + async Remove() { + const storage = await this.getStorage(); + const { guid } = await this.Find(); + storage.remove(guid); + } +} + +async function DumpStorage(subStorageName) { + await lazy.formAutofillStorage.initialize(); + Logger.logInfo(`\ndumping ${subStorageName} list\n`, true); + const entries = lazy.formAutofillStorage[subStorageName]._data; + for (const entry of entries) { + Logger.logInfo(JSON.stringify(entry), true); + } + Logger.logInfo(`\n\nend ${subStorageName} list\n`, true); +} + +const ADDRESS_FIELDS = [ + "given-name", + "additional-name", + "family-name", + "organization", + "street-address", + "address-level2", + "address-level1", + "postal-code", + "country", + "tel", + "email", +]; + +export class Address extends FormAutofillBase { + constructor(props) { + super(props, "addresses", ADDRESS_FIELDS); + } +} + +export async function DumpAddresses() { + await DumpStorage("addresses"); +} + +const CREDIT_CARD_FIELDS = [ + "cc-name", + "cc-number", + "cc-exp-month", + "cc-exp-year", +]; + +export class CreditCard extends FormAutofillBase { + constructor(props) { + super(props, "creditCards", CREDIT_CARD_FIELDS); + } + + async Find() { + const storage = await this.getStorage(); + await Promise.all( + storage._data.map( + async entry => + (entry["cc-number"] = await lazy.OSKeyStore.decrypt( + entry["cc-number-encrypted"] + )) + ) + ); + return storage._data.find(entry => { + return this._fields.every(field => entry[field] === this.props[field]); + }); + } +} + +export async function DumpCreditCards() { + await DumpStorage("creditCards"); +} diff --git a/services/sync/tps/extensions/tps/resource/modules/forms.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/forms.sys.mjs new file mode 100644 index 0000000000..35b5f5c03b --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/forms.sys.mjs @@ -0,0 +1,205 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + ChromeUtils.import() and acts as a singleton. Only the following + listed symbols will exposed on import, and only when and where imported. + */ + +import { Logger } from "resource://tps/logger.sys.mjs"; + +import { FormHistory } from "resource://gre/modules/FormHistory.sys.mjs"; + +/** + * FormDB + * + * Helper object containing methods to interact with the FormHistory module. + */ +var FormDB = { + async _update(data) { + await FormHistory.update(data); + }, + + /** + * insertValue + * + * Adds the specified value for the specified fieldname into form history. + * + * @param fieldname The form fieldname to insert + * @param value The form value to insert + * @param us The time, in microseconds, to use for the lastUsed + * and firstUsed columns + * @return Promise<undefined> + */ + insertValue(fieldname, value, us) { + let data = { + op: "add", + fieldname, + value, + timesUsed: 1, + firstUsed: us, + lastUsed: us, + }; + return this._update(data); + }, + + /** + * updateValue + * + * Updates a row in the moz_formhistory table with a new value. + * + * @param id The id of the row to update + * @param newvalue The new value to set + * @return Promise<undefined> + */ + updateValue(id, newvalue) { + return this._update({ op: "update", guid: id, value: newvalue }); + }, + + /** + * getDataForValue + * + * Retrieves a set of values for a row in the database that + * corresponds to the given fieldname and value. + * + * @param fieldname The fieldname of the row to query + * @param value The value of the row to query + * @return Promise<null if no row is found with the specified fieldname and value, + * or an object containing the row's guid, lastUsed, and firstUsed + * values> + */ + async getDataForValue(fieldname, value) { + let results = await FormHistory.search(["guid", "lastUsed", "firstUsed"], { + fieldname, + value, + }); + if (results.length > 1) { + throw new Error("more than 1 result for this query"); + } + return results; + }, + + /** + * remove + * + * Removes the specified GUID from the database. + * + * @param guid The guid of the item to delete + * @return Promise<> + */ + remove(guid) { + return this._update({ op: "remove", guid }); + }, +}; + +/** + * FormData class constructor + * + * Initializes instance properties. + */ +export function FormData(props, msSinceEpoch) { + this.fieldname = null; + this.value = null; + this.date = 0; + this.newvalue = null; + this.usSinceEpoch = msSinceEpoch * 1000; + + for (var prop in props) { + if (prop in this) { + this[prop] = props[prop]; + } + } +} + +/** + * FormData instance methods + */ +FormData.prototype = { + /** + * hours_to_us + * + * Converts hours since present to microseconds since epoch. + * + * @param hours The number of hours since the present time (e.g., 0 is + * 'now', and -1 is 1 hour ago) + * @return the corresponding number of microseconds since the epoch + */ + hours_to_us(hours) { + return this.usSinceEpoch + hours * 60 * 60 * 1000 * 1000; + }, + + /** + * Create + * + * If this FormData object doesn't exist in the moz_formhistory database, + * add it. Throws on error. + * + * @return nothing + */ + Create() { + Logger.AssertTrue( + this.fieldname != null && this.value != null, + "Must specify both fieldname and value" + ); + + return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => { + if (!formdata) { + // this item doesn't exist yet in the db, so we need to insert it + return FormDB.insertValue( + this.fieldname, + this.value, + this.hours_to_us(this.date) + ); + } + /* Right now, we ignore this case. If bug 552531 is ever fixed, + we might need to add code here to update the firstUsed or + lastUsed fields, as appropriate. + */ + return null; + }); + }, + + /** + * Find + * + * Attempts to locate an entry in the moz_formhistory database that + * matches the fieldname and value for this FormData object. + * + * @return true if this entry exists in the database, otherwise false + */ + Find() { + return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => { + let status = formdata != null; + if (status) { + /* + //form history dates currently not synced! bug 552531 + let us = this.hours_to_us(this.date); + status = Logger.AssertTrue( + us >= formdata.firstUsed && us <= formdata.lastUsed, + "No match for with that date value"); + + if (status) + */ + this.id = formdata.guid; + } + return status; + }); + }, + + /** + * Remove + * + * Removes the row represented by this FormData instance from the + * moz_formhistory database. + * + * @return nothing + */ + async Remove() { + const formdata = await FormDB.getDataForValue(this.fieldname, this.value); + if (!formdata) { + return; + } + await FormDB.remove(formdata.guid); + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/history.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/history.sys.mjs new file mode 100644 index 0000000000..845bab3aa9 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/history.sys.mjs @@ -0,0 +1,158 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs"; + +import { PlacesSyncUtils } from "resource://gre/modules/PlacesSyncUtils.sys.mjs"; + +import { Logger } from "resource://tps/logger.sys.mjs"; + +export var DumpHistory = async function TPS_History__DumpHistory() { + let query = PlacesUtils.history.getNewQuery(); + let options = PlacesUtils.history.getNewQueryOptions(); + let root = PlacesUtils.history.executeQuery(query, options).root; + root.containerOpen = true; + Logger.logInfo("\n\ndumping history\n", true); + for (var i = 0; i < root.childCount; i++) { + let node = root.getChild(i); + let uri = node.uri; + let guid = await PlacesSyncUtils.history + .fetchGuidForURL(uri) + .catch(() => "?".repeat(12)); + let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(uri); + for (var visit of curvisits) { + Logger.logInfo( + `GUID: ${guid}, URI: ${uri}, type=${visit.type}, date=${visit.date}`, + true + ); + } + } + root.containerOpen = false; + Logger.logInfo("\nend history dump\n", true); +}; + +/** + * HistoryEntry object + * + * Contains methods for manipulating browser history entries. + */ +export var HistoryEntry = { + /** + * Add + * + * Adds visits for a uri to the history database. Throws on error. + * + * @param item An object representing one or more visits to a specific uri + * @param usSinceEpoch The number of microseconds from Epoch to + * the time the current Crossweave run was started + * @return nothing + */ + async Add(item, msSinceEpoch) { + Logger.AssertTrue( + "visits" in item && "uri" in item, + "History entry in test file must have both 'visits' " + + "and 'uri' properties" + ); + let place = { + url: item.uri, + visits: [], + }; + for (let visit of item.visits) { + let date = new Date( + Math.round(msSinceEpoch + visit.date * 60 * 60 * 1000) + ); + place.visits.push({ date, transition: visit.type }); + } + if ("title" in item) { + place.title = item.title; + } + return PlacesUtils.history.insert(place); + }, + + /** + * Find + * + * Finds visits for a uri to the history database. Throws on error. + * + * @param item An object representing one or more visits to a specific uri + * @param usSinceEpoch The number of microseconds from Epoch to + * the time the current Crossweave run was started + * @return true if all the visits for the uri are found, otherwise false + */ + async Find(item, msSinceEpoch) { + Logger.AssertTrue( + "visits" in item && "uri" in item, + "History entry in test file must have both 'visits' " + + "and 'uri' properties" + ); + let curvisits = await PlacesSyncUtils.history.fetchVisitsForURL(item.uri); + for (let visit of curvisits) { + for (let itemvisit of item.visits) { + // Note: in microseconds. + let expectedDate = + itemvisit.date * 60 * 60 * 1000 * 1000 + msSinceEpoch * 1000; + if (visit.type == itemvisit.type) { + if (itemvisit.date === undefined || visit.date == expectedDate) { + itemvisit.found = true; + } + } + } + } + + let all_items_found = true; + for (let itemvisit of item.visits) { + all_items_found = all_items_found && "found" in itemvisit; + Logger.logInfo( + `History entry for ${item.uri}, type: ${itemvisit.type}, date: ${itemvisit.date}` + + `(${ + itemvisit.date * 60 * 60 * 1000 * 1000 + }), found = ${!!itemvisit.found}` + ); + } + return all_items_found; + }, + + /** + * Delete + * + * Removes visits from the history database. Throws on error. + * + * @param item An object representing items to delete + * @param usSinceEpoch The number of microseconds from Epoch to + * the time the current Crossweave run was started + * @return nothing + */ + async Delete(item, msSinceEpoch) { + if ("uri" in item) { + let removedAny = await PlacesUtils.history.remove(item.uri); + if (!removedAny) { + Logger.log("Warning: Removed 0 history visits for uri " + item.uri); + } + } else if ("host" in item) { + await PlacesUtils.history.removeByFilter({ host: item.host }); + } else if ("begin" in item && "end" in item) { + let filter = { + beginDate: new Date(msSinceEpoch + item.begin * 60 * 60 * 1000), + endDate: new Date(msSinceEpoch + item.end * 60 * 60 * 1000), + }; + let removedAny = await PlacesUtils.history.removeVisitsByFilter(filter); + if (!removedAny) { + Logger.log( + "Warning: Removed 0 history visits with " + + JSON.stringify({ item, filter }) + ); + } + } else { + Logger.AssertTrue( + false, + "invalid entry in delete history " + JSON.stringify(item) + ); + } + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/passwords.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/passwords.sys.mjs new file mode 100644 index 0000000000..976755e989 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/passwords.sys.mjs @@ -0,0 +1,187 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { Logger } from "resource://tps/logger.sys.mjs"; + +var nsLoginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" +); + +export var DumpPasswords = async function TPS__Passwords__DumpPasswords() { + let logins = await Services.logins.getAllLogins(); + Logger.logInfo("\ndumping password list\n", true); + for (var i = 0; i < logins.length; i++) { + Logger.logInfo( + "* origin=" + + logins[i].origin + + ", formActionOrigin=" + + logins[i].formActionOrigin + + ", realm=" + + logins[i].httpRealm + + ", password=" + + logins[i].password + + ", passwordField=" + + logins[i].passwordField + + ", username=" + + logins[i].username + + ", usernameField=" + + logins[i].usernameField, + true + ); + } + Logger.logInfo("\n\nend password list\n", true); +}; + +/** + * PasswordProps object; holds password properties. + */ +function PasswordProps(props) { + this.hostname = null; + this.submitURL = null; + this.realm = null; + this.username = ""; + this.password = ""; + this.usernameField = ""; + this.passwordField = ""; + this.delete = false; + + for (var prop in props) { + if (prop in this) { + this[prop] = props[prop]; + } + } +} + +/** + * Password class constructor. Initializes instance properties. + */ +export function Password(props) { + this.props = new PasswordProps(props); + if ("changes" in props) { + this.updateProps = new PasswordProps(props); + for (var prop in props.changes) { + if (prop in this.updateProps) { + this.updateProps[prop] = props.changes[prop]; + } + } + } else { + this.updateProps = null; + } +} + +/** + * Password instance methods. + */ +Password.prototype = { + /** + * Create + * + * Adds a password entry to the login manager for the password + * represented by this object's properties. Throws on error. + * + * @return the new login guid + */ + async Create() { + let login = new nsLoginInfo( + this.props.hostname, + this.props.submitURL, + this.props.realm, + this.props.username, + this.props.password, + this.props.usernameField, + this.props.passwordField + ); + await Services.logins.addLoginAsync(login); + login.QueryInterface(Ci.nsILoginMetaInfo); + return login.guid; + }, + + /** + * Find + * + * Finds a password entry in the login manager, for the password + * represented by this object's properties. + * + * @return the guid of the password if found, otherwise -1 + */ + async Find() { + let logins = await Services.logins.searchLoginsAsync({ + origin: this.props.hostname, + formActionOrigin: this.props.submitURL, + httpRealm: this.props.realm, + }); + for (var i = 0; i < logins.length; i++) { + if ( + logins[i].username == this.props.username && + logins[i].password == this.props.password && + logins[i].usernameField == this.props.usernameField && + logins[i].passwordField == this.props.passwordField + ) { + logins[i].QueryInterface(Ci.nsILoginMetaInfo); + return logins[i].guid; + } + } + return -1; + }, + + /** + * Update + * + * Updates an existing password entry in the login manager with + * new properties. Throws on error. The 'old' properties are this + * object's properties, the 'new' properties are the properties in + * this object's 'updateProps' object. + * + * @return nothing + */ + Update() { + let oldlogin = new nsLoginInfo( + this.props.hostname, + this.props.submitURL, + this.props.realm, + this.props.username, + this.props.password, + this.props.usernameField, + this.props.passwordField + ); + let newlogin = new nsLoginInfo( + this.updateProps.hostname, + this.updateProps.submitURL, + this.updateProps.realm, + this.updateProps.username, + this.updateProps.password, + this.updateProps.usernameField, + this.updateProps.passwordField + ); + Services.logins.modifyLogin(oldlogin, newlogin); + }, + + /** + * Remove + * + * Removes an entry from the login manager for a password which + * matches this object's properties. Throws on error. + * + * @return nothing + */ + Remove() { + let login = new nsLoginInfo( + this.props.hostname, + this.props.submitURL, + this.props.realm, + this.props.username, + this.props.password, + this.props.usernameField, + this.props.passwordField + ); + Services.logins.removeLogin(login); + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/prefs.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/prefs.sys.mjs new file mode 100644 index 0000000000..c629ef4a73 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/prefs.sys.mjs @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + ChromeUtils.import() and acts as a singleton. + Only the following listed symbols will exposed on import, and only when + and where imported. */ + +const WEAVE_PREF_PREFIX = "services.sync.prefs.sync."; + +import { Logger } from "resource://tps/logger.sys.mjs"; + +/** + * Preference class constructor + * + * Initializes instance properties. + */ +export function Preference(props) { + Logger.AssertTrue( + "name" in props && "value" in props, + "Preference must have both name and value" + ); + + this.name = props.name; + this.value = props.value; +} + +/** + * Preference instance methods + */ +Preference.prototype = { + /** + * Modify + * + * Sets the value of the preference this.name to this.value. + * Throws on error. + * + * @return nothing + */ + Modify() { + // Determine if this pref is actually something Weave even looks at. + let weavepref = WEAVE_PREF_PREFIX + this.name; + try { + let syncPref = Services.prefs.getBoolPref(weavepref); + if (!syncPref) { + Services.prefs.setBoolPref(weavepref, true); + } + } catch (e) { + Logger.AssertTrue(false, "Weave doesn't sync pref " + this.name); + } + + // Modify the pref; throw an exception if the pref type is different + // than the value type specified in the test. + let prefType = Services.prefs.getPrefType(this.name); + switch (prefType) { + case Ci.nsIPrefBranch.PREF_INT: + Logger.AssertEqual( + typeof this.value, + "number", + "Wrong type used for preference value" + ); + Services.prefs.setIntPref(this.name, this.value); + break; + case Ci.nsIPrefBranch.PREF_STRING: + Logger.AssertEqual( + typeof this.value, + "string", + "Wrong type used for preference value" + ); + Services.prefs.setStringPref(this.name, this.value); + break; + case Ci.nsIPrefBranch.PREF_BOOL: + Logger.AssertEqual( + typeof this.value, + "boolean", + "Wrong type used for preference value" + ); + Services.prefs.setBoolPref(this.name, this.value); + break; + } + }, + + /** + * Find + * + * Verifies that the preference this.name has the value + * this.value. Throws on error, or if the pref's type or value + * doesn't match. + * + * @return nothing + */ + Find() { + // Read the pref value. + let value; + try { + let prefType = Services.prefs.getPrefType(this.name); + switch (prefType) { + case Ci.nsIPrefBranch.PREF_INT: + value = Services.prefs.getIntPref(this.name); + break; + case Ci.nsIPrefBranch.PREF_STRING: + value = Services.prefs.getStringPref(this.name); + break; + case Ci.nsIPrefBranch.PREF_BOOL: + value = Services.prefs.getBoolPref(this.name); + break; + } + } catch (e) { + Logger.AssertTrue(false, "Error accessing pref " + this.name); + } + + // Throw an exception if the current and expected values aren't of + // the same type, or don't have the same values. + Logger.AssertEqual( + typeof value, + typeof this.value, + "Value types don't match" + ); + Logger.AssertEqual(value, this.value, "Preference values don't match"); + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/tabs.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/tabs.sys.mjs new file mode 100644 index 0000000000..8ea8f3b780 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/tabs.sys.mjs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + ChromeUtils.import() and acts as a singleton. + Only the following listed symbols will exposed on import, and only when + and where imported. */ + +import { Weave } from "resource://services-sync/main.sys.mjs"; + +import { Logger } from "resource://tps/logger.sys.mjs"; + +// Unfortunately, due to where TPS is run, we can't directly reuse the logic from +// BrowserTestUtils.sys.mjs. Moreover, we can't resolve the URI it loads the content +// frame script from ("chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js"), +// hence the hackiness here and in BrowserTabs.Add. +Services.mm.loadFrameScript( + "data:application/javascript;charset=utf-8," + + encodeURIComponent(` + addEventListener("load", function(event) { + let subframe = event.target != content.document; + sendAsyncMessage("tps:loadEvent", {subframe: subframe, url: event.target.documentURI}); + }, true)`), + true, + true +); + +export var BrowserTabs = { + /** + * Add + * + * Opens a new tab in the current browser window for the + * given uri. Rejects on error. + * + * @param uri The uri to load in the new tab + * @return Promise + */ + async Add(uri) { + let mainWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let browser = mainWindow.gBrowser; + let newtab = browser.addTrustedTab(uri); + + // Wait for the tab to load. + await new Promise(resolve => { + let mm = browser.ownerGlobal.messageManager; + mm.addMessageListener("tps:loadEvent", function onLoad(msg) { + mm.removeMessageListener("tps:loadEvent", onLoad); + resolve(); + }); + }); + + browser.selectedTab = newtab; + }, + + /** + * Find + * + * Finds the specified uri and title in Weave's list of remote tabs + * for the specified profile. + * + * @param uri The uri of the tab to find + * @param title The page title of the tab to find + * @param profile The profile to search for tabs + * @return true if the specified tab could be found, otherwise false + */ + async Find(uri, title, profile) { + // Find the uri in Weave's list of tabs for the given profile. + let tabEngine = Weave.Service.engineManager.get("tabs"); + for (let client of Weave.Service.clientsEngine.remoteClients) { + let tabClients = await tabEngine.getAllClients(); + let tabClient = tabClients.find(x => x.id === client.id); + if (!tabClient || !tabClient.tabs) { + continue; + } + for (let key in tabClient.tabs) { + let tab = tabClient.tabs[key]; + let weaveTabUrl = tab.urlHistory[0]; + if (uri == weaveTabUrl && profile == client.name) { + if (title == undefined || title == tab.title) { + return true; + } + } + } + Logger.logInfo( + `Dumping tabs for ${client.name}...\n` + + JSON.stringify(tabClient.tabs, null, 2) + ); + } + return false; + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/modules/windows.sys.mjs b/services/sync/tps/extensions/tps/resource/modules/windows.sys.mjs new file mode 100644 index 0000000000..b0798b9031 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/modules/windows.sys.mjs @@ -0,0 +1,32 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + ChromeUtils.import() and acts as a singleton. + Only the following listed symbols will exposed on import, and only when + and where imported. */ + +export var BrowserWindows = { + /** + * Add + * + * Opens a new window. Throws on error. + * + * @param aPrivate The private option. + * @return nothing + */ + Add(aPrivate, fn) { + return new Promise(resolve => { + let mainWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let win = mainWindow.OpenBrowserWindow({ private: aPrivate }); + win.addEventListener( + "load", + function () { + resolve(win); + }, + { once: true } + ); + }); + }, +}; diff --git a/services/sync/tps/extensions/tps/resource/quit.sys.mjs b/services/sync/tps/extensions/tps/resource/quit.sys.mjs new file mode 100644 index 0000000000..e2cb8d8c22 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/quit.sys.mjs @@ -0,0 +1,38 @@ +/* -*- indent-tabs-mode: nil -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + From mozilla/toolkit/content + These files did not have a license +*/ +function canQuitApplication() { + try { + var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance( + Ci.nsISupportsPRBool + ); + Services.obs.notifyObservers(cancelQuit, "quit-application-requested"); + + // Something aborted the quit process. + if (cancelQuit.data) { + return false; + } + } catch (ex) {} + + return true; +} + +export function goQuitApplication() { + if (!canQuitApplication()) { + return false; + } + + try { + Services.startup.quit(Ci.nsIAppStartup.eForceQuit); + } catch (ex) { + throw new Error(`goQuitApplication: ${ex.message}`); + } + + return true; +} diff --git a/services/sync/tps/extensions/tps/resource/tps.sys.mjs b/services/sync/tps/extensions/tps/resource/tps.sys.mjs new file mode 100644 index 0000000000..2c4a5994a6 --- /dev/null +++ b/services/sync/tps/extensions/tps/resource/tps.sys.mjs @@ -0,0 +1,1583 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is a JavaScript module (JSM) to be imported via + * ChromeUtils.import() and acts as a singleton. Only the following + * listed symbols will exposed on import, and only when and where imported. + */ + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Addon: "resource://tps/modules/addons.sys.mjs", + AddonValidator: "resource://services-sync/engines/addons.sys.mjs", + Address: "resource://tps/modules/formautofill.sys.mjs", + Async: "resource://services-common/async.sys.mjs", + Authentication: "resource://tps/auth/fxaccounts.sys.mjs", + Bookmark: "resource://tps/modules/bookmarks.sys.mjs", + BookmarkFolder: "resource://tps/modules/bookmarks.sys.mjs", + BookmarkValidator: "resource://tps/modules/bookmarkValidator.sys.mjs", + BrowserTabs: "resource://tps/modules/tabs.sys.mjs", + BrowserWindows: "resource://tps/modules/windows.sys.mjs", + CommonUtils: "resource://services-common/utils.sys.mjs", + CreditCard: "resource://tps/modules/formautofill.sys.mjs", + DumpAddresses: "resource://tps/modules/formautofill.sys.mjs", + DumpBookmarks: "resource://tps/modules/bookmarks.sys.mjs", + DumpCreditCards: "resource://tps/modules/formautofill.sys.mjs", + DumpHistory: "resource://tps/modules/history.sys.mjs", + DumpPasswords: "resource://tps/modules/passwords.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + FormData: "resource://tps/modules/forms.sys.mjs", + FormValidator: "resource://services-sync/engines/forms.sys.mjs", + HistoryEntry: "resource://tps/modules/history.sys.mjs", + JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs", + Livemark: "resource://tps/modules/bookmarks.sys.mjs", + Log: "resource://gre/modules/Log.sys.mjs", + Logger: "resource://tps/logger.sys.mjs", + NetUtil: "resource://gre/modules/NetUtil.sys.mjs", + Password: "resource://tps/modules/passwords.sys.mjs", + PasswordValidator: "resource://services-sync/engines/passwords.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", + Preference: "resource://tps/modules/prefs.sys.mjs", + STATUS_OK: "resource://services-sync/constants.sys.mjs", + Separator: "resource://tps/modules/bookmarks.sys.mjs", + SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", + Svc: "resource://services-sync/util.sys.mjs", + SyncTelemetry: "resource://services-sync/telemetry.sys.mjs", + WEAVE_VERSION: "resource://services-sync/constants.sys.mjs", + Weave: "resource://services-sync/main.sys.mjs", + extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "fileProtocolHandler", () => { + let fileHandler = Services.io.getProtocolHandler("file"); + return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler); +}); + +ChromeUtils.defineLazyGetter(lazy, "gTextDecoder", () => { + return new TextDecoder(); +}); + +// Options for wiping data during a sync +const SYNC_RESET_CLIENT = "resetClient"; +const SYNC_WIPE_CLIENT = "wipeClient"; +const SYNC_WIPE_REMOTE = "wipeRemote"; + +// Actions a test can perform +const ACTION_ADD = "add"; +const ACTION_DELETE = "delete"; +const ACTION_MODIFY = "modify"; +const ACTION_SET_ENABLED = "set-enabled"; +const ACTION_SYNC = "sync"; +const ACTION_SYNC_RESET_CLIENT = SYNC_RESET_CLIENT; +const ACTION_SYNC_WIPE_CLIENT = SYNC_WIPE_CLIENT; +const ACTION_SYNC_WIPE_REMOTE = SYNC_WIPE_REMOTE; +const ACTION_VERIFY = "verify"; +const ACTION_VERIFY_NOT = "verify-not"; + +const OBSERVER_TOPICS = [ + "fxaccounts:onlogin", + "fxaccounts:onlogout", + "profile-before-change", + "weave:service:tracking-started", + "weave:service:tracking-stopped", + "weave:service:login:error", + "weave:service:setup-complete", + "weave:service:sync:finish", + "weave:service:sync:delayed", + "weave:service:sync:error", + "weave:service:sync:start", + "weave:service:resyncs-finished", + "places-browser-init-complete", +]; + +export var TPS = { + _currentAction: -1, + _currentPhase: -1, + _enabledEngines: null, + _errors: 0, + _isTracking: false, + _phaseFinished: false, + _phaselist: {}, + _setupComplete: false, + _syncActive: false, + _syncCount: 0, + _syncsReportedViaTelemetry: 0, + _syncErrors: 0, + _syncWipeAction: null, + _tabsAdded: 0, + _tabsFinished: 0, + _test: null, + _triggeredSync: false, + _msSinceEpoch: 0, + _requestedQuit: false, + shouldValidateAddons: false, + shouldValidateBookmarks: false, + shouldValidatePasswords: false, + shouldValidateForms: false, + _placesInitDeferred: Promise.withResolvers(), + ACTIONS: [ + ACTION_ADD, + ACTION_DELETE, + ACTION_MODIFY, + ACTION_SET_ENABLED, + ACTION_SYNC, + ACTION_SYNC_RESET_CLIENT, + ACTION_SYNC_WIPE_CLIENT, + ACTION_SYNC_WIPE_REMOTE, + ACTION_VERIFY, + ACTION_VERIFY_NOT, + ], + + _init: function TPS__init() { + this.delayAutoSync(); + + OBSERVER_TOPICS.forEach(function (aTopic) { + Services.obs.addObserver(this, aTopic, true); + }, this); + + // Some engines bump their score during their sync, which then causes + // another sync immediately (notably, prefs and addons). We don't want + // this to happen, and there's no obvious preference to kill it - so + // we do this nasty hack to ensure the global score is always zero. + Services.prefs.addObserver("services.sync.globalScore", () => { + if (lazy.Weave.Service.scheduler.globalScore != 0) { + lazy.Weave.Service.scheduler.globalScore = 0; + } + }); + }, + + DumpError(msg, exc = null) { + this._errors++; + let errInfo; + if (exc) { + errInfo = lazy.Log.exceptionStr(exc); // includes details and stack-trace. + } else { + // always write a stack even if no error passed. + errInfo = lazy.Log.stackTrace(new Error()); + } + lazy.Logger.logError(`[phase ${this._currentPhase}] ${msg} - ${errInfo}`); + this.quit(); + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsISupportsWeakReference", + ]), + + observe: function TPS__observe(subject, topic, data) { + try { + lazy.Logger.logInfo("----------event observed: " + topic); + + switch (topic) { + case "profile-before-change": + OBSERVER_TOPICS.forEach(function (topic) { + Services.obs.removeObserver(this, topic); + }, this); + + lazy.Logger.close(); + + break; + + case "places-browser-init-complete": + this._placesInitDeferred.resolve(); + break; + + case "weave:service:setup-complete": + this._setupComplete = true; + + if (this._syncWipeAction) { + lazy.Weave.Svc.PrefBranch.setStringPref( + "firstSync", + this._syncWipeAction + ); + this._syncWipeAction = null; + } + + break; + + case "weave:service:sync:error": + this._syncActive = false; + + this.delayAutoSync(); + + // If this is the first sync error, retry... + if (this._syncErrors === 0) { + lazy.Logger.logInfo("Sync error; retrying..."); + this._syncErrors++; + lazy.CommonUtils.nextTick(() => { + this.RunNextTestAction().catch(err => { + this.DumpError("RunNextTestActionFailed", err); + }); + }); + } else { + this._triggeredSync = false; + this.DumpError("Sync error; aborting test"); + } + + break; + + case "weave:service:resyncs-finished": + this._syncActive = false; + this._syncErrors = 0; + this._triggeredSync = false; + + this.delayAutoSync(); + break; + + case "weave:service:sync:start": + // Ensure that the sync operation has been started by TPS + if (!this._triggeredSync) { + this.DumpError( + "Automatic sync got triggered, which is not allowed." + ); + } + + this._syncActive = true; + break; + + case "weave:service:tracking-started": + this._isTracking = true; + break; + + case "weave:service:tracking-stopped": + this._isTracking = false; + break; + + case "fxaccounts:onlogin": + // A user signed in - for TPS that always means sync - so configure + // that. + lazy.Weave.Service.configure().catch(e => { + this.DumpError("Configuring sync failed.", e); + }); + break; + + default: + lazy.Logger.logInfo(`unhandled event: ${topic}`); + } + } catch (e) { + this.DumpError("Observer failed", e); + } + }, + + /** + * Given that we cannot completely disable the automatic sync operations, we + * massively delay the next sync. Sync operations have to only happen when + * directly called via TPS.Sync()! + */ + delayAutoSync: function TPS_delayAutoSync() { + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.immediateInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.idleInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("scheduler.activeInterval", 7200); + lazy.Weave.Svc.PrefBranch.setIntPref("syncThreshold", 10000000); + }, + + quit: function TPS__quit() { + lazy.Logger.logInfo("quitting"); + this._requestedQuit = true; + this.goQuitApplication(); + }, + + async HandleWindows(aWindow, action) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on window " + + JSON.stringify(aWindow) + ); + switch (action) { + case ACTION_ADD: + await lazy.BrowserWindows.Add(aWindow.private); + break; + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on windows" + ); + }, + + async HandleTabs(tabs, action) { + for (let tab of tabs) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on tab " + + JSON.stringify(tab) + ); + switch (action) { + case ACTION_ADD: + await lazy.BrowserTabs.Add(tab.uri); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue( + typeof tab.profile != "undefined", + "profile must be defined when verifying tabs" + ); + lazy.Logger.AssertTrue( + await lazy.BrowserTabs.Find(tab.uri, tab.title, tab.profile), + "error locating tab" + ); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + typeof tab.profile != "undefined", + "profile must be defined when verifying tabs" + ); + lazy.Logger.AssertTrue( + await !lazy.BrowserTabs.Find(tab.uri, tab.title, tab.profile), + "tab found which was expected to be absent" + ); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on tabs" + ); + }, + + async HandlePrefs(prefs, action) { + for (let pref of prefs) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on pref " + + JSON.stringify(pref) + ); + let preference = new lazy.Preference(pref); + switch (action) { + case ACTION_MODIFY: + preference.Modify(); + break; + case ACTION_VERIFY: + preference.Find(); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on pref" + ); + }, + + async HandleForms(data, action) { + this.shouldValidateForms = true; + for (let datum of data) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on form entry " + + JSON.stringify(datum) + ); + let formdata = new lazy.FormData(datum, this._msSinceEpoch); + switch (action) { + case ACTION_ADD: + await formdata.Create(); + break; + case ACTION_DELETE: + await formdata.Remove(); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue(await formdata.Find(), "form data not found"); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + !(await formdata.Find()), + "form data found, but it shouldn't be present" + ); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on formdata" + ); + }, + + async HandleHistory(entries, action) { + try { + for (let entry of entries) { + const entryString = JSON.stringify(entry); + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on history entry " + + entryString + ); + switch (action) { + case ACTION_ADD: + await lazy.HistoryEntry.Add(entry, this._msSinceEpoch); + break; + case ACTION_DELETE: + await lazy.HistoryEntry.Delete(entry, this._msSinceEpoch); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue( + await lazy.HistoryEntry.Find(entry, this._msSinceEpoch), + "Uri visits not found in history database: " + entryString + ); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + !(await lazy.HistoryEntry.Find(entry, this._msSinceEpoch)), + "Uri visits found in history database, but they shouldn't be: " + + entryString + ); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on history" + ); + } catch (e) { + await lazy.DumpHistory(); + throw e; + } + }, + + async HandlePasswords(passwords, action) { + this.shouldValidatePasswords = true; + try { + for (let password of passwords) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on password " + + JSON.stringify(password) + ); + let passwordOb = new lazy.Password(password); + switch (action) { + case ACTION_ADD: + lazy.Logger.AssertTrue( + (await passwordOb.Create()) > -1, + "error adding password" + ); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue( + (await passwordOb.Find()) != -1, + "password not found" + ); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + (await passwordOb.Find()) == -1, + "password found, but it shouldn't exist" + ); + break; + case ACTION_DELETE: + lazy.Logger.AssertTrue( + (await passwordOb.Find()) != -1, + "password not found" + ); + passwordOb.Remove(); + break; + case ACTION_MODIFY: + if (passwordOb.updateProps != null) { + lazy.Logger.AssertTrue( + (await passwordOb.Find()) != -1, + "password not found" + ); + passwordOb.Update(); + } + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on passwords" + ); + } catch (e) { + await lazy.DumpPasswords(); + throw e; + } + }, + + async HandleAddons(addons, action, state) { + this.shouldValidateAddons = true; + for (let entry of addons) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on addon " + + JSON.stringify(entry) + ); + let addon = new lazy.Addon(this, entry); + switch (action) { + case ACTION_ADD: + await addon.install(); + break; + case ACTION_DELETE: + await addon.uninstall(); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue( + await addon.find(state), + "addon " + addon.id + " not found" + ); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertFalse( + await addon.find(state), + "addon " + addon.id + " is present, but it shouldn't be" + ); + break; + case ACTION_SET_ENABLED: + lazy.Logger.AssertTrue( + await addon.setEnabled(state), + "addon " + addon.id + " not found" + ); + break; + default: + throw new Error("Unknown action for add-on: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on addons" + ); + }, + + async HandleBookmarks(bookmarks, action) { + // wait for default bookmarks to be created. + await this._placesInitDeferred.promise; + this.shouldValidateBookmarks = true; + try { + let items = []; + for (let folder in bookmarks) { + let last_item_pos = -1; + for (let bookmark of bookmarks[folder]) { + lazy.Logger.clearPotentialError(); + let placesItem; + bookmark.location = folder; + + if (last_item_pos != -1) { + bookmark.last_item_pos = last_item_pos; + } + let itemGuid = null; + + if (action != ACTION_MODIFY && action != ACTION_DELETE) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on bookmark " + + JSON.stringify(bookmark) + ); + } + + if ("uri" in bookmark) { + placesItem = new lazy.Bookmark(bookmark); + } else if ("folder" in bookmark) { + placesItem = new lazy.BookmarkFolder(bookmark); + } else if ("livemark" in bookmark) { + placesItem = new lazy.Livemark(bookmark); + } else if ("separator" in bookmark) { + placesItem = new lazy.Separator(bookmark); + } + + if (action == ACTION_ADD) { + itemGuid = await placesItem.Create(); + } else { + itemGuid = await placesItem.Find(); + if (action == ACTION_VERIFY_NOT) { + lazy.Logger.AssertTrue( + itemGuid == null, + "places item exists but it shouldn't: " + + JSON.stringify(bookmark) + ); + } else { + lazy.Logger.AssertTrue(itemGuid, "places item not found", true); + } + } + + last_item_pos = await placesItem.GetItemIndex(); + items.push(placesItem); + } + } + + if (action == ACTION_DELETE || action == ACTION_MODIFY) { + for (let item of items) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on bookmark " + + JSON.stringify(item) + ); + switch (action) { + case ACTION_DELETE: + await item.Remove(); + break; + case ACTION_MODIFY: + if (item.updateProps != null) { + await item.Update(); + } + break; + } + } + } + + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on bookmarks" + ); + } catch (e) { + await lazy.DumpBookmarks(); + throw e; + } + }, + + async HandleAddresses(addresses, action) { + try { + for (let address of addresses) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on address " + + JSON.stringify(address) + ); + let addressOb = new lazy.Address(address); + switch (action) { + case ACTION_ADD: + await addressOb.Create(); + break; + case ACTION_MODIFY: + await addressOb.Update(); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue(await addressOb.Find(), "address not found"); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + !(await addressOb.Find()), + "address found, but it shouldn't exist" + ); + break; + case ACTION_DELETE: + lazy.Logger.AssertTrue(await addressOb.Find(), "address not found"); + await addressOb.Remove(); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on addresses" + ); + } catch (e) { + await lazy.DumpAddresses(); + throw e; + } + }, + + async HandleCreditCards(creditCards, action) { + try { + for (let creditCard of creditCards) { + lazy.Logger.logInfo( + "executing action " + + action.toUpperCase() + + " on creditCard " + + JSON.stringify(creditCard) + ); + let creditCardOb = new lazy.CreditCard(creditCard); + switch (action) { + case ACTION_ADD: + await creditCardOb.Create(); + break; + case ACTION_MODIFY: + await creditCardOb.Update(); + break; + case ACTION_VERIFY: + lazy.Logger.AssertTrue( + await creditCardOb.Find(), + "creditCard not found" + ); + break; + case ACTION_VERIFY_NOT: + lazy.Logger.AssertTrue( + !(await creditCardOb.Find()), + "creditCard found, but it shouldn't exist" + ); + break; + case ACTION_DELETE: + lazy.Logger.AssertTrue( + await creditCardOb.Find(), + "creditCard not found" + ); + await creditCardOb.Remove(); + break; + default: + lazy.Logger.AssertTrue(false, "invalid action: " + action); + } + } + lazy.Logger.logPass( + "executing action " + action.toUpperCase() + " on creditCards" + ); + } catch (e) { + await lazy.DumpCreditCards(); + throw e; + } + }, + + async Cleanup() { + try { + await this.WipeServer(); + } catch (ex) { + lazy.Logger.logError( + "Failed to wipe server: " + lazy.Log.exceptionStr(ex) + ); + } + try { + if (await lazy.Authentication.isLoggedIn()) { + // signout and wait for Sync to completely reset itself. + lazy.Logger.logInfo("signing out"); + let waiter = this.promiseObserver("weave:service:start-over:finish"); + await lazy.Authentication.signOut(); + await waiter; + lazy.Logger.logInfo("signout complete"); + } + await lazy.Authentication.deleteEmail(this.config.fx_account.username); + } catch (e) { + lazy.Logger.logError("Failed to sign out: " + lazy.Log.exceptionStr(e)); + } + }, + + /** + * Use Sync's bookmark validation code to see if we've corrupted the tree. + */ + async ValidateBookmarks() { + let getServerBookmarkState = async () => { + let bookmarkEngine = lazy.Weave.Service.engineManager.get("bookmarks"); + let collection = bookmarkEngine.itemSource(); + let collectionKey = + bookmarkEngine.service.collectionKeys.keyForCollection( + bookmarkEngine.name + ); + collection.full = true; + let items = []; + let resp = await collection.get(); + for (let json of resp.obj) { + let record = new collection._recordObj(); + record.deserialize(json); + await record.decrypt(collectionKey); + items.push(record.cleartext); + } + return items; + }; + let serverRecordDumpStr; + try { + lazy.Logger.logInfo("About to perform bookmark validation"); + let clientTree = await lazy.PlacesUtils.promiseBookmarksTree("", { + includeItemIds: true, + }); + let serverRecords = await getServerBookmarkState(); + // We can't wait until catch to stringify this, since at that point it will have cycles. + serverRecordDumpStr = JSON.stringify(serverRecords); + + let validator = new lazy.BookmarkValidator(); + let { problemData } = await validator.compareServerWithClient( + serverRecords, + clientTree + ); + + for (let { name, count } of problemData.getSummary()) { + // Exclude mobile showing up on the server hackily so that we don't + // report it every time, see bug 1273234 and 1274394 for more information. + if ( + name === "serverUnexpected" && + problemData.serverUnexpected.includes("mobile") + ) { + --count; + } + if (count) { + // Log this out before we assert. This is useful in the context of TPS logs, since we + // can see the IDs in the test files. + lazy.Logger.logInfo( + `Validation problem: "${name}": ${JSON.stringify( + problemData[name] + )}` + ); + } + lazy.Logger.AssertEqual( + count, + 0, + `Bookmark validation error of type ${name}` + ); + } + } catch (e) { + // Dump the client records (should always be doable) + lazy.DumpBookmarks(); + // Dump the server records if gotten them already. + if (serverRecordDumpStr) { + lazy.Logger.logInfo( + "Server bookmark records:\n" + serverRecordDumpStr + "\n" + ); + } + this.DumpError("Bookmark validation failed", e); + } + lazy.Logger.logInfo("Bookmark validation finished"); + }, + + async ValidateCollection(engineName, ValidatorType) { + let serverRecordDumpStr; + let clientRecordDumpStr; + try { + lazy.Logger.logInfo(`About to perform validation for "${engineName}"`); + let engine = lazy.Weave.Service.engineManager.get(engineName); + let validator = new ValidatorType(engine); + let serverRecords = await validator.getServerItems(engine); + let clientRecords = await validator.getClientItems(); + try { + // This substantially improves the logs for addons while not making a + // substantial difference for the other two + clientRecordDumpStr = JSON.stringify( + clientRecords.map(r => { + let res = validator.normalizeClientItem(r); + delete res.original; // Try and prevent cyclic references + return res; + }) + ); + } catch (e) { + // ignore the error, the dump string is just here to make debugging easier. + clientRecordDumpStr = "<Cyclic value>"; + } + try { + serverRecordDumpStr = JSON.stringify(serverRecords); + } catch (e) { + // as above + serverRecordDumpStr = "<Cyclic value>"; + } + let { problemData } = await validator.compareClientWithServer( + clientRecords, + serverRecords + ); + for (let { name, count } of problemData.getSummary()) { + if (count) { + lazy.Logger.logInfo( + `Validation problem: "${name}": ${JSON.stringify( + problemData[name] + )}` + ); + } + lazy.Logger.AssertEqual( + count, + 0, + `Validation error for "${engineName}" of type "${name}"` + ); + } + } catch (e) { + // Dump the client records if possible + if (clientRecordDumpStr) { + lazy.Logger.logInfo( + `Client state for ${engineName}:\n${clientRecordDumpStr}\n` + ); + } + // Dump the server records if gotten them already. + if (serverRecordDumpStr) { + lazy.Logger.logInfo( + `Server state for ${engineName}:\n${serverRecordDumpStr}\n` + ); + } + this.DumpError(`Validation failed for ${engineName}`, e); + } + lazy.Logger.logInfo(`Validation finished for ${engineName}`); + }, + + ValidatePasswords() { + return this.ValidateCollection("passwords", lazy.PasswordValidator); + }, + + ValidateForms() { + return this.ValidateCollection("forms", lazy.FormValidator); + }, + + ValidateAddons() { + return this.ValidateCollection("addons", lazy.AddonValidator); + }, + + async RunNextTestAction() { + lazy.Logger.logInfo("Running next test action"); + try { + if (this._currentAction >= this._phaselist[this._currentPhase].length) { + // Run necessary validations and then finish up + lazy.Logger.logInfo("No more actions - running validations..."); + if (this.shouldValidateBookmarks) { + await this.ValidateBookmarks(); + } + if (this.shouldValidatePasswords) { + await this.ValidatePasswords(); + } + if (this.shouldValidateForms) { + await this.ValidateForms(); + } + if (this.shouldValidateAddons) { + await this.ValidateAddons(); + } + // Force this early so that we run the validation and detect missing pings + // *before* we start shutting down, since if we do it after, the python + // code won't notice the failure. + lazy.SyncTelemetry.shutdown(); + // we're all done + lazy.Logger.logInfo( + "test phase " + + this._currentPhase + + ": " + + (this._errors ? "FAIL" : "PASS") + ); + this._phaseFinished = true; + this.quit(); + return; + } + this.seconds_since_epoch = Services.prefs.getIntPref( + "tps.seconds_since_epoch" + ); + if (this.seconds_since_epoch) { + // Places dislikes it if we add visits in the future. We pretend the + // real time is 1 minute ago to avoid issues caused by places using a + // different clock than the one that set the seconds_since_epoch pref. + this._msSinceEpoch = (this.seconds_since_epoch - 60) * 1000; + } else { + this.DumpError("seconds-since-epoch not set"); + return; + } + + let phase = this._phaselist[this._currentPhase]; + let action = phase[this._currentAction]; + lazy.Logger.logInfo("starting action: " + action[0].name); + await action[0].apply(this, action.slice(1)); + + this._currentAction++; + } catch (e) { + if (lazy.Async.isShutdownException(e)) { + if (this._requestedQuit) { + lazy.Logger.logInfo("Sync aborted due to requested shutdown"); + } else { + this.DumpError( + "Sync aborted due to shutdown, but we didn't request it" + ); + } + } else { + this.DumpError("RunNextTestAction failed", e); + } + return; + } + await this.RunNextTestAction(); + }, + + _getFileRelativeToSourceRoot(testFileURL, relativePath) { + let file = lazy.fileProtocolHandler.getFileFromURLSpec(testFileURL); + let root = file.parent.parent.parent.parent.parent; // <root>/services/sync/tests/tps/test_foo.js // <root>/services/sync/tests/tps // <root>/services/sync/tests // <root>/services/sync // <root>/services // <root> + root.appendRelativePath(relativePath); + root.normalize(); + return root; + }, + + _pingValidator: null, + + // Default ping validator that always says the ping passes. This should be + // overridden unless the `testing.tps.skipPingValidation` pref is true. + get pingValidator() { + return this._pingValidator + ? this._pingValidator + : { + validate() { + lazy.Logger.logInfo( + "Not validating ping -- disabled by pref or failure to load schema" + ); + return { valid: true, errors: [] }; + }, + }; + }, + + // Attempt to load the sync_ping_schema.json and initialize `this.pingValidator` + // based on the source of the tps file. Assumes that it's at "../unit/sync_ping_schema.json" + // relative to the directory the tps test file (testFile) is contained in. + _tryLoadPingSchema(testFile) { + if (Services.prefs.getBoolPref("testing.tps.skipPingValidation", false)) { + return; + } + try { + let schemaFile = this._getFileRelativeToSourceRoot( + testFile, + "services/sync/tests/unit/sync_ping_schema.json" + ); + + let stream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + + stream.init( + schemaFile, + lazy.FileUtils.MODE_RDONLY, + lazy.FileUtils.PERMS_FILE, + 0 + ); + + let bytes = lazy.NetUtil.readInputStream(stream, stream.available()); + let schema = JSON.parse(lazy.gTextDecoder.decode(bytes)); + lazy.Logger.logInfo("Successfully loaded schema"); + + this._pingValidator = new lazy.JsonSchema.Validator(schema); + } catch (e) { + this.DumpError( + `Failed to load ping schema relative to "${testFile}".`, + e + ); + } + }, + + /** + * Runs a single test phase. + * + * This is the main entry point for each phase of a test. The TPS command + * line driver loads this module and calls into the function with the + * arguments from the command line. + * + * When a phase is executed, the file is loaded as JavaScript into the + * current object. + * + * The following keys in the options argument have meaning: + * + * - ignoreUnusedEngines If true, unused engines will be unloaded from + * Sync. This makes output easier to parse and is + * useful for debugging test failures. + * + * @param file + * String URI of the file to open. + * @param phase + * String name of the phase to run. + * @param logpath + * String path of the log file to write to. + * @param options + * Object defining addition run-time options. + */ + async RunTestPhase(file, phase, logpath, options) { + try { + let settings = options || {}; + + lazy.Logger.init(logpath); + lazy.Logger.logInfo("Sync version: " + lazy.WEAVE_VERSION); + lazy.Logger.logInfo("Firefox buildid: " + Services.appinfo.appBuildID); + lazy.Logger.logInfo("Firefox version: " + Services.appinfo.version); + lazy.Logger.logInfo( + "Firefox source revision: " + + (AppConstants.SOURCE_REVISION_URL || "unknown") + ); + lazy.Logger.logInfo("Firefox platform: " + AppConstants.platform); + + // do some sync housekeeping + if (lazy.Weave.Service.isLoggedIn) { + this.DumpError("Sync logged in on startup...profile may be dirty"); + return; + } + + // Wait for Sync service to become ready. + if (!lazy.Weave.Status.ready) { + this.waitForEvent("weave:service:ready"); + } + + await lazy.Weave.Service.promiseInitialized; + + // We only want to do this if we modified the bookmarks this phase. + this.shouldValidateBookmarks = false; + + // Always give Sync an extra tick to initialize. If we waited for the + // service:ready event, this is required to ensure all handlers have + // executed. + await lazy.Async.promiseYield(); + await this._executeTestPhase(file, phase, settings); + } catch (e) { + this.DumpError("RunTestPhase failed", e); + } + }, + + /** + * Executes a single test phase. + * + * This is called by RunTestPhase() after the environment is validated. + */ + async _executeTestPhase(file, phase, settings) { + try { + this.config = JSON.parse(Services.prefs.getStringPref("tps.config")); + // parse the test file + Services.scriptloader.loadSubScript(file, this); + this._currentPhase = phase; + // cleanup phases are in the format `cleanup-${profileName}`. + if (this._currentPhase.startsWith("cleanup-")) { + let profileToClean = this._currentPhase.slice("cleanup-".length); + this.phases[this._currentPhase] = profileToClean; + this.Phase(this._currentPhase, [[this.Cleanup]]); + } else { + // Don't bother doing this for cleanup phases. + this._tryLoadPingSchema(file); + } + let this_phase = this._phaselist[this._currentPhase]; + + if (this_phase == undefined) { + this.DumpError("invalid phase " + this._currentPhase); + return; + } + + if (this.phases[this._currentPhase] == undefined) { + this.DumpError("no profile defined for phase " + this._currentPhase); + return; + } + + // If we have restricted the active engines, unregister engines we don't + // care about. + if (settings.ignoreUnusedEngines && Array.isArray(this._enabledEngines)) { + let names = {}; + for (let name of this._enabledEngines) { + names[name] = true; + } + for (let engine of lazy.Weave.Service.engineManager.getEnabled()) { + if (!(engine.name in names)) { + lazy.Logger.logInfo("Unregistering unused engine: " + engine.name); + await lazy.Weave.Service.engineManager.unregister(engine); + } + } + } + lazy.Logger.logInfo("Starting phase " + this._currentPhase); + + lazy.Logger.logInfo( + "setting client.name to " + this.phases[this._currentPhase] + ); + lazy.Weave.Svc.PrefBranch.setStringPref( + "client.name", + this.phases[this._currentPhase] + ); + + this._interceptSyncTelemetry(); + + // start processing the test actions + this._currentAction = 0; + await lazy.SessionStore.promiseAllWindowsRestored; + await this.RunNextTestAction(); + } catch (e) { + this.DumpError("_executeTestPhase failed", e); + } + }, + + /** + * Override sync telemetry functions so that we can detect errors generating + * the sync ping, and count how many pings we report. + */ + _interceptSyncTelemetry() { + let originalObserve = lazy.SyncTelemetry.observe; + let self = this; + lazy.SyncTelemetry.observe = function () { + try { + originalObserve.apply(this, arguments); + } catch (e) { + self.DumpError("Error when generating sync telemetry", e); + } + }; + lazy.SyncTelemetry.submit = record => { + lazy.Logger.logInfo( + "Intercepted sync telemetry submission: " + JSON.stringify(record) + ); + this._syncsReportedViaTelemetry += + record.syncs.length + (record.discarded || 0); + if (record.discarded) { + if (record.syncs.length != lazy.SyncTelemetry.maxPayloadCount) { + this.DumpError( + "Syncs discarded from ping before maximum payload count reached" + ); + } + } + // If this is the shutdown ping, check and see that the telemetry saw all the syncs. + if (record.why === "shutdown") { + // If we happen to sync outside of tps manually causing it, its not an + // error in the telemetry, so we only complain if we didn't see all of them. + if (this._syncsReportedViaTelemetry < this._syncCount) { + this.DumpError( + `Telemetry missed syncs: Saw ${this._syncsReportedViaTelemetry}, should have >= ${this._syncCount}.` + ); + } + } + if (!record.syncs.length) { + // Note: we're overwriting submit, so this is called even for pings that + // may have no data (which wouldn't be submitted to telemetry and would + // fail validation). + return; + } + // Our ping may have some undefined values, which we rely on JSON stripping + // out as part of the ping submission - but our validator fails with them, + // so round-trip via JSON here to avoid that. + record = JSON.parse(JSON.stringify(record)); + const result = this.pingValidator.validate(record); + if (!result.valid) { + // Note that we already logged the record. + this.DumpError( + "Sync ping validation failed with errors: " + + JSON.stringify(result.errors) + ); + } + }; + }, + + /** + * Register a single phase with the test harness. + * + * This is called when loading individual test files. + * + * @param phasename + * String name of the phase being loaded. + * @param fnlist + * Array of functions/actions to perform. + */ + Phase: function Test__Phase(phasename, fnlist) { + if (Object.keys(this._phaselist).length === 0) { + // This is the first phase we should force a log in + fnlist.unshift([this.Login]); + } + this._phaselist[phasename] = fnlist; + }, + + /** + * Restrict enabled Sync engines to a specified set. + * + * This can be called by a test to limit what engines are enabled. It is + * recommended to call it to reduce the overhead and log clutter for the + * test. + * + * The "clients" engine is special and is always enabled, so there is no + * need to specify it. + * + * @param names + * Array of Strings for engines to make active during the test. + */ + EnableEngines: function EnableEngines(names) { + if (!Array.isArray(names)) { + throw new Error( + "Argument to RestrictEngines() is not an array: " + typeof names + ); + } + + this._enabledEngines = names; + }, + + /** + * Returns a promise that resolves when a specific observer notification is + * resolved. This is similar to the various waitFor* functions, although is + * typically safer if you need to do some other work that may make the event + * fire. + * + * eg: + * doSomething(); // causes the event to be fired. + * await promiseObserver("something"); + * is risky as the call to doSomething may trigger the event before the + * promiseObserver call is made. Contrast with: + * + * let waiter = promiseObserver("something"); + * doSomething(); // causes the event to be fired. + * await waiter; // will return as soon as the event fires, even if it fires + * // before this function is called. + * + * @param aEventName + * String event to wait for. + */ + promiseObserver(aEventName) { + return new Promise(resolve => { + lazy.Logger.logInfo("Setting up wait for " + aEventName + "..."); + let handler = () => { + lazy.Logger.logInfo("Observed " + aEventName); + lazy.Svc.Obs.remove(aEventName, handler); + resolve(); + }; + lazy.Svc.Obs.add(aEventName, handler); + }); + }, + + /** + * Wait for the named event to be observed. + * + * Note that in general, you should probably use promiseObserver unless you + * are 100% sure that the event being waited on can only be sent after this + * call adds the listener. + * + * @param aEventName + * String event to wait for. + */ + async waitForEvent(aEventName) { + await this.promiseObserver(aEventName); + }, + + /** + * Waits for Sync to logged in before returning + */ + async waitForSetupComplete() { + if (!this._setupComplete) { + await this.waitForEvent("weave:service:setup-complete"); + } + }, + + /** + * Waits for Sync to be finished before returning + */ + async waitForSyncFinished() { + if (lazy.Weave.Service.locked) { + await this.waitForEvent("weave:service:resyncs-finished"); + } + }, + + /** + * Waits for Sync to start tracking before returning. + */ + async waitForTracking() { + if (!this._isTracking) { + await this.waitForEvent("weave:service:tracking-started"); + } + }, + + /** + * Login on the server + */ + async Login() { + if (await lazy.Authentication.isReady()) { + return; + } + + lazy.Logger.logInfo("Setting client credentials and login."); + await lazy.Authentication.signIn(this.config.fx_account); + await this.waitForSetupComplete(); + lazy.Logger.AssertEqual( + lazy.Weave.Status.service, + lazy.STATUS_OK, + "Weave status OK" + ); + await this.waitForTracking(); + }, + + /** + * Triggers a sync operation + * + * @param {String} [wipeAction] + * Type of wipe to perform (resetClient, wipeClient, wipeRemote) + * + */ + async Sync(wipeAction) { + if (this._syncActive) { + this.DumpError("Sync currently active which should be impossible"); + return; + } + lazy.Logger.logInfo( + "Executing Sync" + (wipeAction ? ": " + wipeAction : "") + ); + + // Force a wipe action if requested. In case of an initial sync the pref + // will be overwritten by Sync itself (see bug 992198), so ensure that we + // also handle it via the "weave:service:setup-complete" notification. + if (wipeAction) { + this._syncWipeAction = wipeAction; + lazy.Weave.Svc.PrefBranch.setStringPref("firstSync", wipeAction); + } else { + lazy.Weave.Svc.PrefBranch.clearUserPref("firstSync"); + } + if (!(await lazy.Weave.Service.login())) { + // We need to complete verification. + lazy.Logger.logInfo("Logging in before performing sync"); + await this.Login(); + } + ++this._syncCount; + + lazy.Logger.logInfo( + "Executing Sync" + (wipeAction ? ": " + wipeAction : "") + ); + this._triggeredSync = true; + await lazy.Weave.Service.sync(); + lazy.Logger.logInfo("Sync is complete"); + // wait a second for things to settle... + await new Promise(resolve => { + lazy.CommonUtils.namedTimer(resolve, 1000, this, "postsync"); + }); + }, + + async WipeServer() { + lazy.Logger.logInfo("Wiping data from server."); + + await this.Login(); + await lazy.Weave.Service.login(); + await lazy.Weave.Service.wipeServer(); + }, + + /** + * Action which ensures changes are being tracked before returning. + */ + async EnsureTracking() { + await this.Login(); + await this.waitForTracking(); + }, + + Addons: { + async install(addons) { + await TPS.HandleAddons(addons, ACTION_ADD); + }, + async setEnabled(addons, state) { + await TPS.HandleAddons(addons, ACTION_SET_ENABLED, state); + }, + async uninstall(addons) { + await TPS.HandleAddons(addons, ACTION_DELETE); + }, + async verify(addons, state) { + await TPS.HandleAddons(addons, ACTION_VERIFY, state); + }, + async verifyNot(addons) { + await TPS.HandleAddons(addons, ACTION_VERIFY_NOT); + }, + skipValidation() { + TPS.shouldValidateAddons = false; + }, + }, + + Addresses: { + async add(addresses) { + await this.HandleAddresses(addresses, ACTION_ADD); + }, + async modify(addresses) { + await this.HandleAddresses(addresses, ACTION_MODIFY); + }, + async delete(addresses) { + await this.HandleAddresses(addresses, ACTION_DELETE); + }, + async verify(addresses) { + await this.HandleAddresses(addresses, ACTION_VERIFY); + }, + async verifyNot(addresses) { + await this.HandleAddresses(addresses, ACTION_VERIFY_NOT); + }, + }, + + Bookmarks: { + async add(bookmarks) { + await TPS.HandleBookmarks(bookmarks, ACTION_ADD); + }, + async modify(bookmarks) { + await TPS.HandleBookmarks(bookmarks, ACTION_MODIFY); + }, + async delete(bookmarks) { + await TPS.HandleBookmarks(bookmarks, ACTION_DELETE); + }, + async verify(bookmarks) { + await TPS.HandleBookmarks(bookmarks, ACTION_VERIFY); + }, + async verifyNot(bookmarks) { + await TPS.HandleBookmarks(bookmarks, ACTION_VERIFY_NOT); + }, + skipValidation() { + TPS.shouldValidateBookmarks = false; + }, + }, + CreditCards: { + async add(creditCards) { + await this.HandleCreditCards(creditCards, ACTION_ADD); + }, + async modify(creditCards) { + await this.HandleCreditCards(creditCards, ACTION_MODIFY); + }, + async delete(creditCards) { + await this.HandleCreditCards(creditCards, ACTION_DELETE); + }, + async verify(creditCards) { + await this.HandleCreditCards(creditCards, ACTION_VERIFY); + }, + async verifyNot(creditCards) { + await this.HandleCreditCards(creditCards, ACTION_VERIFY_NOT); + }, + }, + + Formdata: { + async add(formdata) { + await this.HandleForms(formdata, ACTION_ADD); + }, + async delete(formdata) { + await this.HandleForms(formdata, ACTION_DELETE); + }, + async verify(formdata) { + await this.HandleForms(formdata, ACTION_VERIFY); + }, + async verifyNot(formdata) { + await this.HandleForms(formdata, ACTION_VERIFY_NOT); + }, + }, + History: { + async add(history) { + await this.HandleHistory(history, ACTION_ADD); + }, + async delete(history) { + await this.HandleHistory(history, ACTION_DELETE); + }, + async verify(history) { + await this.HandleHistory(history, ACTION_VERIFY); + }, + async verifyNot(history) { + await this.HandleHistory(history, ACTION_VERIFY_NOT); + }, + }, + Passwords: { + async add(passwords) { + await this.HandlePasswords(passwords, ACTION_ADD); + }, + async modify(passwords) { + await this.HandlePasswords(passwords, ACTION_MODIFY); + }, + async delete(passwords) { + await this.HandlePasswords(passwords, ACTION_DELETE); + }, + async verify(passwords) { + await this.HandlePasswords(passwords, ACTION_VERIFY); + }, + async verifyNot(passwords) { + await this.HandlePasswords(passwords, ACTION_VERIFY_NOT); + }, + skipValidation() { + TPS.shouldValidatePasswords = false; + }, + }, + Prefs: { + async modify(prefs) { + await TPS.HandlePrefs(prefs, ACTION_MODIFY); + }, + async verify(prefs) { + await TPS.HandlePrefs(prefs, ACTION_VERIFY); + }, + }, + Tabs: { + async add(tabs) { + await TPS.HandleTabs(tabs, ACTION_ADD); + }, + async verify(tabs) { + await TPS.HandleTabs(tabs, ACTION_VERIFY); + }, + async verifyNot(tabs) { + await TPS.HandleTabs(tabs, ACTION_VERIFY_NOT); + }, + }, + Windows: { + async add(aWindow) { + await TPS.HandleWindows(aWindow, ACTION_ADD); + }, + }, + + // Jumping through loads of hoops via calling back into a "HandleXXX" method + // and adding an ACTION_XXX indirection adds no value - let's KISS! + // eslint-disable-next-line no-unused-vars + ExtStorage: { + async set(id, data) { + lazy.Logger.logInfo(`setting data for '${id}': ${data}`); + await lazy.extensionStorageSync.set({ id }, data); + }, + async verify(id, keys, data) { + let got = await lazy.extensionStorageSync.get({ id }, keys); + lazy.Logger.AssertEqual(got, data, `data for '${id}'/${keys}`); + }, + }, +}; + +// Initialize TPS +TPS._init(); diff --git a/services/sync/tps/extensions/tps/schema.json b/services/sync/tps/extensions/tps/schema.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/services/sync/tps/extensions/tps/schema.json @@ -0,0 +1 @@ +[] |